Theme: Add /theme-playground route (#111974)

* add basic theme-playground page

* basic theme playground

* generate schema

* sort of include the schema

* proper json schema

* add base theme

* bit of tidy up

* don't use appEvents.emit

* tidy up ThemeDemo

* extract translations

* add CODEOWNERS
This commit is contained in:
Ashley Harrison 2025-10-09 10:09:51 +01:00 committed by GitHub
parent 3ad54c0c1e
commit 3eadebd950
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 780 additions and 29 deletions

1
.github/CODEOWNERS vendored
View file

@ -949,6 +949,7 @@ playwright.storybook.config.ts @grafana/grafana-frontend-platform
/public/app/features/serviceaccounts/ @grafana/identity-squad
/public/app/features/teams/ @grafana/access-squad
/public/app/features/templating/ @grafana/dashboards-squad
/public/app/features/theme-playground/ @grafana/grafana-frontend-platform
/public/app/features/trails/ @grafana/observability-metrics
/public/app/features/transformers/ @grafana/datapro
/public/app/features/transformers/timeSeriesTable/ @grafana/dataviz-squad @grafana/app-o11y-visualizations

View file

@ -62,7 +62,8 @@
"stats": "webpack --mode production --config scripts/webpack/webpack.prod.js --profile --json > compilation-stats.json",
"storybook": "yarn workspace @grafana/ui storybook --ci",
"storybook:build": "yarn workspace @grafana/ui storybook:build",
"themes-generate": "esbuild --target=es6 ./scripts/cli/generateSassVariableFiles.ts --bundle --platform=node --tsconfig=./scripts/cli/tsconfig.json | node",
"themes-schema": "typescript-json-schema ./tsconfig.json NewThemeOptions --include 'packages/grafana-data/src/themes/createTheme.ts' --out public/app/features/theme-playground/schema.generated.json",
"themes-generate": "yarn themes-schema && esbuild --target=es6 ./scripts/cli/generateSassVariableFiles.ts --bundle --platform=node --tsconfig=./scripts/cli/tsconfig.json | node",
"themes:usage": "eslint . --ignore-pattern '*.test.ts*' --ignore-pattern '*.spec.ts*' --cache --plugin '@grafana' --rule '{ @grafana/theme-token-usage: \"error\" }'",
"typecheck": "tsc --noEmit && yarn run packages:typecheck",
"plugins:build-bundled": "echo 'bundled plugins are no longer supported'",
@ -255,6 +256,7 @@
"ts-jest": "29.4.0",
"ts-node": "10.9.2",
"typescript": "5.9.2",
"typescript-json-schema": "^0.65.1",
"webpack": "5.101.0",
"webpack-assets-manifest": "^5.1.0",
"webpack-cli": "6.0.1",

View file

@ -93,6 +93,7 @@ export { DataTransformerID } from '../transformations/transformers/ids';
export { mergeTransformer } from '../transformations/transformers/merge';
export { getThemeById } from '../themes/registry';
export * as experimentalThemeDefinitions from '../themes/themeDefinitions';
export { GrafanaEdition } from '../types/config';
export { SIPrefix } from '../valueFormats/symbolFormatters';

View file

@ -18,17 +18,20 @@ import { Icon } from '../Icon/Icon';
import { Input } from '../Input/Input';
import { BackgroundColor, BorderColor, Box, BoxShadow } from '../Layout/Box/Box';
import { Stack } from '../Layout/Stack/Stack';
import { ScrollContainer } from '../ScrollContainer/ScrollContainer';
import { Switch } from '../Switch/Switch';
import { Text, TextProps } from '../Text/Text';
interface DemoBoxProps {
bg?: BackgroundColor;
border?: BorderColor;
scrollable?: boolean;
shadow?: BoxShadow;
textColor?: TextProps['color'];
}
const DemoBox = ({ bg, border, children, shadow }: React.PropsWithChildren<DemoBoxProps>) => {
const DemoBox = ({ bg, border, children, shadow, scrollable }: React.PropsWithChildren<DemoBoxProps>) => {
const MaybeScroll = scrollable ? ScrollContainer : React.Fragment;
return (
<Box
backgroundColor={bg ? bg : undefined}
@ -36,9 +39,9 @@ const DemoBox = ({ bg, border, children, shadow }: React.PropsWithChildren<DemoB
borderStyle={border ? 'solid' : undefined}
borderColor={border}
boxShadow={shadow}
borderRadius={'default'}
borderRadius={'lg'}
>
{children}
<MaybeScroll>{children}</MaybeScroll>
</Box>
);
};
@ -121,7 +124,7 @@ export const ThemeDemo = () => {
</DemoBox>
</CollapsableSection>
<CollapsableSection label="Text colors" isOpen={true}>
<Stack justifyContent="flex-start">
<Stack justifyContent="flex-start" wrap="wrap">
<DemoBox>
<TextColors t={t} />
</DemoBox>
@ -134,8 +137,8 @@ export const ThemeDemo = () => {
</Stack>
</CollapsableSection>
<CollapsableSection label="Rich colors" isOpen={true}>
<DemoBox bg="primary">
<table className={colorsTableStyle}>
<DemoBox bg="primary" scrollable>
<table className={colorsTableStyle(t)}>
<thead>
<tr>
<td>name</td>
@ -154,8 +157,8 @@ export const ThemeDemo = () => {
</DemoBox>
</CollapsableSection>
<CollapsableSection label="Viz hues" isOpen={true}>
<DemoBox bg="primary">
<table className={colorsTableStyle}>
<DemoBox bg="primary" scrollable>
<table className={colorsTableStyle(t)}>
<thead>
<tr>
<td>name</td>
@ -229,7 +232,7 @@ export const ThemeDemo = () => {
<CollapsableSection label="Buttons" isOpen={true}>
<DemoBox bg="primary">
<Stack direction="column" gap={3}>
<Stack>
<Stack wrap="wrap">
{allButtonVariants.map((variant) => (
<Button variant={variant} key={variant}>
{variant}
@ -305,7 +308,7 @@ export function RichColorDemo({ theme, color }: RichColorDemoProps) {
background: color.main,
borderRadius: theme.shape.radius.default,
color: color.contrastText,
padding: '8px',
padding: theme.spacing(1),
fontWeight: 500,
})}
>
@ -318,7 +321,7 @@ export function RichColorDemo({ theme, color }: RichColorDemoProps) {
background: color.shade,
color: theme.colors.getContrastText(color.shade, 4.5),
borderRadius: theme.shape.radius.default,
padding: '8px',
padding: theme.spacing(1),
})}
>
{color.shade}
@ -329,7 +332,7 @@ export function RichColorDemo({ theme, color }: RichColorDemoProps) {
className={css({
background: color.transparent,
borderRadius: theme.shape.radius.default,
padding: '8px',
padding: theme.spacing(1),
})}
>
{color.shade}
@ -341,7 +344,7 @@ export function RichColorDemo({ theme, color }: RichColorDemoProps) {
border: `1px solid ${color.border}`,
color: color.text,
borderRadius: theme.shape.radius.default,
padding: '8px',
padding: theme.spacing(1),
})}
>
{color.text}
@ -351,14 +354,16 @@ export function RichColorDemo({ theme, color }: RichColorDemoProps) {
);
}
const colorsTableStyle = css({
textAlign: 'center',
td: {
padding: '8px',
const colorsTableStyle = (theme: GrafanaTheme2) =>
css({
textAlign: 'center',
},
});
overflow: 'auto',
td: {
padding: theme.spacing(1),
textAlign: 'center',
},
});
export function TextColors({ t }: { t: GrafanaTheme2 }) {
return (
@ -380,8 +385,10 @@ export function TextColors({ t }: { t: GrafanaTheme2 }) {
}
export function ShadowDemo({ name, shadow }: { name: string; shadow: string }) {
const t = useTheme2();
const style = css({
padding: '16px',
padding: t.spacing(2),
borderRadius: t.shape.radius.default,
boxShadow: shadow,
});
return <div className={style}>{name}</div>;
@ -391,7 +398,8 @@ export function ActionsDemo() {
const t = useTheme2();
const item = css({
padding: '8px',
borderRadius: t.shape.radius.default,
padding: t.spacing(1),
':hover': {
background: t.colors.action.hover,
},

View file

@ -81,6 +81,7 @@ export { type TooltipPlacement } from '../components/Tooltip/types';
export { ConfirmContent, type ConfirmContentProps } from '../components/ConfirmModal/ConfirmContent';
export { EmotionPerfTest } from '../components/ThemeDemos/EmotionPerfTest';
export { ThemeDemo } from '../components/ThemeDemos/ThemeDemo';
export { VizTooltipContent } from '../components/VizTooltip/VizTooltipContent';
export { VizTooltipFooter, type AdHocFilterModel } from '../components/VizTooltip/VizTooltipFooter';

View file

@ -44,7 +44,8 @@
"outputs": [
"{workspaceRoot}/public/sass/_variables.generated.scss",
"{workspaceRoot}/public/sass/_variables.dark.generated.scss",
"{workspaceRoot}/public/sass/_variables.light.generated.scss"
"{workspaceRoot}/public/sass/_variables.light.generated.scss",
"{workspaceRoot}/public/app/features/theme-playground/schema.json"
],
"cache": true
}

View file

@ -21,6 +21,10 @@ export const ThemeProvider = ({ children, value }: { children: React.ReactNode;
return () => sub.unsubscribe();
}, []);
useEffect(() => {
setTheme(value);
}, [value]);
return (
<ThemeContext.Provider value={theme}>
<SkeletonTheme

View file

@ -0,0 +1,3 @@
# Regenerating the schema
The json schema for the theme options is generated using [typescript-json-schema](https://github.com/YousefED/typescript-json-schema). The schema should be regenerated automatically if the types change. If you need to manually regenerate, run `yarn themes-schema`.

View file

@ -0,0 +1,160 @@
import { css } from '@emotion/css';
import { useId, useState } from 'react';
import { createTheme, GrafanaTheme2, NewThemeOptions } from '@grafana/data';
import { experimentalThemeDefinitions } from '@grafana/data/internal';
import { t } from '@grafana/i18n';
import { useChromeHeaderHeight } from '@grafana/runtime';
import { CodeEditor, Combobox, Field, Stack, useStyles2 } from '@grafana/ui';
import { ThemeDemo } from '@grafana/ui/internal';
import { Page } from 'app/core/components/Page/Page';
import { notifyApp } from '../../core/actions';
import { createErrorNotification } from '../../core/copy/appNotification';
import { HOME_NAV_ID } from '../../core/reducers/navModel';
import { getNavModel } from '../../core/selectors/navModel';
import { ThemeProvider } from '../../core/utils/ConfigProvider';
import { useDispatch, useSelector } from '../../types/store';
import schema from './schema.generated.json';
const themeMap: Record<string, NewThemeOptions> = {
dark: {
name: 'Dark',
colors: {
mode: 'dark',
},
},
light: {
name: 'Light',
colors: {
mode: 'light',
},
},
...experimentalThemeDefinitions,
};
const themeOptions = Object.entries(themeMap).map(([key, theme]) => ({
label: theme.name,
value: key,
}));
export default function ThemePlayground() {
const navIndex = useSelector((state) => state.navIndex);
const homeNav = getNavModel(navIndex, HOME_NAV_ID).main;
const navModel = {
text: t('theme-playground.title', 'Theme playground'),
parentItem: homeNav,
};
const baseId = useId();
const chromeHeaderHeight = useChromeHeaderHeight();
const styles = useStyles2(getStyles, chromeHeaderHeight);
const dispatch = useDispatch();
const [baseThemeId, setBaseThemeId] = useState(Object.keys(themeMap)[0]);
const [theme, setTheme] = useState(createTheme(themeMap[baseThemeId]));
const updateThemePreview = (themeInput: NewThemeOptions) => {
try {
const theme = createTheme(themeInput);
setTheme(theme);
} catch (error) {
dispatch(notifyApp(createErrorNotification(`Failed to create theme: ${error}`)));
}
};
const onEditorBlur = (value: string) => {
try {
const themeInput: NewThemeOptions = JSON.parse(value);
updateThemePreview(themeInput);
} catch (error) {
dispatch(notifyApp(createErrorNotification(`Failed to parse JSON: ${error}`)));
}
};
return (
<Page
navModel={{
node: navModel,
main: navModel,
}}
>
<Stack
direction={{
xs: 'column',
md: 'row',
}}
columnGap={2}
rowGap={1}
height="100%"
>
<div className={styles.left}>
<Field noMargin label={t('theme-playground.label-base-theme', 'Base theme')}>
<Combobox
value={baseThemeId}
onChange={(option) => {
setBaseThemeId(option.value);
updateThemePreview(themeMap[option.value]);
}}
options={themeOptions}
id={baseId}
/>
</Field>
<CodeEditor
width="100%"
value={JSON.stringify(themeMap[baseThemeId], null, 2)}
language="json"
showLineNumbers={true}
showMiniMap={true}
containerStyles={styles.codeEditor}
onBlur={onEditorBlur}
onBeforeEditorMount={(monaco) => {
monaco.languages.json.jsonDefaults.setDiagnosticsOptions({
validate: true,
schemas: [
{
uri: 'theme-schema',
fileMatch: ['*'],
schema,
},
],
});
}}
monacoOptions={{
alwaysConsumeMouseWheel: true,
minimap: {
enabled: false,
},
}}
/>
</div>
<ThemeProvider value={theme}>
<ThemeDemo />
</ThemeProvider>
</Stack>
</Page>
);
}
const getStyles = (theme: GrafanaTheme2, chromeHeaderHeight: number | undefined) => ({
left: css({
background: theme.colors.background.primary,
display: 'flex',
flexDirection: 'column',
gap: theme.spacing(1),
height: '40vh',
minWidth: '300px',
padding: theme.spacing(2, 0),
position: 'sticky',
top: chromeHeaderHeight ?? 0,
width: '100%',
[theme.breakpoints.up('md')]: {
height: `calc(90vh - ${chromeHeaderHeight ?? 0}px - ${theme.spacing(2)})`,
width: '70%',
},
zIndex: theme.zIndex.activePanel,
}),
codeEditor: css({
flex: 1,
}),
});

View file

@ -0,0 +1,511 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"definitions": {
"DeepPartial<ThemeColorsBase<ThemeRichColor>>": {
"properties": {
"action": {
"$ref": "#/definitions/DeepPartial<{selected:string;selectedBorder:string;hover:string;hoverOpacity:number;focus:string;disabledBackground:string;disabledText:string;disabledOpacity:number;}>"
},
"background": {
"$ref": "#/definitions/DeepPartial<{canvas:string;primary:string;secondary:string;elevated:string;}>"
},
"border": {
"$ref": "#/definitions/DeepPartial<{weak:string;medium:string;strong:string;}>"
},
"contrastThreshold": {
"type": "number"
},
"error": {
"$ref": "#/definitions/DeepPartial<ThemeRichColor>"
},
"gradients": {
"$ref": "#/definitions/DeepPartial<{brandVertical:string;brandHorizontal:string;}>"
},
"hoverFactor": {
"type": "number"
},
"info": {
"$ref": "#/definitions/DeepPartial<ThemeRichColor>"
},
"mode": {
"enum": ["dark", "light"],
"type": "string"
},
"primary": {
"$ref": "#/definitions/DeepPartial<ThemeRichColor>"
},
"secondary": {
"$ref": "#/definitions/DeepPartial<ThemeRichColor>"
},
"success": {
"$ref": "#/definitions/DeepPartial<ThemeRichColor>"
},
"text": {
"$ref": "#/definitions/DeepPartial<{primary:string;secondary:string;disabled:string;link:string;maxContrast:string;}>"
},
"tonalOffset": {
"type": "number"
},
"warning": {
"$ref": "#/definitions/DeepPartial<ThemeRichColor>"
}
},
"type": "object"
},
"DeepPartial<ThemeRichColor>": {
"properties": {
"border": {
"description": "Used for borders",
"type": "string"
},
"borderTransparent": {
"description": "Used for weak colored borders like larger alert/banner boxes and smaller badges and tags",
"type": "string"
},
"contrastText": {
"description": "Text color for text ontop of main",
"type": "string"
},
"main": {
"description": "Main color",
"type": "string"
},
"name": {
"description": "color intent (primary, secondary, info, error, etc)",
"type": "string"
},
"shade": {
"description": "Used for hover",
"type": "string"
},
"text": {
"description": "Used for text",
"type": "string"
},
"transparent": {
"description": "Used subtly colored backgrounds",
"type": "string"
}
},
"type": "object"
},
"DeepPartial<{brandVertical:string;brandHorizontal:string;}>": {
"properties": {
"brandHorizontal": {
"type": "string"
},
"brandVertical": {
"type": "string"
}
},
"type": "object"
},
"DeepPartial<{canvas:string;primary:string;secondary:string;elevated:string;}>": {
"properties": {
"canvas": {
"description": "Dashboard and body background",
"type": "string"
},
"elevated": {
"description": "For popovers and menu backgrounds. This is the same color as primary in most light themes but in dark\nthemes it has a brighter shade to help give it contrast against the primary background.",
"type": "string"
},
"primary": {
"description": "Primary content pane background (panels etc)",
"type": "string"
},
"secondary": {
"description": "Cards and elements that need to stand out on the primary background",
"type": "string"
}
},
"type": "object"
},
"DeepPartial<{primary:string;secondary:string;disabled:string;link:string;maxContrast:string;}>": {
"properties": {
"disabled": {
"type": "string"
},
"link": {
"type": "string"
},
"maxContrast": {
"description": "Used for auto white or dark text on colored backgrounds",
"type": "string"
},
"primary": {
"type": "string"
},
"secondary": {
"type": "string"
}
},
"type": "object"
},
"DeepPartial<{selected:string;selectedBorder:string;hover:string;hoverOpacity:number;focus:string;disabledBackground:string;disabledText:string;disabledOpacity:number;}>": {
"properties": {
"disabledBackground": {
"description": "Used for disabled buttons and inputs",
"type": "string"
},
"disabledOpacity": {
"description": "Disablerd opacity",
"type": "number"
},
"disabledText": {
"description": "Disabled text",
"type": "string"
},
"focus": {
"description": "Used focused menu item / select option",
"type": "string"
},
"hover": {
"description": "Used for hovered menu item / select option",
"type": "string"
},
"hoverOpacity": {
"description": "Used for button/colored background hover opacity",
"type": "number"
},
"selected": {
"description": "Used for selected menu item / select option",
"type": "string"
},
"selectedBorder": {
"type": "string"
}
},
"type": "object"
},
"DeepPartial<{weak:string;medium:string;strong:string;}>": {
"properties": {
"medium": {
"type": "string"
},
"strong": {
"type": "string"
},
"weak": {
"type": "string"
}
},
"type": "object"
},
"ThemeShapeInput": {
"properties": {
"borderRadius": {
"type": "number"
}
},
"type": "object"
},
"ThemeTypographyInput": {
"properties": {
"fontFamily": {
"type": "string"
},
"fontFamilyMonospace": {
"type": "string"
},
"fontSize": {
"type": "number"
},
"fontWeightBold": {
"type": "number"
},
"fontWeightLight": {
"type": "number"
},
"fontWeightMedium": {
"type": "number"
},
"fontWeightRegular": {
"type": "number"
},
"htmlFontSize": {
"type": "number"
}
},
"type": "object"
},
"ThemeVizColor<\"blue\">": {
"properties": {
"aliases": {
"items": {
"type": "string"
},
"type": "array"
},
"color": {
"type": "string"
},
"name": {
"$ref": "#/definitions/ThemeVizColorShadeName_4"
},
"primary": {
"type": "boolean"
}
},
"type": "object"
},
"ThemeVizColor<\"green\">": {
"properties": {
"aliases": {
"items": {
"type": "string"
},
"type": "array"
},
"color": {
"type": "string"
},
"name": {
"$ref": "#/definitions/ThemeVizColorShadeName_3"
},
"primary": {
"type": "boolean"
}
},
"type": "object"
},
"ThemeVizColor<\"orange\">": {
"properties": {
"aliases": {
"items": {
"type": "string"
},
"type": "array"
},
"color": {
"type": "string"
},
"name": {
"$ref": "#/definitions/ThemeVizColorShadeName_1"
},
"primary": {
"type": "boolean"
}
},
"type": "object"
},
"ThemeVizColor<\"purple\">": {
"properties": {
"aliases": {
"items": {
"type": "string"
},
"type": "array"
},
"color": {
"type": "string"
},
"name": {
"$ref": "#/definitions/ThemeVizColorShadeName_5"
},
"primary": {
"type": "boolean"
}
},
"type": "object"
},
"ThemeVizColor<\"red\">": {
"properties": {
"aliases": {
"items": {
"type": "string"
},
"type": "array"
},
"color": {
"type": "string"
},
"name": {
"$ref": "#/definitions/ThemeVizColorShadeName"
},
"primary": {
"type": "boolean"
}
},
"type": "object"
},
"ThemeVizColor<\"yellow\">": {
"properties": {
"aliases": {
"items": {
"type": "string"
},
"type": "array"
},
"color": {
"type": "string"
},
"name": {
"$ref": "#/definitions/ThemeVizColorShadeName_2"
},
"primary": {
"type": "boolean"
}
},
"type": "object"
},
"ThemeVizColorShadeName": {
"enum": ["dark-red", "light-red", "red", "semi-dark-red", "super-light-red"],
"type": "string"
},
"ThemeVizColorShadeName_1": {
"enum": ["dark-orange", "light-orange", "orange", "semi-dark-orange", "super-light-orange"],
"type": "string"
},
"ThemeVizColorShadeName_2": {
"enum": ["dark-yellow", "light-yellow", "semi-dark-yellow", "super-light-yellow", "yellow"],
"type": "string"
},
"ThemeVizColorShadeName_3": {
"enum": ["dark-green", "green", "light-green", "semi-dark-green", "super-light-green"],
"type": "string"
},
"ThemeVizColorShadeName_4": {
"enum": ["blue", "dark-blue", "light-blue", "semi-dark-blue", "super-light-blue"],
"type": "string"
},
"ThemeVizColorShadeName_5": {
"enum": ["dark-purple", "light-purple", "purple", "semi-dark-purple", "super-light-purple"],
"type": "string"
},
"ThemeVizHue": {
"anyOf": [
{
"properties": {
"name": {
"const": "red",
"type": "string"
},
"shades": {
"items": {
"$ref": "#/definitions/ThemeVizColor<\"red\">"
},
"type": "array"
}
},
"type": "object"
},
{
"properties": {
"name": {
"const": "orange",
"type": "string"
},
"shades": {
"items": {
"$ref": "#/definitions/ThemeVizColor<\"orange\">"
},
"type": "array"
}
},
"type": "object"
},
{
"properties": {
"name": {
"const": "yellow",
"type": "string"
},
"shades": {
"items": {
"$ref": "#/definitions/ThemeVizColor<\"yellow\">"
},
"type": "array"
}
},
"type": "object"
},
{
"properties": {
"name": {
"const": "green",
"type": "string"
},
"shades": {
"items": {
"$ref": "#/definitions/ThemeVizColor<\"green\">"
},
"type": "array"
}
},
"type": "object"
},
{
"properties": {
"name": {
"const": "blue",
"type": "string"
},
"shades": {
"items": {
"$ref": "#/definitions/ThemeVizColor<\"blue\">"
},
"type": "array"
}
},
"type": "object"
},
{
"properties": {
"name": {
"const": "purple",
"type": "string"
},
"shades": {
"items": {
"$ref": "#/definitions/ThemeVizColor<\"purple\">"
},
"type": "array"
}
},
"type": "object"
}
]
}
},
"properties": {
"colors": {
"$ref": "#/definitions/DeepPartial<ThemeColorsBase<ThemeRichColor>>"
},
"name": {
"type": "string"
},
"shape": {
"$ref": "#/definitions/ThemeShapeInput"
},
"spacing": {
"properties": {
"gridSize": {
"type": "number"
}
},
"type": "object"
},
"typography": {
"$ref": "#/definitions/ThemeTypographyInput"
},
"visualization": {
"properties": {
"hues": {
"items": {
"$ref": "#/definitions/ThemeVizHue"
},
"type": "array"
},
"palette": {
"items": {
"type": "string"
},
"type": "array"
}
},
"type": "object"
}
},
"type": "object"
}

View file

@ -520,6 +520,12 @@ export function getAppRoutes(): RouteDescriptor[] {
() => import(/* webpackChunkName: "BookmarksPage"*/ 'app/features/bookmarks/BookmarksPage')
),
},
{
path: '/theme-playground',
component: SafeDynamicImport(
() => import(/* webpackChunkName: "ThemePlayground"*/ 'app/features/theme-playground/ThemePlayground')
),
},
config.featureToggles.restoreDashboards && {
path: '/dashboard/recently-deleted',
roles: () => ['Admin', 'ServerAdmin'],

View file

@ -12955,6 +12955,10 @@
"name-show-line-numbers": "Show line numbers",
"name-show-mini-map": "Show mini map"
},
"theme-playground": {
"label-base-theme": "Base theme",
"title": "Theme playground"
},
"theme-preview": {
"breadcrumbs": {
"dashboards": "Dashboards",

View file

@ -9800,6 +9800,15 @@ __metadata:
languageName: node
linkType: hard
"@types/node@npm:^18.11.9":
version: 18.19.129
resolution: "@types/node@npm:18.19.129"
dependencies:
undici-types: "npm:~5.26.4"
checksum: 10/0db4cb246d6012b1b523661a59c2e8e0b24527f1c02cfa3deb8e0b884492a07f2547c5353f56272b70037408e3dbe690ae923b8073fd7b0814e389148245e59f
languageName: node
linkType: hard
"@types/nodemailer@npm:*":
version: 6.4.15
resolution: "@types/nodemailer@npm:6.4.15"
@ -18038,7 +18047,7 @@ __metadata:
languageName: node
linkType: hard
"glob@npm:^7.0.3, glob@npm:^7.1.2, glob@npm:^7.1.3, glob@npm:^7.1.4, glob@npm:^7.1.6":
"glob@npm:^7.0.3, glob@npm:^7.1.2, glob@npm:^7.1.3, glob@npm:^7.1.4, glob@npm:^7.1.6, glob@npm:^7.1.7":
version: 7.2.3
resolution: "glob@npm:7.2.3"
dependencies:
@ -18578,6 +18587,7 @@ __metadata:
tween-functions: "npm:^1.2.0"
type-fest: "npm:^4.18.2"
typescript: "npm:5.9.2"
typescript-json-schema: "npm:^0.65.1"
uplot: "npm:1.6.32"
uuid: "npm:11.1.0"
vis-data: "npm:^8.0.0"
@ -25336,6 +25346,13 @@ __metadata:
languageName: node
linkType: hard
"path-equal@npm:^1.2.5":
version: 1.2.5
resolution: "path-equal@npm:1.2.5"
checksum: 10/fa4ef398dea6bd7bf36c5fe62b5f5c2c14fe1f1340cf355eb8a40c86577318dfa0401df86464bb0cc33ed227f115b2afec10d1adaa64260dedbbc23d33f3abbb
languageName: node
linkType: hard
"path-exists@npm:^3.0.0":
version: 3.0.0
resolution: "path-exists@npm:3.0.0"
@ -28828,6 +28845,13 @@ __metadata:
languageName: node
linkType: hard
"safe-stable-stringify@npm:^2.2.0":
version: 2.5.0
resolution: "safe-stable-stringify@npm:2.5.0"
checksum: 10/2697fa186c17c38c3ca5309637b4ac6de2f1c3d282da27cd5e1e3c88eca0fb1f9aea568a6aabdf284111592c8782b94ee07176f17126031be72ab1313ed46c5c
languageName: node
linkType: hard
"safer-buffer@npm:>= 2.1.2 < 3, safer-buffer@npm:>= 2.1.2 < 3.0.0, safer-buffer@npm:^2.0.2, safer-buffer@npm:^2.1.0, safer-buffer@npm:~2.1.0":
version: 2.1.2
resolution: "safer-buffer@npm:2.1.2"
@ -31346,7 +31370,7 @@ __metadata:
languageName: node
linkType: hard
"ts-node@npm:10.9.2":
"ts-node@npm:10.9.2, ts-node@npm:^10.9.1":
version: 10.9.2
resolution: "ts-node@npm:10.9.2"
dependencies:
@ -31688,6 +31712,24 @@ __metadata:
languageName: node
linkType: hard
"typescript-json-schema@npm:^0.65.1":
version: 0.65.1
resolution: "typescript-json-schema@npm:0.65.1"
dependencies:
"@types/json-schema": "npm:^7.0.9"
"@types/node": "npm:^18.11.9"
glob: "npm:^7.1.7"
path-equal: "npm:^1.2.5"
safe-stable-stringify: "npm:^2.2.0"
ts-node: "npm:^10.9.1"
typescript: "npm:~5.5.0"
yargs: "npm:^17.1.1"
bin:
typescript-json-schema: bin/typescript-json-schema
checksum: 10/50a1935378639d5d47e452702766a3fdab22e1d06192f26f81b79e0da504e71af987ff21cb13909479a202aad8d1216a654f16ebda2ee2056b5f859584b4c7d2
languageName: node
linkType: hard
"typescript-string-operations@npm:^1.4.1":
version: 1.5.1
resolution: "typescript-string-operations@npm:1.5.1"
@ -31695,7 +31737,7 @@ __metadata:
languageName: node
linkType: hard
"typescript@npm:5.5.4":
"typescript@npm:5.5.4, typescript@npm:~5.5.0":
version: 5.5.4
resolution: "typescript@npm:5.5.4"
bin:
@ -31725,7 +31767,7 @@ __metadata:
languageName: node
linkType: hard
"typescript@patch:typescript@npm%3A5.5.4#optional!builtin<compat/typescript>":
"typescript@patch:typescript@npm%3A5.5.4#optional!builtin<compat/typescript>, typescript@patch:typescript@npm%3A~5.5.0#optional!builtin<compat/typescript>":
version: 5.5.4
resolution: "typescript@patch:typescript@npm%3A5.5.4#optional!builtin<compat/typescript>::version=5.5.4&hash=379a07"
bin:
@ -31814,6 +31856,13 @@ __metadata:
languageName: node
linkType: hard
"undici-types@npm:~5.26.4":
version: 5.26.5
resolution: "undici-types@npm:5.26.5"
checksum: 10/0097779d94bc0fd26f0418b3a05472410408877279141ded2bd449167be1aed7ea5b76f756562cb3586a07f251b90799bab22d9019ceba49c037c76445f7cddd
languageName: node
linkType: hard
"undici-types@npm:~6.21.0":
version: 6.21.0
resolution: "undici-types@npm:6.21.0"
@ -33334,7 +33383,7 @@ __metadata:
languageName: node
linkType: hard
"yargs@npm:17.7.2, yargs@npm:^17.0.1, yargs@npm:^17.3.1, yargs@npm:^17.6.2, yargs@npm:^17.7.2":
"yargs@npm:17.7.2, yargs@npm:^17.0.1, yargs@npm:^17.1.1, yargs@npm:^17.3.1, yargs@npm:^17.6.2, yargs@npm:^17.7.2":
version: 17.7.2
resolution: "yargs@npm:17.7.2"
dependencies: