From 883bb70e7e0a836dd468c8eca18b588881bb3a81 Mon Sep 17 00:00:00 2001 From: Brian Ingles Date: Wed, 29 Nov 2023 08:39:42 -0600 Subject: [PATCH 01/12] Moved getThemeDataFromPlugins #1660 --- .../src/components/ThemeBootstrap.tsx | 3 +- .../src/plugins/PluginUtils.test.tsx | 104 --------------- .../app-utils/src/plugins/PluginUtils.tsx | 37 ------ packages/components/src/theme/ThemeUtils.ts | 1 + packages/plugin/package.json | 1 + packages/plugin/src/PluginTypes.ts | 2 + packages/plugin/src/PluginUtils.test.tsx | 118 +++++++++++++++++- packages/plugin/src/PluginUtils.tsx | 48 ++++++- packages/plugin/src/PluginsContext.ts | 4 +- packages/plugin/src/usePlugins.ts | 3 +- packages/plugin/tsconfig.json | 1 + 11 files changed, 170 insertions(+), 152 deletions(-) delete mode 100644 packages/app-utils/src/plugins/PluginUtils.test.tsx diff --git a/packages/app-utils/src/components/ThemeBootstrap.tsx b/packages/app-utils/src/components/ThemeBootstrap.tsx index 2beeabb2e0..2b97ecf69a 100644 --- a/packages/app-utils/src/components/ThemeBootstrap.tsx +++ b/packages/app-utils/src/components/ThemeBootstrap.tsx @@ -1,8 +1,7 @@ import { useContext, useMemo } from 'react'; import { ChartThemeProvider } from '@deephaven/chart'; import { ThemeProvider } from '@deephaven/components'; -import { PluginsContext } from '@deephaven/plugin'; -import { getThemeDataFromPlugins } from '../plugins'; +import { getThemeDataFromPlugins, PluginsContext } from '@deephaven/plugin'; export interface ThemeBootstrapProps { children: React.ReactNode; diff --git a/packages/app-utils/src/plugins/PluginUtils.test.tsx b/packages/app-utils/src/plugins/PluginUtils.test.tsx deleted file mode 100644 index d3551471f2..0000000000 --- a/packages/app-utils/src/plugins/PluginUtils.test.tsx +++ /dev/null @@ -1,104 +0,0 @@ -import { ThemeData } from '@deephaven/components'; -import { DashboardPlugin, PluginModule, ThemePlugin } from '@deephaven/plugin'; -import { getThemeDataFromPlugins } from './PluginUtils'; - -beforeEach(() => { - document.body.removeAttribute('style'); - document.head.innerHTML = ''; - jest.clearAllMocks(); - expect.hasAssertions(); -}); - -describe('getThemeDataFromPlugins', () => { - const themePluginSingleDark: ThemePlugin = { - name: 'mock.themePluginNameA', - type: 'ThemePlugin', - themes: { - name: 'mock.customDark', - baseTheme: 'dark', - styleContent: 'mock.styleContent', - }, - }; - - const themePluginSingleLight: ThemePlugin = { - name: 'mock.themePluginNameB', - type: 'ThemePlugin', - themes: { - name: 'mock.customLight', - baseTheme: 'light', - styleContent: 'mock.styleContent', - }, - }; - - const themePluginMultiConfig: ThemePlugin = { - name: 'mock.themePluginNameC', - type: 'ThemePlugin', - themes: [ - { - name: 'mock.customDark', - baseTheme: 'dark', - styleContent: 'mock.styleContent', - }, - { - name: 'mock.customLight', - baseTheme: 'light', - styleContent: 'mock.styleContent', - }, - { - name: 'mock.customUndefined', - styleContent: 'mock.styleContent', - }, - ], - }; - - const otherPlugin: DashboardPlugin = { - name: 'mock.otherPluginName', - type: 'DashboardPlugin', - component: () => null, - }; - - const pluginMap = new Map([ - ['mock.themePluginNameA', themePluginSingleDark], - ['mock.themePluginNameB', themePluginSingleLight], - ['mock.themePluginNameC', themePluginMultiConfig], - ['mock.otherPluginName', otherPlugin], - ]); - - it('should return theme data from plugins', () => { - const actual = getThemeDataFromPlugins(pluginMap); - const expected: ThemeData[] = [ - { - name: 'mock.customDark', - baseThemeKey: 'default-dark', - themeKey: 'mock.themePluginNameA_mock.customDark', - styleContent: 'mock.styleContent', - }, - { - name: 'mock.customLight', - baseThemeKey: 'default-light', - themeKey: 'mock.themePluginNameB_mock.customLight', - styleContent: 'mock.styleContent', - }, - { - name: 'mock.customDark', - baseThemeKey: 'default-dark', - themeKey: 'mock.themePluginNameC_mock.customDark', - styleContent: 'mock.styleContent', - }, - { - name: 'mock.customLight', - baseThemeKey: 'default-light', - themeKey: 'mock.themePluginNameC_mock.customLight', - styleContent: 'mock.styleContent', - }, - { - name: 'mock.customUndefined', - baseThemeKey: 'default-dark', - themeKey: 'mock.themePluginNameC_mock.customUndefined', - styleContent: 'mock.styleContent', - }, - ]; - - expect(actual).toEqual(expected); - }); -}); diff --git a/packages/app-utils/src/plugins/PluginUtils.tsx b/packages/app-utils/src/plugins/PluginUtils.tsx index a7098041b6..75003fc18f 100644 --- a/packages/app-utils/src/plugins/PluginUtils.tsx +++ b/packages/app-utils/src/plugins/PluginUtils.tsx @@ -1,4 +1,3 @@ -import { getThemeKey, ThemeData } from '@deephaven/components'; import Log from '@deephaven/log'; import { type PluginModuleMap, @@ -11,8 +10,6 @@ import { PluginType, isLegacyAuthPlugin, isLegacyPlugin, - isThemePlugin, - ThemePlugin, } from '@deephaven/plugin'; import loadRemoteModule from './loadRemoteModule'; @@ -172,37 +169,3 @@ export function getAuthPluginComponent( return component; } - -/** - * Extract theme data from theme plugins in the given plugin map. - * @param pluginMap - */ -export function getThemeDataFromPlugins( - pluginMap: PluginModuleMap -): ThemeData[] { - const themePluginEntries = [...pluginMap.entries()].filter( - (entry): entry is [string, ThemePlugin] => isThemePlugin(entry[1]) - ); - - log.debug('Getting theme data from plugins', themePluginEntries); - - return themePluginEntries - .map(([pluginName, plugin]) => { - // Normalize to an array since config can be an array of configs or a - // single config - const configs = Array.isArray(plugin.themes) - ? plugin.themes - : [plugin.themes]; - - return configs.map( - ({ name, baseTheme, styleContent }) => - ({ - baseThemeKey: `default-${baseTheme ?? 'dark'}`, - themeKey: getThemeKey(pluginName, name), - name, - styleContent, - }) as const - ); - }) - .flat(); -} diff --git a/packages/components/src/theme/ThemeUtils.ts b/packages/components/src/theme/ThemeUtils.ts index 85eec63020..3273f20c3a 100644 --- a/packages/components/src/theme/ThemeUtils.ts +++ b/packages/components/src/theme/ThemeUtils.ts @@ -344,6 +344,7 @@ export function preloadTheme(): void { log.debug('Preloading theme content:', `'${preloadStyleContent}'`); const style = document.createElement('style'); + style.id = 'theme-preload'; style.innerHTML = preloadStyleContent; document.head.appendChild(style); } diff --git a/packages/plugin/package.json b/packages/plugin/package.json index 49a2237868..e0ea0803a1 100644 --- a/packages/plugin/package.json +++ b/packages/plugin/package.json @@ -27,6 +27,7 @@ "@deephaven/icons": "file:../icons", "@deephaven/iris-grid": "file:../iris-grid", "@deephaven/jsapi-types": "file:../jsapi-types", + "@deephaven/log": "file:../log", "@deephaven/react-hooks": "file:../react-hooks", "@fortawesome/fontawesome-common-types": "^6.1.1", "@fortawesome/react-fontawesome": "^0.2.0" diff --git a/packages/plugin/src/PluginTypes.ts b/packages/plugin/src/PluginTypes.ts index 27cb33861a..15ea49f05a 100644 --- a/packages/plugin/src/PluginTypes.ts +++ b/packages/plugin/src/PluginTypes.ts @@ -42,6 +42,8 @@ export function isLegacyAuthPlugin( return 'AuthPlugin' in plugin; } +export type PluginModuleMap = Map; + /** * @deprecated Use TablePlugin instead */ diff --git a/packages/plugin/src/PluginUtils.test.tsx b/packages/plugin/src/PluginUtils.test.tsx index 27bbac972e..af34950ac6 100644 --- a/packages/plugin/src/PluginUtils.test.tsx +++ b/packages/plugin/src/PluginUtils.test.tsx @@ -1,8 +1,19 @@ import React from 'react'; -import { dhTruck, vsPreview } from '@deephaven/icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { DashboardPlugin, PluginType, type WidgetPlugin } from './PluginTypes'; -import { pluginSupportsType, getIconForPlugin } from './PluginUtils'; +import { ThemeData } from '@deephaven/components'; +import { dhTruck, vsPreview } from '@deephaven/icons'; +import { + type DashboardPlugin, + type PluginModule, + PluginType, + type ThemePlugin, + type WidgetPlugin, +} from './PluginTypes'; +import { + pluginSupportsType, + getIconForPlugin, + getThemeDataFromPlugins, +} from './PluginUtils'; function TestWidget() { return
TestWidget
; @@ -58,3 +69,104 @@ describe('getIconForPlugin', () => { expect(getIconForPlugin(customWidgetPlugin)).toEqual(customIcon); }); }); + +describe('getThemeDataFromPlugins', () => { + beforeEach(() => { + document.body.removeAttribute('style'); + document.head.innerHTML = ''; + jest.clearAllMocks(); + expect.hasAssertions(); + }); + + const themePluginSingleDark: ThemePlugin = { + name: 'mock.themePluginNameA', + type: 'ThemePlugin', + themes: { + name: 'mock.customDark', + baseTheme: 'dark', + styleContent: 'mock.styleContent', + }, + }; + + const themePluginSingleLight: ThemePlugin = { + name: 'mock.themePluginNameB', + type: 'ThemePlugin', + themes: { + name: 'mock.customLight', + baseTheme: 'light', + styleContent: 'mock.styleContent', + }, + }; + + const themePluginMultiConfig: ThemePlugin = { + name: 'mock.themePluginNameC', + type: 'ThemePlugin', + themes: [ + { + name: 'mock.customDark', + baseTheme: 'dark', + styleContent: 'mock.styleContent', + }, + { + name: 'mock.customLight', + baseTheme: 'light', + styleContent: 'mock.styleContent', + }, + { + name: 'mock.customUndefined', + styleContent: 'mock.styleContent', + }, + ], + }; + + const otherPlugin: DashboardPlugin = { + name: 'mock.otherPluginName', + type: 'DashboardPlugin', + component: () => null, + }; + + const pluginMap = new Map([ + ['mock.themePluginNameA', themePluginSingleDark], + ['mock.themePluginNameB', themePluginSingleLight], + ['mock.themePluginNameC', themePluginMultiConfig], + ['mock.otherPluginName', otherPlugin], + ]); + + it('should return theme data from plugins', () => { + const actual = getThemeDataFromPlugins(pluginMap); + const expected: ThemeData[] = [ + { + name: 'mock.customDark', + baseThemeKey: 'default-dark', + themeKey: 'mock.themePluginNameA_mock.customDark', + styleContent: 'mock.styleContent', + }, + { + name: 'mock.customLight', + baseThemeKey: 'default-light', + themeKey: 'mock.themePluginNameB_mock.customLight', + styleContent: 'mock.styleContent', + }, + { + name: 'mock.customDark', + baseThemeKey: 'default-dark', + themeKey: 'mock.themePluginNameC_mock.customDark', + styleContent: 'mock.styleContent', + }, + { + name: 'mock.customLight', + baseThemeKey: 'default-light', + themeKey: 'mock.themePluginNameC_mock.customLight', + styleContent: 'mock.styleContent', + }, + { + name: 'mock.customUndefined', + baseThemeKey: 'default-dark', + themeKey: 'mock.themePluginNameC_mock.customUndefined', + styleContent: 'mock.styleContent', + }, + ]; + + expect(actual).toEqual(expected); + }); +}); diff --git a/packages/plugin/src/PluginUtils.tsx b/packages/plugin/src/PluginUtils.tsx index 6fa9024153..a2ef110b6b 100644 --- a/packages/plugin/src/PluginUtils.tsx +++ b/packages/plugin/src/PluginUtils.tsx @@ -1,7 +1,17 @@ import { isValidElement } from 'react'; -import { vsPreview } from '@deephaven/icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { type PluginModule, isWidgetPlugin } from './PluginTypes'; +import { getThemeKey, type ThemeData } from '@deephaven/components'; +import { vsPreview } from '@deephaven/icons'; +import Log from '@deephaven/log'; +import { + type PluginModule, + isWidgetPlugin, + PluginModuleMap, + ThemePlugin, + isThemePlugin, +} from './PluginTypes'; + +const log = Log.module('@deephaven/plugin.PluginUtils'); export function pluginSupportsType( plugin: PluginModule | undefined, @@ -32,3 +42,37 @@ export function getIconForPlugin(plugin: PluginModule): React.ReactElement { return ; } + +/** + * Extract theme data from theme plugins in the given plugin map. + * @param pluginMap + */ +export function getThemeDataFromPlugins( + pluginMap: PluginModuleMap +): ThemeData[] { + const themePluginEntries = [...pluginMap.entries()].filter( + (entry): entry is [string, ThemePlugin] => isThemePlugin(entry[1]) + ); + + log.debug('Getting theme data from plugins', themePluginEntries); + + return themePluginEntries + .map(([pluginName, plugin]) => { + // Normalize to an array since config can be an array of configs or a + // single config + const configs = Array.isArray(plugin.themes) + ? plugin.themes + : [plugin.themes]; + + return configs.map( + ({ name, baseTheme, styleContent }) => + ({ + baseThemeKey: `default-${baseTheme ?? 'dark'}`, + themeKey: getThemeKey(pluginName, name), + name, + styleContent, + }) as const + ); + }) + .flat(); +} diff --git a/packages/plugin/src/PluginsContext.ts b/packages/plugin/src/PluginsContext.ts index 81232ec3b5..6c2c39f03c 100644 --- a/packages/plugin/src/PluginsContext.ts +++ b/packages/plugin/src/PluginsContext.ts @@ -1,7 +1,5 @@ import { createContext } from 'react'; -import { type PluginModule } from './PluginTypes'; - -export type PluginModuleMap = Map; +import { type PluginModuleMap } from './PluginTypes'; export const PluginsContext = createContext(null); diff --git a/packages/plugin/src/usePlugins.ts b/packages/plugin/src/usePlugins.ts index 33bd7d8c2d..30f59235fb 100644 --- a/packages/plugin/src/usePlugins.ts +++ b/packages/plugin/src/usePlugins.ts @@ -1,5 +1,6 @@ import { useContextOrThrow } from '@deephaven/react-hooks'; -import { type PluginModuleMap, PluginsContext } from './PluginsContext'; +import { PluginsContext } from './PluginsContext'; +import { type PluginModuleMap } from './PluginTypes'; export function usePlugins(): PluginModuleMap { return useContextOrThrow( diff --git a/packages/plugin/tsconfig.json b/packages/plugin/tsconfig.json index 2965f3904c..b841f0614e 100644 --- a/packages/plugin/tsconfig.json +++ b/packages/plugin/tsconfig.json @@ -10,6 +10,7 @@ { "path": "../components" }, { "path": "../golden-layout" }, { "path": "../iris-grid" }, + { "path": "../log" }, { "path": "../jsapi-types" }, { "path": "../react-hooks" } ] From 1120f3e0064c6ecb8c1a7f87a35bb2a97a66429a Mon Sep 17 00:00:00 2001 From: Brian Ingles Date: Wed, 29 Nov 2023 09:43:46 -0600 Subject: [PATCH 02/12] Added theme picker to styleguide #1660 --- .../code-studio/src/styleguide/StyleGuide.tsx | 8 +++++- packages/components/src/theme/ThemePicker.tsx | 27 +++++++++++++++++++ .../components/src/theme/ThemeProvider.tsx | 23 ++++++++++------ .../components/src/theme/ThemeUtils.test.ts | 5 ---- packages/components/src/theme/ThemeUtils.ts | 27 ++++++------------- .../__snapshots__/ThemeProvider.test.tsx.snap | 9 +++++-- packages/components/src/theme/index.ts | 1 + 7 files changed, 65 insertions(+), 35 deletions(-) create mode 100644 packages/components/src/theme/ThemePicker.tsx diff --git a/packages/code-studio/src/styleguide/StyleGuide.tsx b/packages/code-studio/src/styleguide/StyleGuide.tsx index 68bc01be25..cd3cdac065 100644 --- a/packages/code-studio/src/styleguide/StyleGuide.tsx +++ b/packages/code-studio/src/styleguide/StyleGuide.tsx @@ -1,7 +1,7 @@ /* eslint-disable react/jsx-props-no-spreading */ import React from 'react'; import { Flex } from '@adobe/react-spectrum'; -import { ContextMenuRoot } from '@deephaven/components'; +import { ContextMenuRoot, ThemePicker, useTheme } from '@deephaven/components'; import Alerts from './Alerts'; import Buttons from './Buttons'; @@ -40,6 +40,8 @@ const stickyProps = { function StyleGuide(): React.ReactElement { const isolateSection = window.location.search.includes('isolateSection=true'); + const { themes } = useTheme(); + const hasMultipleThemes = themes.length > 1; return (
@@ -67,7 +69,10 @@ function StyleGuide(): React.ReactElement { UNSAFE_className={HIDE_FROM_E2E_TESTS_CLASS} marginTop={-56} top={20} + gap={10} + alignItems="end" > + {hasMultipleThemes ? : null} diff --git a/packages/components/src/theme/ThemePicker.tsx b/packages/components/src/theme/ThemePicker.tsx new file mode 100644 index 0000000000..9169752285 --- /dev/null +++ b/packages/components/src/theme/ThemePicker.tsx @@ -0,0 +1,27 @@ +import { Key, useCallback } from 'react'; +import { Item, Picker } from '@adobe/react-spectrum'; +import useTheme from './useTheme'; + +export function ThemePicker(): JSX.Element | null { + const { selectedThemeKey, setSelectedThemeKey, themes } = useTheme(); + + const onSelectionChange = useCallback( + (key: Key) => { + setSelectedThemeKey(key as string); + }, + [setSelectedThemeKey] + ); + + return ( + + {item => {item.name}} + + ); +} + +export default ThemePicker; diff --git a/packages/components/src/theme/ThemeProvider.tsx b/packages/components/src/theme/ThemeProvider.tsx index abf61a9800..99b680721f 100644 --- a/packages/components/src/theme/ThemeProvider.tsx +++ b/packages/components/src/theme/ThemeProvider.tsx @@ -13,6 +13,7 @@ import { SpectrumThemeProvider } from './SpectrumThemeProvider'; export interface ThemeContextValue { activeThemes: ThemeData[] | null; selectedThemeKey: string; + themes: ThemeData[]; setSelectedThemeKey: (themeKey: string) => void; } @@ -31,7 +32,7 @@ export interface ThemeProviderProps { } export function ThemeProvider({ - themes, + themes: customThemes, children, }: ThemeProviderProps): JSX.Element { const baseThemes = useMemo(() => getDefaultBaseThemes(), []); @@ -43,19 +44,24 @@ export function ThemeProvider({ // Calculate active themes once a non-null themes array is provided. const activeThemes = useMemo( () => - themes == null + customThemes == null ? null : getActiveThemes(selectedThemeKey, { base: baseThemes, - custom: themes ?? [], + custom: customThemes ?? [], }), - [baseThemes, selectedThemeKey, themes] + [baseThemes, selectedThemeKey, customThemes] + ); + + const themes = useMemo( + () => [...baseThemes, ...(customThemes ?? [])], + [baseThemes, customThemes] ); useEffect( function updateThemePreloadData() { // Don't update preload data until themes have been loaded and activated - if (activeThemes == null || themes == null) { + if (activeThemes == null || customThemes == null) { return; } @@ -63,7 +69,7 @@ export function ThemeProvider({ log.debug2('updateThemePreloadData:', { active: activeThemes.map(theme => theme.themeKey), - all: themes.map(theme => theme.themeKey), + custom: customThemes.map(theme => theme.themeKey), preloadStyleContent, selectedThemeKey, }); @@ -73,16 +79,17 @@ export function ThemeProvider({ preloadStyleContent, }); }, - [activeThemes, selectedThemeKey, themes] + [activeThemes, selectedThemeKey, customThemes] ); const value = useMemo( () => ({ activeThemes, selectedThemeKey, + themes, setSelectedThemeKey, }), - [activeThemes, selectedThemeKey] + [activeThemes, selectedThemeKey, themes] ); return ( diff --git a/packages/components/src/theme/ThemeUtils.test.ts b/packages/components/src/theme/ThemeUtils.test.ts index 80e5234e8d..1328a824de 100644 --- a/packages/components/src/theme/ThemeUtils.test.ts +++ b/packages/components/src/theme/ThemeUtils.test.ts @@ -221,11 +221,6 @@ describe('getDefaultBaseThemes', () => { './theme-dark-components.css?raw', ].join('\n'), }, - { - name: 'Default Light', - themeKey: 'default-light', - styleContent: './theme-light-palette.css?raw', - }, ]); }); }); diff --git a/packages/components/src/theme/ThemeUtils.ts b/packages/components/src/theme/ThemeUtils.ts index 3273f20c3a..18042d7932 100644 --- a/packages/components/src/theme/ThemeUtils.ts +++ b/packages/components/src/theme/ThemeUtils.ts @@ -1,22 +1,8 @@ import Log from '@deephaven/log'; import { assertNotNull, ColorUtils } from '@deephaven/utils'; -// Note that ?inline imports are natively supported by Vite, but consumers of -// @deephaven/components using Webpack will need to add a rule to their module -// config. -// e.g. -// module: { -// rules: [ -// { -// resourceQuery: /inline/, -// type: 'asset/source', -// }, -// ], -// }, import { themeDark } from './theme-dark'; -import { themeLight } from './theme-light'; import { DEFAULT_DARK_THEME_KEY, - DEFAULT_LIGHT_THEME_KEY, DEFAULT_PRELOAD_DATA_VARIABLES, ThemeData, ThemePreloadData, @@ -125,11 +111,14 @@ export function getDefaultBaseThemes(): ThemeData[] { themeKey: DEFAULT_DARK_THEME_KEY, styleContent: themeDark, }, - { - name: 'Default Light', - themeKey: DEFAULT_LIGHT_THEME_KEY, - styleContent: themeLight, - }, + // The ThemePicker shows whenever more than 1 theme is available. Disable + // light theme for now to keep the picker hidden until it is fully + // implemented by #1539. + // { + // name: 'Default Light', + // themeKey: DEFAULT_LIGHT_THEME_KEY, + // styleContent: themeLight, + // }, ]; } diff --git a/packages/components/src/theme/__snapshots__/ThemeProvider.test.tsx.snap b/packages/components/src/theme/__snapshots__/ThemeProvider.test.tsx.snap index bd68d2ab79..a4c3b2b601 100644 --- a/packages/components/src/theme/__snapshots__/ThemeProvider.test.tsx.snap +++ b/packages/components/src/theme/__snapshots__/ThemeProvider.test.tsx.snap @@ -4,9 +4,14 @@ exports[`ThemeProvider setSelectedThemeKey: [ [Object] ] should change selected
Date: Wed, 29 Nov 2023 09:58:58 -0600 Subject: [PATCH 03/12] Added ThemePicker to SettingsMenu #1660 --- .../code-studio/src/settings/SettingsMenu.tsx | 40 ++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/packages/code-studio/src/settings/SettingsMenu.tsx b/packages/code-studio/src/settings/SettingsMenu.tsx index 474cdc19b6..c61ec5c0b3 100644 --- a/packages/code-studio/src/settings/SettingsMenu.tsx +++ b/packages/code-studio/src/settings/SettingsMenu.tsx @@ -6,16 +6,24 @@ import { vsRecordKeys, vsInfo, vsLayers, + vsPaintcan, dhUserIncognito, dhUser, } from '@deephaven/icons'; -import { Button, CopyButton, Tooltip } from '@deephaven/components'; +import { + Button, + CopyButton, + ThemeContext, + ThemePicker, + Tooltip, +} from '@deephaven/components'; import { ServerConfigValues, User } from '@deephaven/redux'; import { BROADCAST_CHANNEL_NAME, BROADCAST_LOGOUT_MESSAGE, makeMessage, } from '@deephaven/jsapi-utils'; +import { assertNotNull } from '@deephaven/utils'; import Logo from './community-wordmark-app.svg'; import FormattingSectionContent from './FormattingSectionContent'; import LegalNotice from './LegalNotice'; @@ -51,6 +59,8 @@ export class SettingsMenu extends Component< static SHORTCUT_SECTION_KEY = 'SettingsMenu.shortcuts'; + static THEME_SECTION_KEY = 'SettingsMenu.theme'; + static focusFirstInputInContainer(container: HTMLDivElement | null): void { const input = container?.querySelector('input, select, textarea'); if (input) { @@ -232,6 +242,34 @@ export class SettingsMenu extends Component< + + {contextValue => { + assertNotNull(contextValue, 'ThemeContext value is null'); + + return contextValue.themes.length > 1 ? ( + + + Theme + + } + > + + + ) : null; + }} + + Date: Wed, 29 Nov 2023 12:30:04 -0600 Subject: [PATCH 04/12] Support local vite config #1660 --- .gitignore | 1 + README.md | 30 ++++++++++++++++++++++++++++++ tsconfig.eslint.json | 2 ++ 3 files changed, 33 insertions(+) diff --git a/.gitignore b/.gitignore index 216ba60082..a9ccc179b1 100644 --- a/.gitignore +++ b/.gitignore @@ -41,3 +41,4 @@ packages/*/package-lock.json /playwright/.cache/ /tests/*-snapshots/* !/tests/*-snapshots/*-linux* +vite.config.local.ts \ No newline at end of file diff --git a/README.md b/README.md index 830cd6c656..6f662667ca 100644 --- a/README.md +++ b/README.md @@ -52,6 +52,36 @@ If your DHC address is different from the default `http://localhost:10000`, edit VITE_PROXY_URL=http://: ``` +## Local Vite Config +If you'd like to override the vite config for local dev, you can define a `packages/code-studio/vite.config.local.ts` file that extends from `vite.config.local`. This file is excluded via `.gitignore` which makes it easy to keep local overrides in tact. + +The config can be used by running: + +`npm run start:app -- -- -- --config=vite.config.local.ts` + +For example, to proxy `js-plugins` requests to a local server, you could use this `vite.config.local.ts`: + +```typescript +export default defineConfig((config: ConfigEnv) => { + const baseConfig = (createBaseConfig as UserConfigFn)(config) as UserConfig; + + return { + ...baseConfig, + server: { + ...baseConfig.server, + proxy: { + ...baseConfig.server?.proxy, + '/js-plugins': { + target: 'http://localhost:5173', + changeOrigin: true, + rewrite: path => path.replace(/^\/js-plugins/, ''), + }, + }, + }, + }; +}); +``` + ## Debugging from VSCode We have a pre-defined launch config that lets you set breakpoints directly in VSCode for debugging browser code. The `Launch Deephaven` config will launch a new Chrome window that stores its data in your repo workspace. With this setup, you only need to install the React and Redux devtool extensions once. They will persist to future launches using the launch config. diff --git a/tsconfig.eslint.json b/tsconfig.eslint.json index 8fda64ef51..8cd9e023ba 100644 --- a/tsconfig.eslint.json +++ b/tsconfig.eslint.json @@ -5,6 +5,8 @@ "packages/*/src/**/**.tsx", "packages/*/src/**/**.js", "packages/*/src/**/**.jsx", + /* Vite config including any local overrides (e.g. vite.config.local.ts) */ + "packages/*/vite.config*.ts", "tests/**/**.ts" ], "exclude": [] From 0773b57447165d183b6f3485a5ad77a1ab4d5d36 Mon Sep 17 00:00:00 2001 From: Brian Ingles Date: Wed, 29 Nov 2023 17:10:30 -0600 Subject: [PATCH 05/12] Refresh irisgrid and monaco on theme change #1660 --- packages/app-utils/package.json | 1 + .../src/components/ThemeBootstrap.tsx | 8 ++++- packages/app-utils/tsconfig.json | 1 + packages/code-studio/src/styleguide/Grids.tsx | 6 ++-- .../src/styleguide/ThemeColors.tsx | 33 +++++++++++------ packages/code-studio/src/styleguide/index.tsx | 9 +++-- packages/console/src/index.ts | 1 + .../src/monaco/MonacoThemeProvider.tsx | 25 +++++++++++++ packages/console/src/monaco/MonacoUtils.ts | 21 +++++++---- packages/console/src/monaco/index.ts | 1 + packages/grid/src/Grid.tsx | 4 +-- packages/grid/src/GridThemeContext.ts | 9 +++++ packages/grid/src/ThemeContext.ts | 7 ---- packages/grid/src/index.ts | 2 +- packages/iris-grid/src/IrisGrid.tsx | 11 +++++- .../iris-grid/src/IrisGridThemeProvider.tsx | 35 +++++++++++++++++++ packages/iris-grid/src/index.ts | 1 + 17 files changed, 140 insertions(+), 35 deletions(-) create mode 100644 packages/console/src/monaco/MonacoThemeProvider.tsx create mode 100644 packages/grid/src/GridThemeContext.ts delete mode 100644 packages/grid/src/ThemeContext.ts create mode 100644 packages/iris-grid/src/IrisGridThemeProvider.tsx diff --git a/packages/app-utils/package.json b/packages/app-utils/package.json index e2503c39a7..dd789e4f20 100644 --- a/packages/app-utils/package.json +++ b/packages/app-utils/package.json @@ -32,6 +32,7 @@ "@deephaven/auth-plugins": "file:../auth-plugins", "@deephaven/chart": "file:../chart", "@deephaven/components": "file:../components", + "@deephaven/console": "file:../console", "@deephaven/dashboard": "file:../dashboard", "@deephaven/icons": "file:../icons", "@deephaven/iris-grid": "file:../iris-grid", diff --git a/packages/app-utils/src/components/ThemeBootstrap.tsx b/packages/app-utils/src/components/ThemeBootstrap.tsx index 2b97ecf69a..9d683ad984 100644 --- a/packages/app-utils/src/components/ThemeBootstrap.tsx +++ b/packages/app-utils/src/components/ThemeBootstrap.tsx @@ -1,6 +1,8 @@ import { useContext, useMemo } from 'react'; import { ChartThemeProvider } from '@deephaven/chart'; +import { MonacoThemeProvider } from '@deephaven/console'; import { ThemeProvider } from '@deephaven/components'; +import { IrisGridThemeProvider } from '@deephaven/iris-grid'; import { getThemeDataFromPlugins, PluginsContext } from '@deephaven/plugin'; export interface ThemeBootstrapProps { @@ -21,7 +23,11 @@ export function ThemeBootstrap({ children }: ThemeBootstrapProps): JSX.Element { return ( - {children} + + + {children} + + ); } diff --git a/packages/app-utils/tsconfig.json b/packages/app-utils/tsconfig.json index 5f14608102..48d570ad4c 100644 --- a/packages/app-utils/tsconfig.json +++ b/packages/app-utils/tsconfig.json @@ -10,6 +10,7 @@ { "path": "../auth-plugins" }, { "path": "../chart" }, { "path": "../components" }, + { "path": "../console" }, { "path": "../dashboard" }, { "path": "../iris-grid" }, { "path": "../jsapi-bootstrap" }, diff --git a/packages/code-studio/src/styleguide/Grids.tsx b/packages/code-studio/src/styleguide/Grids.tsx index 0c16a7e015..dc2f1a68c1 100644 --- a/packages/code-studio/src/styleguide/Grids.tsx +++ b/packages/code-studio/src/styleguide/Grids.tsx @@ -5,7 +5,7 @@ import { GridThemeType, MockGridModel, MockTreeGridModel, - ThemeContext, + GridThemeContext, } from '@deephaven/grid'; import { IrisGrid } from '@deephaven/iris-grid'; import { useApi } from '@deephaven/jsapi-bootstrap'; @@ -38,7 +38,7 @@ function Grids(): ReactElement { }); return (
- +

Grid

@@ -81,7 +81,7 @@ function Grids(): ReactElement { -
+
); } diff --git a/packages/code-studio/src/styleguide/ThemeColors.tsx b/packages/code-studio/src/styleguide/ThemeColors.tsx index f22a70e748..dbd51d6028 100644 --- a/packages/code-studio/src/styleguide/ThemeColors.tsx +++ b/packages/code-studio/src/styleguide/ThemeColors.tsx @@ -1,6 +1,6 @@ /* eslint-disable react/jsx-props-no-spreading */ -import React from 'react'; -import { Tooltip } from '@deephaven/components'; +import React, { useEffect, useState } from 'react'; +import { Tooltip, useTheme } from '@deephaven/components'; import { ColorUtils } from '@deephaven/utils'; import palette from '@deephaven/components/src/theme/theme-dark/theme-dark-palette.css?inline'; import semantic from '@deephaven/components/src/theme/theme-dark/theme-dark-semantic.css?inline'; @@ -75,16 +75,29 @@ const renameGroups = { }, }; -const swatchDataGroups = { - 'Theme Color Palette': buildColorGroups(palette, 1), - 'Semantic Colors': buildColorGroups(semantic, 1, renameGroups.semantic), - 'Chart Colors': buildColorGroups(chart, 2, renameGroups.chart), - 'Editor Colors': buildColorGroups(semanticEditor, 2, renameGroups.editor), - 'Grid Colors': buildColorGroups(semanticGrid, 2, renameGroups.grid), - 'Component Colors': buildColorGroups(components, 1), -}; +function buildSwatchDataGroups() { + return { + 'Theme Color Palette': buildColorGroups(palette, 1), + 'Semantic Colors': buildColorGroups(semantic, 1, renameGroups.semantic), + 'Chart Colors': buildColorGroups(chart, 2, renameGroups.chart), + 'Editor Colors': buildColorGroups(semanticEditor, 2, renameGroups.editor), + 'Grid Colors': buildColorGroups(semanticGrid, 2, renameGroups.grid), + 'Component Colors': buildColorGroups(components, 1), + }; +} export function ThemeColors(): JSX.Element { + const [swatchDataGroups, setSwatchDataGroups] = useState( + buildSwatchDataGroups + ); + + const { selectedThemeKey } = useTheme(); + + useEffect(() => { + // Rebuild swatches when themes change + setSwatchDataGroups(buildSwatchDataGroups()); + }, [selectedThemeKey]); + return ( <> {Object.entries(swatchDataGroups).map(([label, data]) => ( diff --git a/packages/code-studio/src/styleguide/index.tsx b/packages/code-studio/src/styleguide/index.tsx index e2bb431cce..f35c3b3c06 100644 --- a/packages/code-studio/src/styleguide/index.tsx +++ b/packages/code-studio/src/styleguide/index.tsx @@ -7,6 +7,7 @@ import { ThemeData, ThemeProvider, } from '@deephaven/components'; +import { IrisGridThemeProvider } from '@deephaven/iris-grid'; import { ApiBootstrap } from '@deephaven/jsapi-bootstrap'; import logInit from '../log/LogInit'; @@ -35,9 +36,11 @@ ReactDOM.render( }> - - - + + + + + , diff --git a/packages/console/src/index.ts b/packages/console/src/index.ts index f9cef78978..e7d9db329a 100644 --- a/packages/console/src/index.ts +++ b/packages/console/src/index.ts @@ -6,6 +6,7 @@ export { default as ConsoleInput } from './ConsoleInput'; export { default as ConsoleMenu } from './ConsoleMenu'; export { default as SHORTCUTS } from './ConsoleShortcuts'; export { default as ConsoleStatusBar } from './ConsoleStatusBar'; +export * from './monaco/MonacoThemeProvider'; export { default as MonacoUtils } from './monaco/MonacoUtils'; export { default as Editor } from './notebook/Editor'; export { default as ScriptEditor } from './notebook/ScriptEditor'; diff --git a/packages/console/src/monaco/MonacoThemeProvider.tsx b/packages/console/src/monaco/MonacoThemeProvider.tsx new file mode 100644 index 0000000000..84e5da6296 --- /dev/null +++ b/packages/console/src/monaco/MonacoThemeProvider.tsx @@ -0,0 +1,25 @@ +import { ReactNode, useEffect } from 'react'; +import { useTheme } from '@deephaven/components'; +import MonacoUtils from './MonacoUtils'; + +export interface MonacoThemeProviderProps { + children: ReactNode; +} + +export function MonacoThemeProvider({ + children, +}: MonacoThemeProviderProps): JSX.Element { + const { activeThemes } = useTheme(); + + useEffect( + function refreshMonacoTheme() { + if (activeThemes != null) { + MonacoUtils.initTheme(); + } + }, + [activeThemes] + ); + + // eslint-disable-next-line react/jsx-no-useless-fragment + return <>{children}; +} diff --git a/packages/console/src/monaco/MonacoUtils.ts b/packages/console/src/monaco/MonacoUtils.ts index 1dca213375..28d69cdf1d 100644 --- a/packages/console/src/monaco/MonacoUtils.ts +++ b/packages/console/src/monaco/MonacoUtils.ts @@ -43,7 +43,20 @@ class MonacoUtils { MonacoUtils.registerGetWorker(getWorker); } - const { registerLanguages, removeHashtag } = MonacoUtils; + const { registerLanguages } = MonacoUtils; + + registerLanguages([DbLang, PyLang, GroovyLang, LogLang, ScalaLang]); + + MonacoUtils.removeConflictingKeybindings(); + + log.debug('Monaco initialized.'); + } + + /** + * Initialize current Monaco theme based on the current DH theme. + */ + static initTheme(): void { + const { removeHashtag } = MonacoUtils; const MonacoTheme = resolveCssVariablesInRecord(MonacoThemeRaw); log.debug2('Monaco theme:', MonacoThemeRaw); @@ -171,12 +184,6 @@ class MonacoUtils { ); monaco.editor.setTheme('vs-dark'); } - - registerLanguages([DbLang, PyLang, GroovyLang, LogLang, ScalaLang]); - - MonacoUtils.removeConflictingKeybindings(); - - log.debug('Monaco initialized.'); } /** diff --git a/packages/console/src/monaco/index.ts b/packages/console/src/monaco/index.ts index 98cf56f2af..8ad0b9f10a 100644 --- a/packages/console/src/monaco/index.ts +++ b/packages/console/src/monaco/index.ts @@ -1,3 +1,4 @@ export { default as MonacoUtils } from './MonacoUtils'; export { default as MonacoProviders } from './MonacoProviders'; export { default as MonacoTheme } from './MonacoTheme.module.scss'; +export * from './MonacoThemeProvider'; diff --git a/packages/grid/src/Grid.tsx b/packages/grid/src/Grid.tsx index fed4bd08c7..bcd3969355 100644 --- a/packages/grid/src/Grid.tsx +++ b/packages/grid/src/Grid.tsx @@ -57,7 +57,7 @@ import { } from './EditableGridModel'; import { EventHandlerResultOptions } from './EventHandlerResult'; import { assertIsDefined } from './errors'; -import ThemeContext from './ThemeContext'; +import GridThemeContext from './GridThemeContext'; import { DraggingColumn } from './mouse-handlers/GridColumnMoveMouseHandler'; import { EditingCell, @@ -211,7 +211,7 @@ export type GridState = { * Can also add onClick and onContextMenu handlers to add custom functionality and menus. */ class Grid extends PureComponent { - static contextType = ThemeContext; + static contextType = GridThemeContext; static defaultProps = { canvasOptions: { alpha: false } as CanvasRenderingContext2DSettings, diff --git a/packages/grid/src/GridThemeContext.ts b/packages/grid/src/GridThemeContext.ts new file mode 100644 index 0000000000..4c3fb3fa13 --- /dev/null +++ b/packages/grid/src/GridThemeContext.ts @@ -0,0 +1,9 @@ +import React from 'react'; +import { GridTheme as GridThemeType } from './GridTheme'; + +export type GridThemeContextValue = Partial; + +export const GridThemeContext: React.Context = + React.createContext({}); + +export default GridThemeContext; diff --git a/packages/grid/src/ThemeContext.ts b/packages/grid/src/ThemeContext.ts deleted file mode 100644 index 5fb3e3cd2e..0000000000 --- a/packages/grid/src/ThemeContext.ts +++ /dev/null @@ -1,7 +0,0 @@ -import React from 'react'; -import { GridTheme as GridThemeType } from './GridTheme'; - -export const ThemeContext: React.Context> = - React.createContext({}); - -export default ThemeContext; diff --git a/packages/grid/src/index.ts b/packages/grid/src/index.ts index a8d177b299..4143cee80e 100644 --- a/packages/grid/src/index.ts +++ b/packages/grid/src/index.ts @@ -25,7 +25,7 @@ export * from './key-handlers'; export * from './mouse-handlers'; export * from './errors'; export * from './EventHandlerResult'; -export { default as ThemeContext } from './ThemeContext'; +export * from './GridThemeContext'; export type { default as CellRenderer, CellRenderType } from './CellRenderer'; export { default as TextCellRenderer } from './TextCellRenderer'; export { default as DataBarCellRenderer } from './DataBarCellRenderer'; diff --git a/packages/iris-grid/src/IrisGrid.tsx b/packages/iris-grid/src/IrisGrid.tsx index b4bd389e17..7f7605097c 100644 --- a/packages/iris-grid/src/IrisGrid.tsx +++ b/packages/iris-grid/src/IrisGrid.tsx @@ -187,6 +187,7 @@ import { OperationMap, } from './CommonTypes'; import ColumnHeaderGroup from './ColumnHeaderGroup'; +import { IrisGridThemeContext } from './IrisGridThemeProvider'; const log = Log.module('IrisGrid'); @@ -437,6 +438,8 @@ export interface IrisGridState { } export class IrisGrid extends Component { + static contextType = IrisGridThemeContext; + static minDebounce = 150; static maxDebounce = 500; @@ -1359,6 +1362,7 @@ export class IrisGrid extends Component { getCachedTheme = memoize( ( + contextTheme: GridThemeType | null, theme: GridThemeType | null, isEditable: boolean, floatingRowCount: number @@ -1368,11 +1372,14 @@ export class IrisGrid extends Component { // We only show the row footers when we have floating rows for aggregations const rowFooterWidth = floatingRowCount > 0 - ? theme?.rowFooterWidth ?? defaultTheme.rowFooterWidth + ? theme?.rowFooterWidth ?? + contextTheme?.rowFooterWidth ?? + defaultTheme.rowFooterWidth : 0; return { ...defaultTheme, + ...contextTheme, ...theme, autoSelectRow: !isEditable, rowFooterWidth, @@ -1436,7 +1443,9 @@ export class IrisGrid extends Component { getTheme(): Partial { const { model, theme } = this.props; + return this.getCachedTheme( + this.context, theme, (isEditableGridModel(model) && model.isEditable) ?? false, model.floatingTopRowCount + model.floatingBottomRowCount diff --git a/packages/iris-grid/src/IrisGridThemeProvider.tsx b/packages/iris-grid/src/IrisGridThemeProvider.tsx new file mode 100644 index 0000000000..38c315e127 --- /dev/null +++ b/packages/iris-grid/src/IrisGridThemeProvider.tsx @@ -0,0 +1,35 @@ +import { useTheme } from '@deephaven/components'; +import { createContext, ReactNode, useEffect, useState } from 'react'; +import { createDefaultIrisGridTheme, IrisGridThemeType } from './IrisGridTheme'; + +export type IrisGridThemeContextValue = Partial; + +export const IrisGridThemeContext = + createContext(null); + +export interface IrisGridThemeProviderProps { + children: ReactNode; +} + +export function IrisGridThemeProvider({ + children, +}: IrisGridThemeProviderProps): JSX.Element { + const { activeThemes } = useTheme(); + + const [gridTheme, setGridTheme] = useState({}); + + useEffect( + function refreshIrisGridTheme() { + if (activeThemes != null) { + setGridTheme(createDefaultIrisGridTheme()); + } + }, + [activeThemes] + ); + + return ( + + {children} + + ); +} diff --git a/packages/iris-grid/src/index.ts b/packages/iris-grid/src/index.ts index 0d5efc174a..0f9df3920d 100644 --- a/packages/iris-grid/src/index.ts +++ b/packages/iris-grid/src/index.ts @@ -18,6 +18,7 @@ export * from './IrisGridTreeTableModel'; export { default as IrisGridModelFactory } from './IrisGridModelFactory'; export { createDefaultIrisGridTheme } from './IrisGridTheme'; export type { IrisGridThemeType } from './IrisGridTheme'; +export * from './IrisGridThemeProvider'; export { default as IrisGridTestUtils } from './IrisGridTestUtils'; export { default as IrisGridUtils } from './IrisGridUtils'; export * from './IrisGridUtils'; From a6eb9522ef9ceb309adc5473e98ff91e410dcaaf Mon Sep 17 00:00:00 2001 From: Brian Ingles Date: Wed, 29 Nov 2023 17:20:35 -0600 Subject: [PATCH 06/12] Removed unnecessary providers #1660 --- packages/code-studio/src/styleguide/index.tsx | 23 ++++--------------- 1 file changed, 4 insertions(+), 19 deletions(-) diff --git a/packages/code-studio/src/styleguide/index.tsx b/packages/code-studio/src/styleguide/index.tsx index f35c3b3c06..a08f6c21a0 100644 --- a/packages/code-studio/src/styleguide/index.tsx +++ b/packages/code-studio/src/styleguide/index.tsx @@ -1,23 +1,12 @@ import React, { Suspense } from 'react'; import ReactDOM from 'react-dom'; import '@deephaven/components/scss/BaseStyleSheet.scss'; -import { - LoadingOverlay, - preloadTheme, - ThemeData, - ThemeProvider, -} from '@deephaven/components'; -import { IrisGridThemeProvider } from '@deephaven/iris-grid'; +import { LoadingOverlay } from '@deephaven/components'; import { ApiBootstrap } from '@deephaven/jsapi-bootstrap'; import logInit from '../log/LogInit'; logInit(); -preloadTheme(); - -// Provide a non-null array to ThemeProvider to tell it to initialize -const customThemes: ThemeData[] = []; - // eslint-disable-next-line react-refresh/only-export-components const StyleGuideRoot = React.lazy(() => import('./StyleGuideRoot')); @@ -35,13 +24,9 @@ const apiURL = new URL( ReactDOM.render( }> - - - - - - - + + + , document.getElementById('root') From b74a4f51c7c5f2b607a4f6ace4bfe40106c6862a Mon Sep 17 00:00:00 2001 From: Brian Ingles Date: Thu, 30 Nov 2023 09:22:12 -0600 Subject: [PATCH 07/12] Fixed failing tests #1660 --- .../app-utils/src/components/AppBootstrap.test.tsx | 5 ++--- packages/console/src/monaco/MonacoUtils.ts | 11 ++++++----- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/app-utils/src/components/AppBootstrap.test.tsx b/packages/app-utils/src/components/AppBootstrap.test.tsx index dc838d24b7..1b8b8cfe90 100644 --- a/packages/app-utils/src/components/AppBootstrap.test.tsx +++ b/packages/app-utils/src/components/AppBootstrap.test.tsx @@ -1,6 +1,8 @@ import React, { useContext } from 'react'; +import { act, render, screen } from '@testing-library/react'; import { AUTH_HANDLER_TYPE_ANONYMOUS } from '@deephaven/auth-plugins'; import { ApiContext } from '@deephaven/jsapi-bootstrap'; +import { PluginModuleMap, PluginsContext } from '@deephaven/plugin'; import { BROADCAST_LOGIN_MESSAGE } from '@deephaven/jsapi-utils'; import type { CoreClient, @@ -8,10 +10,7 @@ import type { dh as DhType, } from '@deephaven/jsapi-types'; import { TestUtils } from '@deephaven/utils'; -import { act, render, screen } from '@testing-library/react'; import AppBootstrap from './AppBootstrap'; -import { PluginsContext } from './PluginsBootstrap'; -import { PluginModuleMap } from '../plugins'; const { asMock } = TestUtils; diff --git a/packages/console/src/monaco/MonacoUtils.ts b/packages/console/src/monaco/MonacoUtils.ts index 28d69cdf1d..362e02960f 100644 --- a/packages/console/src/monaco/MonacoUtils.ts +++ b/packages/console/src/monaco/MonacoUtils.ts @@ -96,7 +96,7 @@ class MonacoUtils { }, { token: 'error.log', - foreground: MonacoTheme['log-error'].substring(1), + foreground: MonacoTheme['log-error']?.substring(1) ?? '', }, { token: 'warn.log', @@ -178,9 +178,10 @@ class MonacoUtils { try { monaco.editor.setTheme('dh-dark'); - } catch { + } catch (err) { log.error( - `Failed to set 'dh-dark' Monaco theme, falling back to vs-dark` + `Failed to set 'dh-dark' Monaco theme, falling back to vs-dark`, + err ); monaco.editor.setTheme('vs-dark'); } @@ -202,8 +203,8 @@ class MonacoUtils { * Monaco expects colors to be the value only, no hashtag. * @param color The hex color string to remove the hashtag from, eg. '#ffffff' */ - static removeHashtag(color: string): string { - return color.substring(1); + static removeHashtag(color?: string): string { + return color?.substring(1) ?? ''; } static registerLanguages(languages: Language[]): void { From 3f9897a158c6d71a82579c30de1a957e96bdc744 Mon Sep 17 00:00:00 2001 From: Brian Ingles Date: Thu, 30 Nov 2023 10:39:05 -0600 Subject: [PATCH 08/12] Env config for local plugin dev #1660 --- README.md | 9 +++++++++ packages/code-studio/vite.config.ts | 10 ++++++++++ 2 files changed, 19 insertions(+) diff --git a/README.md b/README.md index 6f662667ca..8549053ea0 100644 --- a/README.md +++ b/README.md @@ -52,6 +52,15 @@ If your DHC address is different from the default `http://localhost:10000`, edit VITE_PROXY_URL=http://: ``` +## Local Plugin Development +The plugins repo supports [serving plugins locally](https://github.com/deephaven/deephaven-plugins/blob/main/README.md#serve-plugins). DHC can be configured to proxy `js-plugins`requests to the local dev server by setting `VITE_JS_PLUGINS_DEV_PORT` in `packages/code-studio/.env.development.local`. + +e.g. To point to the default dev port: + +``` +VITE_JS_PLUGINS_DEV_PORT=4100 +``` + ## Local Vite Config If you'd like to override the vite config for local dev, you can define a `packages/code-studio/vite.config.local.ts` file that extends from `vite.config.local`. This file is excluded via `.gitignore` which makes it easy to keep local overrides in tact. diff --git a/packages/code-studio/vite.config.ts b/packages/code-studio/vite.config.ts index 2b79e00d3c..fdae3080cf 100644 --- a/packages/code-studio/vite.config.ts +++ b/packages/code-studio/vite.config.ts @@ -55,6 +55,16 @@ export default defineConfig(({ mode }) => { }); } + // Proxy to local dev server for js-plugins + if (env.VITE_JS_PLUGINS_DEV_PORT && env.VITE_MODULE_PLUGINS_URL) { + const route = new URL(env.VITE_MODULE_PLUGINS_URL, baseURL).pathname; + proxy[route] = { + target: `http://localhost:${env.VITE_JS_PLUGINS_DEV_PORT}`, + changeOrigin: true, + rewrite: path => path.replace(/^\/js-plugins/, ''), + }; + } + return { // Vite does not read this env variable, it sets it based on the config // For easy changes using our .env files, read it here and vite will just set it to the existing value From a8b3a4a4d62916a4f1b691496636796c3c1145f9 Mon Sep 17 00:00:00 2001 From: Brian Ingles Date: Thu, 30 Nov 2023 13:09:08 -0600 Subject: [PATCH 09/12] Updated package lock #1660 --- package-lock.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/package-lock.json b/package-lock.json index cb16133cc9..8f56af58e7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -27950,6 +27950,7 @@ "@deephaven/auth-plugins": "file:../auth-plugins", "@deephaven/chart": "file:../chart", "@deephaven/components": "file:../components", + "@deephaven/console": "file:../console", "@deephaven/dashboard": "file:../dashboard", "@deephaven/icons": "file:../icons", "@deephaven/iris-grid": "file:../iris-grid", @@ -28699,6 +28700,7 @@ "@deephaven/icons": "file:../icons", "@deephaven/iris-grid": "file:../iris-grid", "@deephaven/jsapi-types": "file:../jsapi-types", + "@deephaven/log": "file:../log", "@deephaven/react-hooks": "file:../react-hooks", "@fortawesome/fontawesome-common-types": "^6.1.1", "@fortawesome/react-fontawesome": "^0.2.0" @@ -30097,6 +30099,7 @@ "@deephaven/auth-plugins": "file:../auth-plugins", "@deephaven/chart": "file:../chart", "@deephaven/components": "file:../components", + "@deephaven/console": "file:../console", "@deephaven/dashboard": "file:../dashboard", "@deephaven/icons": "file:../icons", "@deephaven/iris-grid": "file:../iris-grid", @@ -30583,6 +30586,7 @@ "@deephaven/icons": "file:../icons", "@deephaven/iris-grid": "file:../iris-grid", "@deephaven/jsapi-types": "file:../jsapi-types", + "@deephaven/log": "file:../log", "@deephaven/react-hooks": "file:../react-hooks", "@fortawesome/fontawesome-common-types": "^6.1.1", "@fortawesome/react-fontawesome": "^0.2.0" From 2fd804cbb2bfe5695c58d242eb5a6349bbc71ece Mon Sep 17 00:00:00 2001 From: Brian Ingles Date: Thu, 30 Nov 2023 13:34:57 -0600 Subject: [PATCH 10/12] Initialize Monaco theme as part of init #1660 --- packages/console/src/monaco/MonacoUtils.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/console/src/monaco/MonacoUtils.ts b/packages/console/src/monaco/MonacoUtils.ts index 362e02960f..3fade7a5eb 100644 --- a/packages/console/src/monaco/MonacoUtils.ts +++ b/packages/console/src/monaco/MonacoUtils.ts @@ -43,7 +43,9 @@ class MonacoUtils { MonacoUtils.registerGetWorker(getWorker); } - const { registerLanguages } = MonacoUtils; + const { initTheme, registerLanguages } = MonacoUtils; + + initTheme(); registerLanguages([DbLang, PyLang, GroovyLang, LogLang, ScalaLang]); From 9f4f3aa465eef075ec605facff7a2b5f85d2c70d Mon Sep 17 00:00:00 2001 From: Brian Ingles Date: Wed, 6 Dec 2023 10:14:38 -0600 Subject: [PATCH 11/12] Code review changes #1660 --- packages/code-studio/src/styleguide/Grids.tsx | 6 +++--- packages/code-studio/src/styleguide/ThemeColors.tsx | 12 ++---------- packages/grid/src/Grid.tsx | 4 ++-- packages/grid/src/GridThemeContext.ts | 9 --------- packages/grid/src/ThemeContext.ts | 9 +++++++++ packages/grid/src/index.ts | 2 +- 6 files changed, 17 insertions(+), 25 deletions(-) delete mode 100644 packages/grid/src/GridThemeContext.ts create mode 100644 packages/grid/src/ThemeContext.ts diff --git a/packages/code-studio/src/styleguide/Grids.tsx b/packages/code-studio/src/styleguide/Grids.tsx index dc2f1a68c1..0c16a7e015 100644 --- a/packages/code-studio/src/styleguide/Grids.tsx +++ b/packages/code-studio/src/styleguide/Grids.tsx @@ -5,7 +5,7 @@ import { GridThemeType, MockGridModel, MockTreeGridModel, - GridThemeContext, + ThemeContext, } from '@deephaven/grid'; import { IrisGrid } from '@deephaven/iris-grid'; import { useApi } from '@deephaven/jsapi-bootstrap'; @@ -38,7 +38,7 @@ function Grids(): ReactElement { }); return (
- +

Grid

@@ -81,7 +81,7 @@ function Grids(): ReactElement { -
+
); } diff --git a/packages/code-studio/src/styleguide/ThemeColors.tsx b/packages/code-studio/src/styleguide/ThemeColors.tsx index 1c14d3d33c..75f7d09b59 100644 --- a/packages/code-studio/src/styleguide/ThemeColors.tsx +++ b/packages/code-studio/src/styleguide/ThemeColors.tsx @@ -1,5 +1,5 @@ /* eslint-disable react/jsx-props-no-spreading */ -import React, { useEffect, useState } from 'react'; +import React, { useMemo } from 'react'; import { Tooltip, useTheme } from '@deephaven/components'; import { ColorUtils } from '@deephaven/utils'; import palette from '@deephaven/components/src/theme/theme-dark/theme-dark-palette.css?inline'; @@ -28,16 +28,8 @@ function buildSwatchDataGroups() { } export function ThemeColors(): JSX.Element { - const [swatchDataGroups, setSwatchDataGroups] = useState( - buildSwatchDataGroups - ); - const { selectedThemeKey } = useTheme(); - - useEffect(() => { - // Rebuild swatches when themes change - setSwatchDataGroups(buildSwatchDataGroups()); - }, [selectedThemeKey]); + const swatchDataGroups = useMemo(buildSwatchDataGroups, [selectedThemeKey]); return ( <> diff --git a/packages/grid/src/Grid.tsx b/packages/grid/src/Grid.tsx index bcd3969355..fed4bd08c7 100644 --- a/packages/grid/src/Grid.tsx +++ b/packages/grid/src/Grid.tsx @@ -57,7 +57,7 @@ import { } from './EditableGridModel'; import { EventHandlerResultOptions } from './EventHandlerResult'; import { assertIsDefined } from './errors'; -import GridThemeContext from './GridThemeContext'; +import ThemeContext from './ThemeContext'; import { DraggingColumn } from './mouse-handlers/GridColumnMoveMouseHandler'; import { EditingCell, @@ -211,7 +211,7 @@ export type GridState = { * Can also add onClick and onContextMenu handlers to add custom functionality and menus. */ class Grid extends PureComponent { - static contextType = GridThemeContext; + static contextType = ThemeContext; static defaultProps = { canvasOptions: { alpha: false } as CanvasRenderingContext2DSettings, diff --git a/packages/grid/src/GridThemeContext.ts b/packages/grid/src/GridThemeContext.ts deleted file mode 100644 index 4c3fb3fa13..0000000000 --- a/packages/grid/src/GridThemeContext.ts +++ /dev/null @@ -1,9 +0,0 @@ -import React from 'react'; -import { GridTheme as GridThemeType } from './GridTheme'; - -export type GridThemeContextValue = Partial; - -export const GridThemeContext: React.Context = - React.createContext({}); - -export default GridThemeContext; diff --git a/packages/grid/src/ThemeContext.ts b/packages/grid/src/ThemeContext.ts new file mode 100644 index 0000000000..4b89a746d9 --- /dev/null +++ b/packages/grid/src/ThemeContext.ts @@ -0,0 +1,9 @@ +import React from 'react'; +import { GridTheme as GridThemeType } from './GridTheme'; + +export type ThemeContextValue = Partial; + +export const ThemeContext: React.Context = + React.createContext({}); + +export default ThemeContext; diff --git a/packages/grid/src/index.ts b/packages/grid/src/index.ts index 4143cee80e..d19a66d01b 100644 --- a/packages/grid/src/index.ts +++ b/packages/grid/src/index.ts @@ -25,7 +25,7 @@ export * from './key-handlers'; export * from './mouse-handlers'; export * from './errors'; export * from './EventHandlerResult'; -export * from './GridThemeContext'; +export * from './ThemeContext'; export type { default as CellRenderer, CellRenderType } from './CellRenderer'; export { default as TextCellRenderer } from './TextCellRenderer'; export { default as DataBarCellRenderer } from './DataBarCellRenderer'; From a4597dc378eac4ca59f279d336d1a4301da161e8 Mon Sep 17 00:00:00 2001 From: Brian Ingles Date: Wed, 6 Dec 2023 10:15:42 -0600 Subject: [PATCH 12/12] Fixed README #1660 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8549053ea0..5618976194 100644 --- a/README.md +++ b/README.md @@ -62,7 +62,7 @@ VITE_JS_PLUGINS_DEV_PORT=4100 ``` ## Local Vite Config -If you'd like to override the vite config for local dev, you can define a `packages/code-studio/vite.config.local.ts` file that extends from `vite.config.local`. This file is excluded via `.gitignore` which makes it easy to keep local overrides in tact. +If you'd like to override the vite config for local dev, you can define a `packages/code-studio/vite.config.local.ts` file that extends from `vite.config.ts`. This file is excluded via `.gitignore` which makes it easy to keep local overrides in tact. The config can be used by running: