import { Theme } from './themes/type'; import theme_light from './themes/light'; import theme_dark from './themes/dark'; import theme_dracula from './themes/dracula'; import theme_solarizedLight from './themes/solarizedLight'; import theme_solarizedDark from './themes/solarizedDark'; import theme_nord from './themes/nord'; import theme_aritimDark from './themes/aritimDark'; import theme_oledDark from './themes/oledDark'; import Setting from './models/Setting'; const Color = require('color'); const themes: any = { [Setting.THEME_LIGHT]: theme_light, [Setting.THEME_DARK]: theme_dark, [Setting.THEME_DRACULA]: theme_dracula, [Setting.THEME_SOLARIZED_LIGHT]: theme_solarizedLight, [Setting.THEME_SOLARIZED_DARK]: theme_solarizedDark, [Setting.THEME_NORD]: theme_nord, [Setting.THEME_ARITIM_DARK]: theme_aritimDark, [Setting.THEME_OLED_DARK]: theme_oledDark, }; export function themeById(themeId: string) { if (!themes[themeId]) throw new Error(`Invalid theme ID: ${themeId}`); const output = Object.assign({}, themes[themeId]); if (!output.headerBackgroundColor) { output.headerBackgroundColor = output.appearance === 'light' ? '#F0F0F0' : '#2D3136'; } if (!output.textSelectionColor) { output.textSelectionColor = output.appearance === 'light' ? '#0096FF' : '#00AEFF'; } if (!output.colorBright2) { output.colorBright2 = output.appearance === 'light' ? '#ffffff' : '#ffffff'; } return output; } // globalStyle should be used for properties that do not change across themes // i.e. should not be used for colors const globalStyle: any = { fontFamily: 'Roboto', // 'sans-serif', margin: 15, // No text and no interactive component should be within this margin itemMarginTop: 10, itemMarginBottom: 10, disabledOpacity: 0.3, buttonMinWidth: 50, buttonMinHeight: 30, editorFontSize: 12, textAreaLineHeight: 17, lineHeight: '1.6em', headerButtonHPadding: 6, toolbarHeight: 26, toolbarPadding: 6, appearance: 'light', mainPadding: 12, topRowHeight: 50, editorPaddingLeft: 8, }; globalStyle.marginRight = globalStyle.margin; globalStyle.marginLeft = globalStyle.margin; globalStyle.marginTop = globalStyle.margin; globalStyle.marginBottom = globalStyle.margin; globalStyle.icon = { fontSize: 30, }; globalStyle.lineInput = { fontFamily: globalStyle.fontFamily, maxHeight: 22, height: 22, paddingLeft: 5, }; globalStyle.headerStyle = { fontFamily: globalStyle.fontFamily, }; globalStyle.inputStyle = { border: '1px solid', height: 24, maxHeight: 24, paddingLeft: 5, paddingRight: 5, boxSizing: 'border-box', }; globalStyle.containerStyle = { overflow: 'auto', overflowY: 'auto', }; globalStyle.buttonStyle = { // marginRight: 10, border: '1px solid', minHeight: 26, minWidth: 80, // maxWidth: 220, paddingLeft: 12, paddingRight: 12, paddingTop: 6, paddingBottom: 6, // boxShadow: '0px 1px 1px rgba(0,0,0,0.3)', fontSize: globalStyle.fontSize, borderRadius: 4, }; function addMissingProperties(theme: Theme) { // if (!('backgroundColor3' in theme)) theme.backgroundColor3 = theme.backgroundColor; // if (!('color3' in theme)) theme.color3 = theme.color; // if (!('selectionBackgroundColor3' in theme)) { // if (theme.appearance === 'dark') { // theme.selectionBackgroundColor3 = '#ffffff77'; // } else { // theme.selectionBackgroundColor3 = '#00000077'; // } // } // if (!('backgroundColorHover3' in theme)) theme.backgroundColorHover3 = Color(theme.selectionBackgroundColor3).alpha(0.5).rgb(); // if (!('selectionBorderColor3' in theme)) theme.selectionBorderColor3 = theme.backgroundColor3; // TODO: pick base theme based on appearence // const lightTheme = themes[Setting.THEME_LIGHT]; // for (const n in lightTheme) { // if (!(n in theme)) theme[n] = lightTheme[n]; // } return theme; } function addExtraStyles(style: any) { style.selectedDividerColor = Color(style.dividerColor).darken(0.2).hex(); style.iconColor = Color(style.color).alpha(0.8); style.colorFaded2 = Color(style.color2).alpha(0.5).rgb(); style.colorHover2 = Color(style.color2).alpha(0.7).rgb(); style.colorActive2 = Color(style.color2).alpha(0.9).rgb(); style.backgroundColorHoverDim3 = Color(style.backgroundColorHover3).alpha(0.3).rgb(); style.backgroundColorActive3 = Color(style.backgroundColorHover3).alpha(0.5).rgb(); const bgColor4 = style.backgroundColor4; style.backgroundColorHover2 = Color(style.selectedColor2).alpha(0.4).rgb(); style.backgroundColorHover4 = Color(style.backgroundColorHover3).alpha(0.3).rgb(); style.backgroundColorActive4 = Color(style.backgroundColorHover3).alpha(0.8).rgb(); style.borderColor4 = Color(style.color).alpha(0.3); style.backgroundColor4 = bgColor4; style.color5 = bgColor4; style.backgroundColor5 = style.color4; style.backgroundColorHover5 = Color(style.backgroundColor5).darken(0.2).hex(); style.backgroundColorActive5 = Color(style.backgroundColor5).darken(0.4).hex(); style.configScreenPadding = style.mainPadding * 2; style.icon = Object.assign({}, style.icon, { color: style.color } ); style.lineInput = Object.assign({}, style.lineInput, { color: style.color, backgroundColor: style.backgroundColor, } ); style.headerStyle = Object.assign({}, style.headerStyle, { color: style.color, backgroundColor: style.backgroundColor, } ); style.inputStyle = Object.assign({}, style.inputStyle, { color: style.color, backgroundColor: style.backgroundColor, borderColor: style.dividerColor, } ); style.containerStyle = Object.assign({}, style.containerStyle, { color: style.color, backgroundColor: style.backgroundColor, } ); style.buttonStyle = Object.assign({}, style.buttonStyle, { color: style.color4, backgroundColor: style.backgroundColor4, borderColor: style.borderColor4, userSelect: 'none', // cursor: 'pointer', } ); style.tagStyle = { fontSize: style.fontSize, fontFamily: style.fontFamily, paddingTop: 4, paddingBottom: 4, paddingRight: 10, paddingLeft: 10, backgroundColor: style.backgroundColor3, color: style.color3, display: 'flex', alignItems: 'center', justifyContent: 'center', marginRight: 8, borderRadius: 100, borderWidth: 0, }; style.toolbarStyle = { height: style.toolbarHeight, minWidth: style.toolbarHeight, display: 'flex', alignItems: 'center', paddingLeft: style.headerButtonHPadding, paddingRight: style.headerButtonHPadding, textDecoration: 'none', fontFamily: style.fontFamily, fontSize: style.fontSize, boxSizing: 'border-box', cursor: 'default', justifyContent: 'center', color: style.color, whiteSpace: 'nowrap', }; style.textStyle = { fontFamily: globalStyle.fontFamily, fontSize: style.fontSize, lineHeight: '1.6em', color: style.color, }; style.clickableTextStyle = Object.assign({}, style.textStyle, { userSelect: 'none', }); style.textStyle2 = Object.assign({}, style.textStyle, { color: style.color2 } ); style.textStyleMinor = Object.assign({}, style.textStyle, { color: style.colorFaded, fontSize: style.fontSize * 0.8, } ); style.urlStyle = Object.assign({}, style.textStyle, { textDecoration: 'underline', color: style.urlColor, } ); style.h1Style = Object.assign({}, style.textStyle, { color: style.color, fontSize: style.textStyle.fontSize * 1.5, fontWeight: 'bold', } ); style.h2Style = Object.assign({}, style.textStyle, { color: style.color, fontSize: style.textStyle.fontSize * 1.3, fontWeight: 'bold', } ); style.dialogModalLayer = { zIndex: 9999, display: 'flex', position: 'absolute', top: 0, left: 0, width: '100%', height: '100%', backgroundColor: 'rgba(0,0,0,0.6)', alignItems: 'flex-start', justifyContent: 'center', }; style.controlBox = { marginBottom: '1em', color: 'black', // This will apply for the calendar display: 'flex', flexDirection: 'row', alignItems: 'center', }; style.controlBoxLabel = { marginRight: '1em', width: '10em', display: 'inline-block', fontWeight: 'bold', }; style.controlBoxValue = { display: 'inline-block', }; style.dialogBox = { backgroundColor: style.backgroundColor, padding: 16, boxShadow: '6px 6px 20px rgba(0,0,0,0.5)', marginTop: 20, maxHeight: '80%', display: 'flex', flexDirection: 'column', }; style.buttonIconStyle = { color: style.iconColor, marginRight: 6, }; style.notificationBox = { backgroundColor: style.warningBackgroundColor, display: 'flex', alignItems: 'center', padding: 10, fontSize: style.fontSize, }; style.dialogTitle = Object.assign({}, style.h1Style, { marginBottom: '1.2em' }); style.dropdownList = Object.assign({}, style.inputStyle); style.colorHover = style.color; style.backgroundHover = `${style.selectedColor2}44`; // In general the highlighted color, used to highlight text or icons, should be the same as selectedColor2 // but some times, depending on the theme, it might be too dark or too light, so it can be // specified directly by the theme too. if (!style.highlightedColor) style.highlightedColor = style.selectedColor2; return style; } const themeCache_: any = {}; export function themeStyle(themeId: number) { if (!themeId) throw new Error('Theme must be specified'); const zoomRatio = 1; const cacheKey = themeId; if (themeCache_[cacheKey]) return themeCache_[cacheKey]; // Font size are not theme specific, but they must be referenced // and computed here to allow them to respond to settings changes // without the need to restart const fontSizes: any = { fontSize: Math.round(12 * zoomRatio), toolbarIconSize: 18, }; fontSizes.noteViewerFontSize = Math.round(fontSizes.fontSize * 1.25); let output: any = {}; output.zoomRatio = zoomRatio; // All theme are based on the light style, and just override the // relevant properties output = Object.assign({}, globalStyle, fontSizes, themes[themeId]); output = addMissingProperties(output); output = addExtraStyles(output); output.cacheKey = cacheKey; themeCache_[cacheKey] = output; return themeCache_[cacheKey]; } const cachedStyles_: any = { themeId: null, styles: {}, }; // cacheKey must be a globally unique key, and must change whenever // the dependencies of the style change. If the style depends only // on the theme, a static string can be provided as a cache key. export function buildStyle(cacheKey: any, themeId: number, callback: Function) { cacheKey = Array.isArray(cacheKey) ? cacheKey.join('_') : cacheKey; // We clear the cache whenever switching themes if (cachedStyles_.themeId !== themeId) { cachedStyles_.themeId = themeId; cachedStyles_.styles = {}; } if (cachedStyles_.styles[cacheKey]) return cachedStyles_.styles[cacheKey].style; const s = callback(themeStyle(themeId)); cachedStyles_.styles[cacheKey] = { style: s, timestamp: Date.now(), }; return cachedStyles_.styles[cacheKey].style; }