Clean up MCP Monaco editor (#21643)

Rebuild the MCP setup block on the shared Monaco editor with theme-driven colors, padding, and auto-height; add the settings description line-height token; revert the clipboard util to the shared navigator.clipboard implementation; and consolidate the code editor's auto-height to a single disposed, reactive effect.
This commit is contained in:
Thomas des Francs
2026-06-16 13:57:44 +02:00
committed by GitHub
parent ce92a1f1e1
commit a67fdf2dac
14 changed files with 393 additions and 149 deletions
@@ -79,6 +79,7 @@ const StyledIconChevronRightContainer = styled.span`
`;
const StyledDescription = styled.div`
line-height: ${themeCssVariables.text.lineHeight.lg};
padding-bottom: ${themeCssVariables.spacing[2]};
padding-left: ${themeCssVariables.spacing[7]};
`;
@@ -48,6 +48,7 @@ const StyledLabel = styled.span`
const StyledDescription = styled.span`
color: ${themeCssVariables.font.color.light};
font-weight: ${themeCssVariables.font.weight.regular};
line-height: ${themeCssVariables.text.lineHeight.lg};
padding-left: ${themeCssVariables.spacing[1]};
`;
@@ -70,6 +70,7 @@ const StyledLabel = styled.span`
const StyledDescription = styled.span`
color: ${themeCssVariables.font.color.light};
font-size: ${themeCssVariables.font.size.sm};
line-height: ${themeCssVariables.text.lineHeight.lg};
`;
const StyledError = styled.span`
@@ -41,7 +41,7 @@ export const StyledSettingsCardTextContainer = styled.div`
export const StyledSettingsCardDescription = styled.div`
color: ${themeCssVariables.font.color.secondary};
font-size: ${themeCssVariables.font.size.sm};
line-height: 1.5;
line-height: ${themeCssVariables.text.lineHeight.lg};
overflow: hidden;
a {
@@ -40,6 +40,7 @@ const StyledTitle = styled.div`
const StyledDescription = styled.div`
color: ${themeCssVariables.font.color.tertiary};
font-size: ${themeCssVariables.font.size.sm};
line-height: ${themeCssVariables.text.lineHeight.lg};
`;
type SettingsRadioCardProps = {
@@ -0,0 +1,12 @@
<svg width="180" height="180" viewBox="0 0 180 180" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_19_13)">
<path d="M18 84.8528L85.8822 16.9706C95.2548 7.59798 110.451 7.59798 119.823 16.9706C129.196 26.3431 129.196 41.5391 119.823 50.9117L68.5581 102.177" stroke="currentColor" stroke-width="12" stroke-linecap="round" />
<path d="M69.2652 101.47L119.823 50.9117C129.196 41.5391 144.392 41.5391 153.765 50.9117L154.118 51.2652C163.491 60.6378 163.491 75.8338 154.118 85.2063L92.7248 146.6C89.6006 149.724 89.6006 154.789 92.7248 157.913L105.331 170.52" stroke="currentColor" stroke-width="12" stroke-linecap="round" />
<path d="M102.853 33.9411L52.6482 84.1457C43.2756 93.5183 43.2756 108.714 52.6482 118.087C62.0208 127.459 77.2167 127.459 86.5893 118.087L136.794 67.8822" stroke="currentColor" stroke-width="12" stroke-linecap="round" />
</g>
<defs>
<clipPath id="clip0_19_13">
<rect width="180" height="180" fill="white" />
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1016 B

@@ -1,42 +1,24 @@
import { styled } from '@linaria/react';
import { useLingui } from '@lingui/react/macro';
import { type ReactNode } from 'react';
import { H2Title, IconCopy } from 'twenty-ui-deprecated/display';
import { LightIconButton } from 'twenty-ui-deprecated/input';
import { Card, CardContent, Section } from 'twenty-ui-deprecated/layout';
import { LightCopyIconButton } from '@/object-record/record-field/ui/components/LightCopyIconButton';
import ModelContextProtocolLogo from '@/settings/playground/assets/model-context-protocol-logo.svg?react';
import { H2Title } from 'twenty-ui-deprecated/display';
import { CodeEditor, CoreEditorHeader } from 'twenty-ui-deprecated/input';
import { Section } from 'twenty-ui-deprecated/layout';
import { themeCssVariables } from 'twenty-ui-deprecated/theme-constants';
import { REACT_APP_SERVER_BASE_URL } from '~/config';
import { useCopyToClipboard } from '~/hooks/useCopyToClipboard';
const StyledMcpConfigContainer = styled.div`
position: relative;
const StyledMcpEditorHeaderTitle = styled.div`
align-items: center;
display: flex;
gap: ${themeCssVariables.spacing[2]};
`;
const StyledCopyButtonContainer = styled.div`
position: absolute;
right: 0;
top: 0;
`;
const StyledPre = styled.pre`
color: ${themeCssVariables.font.color.secondary};
font-family: monospace;
line-height: 1.5;
margin: 0;
overflow-x: auto;
white-space: pre;
`;
const StyledJsonKey = styled.span`
color: ${themeCssVariables.color.blue};
`;
const StyledJsonString = styled.span`
color: ${themeCssVariables.color.green};
`;
const StyledJsonPlaceholder = styled.span`
color: ${themeCssVariables.color.orange};
const StyledMcpIcon = styled(ModelContextProtocolLogo)`
color: inherit;
flex-shrink: 0;
height: calc(${themeCssVariables.icon.size.md} * 1px);
width: calc(${themeCssVariables.icon.size.md} * 1px);
`;
const buildMcpConfig = (serverUrl: string) =>
@@ -51,91 +33,43 @@ const buildMcpConfig = (serverUrl: string) =>
}
}`;
const renderJsonString = (token: string, key: string) => {
const apiKeyPlaceholder = '<YOUR_API_KEY>';
if (!token.includes(apiKeyPlaceholder)) {
return <StyledJsonString key={key}>{token}</StyledJsonString>;
}
const [beforePlaceholder, afterPlaceholder] = token.split(apiKeyPlaceholder);
return (
<span key={key}>
<StyledJsonString>{beforePlaceholder}</StyledJsonString>
<StyledJsonPlaceholder>{apiKeyPlaceholder}</StyledJsonPlaceholder>
<StyledJsonString>{afterPlaceholder}</StyledJsonString>
</span>
);
};
const getHighlightedMcpConfig = (mcpConfig: string) => {
const jsonStringTokenRegex = /("(?:[^"\\]|\\.)*")(\s*:)?/g;
const tokens: ReactNode[] = [];
let previousMatchEndIndex = 0;
let tokenIndex = 0;
let match: RegExpExecArray | null;
while ((match = jsonStringTokenRegex.exec(mcpConfig)) !== null) {
const [fullMatch, quotedString, trailingColon] = match;
const matchIndex = match.index;
if (matchIndex > previousMatchEndIndex) {
tokens.push(mcpConfig.slice(previousMatchEndIndex, matchIndex));
}
if (trailingColon) {
tokens.push(
<StyledJsonKey key={`json-key-${tokenIndex}`}>
{quotedString}
</StyledJsonKey>,
);
tokens.push(trailingColon);
} else {
tokens.push(renderJsonString(quotedString, `json-string-${tokenIndex}`));
}
previousMatchEndIndex = matchIndex + fullMatch.length;
tokenIndex += 1;
}
if (previousMatchEndIndex < mcpConfig.length) {
tokens.push(mcpConfig.slice(previousMatchEndIndex));
}
return tokens;
};
export const SettingsMcpSetup = () => {
const { t } = useLingui();
const { copyToClipboard } = useCopyToClipboard();
const mcpConfig = buildMcpConfig(REACT_APP_SERVER_BASE_URL);
return (
<Section>
<H2Title
title={t`Connect your AI assistant`}
description={t`Add Twenty as a Model Context Protocol (MCP) server. Paste this config into Claude Desktop, Cursor, Cline, Continue, Zed, or any other MCP-aware client.`}
description={t`Paste this configuration into MCP-aware clients like Claude, Codex, or Cursor.`}
/>
<CoreEditorHeader
leftNodes={[
<StyledMcpEditorHeaderTitle>
<StyledMcpIcon aria-hidden focusable={false} />
<span>{t`MCP client configuration`}</span>
</StyledMcpEditorHeaderTitle>,
]}
rightNodes={[<LightCopyIconButton copyText={mcpConfig} />]}
/>
<CodeEditor
value={mcpConfig}
language="json"
variant="with-header"
contentPadding="comfortable"
autoHeight
options={{
readOnly: true,
domReadOnly: true,
lineNumbers: 'off',
lineNumbersMinChars: 0,
folding: false,
glyphMargin: false,
scrollBeyondLastLine: false,
renderLineHighlight: 'none',
wordWrap: 'on',
}}
/>
<Card rounded>
<CardContent>
<StyledMcpConfigContainer>
<StyledPre>{getHighlightedMcpConfig(mcpConfig)}</StyledPre>
<StyledCopyButtonContainer>
<LightIconButton
Icon={IconCopy}
size="small"
accent="tertiary"
aria-label={t`Copy MCP config`}
title={t`Copy MCP config`}
onClick={() =>
copyToClipboard(mcpConfig, t`MCP config copied to clipboard`)
}
/>
</StyledCopyButtonContainer>
</StyledMcpConfigContainer>
</CardContent>
</Card>
</Section>
);
};
@@ -358,7 +358,6 @@ export const WorkflowEditActionCode = ({
readOnly: actionOptions.readonly,
domReadOnly: actionOptions.readonly,
scrollBeyondLastLine: false,
padding: { top: 4, bottom: 4 },
}}
/>
</StyledFullScreenCodeEditorContainer>
@@ -397,7 +396,6 @@ export const WorkflowEditActionCode = ({
readOnly: actionOptions.readonly,
domReadOnly: actionOptions.readonly,
scrollBeyondLastLine: false,
padding: { top: 4, bottom: 4 },
lineNumbersMinChars: 2,
fixedOverflowWidgets: true,
}}
@@ -32,6 +32,7 @@ const StyledDescription = styled.h3`
color: ${themeCssVariables.font.color.tertiary};
font-size: ${themeCssVariables.font.size.md};
font-weight: ${themeCssVariables.font.weight.regular};
line-height: ${themeCssVariables.text.lineHeight.lg};
margin: 0;
margin-top: ${themeCssVariables.spacing[2]};
`;
@@ -5,12 +5,29 @@ import { ResizeHandle } from '@ui/layout/resize-handle/components/ResizeHandle';
import { BASE_CODE_EDITOR_THEME_ID } from '@ui/input/code-editor/constants/BaseCodeEditorThemeId';
import { useResizeHandle } from '@ui/layout/resize-handle/hooks/useResizeHandle';
import { getBaseCodeEditorTheme } from '@ui/input/code-editor/theme/utils/getBaseCodeEditorTheme';
import { ThemeContext, themeCssVariables } from '@ui/theme-constants';
import {
ThemeContext,
themeCssVariables,
type ThemeType,
} from '@ui/theme-constants';
import { type editor } from 'monaco-editor';
import { type KeyboardEvent, useContext, useState } from 'react';
import { type KeyboardEvent, useContext, useEffect, useState } from 'react';
import { isDefined } from 'twenty-shared/utils';
type CodeEditorVariant = 'default' | 'with-header' | 'borderless';
type CodeEditorContentPadding = 'default' | 'comfortable';
const setCodeEditorTheme = (
monaco: Monaco,
theme: ThemeType,
colorScheme: 'light' | 'dark',
) => {
monaco.editor.defineTheme(
BASE_CODE_EDITOR_THEME_ID,
getBaseCodeEditorTheme(theme, colorScheme),
);
monaco.editor.setTheme(BASE_CODE_EDITOR_THEME_ID);
};
type CodeEditorProps = Pick<
EditorProps,
@@ -22,6 +39,8 @@ type CodeEditorProps = Pick<
isLoading?: boolean;
transparentBackground?: boolean;
resizable?: boolean;
contentPadding?: CodeEditorContentPadding;
autoHeight?: boolean;
};
const StyledEditorLoader = styled.div<{
@@ -121,13 +140,18 @@ export const CodeEditor = ({
isLoading = false,
options,
resizable = false,
contentPadding = 'default',
autoHeight = false,
}: CodeEditorProps) => {
const { theme } = useContext(ThemeContext);
const { theme, colorScheme } = useContext(ThemeContext);
const [monaco, setMonaco] = useState<Monaco | undefined>(undefined);
const [editor, setEditor] = useState<
editor.IStandaloneCodeEditor | undefined
>(undefined);
const [isEditorFocused, setIsEditorFocused] = useState(false);
const [autoHeightContentHeight, setAutoHeightContentHeight] = useState<
number | undefined
>(undefined);
const numericHeight = typeof height === 'number' ? height : 450;
const {
@@ -139,7 +163,28 @@ export const CodeEditor = ({
initialSize: numericHeight,
});
const currentHeight = resizable ? resizableHeight : height;
const shouldAutoHeight = autoHeight && !resizable;
const codeEditorPadding =
typeof theme.spacingMultiplicator === 'number'
? theme.spacingMultiplicator * 4
: undefined;
const currentHeight = shouldAutoHeight
? (autoHeightContentHeight ?? height)
: resizable
? resizableHeight
: height;
const contentPaddingOptions = {
...(contentPadding === 'comfortable'
? {
lineDecorationsWidth: codeEditorPadding,
}
: {}),
padding: {
bottom: codeEditorPadding,
top: codeEditorPadding,
},
};
const { padding: _callerPadding, ...editorOptions } = options ?? {};
const setModelMarkers = (
editor: editor.IStandaloneCodeEditor | undefined,
@@ -161,6 +206,43 @@ export const CodeEditor = ({
}
};
useEffect(() => {
if (!isDefined(monaco)) {
return;
}
setCodeEditorTheme(monaco, theme, colorScheme);
}, [colorScheme, monaco, theme]);
// Drive the container height from Monaco's content height; the editor's
// default automaticLayout then re-fits the canvas (no manual layout needed).
useEffect(() => {
if (!shouldAutoHeight || !isDefined(editor)) {
setAutoHeightContentHeight(undefined);
return;
}
const updateAutoHeight = () => {
const nextHeight = editor.getContentHeight();
if (!Number.isFinite(nextHeight) || nextHeight <= 0) {
return;
}
setAutoHeightContentHeight((currentHeight) =>
currentHeight === nextHeight ? currentHeight : nextHeight,
);
};
updateAutoHeight();
const disposable = editor.onDidContentSizeChange(updateAutoHeight);
return () => {
disposable.dispose();
};
}, [editor, shouldAutoHeight]);
return isLoading ? (
<StyledEditorLoader height={currentHeight} variant={variant}>
<Loader />
@@ -186,11 +268,7 @@ export const CodeEditor = ({
setMonaco(monaco);
setEditor(editor);
monaco.editor.defineTheme(
BASE_CODE_EDITOR_THEME_ID,
getBaseCodeEditorTheme(theme),
);
monaco.editor.setTheme(BASE_CODE_EDITOR_THEME_ID);
setCodeEditorTheme(monaco, theme, colorScheme);
editor.onDidFocusEditorWidget(() => {
setIsEditorFocused(true);
@@ -214,6 +292,10 @@ export const CodeEditor = ({
options={{
formatOnPaste: true,
formatOnType: true,
fontFamily: theme.code.font.family,
bracketPairColorization: {
enabled: false,
},
overviewRulerLanes: 0,
scrollBeyondLastLine: false,
scrollbar: {
@@ -223,7 +305,8 @@ export const CodeEditor = ({
minimap: {
enabled: false,
},
...options,
...editorOptions,
...contentPaddingOptions,
}}
/>
</StyledEditorWrapper>
@@ -3,6 +3,10 @@ import { type editor } from 'monaco-editor';
import { isDefined } from 'twenty-shared/utils';
const convertColorToHex = (color: string): string => {
if (color.trim() === 'transparent') {
return '#00000000';
}
const displayP3Match = color.match(
/color\(display-p3\s+([\d.]+)\s+([\d.]+)\s+([\d.]+)(?:\s+\/\s+([\d.]+))?\)/,
);
@@ -33,35 +37,81 @@ const convertColorToHex = (color: string): string => {
export const getBaseCodeEditorTheme = (
theme: ThemeType,
colorScheme: 'light' | 'dark',
): editor.IStandaloneThemeData => {
return {
base: 'vs',
base: colorScheme === 'dark' ? 'vs-dark' : 'vs',
inherit: true,
rules: [
{
token: '',
foreground: convertColorToHex(theme.code.text.gray),
fontStyle: 'bold',
foreground: convertColorToHex(theme.font.color.secondary),
},
{
token: 'keyword',
foreground: convertColorToHex(theme.code.text.sky),
foreground: convertColorToHex(theme.color.pink11),
},
{
token: 'delimiter',
foreground: convertColorToHex(theme.code.text.gray),
token: 'keyword.control',
foreground: convertColorToHex(theme.color.pink11),
},
{
token: 'keyword.json',
foreground: convertColorToHex(theme.color.orange11),
},
{
token: 'number',
foreground: convertColorToHex(theme.color.orange11),
},
{
token: 'number.json',
foreground: convertColorToHex(theme.color.orange11),
},
{
token: 'regexp',
foreground: convertColorToHex(theme.color.orange11),
},
{
token: 'type',
foreground: convertColorToHex(theme.color.green11),
},
{
token: 'attribute.name',
foreground: convertColorToHex(theme.color.blue11),
},
{
token: 'tag',
foreground: convertColorToHex(theme.color.pink11),
},
{
token: 'string',
foreground: convertColorToHex(theme.code.text.pink),
foreground: convertColorToHex(theme.color.green11),
},
{
token: 'string.key.json',
foreground: convertColorToHex(theme.color.blue11),
},
{
token: 'delimiter',
foreground: convertColorToHex(theme.font.color.light),
},
{
token: 'delimiter.bracket.json',
foreground: convertColorToHex(theme.font.color.light),
},
{
token: 'string.value.json',
foreground: convertColorToHex(theme.color.green11),
},
{
token: 'comment',
foreground: convertColorToHex(theme.code.text.orange),
foreground: convertColorToHex(theme.font.color.light),
fontStyle: 'italic',
},
],
colors: {
'editor.background': '#00000000',
'editor.background': convertColorToHex('transparent'),
'editor.foreground': convertColorToHex(theme.font.color.secondary),
'editorCursor.foreground': convertColorToHex(theme.font.color.primary),
'editorLineNumber.foreground': convertColorToHex(
theme.font.color.extraLight,
@@ -72,6 +122,22 @@ export const getBaseCodeEditorTheme = (
'editor.lineHighlightBackground': convertColorToHex(
theme.background.tertiary,
),
'editor.selectionBackground': convertColorToHex(
theme.background.transparent.blue,
),
'editor.inactiveSelectionBackground': convertColorToHex(
theme.background.transparent.light,
),
'editorIndentGuide.background1': convertColorToHex(
theme.border.color.light,
),
'editorIndentGuide.activeBackground1': convertColorToHex(
theme.border.color.medium,
),
'editorBracketMatch.background': convertColorToHex(
theme.background.transparent.light,
),
'editorBracketMatch.border': convertColorToHex(theme.border.color.medium),
},
};
};
@@ -21,6 +21,7 @@
color: var(--t-font-color-tertiary);
font-size: var(--t-font-size-md);
font-weight: var(--t-font-weight-regular);
line-height: var(--t-text-line-height-lg);
margin: 0;
margin-top: var(--t-spacing-2);
}
@@ -4,14 +4,27 @@ import { BASE_CODE_EDITOR_THEME_ID } from '@ui/input/code-editor/constants/BaseC
import { getBaseCodeEditorTheme } from '@ui/input/code-editor/theme/utils/getBaseCodeEditorTheme';
import { ResizeHandle } from '@ui/layout/resize-handle/components/ResizeHandle';
import { useResizeHandle } from '@ui/layout/resize-handle/hooks/useResizeHandle';
import { ThemeContext } from '@ui/theme-constants';
import { ThemeContext, type ThemeType } from '@ui/theme-constants';
import { type editor } from 'monaco-editor';
import { type KeyboardEvent, useContext, useState } from 'react';
import { type KeyboardEvent, useContext, useEffect, useState } from 'react';
import { isDefined } from '@ui/utilities/utils/isDefined';
import styles from './CodeEditor.module.scss';
type CodeEditorVariant = 'default' | 'with-header' | 'borderless';
type CodeEditorContentPadding = 'default' | 'comfortable';
const setCodeEditorTheme = (
monaco: Monaco,
theme: ThemeType,
colorScheme: 'light' | 'dark',
) => {
monaco.editor.defineTheme(
BASE_CODE_EDITOR_THEME_ID,
getBaseCodeEditorTheme(theme, colorScheme),
);
monaco.editor.setTheme(BASE_CODE_EDITOR_THEME_ID);
};
type CodeEditorProps = Pick<
EditorProps,
@@ -23,6 +36,8 @@ type CodeEditorProps = Pick<
isLoading?: boolean;
transparentBackground?: boolean;
resizable?: boolean;
contentPadding?: CodeEditorContentPadding;
autoHeight?: boolean;
};
export const CodeEditor = ({
@@ -38,13 +53,18 @@ export const CodeEditor = ({
isLoading = false,
options,
resizable = false,
contentPadding = 'default',
autoHeight = false,
}: CodeEditorProps) => {
const { theme } = useContext(ThemeContext);
const { theme, colorScheme } = useContext(ThemeContext);
const [monaco, setMonaco] = useState<Monaco | undefined>(undefined);
const [editor, setEditor] = useState<
editor.IStandaloneCodeEditor | undefined
>(undefined);
const [isEditorFocused, setIsEditorFocused] = useState(false);
const [autoHeightContentHeight, setAutoHeightContentHeight] = useState<
number | undefined
>(undefined);
const numericHeight = typeof height === 'number' ? height : 450;
const {
@@ -56,7 +76,28 @@ export const CodeEditor = ({
initialSize: numericHeight,
});
const currentHeight = resizable ? resizableHeight : height;
const shouldAutoHeight = autoHeight && !resizable;
const codeEditorPadding =
typeof theme.spacingMultiplicator === 'number'
? theme.spacingMultiplicator * 4
: undefined;
const currentHeight = shouldAutoHeight
? (autoHeightContentHeight ?? height)
: resizable
? resizableHeight
: height;
const contentPaddingOptions = {
...(contentPadding === 'comfortable'
? {
lineDecorationsWidth: codeEditorPadding,
}
: {}),
padding: {
bottom: codeEditorPadding,
top: codeEditorPadding,
},
};
const { padding: _callerPadding, ...editorOptions } = options ?? {};
const setModelMarkers = (
editor: editor.IStandaloneCodeEditor | undefined,
@@ -78,6 +119,43 @@ export const CodeEditor = ({
}
};
useEffect(() => {
if (!isDefined(monaco)) {
return;
}
setCodeEditorTheme(monaco, theme, colorScheme);
}, [colorScheme, monaco, theme]);
// Drive the container height from Monaco's content height; the editor's
// default automaticLayout then re-fits the canvas (no manual layout needed).
useEffect(() => {
if (!shouldAutoHeight || !isDefined(editor)) {
setAutoHeightContentHeight(undefined);
return;
}
const updateAutoHeight = () => {
const nextHeight = editor.getContentHeight();
if (!Number.isFinite(nextHeight) || nextHeight <= 0) {
return;
}
setAutoHeightContentHeight((currentHeight) =>
currentHeight === nextHeight ? currentHeight : nextHeight,
);
};
updateAutoHeight();
const disposable = editor.onDidContentSizeChange(updateAutoHeight);
return () => {
disposable.dispose();
};
}, [editor, shouldAutoHeight]);
return isLoading ? (
<div
className={styles.editorLoader}
@@ -115,11 +193,7 @@ export const CodeEditor = ({
setMonaco(monaco);
setEditor(editor);
monaco.editor.defineTheme(
BASE_CODE_EDITOR_THEME_ID,
getBaseCodeEditorTheme(theme),
);
monaco.editor.setTheme(BASE_CODE_EDITOR_THEME_ID);
setCodeEditorTheme(monaco, theme, colorScheme);
editor.onDidFocusEditorWidget(() => {
setIsEditorFocused(true);
@@ -143,6 +217,10 @@ export const CodeEditor = ({
options={{
formatOnPaste: true,
formatOnType: true,
fontFamily: theme.code.font.family,
bracketPairColorization: {
enabled: false,
},
overviewRulerLanes: 0,
scrollBeyondLastLine: false,
scrollbar: {
@@ -152,7 +230,8 @@ export const CodeEditor = ({
minimap: {
enabled: false,
},
...options,
...editorOptions,
...contentPaddingOptions,
}}
/>
</div>
@@ -3,6 +3,10 @@ import { type editor } from 'monaco-editor';
import { isDefined } from '@ui/utilities/utils/isDefined';
const convertColorToHex = (color: string): string => {
if (color.trim() === 'transparent') {
return '#00000000';
}
const displayP3Match = color.match(
/color\(display-p3\s+([\d.]+)\s+([\d.]+)\s+([\d.]+)(?:\s+\/\s+([\d.]+))?\)/,
);
@@ -33,35 +37,81 @@ const convertColorToHex = (color: string): string => {
export const getBaseCodeEditorTheme = (
theme: ThemeType,
colorScheme: 'light' | 'dark',
): editor.IStandaloneThemeData => {
return {
base: 'vs',
base: colorScheme === 'dark' ? 'vs-dark' : 'vs',
inherit: true,
rules: [
{
token: '',
foreground: convertColorToHex(theme.code.text.gray),
fontStyle: 'bold',
foreground: convertColorToHex(theme.font.color.secondary),
},
{
token: 'keyword',
foreground: convertColorToHex(theme.code.text.sky),
foreground: convertColorToHex(theme.color.pink11),
},
{
token: 'delimiter',
foreground: convertColorToHex(theme.code.text.gray),
token: 'keyword.control',
foreground: convertColorToHex(theme.color.pink11),
},
{
token: 'keyword.json',
foreground: convertColorToHex(theme.color.orange11),
},
{
token: 'number',
foreground: convertColorToHex(theme.color.orange11),
},
{
token: 'number.json',
foreground: convertColorToHex(theme.color.orange11),
},
{
token: 'regexp',
foreground: convertColorToHex(theme.color.orange11),
},
{
token: 'type',
foreground: convertColorToHex(theme.color.green11),
},
{
token: 'attribute.name',
foreground: convertColorToHex(theme.color.blue11),
},
{
token: 'tag',
foreground: convertColorToHex(theme.color.pink11),
},
{
token: 'string',
foreground: convertColorToHex(theme.code.text.pink),
foreground: convertColorToHex(theme.color.green11),
},
{
token: 'string.key.json',
foreground: convertColorToHex(theme.color.blue11),
},
{
token: 'delimiter',
foreground: convertColorToHex(theme.font.color.light),
},
{
token: 'delimiter.bracket.json',
foreground: convertColorToHex(theme.font.color.light),
},
{
token: 'string.value.json',
foreground: convertColorToHex(theme.color.green11),
},
{
token: 'comment',
foreground: convertColorToHex(theme.code.text.orange),
foreground: convertColorToHex(theme.font.color.light),
fontStyle: 'italic',
},
],
colors: {
'editor.background': '#00000000',
'editor.background': convertColorToHex('transparent'),
'editor.foreground': convertColorToHex(theme.font.color.secondary),
'editorCursor.foreground': convertColorToHex(theme.font.color.primary),
'editorLineNumber.foreground': convertColorToHex(
theme.font.color.extraLight,
@@ -72,6 +122,22 @@ export const getBaseCodeEditorTheme = (
'editor.lineHighlightBackground': convertColorToHex(
theme.background.tertiary,
),
'editor.selectionBackground': convertColorToHex(
theme.background.transparent.blue,
),
'editor.inactiveSelectionBackground': convertColorToHex(
theme.background.transparent.light,
),
'editorIndentGuide.background1': convertColorToHex(
theme.border.color.light,
),
'editorIndentGuide.activeBackground1': convertColorToHex(
theme.border.color.medium,
),
'editorBracketMatch.background': convertColorToHex(
theme.background.transparent.light,
),
'editorBracketMatch.border': convertColorToHex(theme.border.color.medium),
},
};
};