Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

inject app theme into plugin instead of styles #2703

Merged
merged 2 commits into from
Nov 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { mock } from '../../../../../testing';

describe('ThemeDirective', () => {
it('should create an instance', () => {
const directive = new ThemeDirective(mock(), mock());
const directive = new ThemeDirective(mock());
expect(directive).toBeTruthy();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
hexToRgbStr,
ICustomTheme,
ITheme,
getCSS,
} from 'altair-graphql-core/build/theme';

import { css } from '@emotion/css';
Expand All @@ -20,10 +21,7 @@ export class ThemeDirective implements OnInit, OnChanges {

private className = '';

constructor(
private themeRegistry: ThemeRegistryService,
private nzConfigService: NzConfigService
) {}
constructor(private nzConfigService: NzConfigService) {}

ngOnInit() {
this.applyTheme(this.appTheme, this.appDarkTheme, this.appAccentColor);
Expand All @@ -43,118 +41,12 @@ export class ThemeDirective implements OnInit, OnChanges {
}
}

getCssString(theme: ITheme) {
return `
--baseline-size: ${theme.type.fontSize.base};
--rem-base: ${theme.type.fontSize.remBase};
--body-font-size: ${theme.type.fontSize.body};

--app-easing: ${theme.easing};

--black-color: ${theme.colors.black};
--dark-grey-color: ${theme.colors.darkGray};
--grey-color: ${theme.colors.gray};
--light-grey-color: ${theme.colors.lightGray};
--white-color: ${theme.colors.white};
--green-color: ${theme.colors.green};
--blue-color: ${theme.colors.blue};
--cerise-color: ${theme.colors.cerise};
--red-color: ${theme.colors.red};
--rose-color: ${theme.colors.rose};
--orange-color: ${theme.colors.orange};
--yellow-color: ${theme.colors.yellow};
--light-red-color: ${theme.colors.lightRed};
--dark-purple-color: ${theme.colors.darkPurple};

--primary-color: ${theme.colors.primary};
--secondary-color: ${theme.colors.secondary};
--tertiary-color: ${theme.colors.tertiary};

--shadow-bg: rgba(${hexToRgbStr(theme.shadow.color)}, ${theme.shadow.opacity});

--font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", 'Helvetica Neue', Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";

--rgb-black: ${hexToRgbStr(theme.colors.black)};
--rgb-dark-grey: ${hexToRgbStr(theme.colors.darkGray)};
--rgb-grey: ${hexToRgbStr(theme.colors.gray)};
--rgb-light-grey: ${hexToRgbStr(theme.colors.lightGray)};
--rgb-white: ${hexToRgbStr(theme.colors.white)};
--rgb-green: ${hexToRgbStr(theme.colors.green)};
--rgb-blue: ${hexToRgbStr(theme.colors.blue)};
--rgb-cerise: ${hexToRgbStr(theme.colors.cerise)};
--rgb-red: ${hexToRgbStr(theme.colors.red)};
--rgb-orange: ${hexToRgbStr(theme.colors.orange)};
--rgb-yellow: ${hexToRgbStr(theme.colors.yellow)};
--rgb-light-red: ${hexToRgbStr(theme.colors.lightRed)};
--rgb-dark-purple: ${hexToRgbStr(theme.colors.darkPurple)};

--editor-font-family: ${theme.editor.fontFamily.default};
--editor-font-size: ${theme.editor.fontSize};

--theme-bg-color: ${theme.colors.bg};
--theme-off-bg-color: ${theme.colors.offBg};
--theme-font-color: ${theme.colors.font};
--theme-off-font-color: ${theme.colors.offFont};
--theme-border-color: ${theme.colors.border};
--theme-off-border-color: ${theme.colors.offBorder};
--header-bg-color: ${theme.colors.headerBg || theme.colors.offBg};

--rgb-primary: ${hexToRgbStr(theme.colors.primary)};
--rgb-secondary: ${hexToRgbStr(theme.colors.secondary)};
--rgb-tertiary: ${hexToRgbStr(theme.colors.tertiary)};

--rgb-theme-bg: ${hexToRgbStr(theme.colors.bg)};
--rgb-theme-off-bg: ${hexToRgbStr(theme.colors.offBg)};
--rgb-theme-font: ${hexToRgbStr(theme.colors.font)};
--rgb-theme-off-font: ${hexToRgbStr(theme.colors.offFont)};
--rgb-theme-border: ${hexToRgbStr(theme.colors.border)};
--rgb-theme-off-border: ${hexToRgbStr(theme.colors.offBorder)};
--rgb-header-bg: ${hexToRgbStr(theme.colors.headerBg || theme.colors.offBg)};

--editor-comment-color: ${theme.editor.colors.comment};
--editor-string-color: ${theme.editor.colors.string};
--editor-number-color: ${theme.editor.colors.number};
--editor-variable-color: ${theme.editor.colors.variable};
--editor-attribute-color: ${theme.editor.colors.attribute};
--editor-keyword-color: ${theme.editor.colors.keyword};
--editor-atom-color: ${theme.editor.colors.atom};
--editor-property-color: ${theme.editor.colors.property};
--editor-punctuation-color: ${theme.editor.colors.punctuation};
--editor-cursor-color: ${theme.editor.colors.cursor};
--editor-def-color: ${theme.editor.colors.definition};
--editor-builtin-color: ${theme.editor.colors.builtin};
`;
}

getDynamicClassName(
appTheme: ICustomTheme,
appDarkTheme?: ICustomTheme,
accentColor?: string
) {
const extraTheme = accentColor ? { colors: { primary: accentColor } } : {};
if (appTheme && appDarkTheme) {
return css(`
${this.getCssString(createTheme(appTheme, extraTheme))}
@media (prefers-color-scheme: dark) {
${this.getCssString(createTheme(appDarkTheme, extraTheme))}
}
`);
}

if (!appTheme || appTheme.isSystem) {
return css(`
${this.getCssString(
createTheme(this.themeRegistry.getTheme('light')!, appTheme, extraTheme)
)}
@media (prefers-color-scheme: dark) {
${this.getCssString(
createTheme(this.themeRegistry.getTheme('dark')!, appTheme, extraTheme)
)}
}
`);
}

return css(this.getCssString(createTheme(appTheme, extraTheme)));
return css(getCSS(appTheme, appDarkTheme, accentColor));
}

applyTheme(theme: ICustomTheme, darkTheme?: ICustomTheme, accentColor?: string) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -314,7 +314,19 @@ export class PluginContextService implements PluginContextGenerator {
const sanitized = DOMPurify.default.sanitize(manifest.icon.src);
iconSvg = self.sanitizer.bypassSecurityTrustHtml(sanitized);
}
const engine = new PluginParentEngine(panelWorker);

const settings = await firstValueFrom(
self.store.select('settings').pipe(take(1))
);
const selectedTheme = self.themeRegistryService.getTheme(settings.theme) || {
isSystem: true,
};
const settingsThemeConfig = settings.themeConfig || {};
const theme = self.themeRegistryService.mergeThemes(
selectedTheme,
settingsThemeConfig
);
const engine = new PluginParentEngine(panelWorker, { theme });
const panel = new AltairPanel(
title,
panelWorker.getIframe(),
Expand Down
10 changes: 8 additions & 2 deletions packages/altair-core/src/plugin/v3/panel.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { ICustomTheme, getCSS } from '../../theme';
import { PluginV3Context } from './context';

interface StylesData {
styleUrls: string[];
styles: string[];
htmlClasses: string[];
theme?: ICustomTheme;
}
export abstract class AltairV3Panel {
abstract create(ctx: PluginV3Context, container: HTMLElement): void;
Expand Down Expand Up @@ -35,7 +37,11 @@ export abstract class AltairV3Panel {
document.head.appendChild(link);
});

this.injectCSS(data.styles.join('\n'));
if (data.styles.length) {
this.injectCSS(data.styles.join('\n'));
} else if (data.theme) {
this.injectCSS(getCSS(data.theme));
}

// set the background color of the panel to the theme background color
this.injectCSS(`
Expand All @@ -51,7 +57,7 @@ export abstract class AltairV3Panel {

private injectCSS(css: string) {
let el = document.createElement('style');
el.innerText = css;
el.innerText = css.replace(/[\n\r]/g, '');
document.head.appendChild(el);
return el;
}
Expand Down
38 changes: 27 additions & 11 deletions packages/altair-core/src/plugin/v3/parent-engine.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { ICustomTheme } from '../../theme';
import { CreateActionOptions } from '../context/context.interface';
import { PluginEventPayloadMap } from '../event/event.interfaces';
import { PluginV3Context } from './context';
Expand All @@ -20,11 +21,33 @@ const mainInstanceOnlyEvents: (keyof PluginV3Context)[] = [
// methods to be excluded from the generic listener creation since they are handled specially
const speciallyHandledMethods: (keyof PluginV3Context)[] = ['on', 'createAction'];

function getCssStyles(relevantClasses: string[]) {
try {
const styleSheets = Array.from(document.styleSheets);
return styleSheets
.map((sheet) => {
return Array.from(sheet.cssRules)
.map((rule) => rule.cssText)
.join('');
})
.filter((css) => {
return relevantClasses.some((htmlClass) => css.includes(`.${htmlClass}`));
});
} catch {
return [];
}
}
interface PluginParentEngineOptions {
theme?: ICustomTheme;
}
export class PluginParentEngine {
private context?: PluginV3Context;
subscribedEvents: string[] = [];

constructor(private worker: PluginParentWorker) {}
constructor(
private worker: PluginParentWorker,
private opts?: PluginParentEngineOptions
) {}

start(context: PluginV3Context) {
this.context = context;
Expand Down Expand Up @@ -101,17 +124,10 @@ export class PluginParentEngine {

const htmlClasses = Array.from(document.documentElement.classList);
// Get the styles that are applicable to the current theme of the page
const styles = styleSheets
.map((sheet) => {
return Array.from(sheet.cssRules)
.map((rule) => rule.cssText)
.join('');
})
.filter((css) => {
return htmlClasses.some((htmlClass) => css.includes(`.${htmlClass}`));
});
// Doesn't work crossorigin cases. e.g. when loading from CDN. Fallback to theme instead.
const styles = getCssStyles(htmlClasses);

return { styleUrls, styles, htmlClasses };
return { styleUrls, styles, htmlClasses, theme: this.opts?.theme };
});
}

Expand Down
130 changes: 130 additions & 0 deletions packages/altair-core/src/theme/css.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
import lightTheme from './defaults/light';
import darkTheme from './defaults/dark';
import { ICustomTheme, ITheme, createTheme, hexToRgbStr } from './theme';

const COLOR_VARS = {
// Base colors
'black-color': (t: ITheme) => t.colors.black,
'dark-grey-color': (t: ITheme) => t.colors.darkGray,
'grey-color': (t: ITheme) => t.colors.gray,
'light-grey-color': (t: ITheme) => t.colors.lightGray,
'white-color': (t: ITheme) => t.colors.white,
'green-color': (t: ITheme) => t.colors.green,
'blue-color': (t: ITheme) => t.colors.blue,
'cerise-color': (t: ITheme) => t.colors.cerise,
'red-color': (t: ITheme) => t.colors.red,
'rose-color': (t: ITheme) => t.colors.rose,
'orange-color': (t: ITheme) => t.colors.orange,
'yellow-color': (t: ITheme) => t.colors.yellow,
'light-red-color': (t: ITheme) => t.colors.lightRed,
'dark-purple-color': (t: ITheme) => t.colors.darkPurple,

'primary-color': (t: ITheme) => t.colors.primary,
'secondary-color': (t: ITheme) => t.colors.secondary,
'tertiary-color': (t: ITheme) => t.colors.tertiary,

'theme-bg-color': (t: ITheme) => t.colors.bg,
'theme-off-bg-color': (t: ITheme) => t.colors.offBg,
'theme-font-color': (t: ITheme) => t.colors.font,
'theme-off-font-color': (t: ITheme) => t.colors.offFont,
'theme-border-color': (t: ITheme) => t.colors.border,
'theme-off-border-color': (t: ITheme) => t.colors.offBorder,
'header-bg-color': (t: ITheme) => t.colors.headerBg || t.colors.offBg,

'editor-comment-color': (t: ITheme) => t.editor.colors.comment,
'editor-string-color': (t: ITheme) => t.editor.colors.string,
'editor-number-color': (t: ITheme) => t.editor.colors.number,
'editor-variable-color': (t: ITheme) => t.editor.colors.variable,
'editor-attribute-color': (t: ITheme) => t.editor.colors.attribute,
'editor-keyword-color': (t: ITheme) => t.editor.colors.keyword,
'editor-atom-color': (t: ITheme) => t.editor.colors.atom,
'editor-property-color': (t: ITheme) => t.editor.colors.property,
'editor-punctuation-color': (t: ITheme) => t.editor.colors.punctuation,
'editor-cursor-color': (t: ITheme) => t.editor.colors.cursor,
'editor-def-color': (t: ITheme) => t.editor.colors.definition,
'editor-builtin-color': (t: ITheme) => t.editor.colors.builtin,
} as const;

const RGB_VARS = {
'rgb-black': (t: ITheme) => hexToRgbStr(t.colors.black),
'rgb-dark-grey': (t: ITheme) => hexToRgbStr(t.colors.darkGray),
'rgb-grey': (t: ITheme) => hexToRgbStr(t.colors.gray),
'rgb-light-grey': (t: ITheme) => hexToRgbStr(t.colors.lightGray),
'rgb-white': (t: ITheme) => hexToRgbStr(t.colors.white),
'rgb-green': (t: ITheme) => hexToRgbStr(t.colors.green),
'rgb-blue': (t: ITheme) => hexToRgbStr(t.colors.blue),
'rgb-cerise': (t: ITheme) => hexToRgbStr(t.colors.cerise),
'rgb-red': (t: ITheme) => hexToRgbStr(t.colors.red),
'rgb-rose': (t: ITheme) => hexToRgbStr(t.colors.rose),
'rgb-orange': (t: ITheme) => hexToRgbStr(t.colors.orange),
'rgb-yellow': (t: ITheme) => hexToRgbStr(t.colors.yellow),
'rgb-light-red': (t: ITheme) => hexToRgbStr(t.colors.lightRed),
'rgb-dark-purple': (t: ITheme) => hexToRgbStr(t.colors.darkPurple),

'rgb-primary': (t: ITheme) => hexToRgbStr(t.colors.primary),
'rgb-secondary': (t: ITheme) => hexToRgbStr(t.colors.secondary),
'rgb-tertiary': (t: ITheme) => hexToRgbStr(t.colors.tertiary),

'rgb-theme-bg': (t: ITheme) => hexToRgbStr(t.colors.bg),
'rgb-theme-off-bg': (t: ITheme) => hexToRgbStr(t.colors.offBg),
'rgb-theme-font': (t: ITheme) => hexToRgbStr(t.colors.font),
'rgb-theme-off-font': (t: ITheme) => hexToRgbStr(t.colors.offFont),
'rgb-theme-border': (t: ITheme) => hexToRgbStr(t.colors.border),
'rgb-theme-off-border': (t: ITheme) => hexToRgbStr(t.colors.offBorder),
'rgb-header-bg': (t: ITheme) => hexToRgbStr(t.colors.headerBg || t.colors.offBg),
// ... other rgb values
} as const;

const createVars = (mapping: Record<string, (t: ITheme) => string>, theme: ITheme) =>
Object.entries(mapping)
.map(([key, getValue]) => `--${key}: ${getValue(theme)};`)
.join('\n ');

const asCSSVariablesString = (theme: ITheme) => {
return `
:root {
--shadow-bg: rgba(${hexToRgbStr(theme.shadow.color)}, ${theme.shadow.opacity});

--font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", 'Helvetica Neue', Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";

--editor-font-family: ${theme.editor.fontFamily.default};
--editor-font-size: ${theme.editor.fontSize};

--baseline-size: ${theme.type.fontSize.base};
--rem-base: ${theme.type.fontSize.remBase};
--body-font-size: ${theme.type.fontSize.body};

--app-easing: ${theme.easing};

${createVars(COLOR_VARS, theme)}
${createVars(RGB_VARS, theme)}
}
`;
};

export const getCSS = (
appTheme: ICustomTheme,
appDarkTheme?: ICustomTheme,
accentColor?: string
) => {
const extraTheme = accentColor ? { colors: { primary: accentColor } } : {};
if (appTheme && appDarkTheme) {
return `
${asCSSVariablesString(createTheme(appTheme, extraTheme))}
@media (prefers-color-scheme: dark) {
${asCSSVariablesString(createTheme(appDarkTheme, extraTheme))}
}
`;
}

if (!appTheme || appTheme.isSystem) {
return `
${asCSSVariablesString(createTheme(lightTheme, appTheme, extraTheme))}
@media (prefers-color-scheme: dark) {
${asCSSVariablesString(createTheme(darkTheme, appTheme, extraTheme))}
}
`;
}

return asCSSVariablesString(createTheme(appTheme, extraTheme));
};
Loading
Loading