// @flow import { render, THROW_ON_ERROR } from '../common/render'; import { getThemes } from './index'; import type { Theme } from './index'; type ThemeBlock = { background?: { default: string, success?: string, notice?: string, warning?: string, danger?: string, surprise?: string, info?: string }, foreground?: { default: string, success?: string, notice?: string, warning?: string, danger?: string, surprise?: string, info?: string }, highlight?: { default: string, xxs?: string, xs?: string, sm?: string, md?: string, lg?: string, xl?: string } }; type ThemeInner = { ...ThemeBlock, rawCss?: string, styles: ?{ overlay?: ThemeBlock, dropdown?: ThemeBlock, tooltip?: ThemeBlock, sidebar?: ThemeBlock, sidebarHeader?: ThemeBlock, sidebarList?: ThemeBlock, sidebarActions?: ThemeBlock, pane?: ThemeBlock, paneHeader?: ThemeBlock, dialog?: ThemeBlock, dialogHeader?: ThemeBlock, dialogFooter?: ThemeBlock, transparentOverlay?: ThemeBlock, link?: ThemeBlock } }; export type PluginTheme = { name: string, displayName: string, theme: ThemeInner }; export async function generateThemeCSS(theme: PluginTheme): Promise { const renderedTheme: ThemeInner = await render( theme.theme, theme.theme, null, THROW_ON_ERROR, theme.name ); const n = theme.name; let css = ''; css += wrapStyles(n, '', getThemeBlockCSS(renderedTheme)); if (renderedTheme.styles) { const styles = renderedTheme.styles; // Dropdown Menus css += wrapStyles(n, '.theme--dropdown__menu', getThemeBlockCSS(styles.dialog)); css += wrapStyles(n, '.theme--dropdown__menu', getThemeBlockCSS(styles.dropdown)); // Tooltips css += wrapStyles(n, '.theme--tooltip', getThemeBlockCSS(styles.dialog)); css += wrapStyles(n, '.theme--tooltip', getThemeBlockCSS(styles.tooltip)); // Overlay css += wrapStyles( n, '.theme--transparent-overlay', getThemeBlockCSS(styles.transparentOverlay) ); // Dialogs css += wrapStyles(n, '.theme--dialog', getThemeBlockCSS(styles.dialog)); css += wrapStyles(n, '.theme--dialog__header', getThemeBlockCSS(styles.dialogHeader)); css += wrapStyles(n, '.theme--dialog__footer', getThemeBlockCSS(styles.dialogFooter)); // Panes css += wrapStyles(n, '.theme--pane', getThemeBlockCSS(styles.pane)); css += wrapStyles(n, '.theme--pane__header', getThemeBlockCSS(styles.paneHeader)); // Sidebar Styles css += wrapStyles(n, '.theme--sidebar', getThemeBlockCSS(styles.sidebar)); css += wrapStyles(n, '.theme--sidebar__list', getThemeBlockCSS(styles.sidebarList)); css += wrapStyles(n, '.theme--sidebar__header', getThemeBlockCSS(styles.sidebarHeader)); // Link css += wrapStyles(n, '.theme--link', getThemeBlockCSS(styles.link)); // HACK: Dialog styles for CodeMirror dialogs too css += wrapStyles(n, '.CodeMirror-info', getThemeBlockCSS(styles.dialog)); } return css; } function getThemeBlockCSS(block?: ThemeBlock): string { if (!block) { return ''; } const indent = '\t'; let css = ''; const addVar = (variable?: string, value?: string) => { if (variable && value) { css += `${indent}--${variable}: ${value};\n`; } }; const addComment = comment => { css += `${indent}/* ${comment} */\n`; }; const addNewLine = () => { css += `\n`; }; if (block.background) { const { background } = block; addComment('Background'); addVar('color-bg', background.default); addVar('color-success', background.success); addVar('color-notice', background.notice); addVar('color-warning', background.warning); addVar('color-danger', background.danger); addVar('color-surprise', background.surprise); addVar('color-info', background.info); addNewLine(); } if (block.foreground) { const { foreground } = block; addComment('Foreground'); addVar('color-font', foreground.default); addVar('color-font-success', foreground.success); addVar('color-font-notice', foreground.notice); addVar('color-font-warning', foreground.warning); addVar('color-font-danger', foreground.danger); addVar('color-font-surprise', foreground.surprise); addVar('color-font-info', foreground.info); addNewLine(); } if (block.highlight) { const { highlight } = block; addComment('Highlight'); addVar('hl', highlight.default); addVar('hl-xxs', highlight.xxs); addVar('hl-xs', highlight.xs); addVar('hl-sm', highlight.sm); addVar('hl-md', highlight.md); addVar('hl-lg', highlight.lg); addVar('hl-xl', highlight.xl); addNewLine(); } return css.replace(/\s+$/, ''); } function wrapStyles(theme: string, selector: string, styles: string) { if (!styles) { return ''; } return [ `[theme="${theme}"] ${selector}, `, `[subtheme="${theme}"] ${selector ? selector + '--sub' : ''} {`, styles, '}', '', '' ].join('\n'); } export async function setTheme(themeName: string) { if (!document) { return; } const head = document.head; const body = document.body; if (!head || !body) { return; } body.setAttribute('theme', themeName); const themes: Array = await getThemes(); for (const theme of themes) { let themeCSS = (await generateThemeCSS(theme.theme)) + '\n'; const { name } = theme.theme; const { rawCss } = theme.theme.theme; let s = document.querySelector(`style[data-theme-name="${name}"]`); if (!s) { s = document.createElement('style'); s.setAttribute('data-theme-name', name); head.appendChild(s); } if (typeof rawCss === 'string' && name === themeName) { themeCSS += '\n\n' + rawCss; } s.innerHTML = themeCSS; } } export async function setFont(settings: Object) { if (!document) { return; } const html = document.querySelector('html'); if (!html) { return; } html.style.setProperty('--font-default', settings.fontInterface); html.style.setProperty('--font-monospace', settings.fontMonospace); html.style.setProperty('--font-ligatures', settings.fontVariantLigatures ? 'normal' : 'none'); html.style.setProperty('font-size', `${settings.fontSize}px`); }