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

feat: Improved preload variable handling #1723

Merged
merged 4 commits into from
Jan 11, 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
27 changes: 21 additions & 6 deletions packages/code-studio/src/styleguide/SpectrumComparison.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import {
RadioItem,
Select,
} from '@deephaven/components';
import { EMPTY_FUNCTION } from '@deephaven/utils';
import {
SAMPLE_SECTION_E2E_IGNORE,
SPECTRUM_COMPARISON_SAMPLES_ID,
Expand Down Expand Up @@ -73,15 +74,21 @@ export function SpectrumComparison(): JSX.Element {

{buttons.map(([level, variant]) => (
<Fragment key={level}>
<BootstrapButtonOld kind={level}>Button</BootstrapButtonOld>
<BootstrapButtonOld onClick={EMPTY_FUNCTION} kind={level}>
Button
</BootstrapButtonOld>

<Button variant={variant} style="fill">
Button
</Button>
</Fragment>
))}

<BootstrapButtonOld kind="primary" disabled>
<BootstrapButtonOld
onClick={EMPTY_FUNCTION}
kind="primary"
disabled
>
Disabled
</BootstrapButtonOld>
<Button variant="accent" style="fill" isDisabled>
Expand All @@ -98,14 +105,20 @@ export function SpectrumComparison(): JSX.Element {

{buttons.map(([level, variant]) => (
<Fragment key={level}>
<BootstrapButtonOld kind={level}>{level}</BootstrapButtonOld>
<BootstrapButtonOld onClick={EMPTY_FUNCTION} kind={level}>
{level}
</BootstrapButtonOld>
<Button variant={variant} style="outline">
{variant}
</Button>
</Fragment>
))}

<BootstrapButtonOld kind="secondary" disabled>
<BootstrapButtonOld
onClick={EMPTY_FUNCTION}
kind="secondary"
disabled
>
Disabled
</BootstrapButtonOld>
<Button variant="primary" style="outline" isDisabled>
Expand All @@ -121,10 +134,12 @@ export function SpectrumComparison(): JSX.Element {
<label>Bootstrap</label>
<label>Spectrum</label>

<BootstrapButtonOld kind="inline">Inline</BootstrapButtonOld>
<BootstrapButtonOld onClick={EMPTY_FUNCTION} kind="inline">
Inline
</BootstrapButtonOld>
<ActionButton>Action</ActionButton>

<BootstrapButtonOld kind="inline" disabled>
<BootstrapButtonOld onClick={EMPTY_FUNCTION} kind="inline" disabled>
Disabled
</BootstrapButtonOld>
<ActionButton isDisabled>Disabled</ActionButton>
Expand Down
2 changes: 1 addition & 1 deletion packages/components/src/theme/ThemeModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ export const DEFAULT_LIGHT_THEME_KEY = 'default-light' satisfies BaseThemeKey;

// Hex versions of some of the default dark theme color palette needed for
// preload defaults.
const DEFAULT_DARK_THEME_PALETTE = {
export const DEFAULT_DARK_THEME_PALETTE = {
blue: {
500: '#2f5bc0',
400: '#254ba4',
Expand Down
15 changes: 11 additions & 4 deletions packages/components/src/theme/ThemeProvider.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import { createContext, ReactNode, useEffect, useMemo, useState } from 'react';
import Log from '@deephaven/log';
import { DEFAULT_DARK_THEME_KEY, ThemeData } from './ThemeModel';
import {
DEFAULT_DARK_THEME_KEY,
DEFAULT_PRELOAD_DATA_VARIABLES,
ThemeData,
} from './ThemeModel';
import {
calculatePreloadStyleContent,
getActiveThemes,
Expand Down Expand Up @@ -30,11 +34,13 @@ export interface ThemeProviderProps {
* tell the provider to activate the base themes.
*/
themes: ThemeData[] | null;
defaultPreloadValues?: Record<string, string>;
children: ReactNode;
}

export function ThemeProvider({
themes: customThemes,
defaultPreloadValues = DEFAULT_PRELOAD_DATA_VARIABLES,
children,
}: ThemeProviderProps): JSX.Element | null {
const baseThemes = useMemo(() => getDefaultBaseThemes(), []);
Expand Down Expand Up @@ -71,9 +77,10 @@ export function ThemeProvider({

// Override fill color for certain inline SVGs (the originals are provided
// by theme-svg.scss)
overrideSVGFillColors();
overrideSVGFillColors(defaultPreloadValues);

const preloadStyleContent = calculatePreloadStyleContent();
const preloadStyleContent =
calculatePreloadStyleContent(defaultPreloadValues);

log.debug2('updateThemePreloadData:', {
active: activeThemes.map(theme => theme.themeKey),
Expand All @@ -87,7 +94,7 @@ export function ThemeProvider({
preloadStyleContent,
});
},
[activeThemes, selectedThemeKey, customThemes]
[activeThemes, selectedThemeKey, customThemes, defaultPreloadValues]
);

useEffect(() => {
Expand Down
45 changes: 33 additions & 12 deletions packages/components/src/theme/ThemeUtils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,9 @@ describe('calculatePreloadStyleContent', () => {
}

it('should set defaults if css variables are not defined', () => {
expect(calculatePreloadStyleContent()).toEqual(
expectedContent(DEFAULT_PRELOAD_DATA_VARIABLES)
);
expect(
calculatePreloadStyleContent(DEFAULT_PRELOAD_DATA_VARIABLES)
).toEqual(expectedContent(DEFAULT_PRELOAD_DATA_VARIABLES));
});

it('should resolve css variables', () => {
Expand All @@ -71,7 +71,9 @@ describe('calculatePreloadStyleContent', () => {
);
document.body.style.setProperty('--dh-color-bg', 'orange');

expect(calculatePreloadStyleContent()).toEqual(
expect(
calculatePreloadStyleContent(DEFAULT_PRELOAD_DATA_VARIABLES)
).toEqual(
expectedContent({
...DEFAULT_PRELOAD_DATA_VARIABLES,
'--dh-color-loading-spinner-primary': 'pink',
Expand All @@ -96,7 +98,10 @@ describe.each([document.body, document.createElement('div')])(
it('should return empty string if property does not exist and no default value exists', () => {
asMock(computedStyle.getPropertyValue).mockReturnValue('');

const resolver = createCssVariableResolver(targetElement);
const resolver = createCssVariableResolver(
targetElement,
DEFAULT_PRELOAD_DATA_VARIABLES
);

expect(getComputedStyle).toHaveBeenCalledWith(targetElement);

Expand All @@ -113,7 +118,10 @@ describe.each([document.body, document.createElement('div')])(
(key, value) => {
asMock(computedStyle.getPropertyValue).mockReturnValue('');

const resolver = createCssVariableResolver(targetElement);
const resolver = createCssVariableResolver(
targetElement,
DEFAULT_PRELOAD_DATA_VARIABLES
);

expect(getComputedStyle).toHaveBeenCalledWith(targetElement);

Expand All @@ -126,7 +134,10 @@ describe.each([document.body, document.createElement('div')])(
name => `resolved:${name}`
);

const resolver = createCssVariableResolver(targetElement);
const resolver = createCssVariableResolver(
targetElement,
DEFAULT_PRELOAD_DATA_VARIABLES
);

expect(getComputedStyle).toHaveBeenCalledWith(targetElement);

Expand Down Expand Up @@ -387,7 +398,7 @@ describe('overrideSVGFillColors', () => {
: 'red'
);

overrideSVGFillColors();
overrideSVGFillColors(DEFAULT_PRELOAD_DATA_VARIABLES);

expect(getComputedStyle).toHaveBeenCalledWith(document.body);
expect(document.body.style.removeProperty).toHaveBeenCalledWith(key);
Expand Down Expand Up @@ -418,12 +429,22 @@ describe('preloadTheme', () => {

preloadTheme();

const styleEl = document.querySelector('style');
const [styleElDefaults, styleElPrevious] =
document.querySelectorAll('style');

expect(styleEl).not.toBeNull();
expect(styleEl?.innerHTML).toEqual(
preloadData?.preloadStyleContent ?? calculatePreloadStyleContent()
expect(styleElDefaults).not.toBeNull();
expect(styleElDefaults?.innerHTML).toEqual(
calculatePreloadStyleContent(DEFAULT_PRELOAD_DATA_VARIABLES)
);

if (preloadData?.preloadStyleContent == null) {
expect(styleElPrevious).toBeUndefined();
} else {
expect(styleElPrevious).toBeDefined();
expect(styleElPrevious?.innerHTML).toEqual(
preloadData?.preloadStyleContent
);
}
});
});

Expand Down
79 changes: 60 additions & 19 deletions packages/components/src/theme/ThemeUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,19 @@ export type VarExpressionResolver = (varExpression: string) => string;
* happens before themes are fully loaded so that we can style things like the
* loading spinner and background color which are shown to the user early on in
* the app lifecycle.
* @defaultPreloadValues Default values to use if a preload variable is not set.
*/
export function calculatePreloadStyleContent(): CssVariableStyleContent {
const resolveVar = createCssVariableResolver(document.body);
export function calculatePreloadStyleContent(
defaultPreloadValues: Record<string, string>
): CssVariableStyleContent {
const resolveVar = createCssVariableResolver(
document.body,
defaultPreloadValues
);

// Calculate the current preload variables. If the variable is not set, use
// the default value.
const pairs = Object.keys(DEFAULT_PRELOAD_DATA_VARIABLES).map(
const pairs = Object.keys(defaultPreloadValues).map(
key => `${key}:${resolveVar(key as ThemePreloadColorVariable)}`
);

Expand All @@ -47,12 +53,14 @@ export function calculatePreloadStyleContent(): CssVariableStyleContent {
/**
* Create a resolver function for calculating the value of a css variable based
* on a given element's computed style. If the variable resolves to '', we check
* DEFAULT_PRELOAD_DATA_VARIABLES for a default value, and if one does not exist,
* `defaultValues` for a default value, and if one does not exist,
* return ''.
* @param el Element to resolve css variables against
* @param defaultValues Default values to use if a variable is not set.
*/
export function createCssVariableResolver(
el: Element
el: Element,
defaultValues: Record<string, string>
): (varName: ThemeCssVariableName) => string {
const computedStyle = getComputedStyle(el);

Expand All @@ -67,12 +75,25 @@ export function createCssVariableResolver(
return value;
}

return (
DEFAULT_PRELOAD_DATA_VARIABLES[varName as ThemePreloadColorVariable] ?? ''
);
return defaultValues[varName as ThemePreloadColorVariable] ?? '';
};
}

/**
* Create a style tag containing preload css variables and add to the head.
* @param id The id of the style tag
* @param preloadStyleContent The css variable content to add to the style tag
*/
export function createPreloadStyleElement(
id: `theme-preload-${string}`,
preloadStyleContent: CssVariableStyleContent
): void {
const style = document.createElement('style');
style.id = id;
style.innerHTML = preloadStyleContent;
document.head.appendChild(style);
}

/**
* Extracts all css variable expressions from the given record and returns
* a set of unique expressions.
Expand Down Expand Up @@ -378,18 +399,35 @@ export function getThemeKey(pluginName: string, themeName: string): string {

/**
* Preload minimal theme variables from the cache.
* @defaultPreloadValues Optional default values to use if a preload variable is not set.
*/
export function preloadTheme(): void {
const preloadStyleContent =
getThemePreloadData()?.preloadStyleContent ??
calculatePreloadStyleContent();
export function preloadTheme(
defaultPreloadValues: Record<string, string> = DEFAULT_PRELOAD_DATA_VARIABLES
): void {
const previousPreloadStyleContent =
getThemePreloadData()?.preloadStyleContent;

const defaultPreloadStyleContent =
calculatePreloadStyleContent(defaultPreloadValues);

log.debug('Preloading theme content:', {
defaultPreloadStyleContent,
previousPreloadStyleContent,
});

log.debug('Preloading theme content:', `'${preloadStyleContent}'`);
createPreloadStyleElement(
'theme-preload-defaults',
defaultPreloadStyleContent
);

const style = document.createElement('style');
style.id = 'theme-preload';
style.innerHTML = preloadStyleContent;
document.head.appendChild(style);
// Any preload variables that were saved by last theme load should override
// the defaults
if (previousPreloadStyleContent != null) {
createPreloadStyleElement(
'theme-preload-previous',
previousPreloadStyleContent
);
}
}

/**
Expand All @@ -406,9 +444,12 @@ export function preloadTheme(): void {
* just change the background color instead of relying on this util, but this
* is not always possible. e.g. <select> elements don't support pseudo elements,
* so there's not a good way to set icons via masks.
* @param defaultValues Default values to use if a variable is not set.
*/
export function overrideSVGFillColors(): void {
const resolveVar = createCssVariableResolver(document.body);
export function overrideSVGFillColors(
defaultValues: Record<string, string>
): void {
const resolveVar = createCssVariableResolver(document.body, defaultValues);

Object.entries(SVG_ICON_MANUAL_COLOR_MAP).forEach(([key, value]) => {
// Clear any previous override so that our variables get resolved against the
Expand Down
Loading