diff --git a/src/renderer/src/components/Playground/CodeEditor/Editor/index.tsx b/src/renderer/src/components/Playground/CodeEditor/Editor/index.tsx index 677c144..f1c94ea 100644 --- a/src/renderer/src/components/Playground/CodeEditor/Editor/index.tsx +++ b/src/renderer/src/components/Playground/CodeEditor/Editor/index.tsx @@ -24,6 +24,7 @@ interface EditorProps { options?: IEditorOptions onJumpFile?: (fileName: string) => void extraLibs?: ExtraLib[] + onEditorDidMount?: (editor: editor.IStandaloneCodeEditor, monaco: Monaco) => void } const Editor = ({ @@ -35,7 +36,8 @@ const Editor = ({ onChange, options, onJumpFile, - extraLibs = [] + extraLibs = [], + onEditorDidMount }: EditorProps) => { const { styles } = useStyles() const editorRef = useRef() @@ -96,6 +98,8 @@ const Editor = ({ monaco.languages.typescript.typescriptDefaults.addExtraLib(item.content, item.path) ) + onEditorDidMount?.(editor, monaco) + void autoLoadExtraLib(editor, monaco, file.value, onWatch) } diff --git a/src/renderer/src/components/Playground/CodeEditor/index.tsx b/src/renderer/src/components/Playground/CodeEditor/index.tsx index c06a6ef..fb32d59 100644 --- a/src/renderer/src/components/Playground/CodeEditor/index.tsx +++ b/src/renderer/src/components/Playground/CodeEditor/index.tsx @@ -1,3 +1,5 @@ +import { Monaco } from '@monaco-editor/react' +import { editor } from 'monaco-editor' import _ from 'lodash' import useStyles from '@/components/Playground/CodeEditor/index.style' import FlexBox from '@/components/common/FlexBox' @@ -22,6 +24,7 @@ interface CodeEditorProps { selectedFileName?: string options?: IEditorOptions extraLibs?: ExtraLib[] + onEditorDidMount?: (editor: editor.IStandaloneCodeEditor, monaco: Monaco) => void onSelectedFileChange?: (fileName: string) => void onAddFile?: (fileName: string, files: IFiles) => void onRemoveFile?: (fileName: string, files: IFiles) => void @@ -46,6 +49,7 @@ const CodeEditor = ({ onChangeFileContent, onError, extraLibs, + onEditorDidMount, ...props }: CodeEditorProps) => { const { styles } = useStyles() @@ -157,6 +161,7 @@ const CodeEditor = ({ onChange={handleOnChangeFileContent} onJumpFile={handleOnChangeSelectedFile} extraLibs={extraLibs} + onEditorDidMount={onEditorDidMount} /> {errorMsg &&
{errorMsg}
} diff --git a/src/renderer/src/components/Playground/Output/Preview/Render.tsx b/src/renderer/src/components/Playground/Output/Preview/Render.tsx index a09dfaa..ea98a55 100644 --- a/src/renderer/src/components/Playground/Output/Preview/Render.tsx +++ b/src/renderer/src/components/Playground/Output/Preview/Render.tsx @@ -2,6 +2,7 @@ import Icon from '@ant-design/icons' import { Background, Controls, MiniMap, Node, Panel, ReactFlow } from '@xyflow/react' import '@xyflow/react/dist/style.css' import { AppContext } from '@/App' +import { generateThemeCssVariable } from '@/util/common' import useStyles from '@/components/Playground/Output/Preview/render.style' import iframeRaw from '@/components/Playground/Output/Preview/iframe.html?raw' import devices, { DeviceName } from '@/components/Playground/Output/Preview/devices' @@ -14,11 +15,12 @@ interface RenderProps { } interface IMessage { - type: 'LOADED' | 'ERROR' | 'UPDATE' | 'DONE' + type: 'LOADED' | 'ERROR' | 'UPDATE' | 'DONE' | 'THEME' msg: string data: { compiledCode?: string zoom?: number + themeSrc?: string } } @@ -57,6 +59,16 @@ const Render = ({ iframeKey, compiledCode, mobileMode = false }: RenderProps) => setIsRotate(!isRotate) } + const loadTheme = () => { + iframeRef.current?.contentWindow?.postMessage( + { + type: 'THEME', + data: { themeSrc: generateThemeCssVariable(theme).styles } + } as IMessage, + '*' + ) + } + useEffect(() => { if (!isLoaded) { return @@ -68,8 +80,16 @@ const Render = ({ iframeKey, compiledCode, mobileMode = false }: RenderProps) => } as IMessage, '*' ) + loadTheme() }, [isLoaded, compiledCode]) + useEffect(() => { + if (!isLoaded) { + return + } + loadTheme() + }, [isLoaded, isDarkMode]) + return mobileMode ? ( <> Preview + diff --git a/src/renderer/src/components/Playground/Output/Preview/simulation.style.ts b/src/renderer/src/components/Playground/Output/Preview/simulation.style.ts index 5e90044..f4099d7 100644 --- a/src/renderer/src/components/Playground/Output/Preview/simulation.style.ts +++ b/src/renderer/src/components/Playground/Output/Preview/simulation.style.ts @@ -2,7 +2,7 @@ import { createStyles } from 'antd-style' export default createStyles(({ token }) => ({ renderRoot: { - border: 'none', + border: `1px solid ${token.colorBorder}`, height: '100%', width: '100%', flex: 1 diff --git a/src/renderer/src/pages/System/Tools/Base.tsx b/src/renderer/src/pages/System/Tools/Base.tsx index 4cd9056..2e4cf03 100644 --- a/src/renderer/src/pages/System/Tools/Base.tsx +++ b/src/renderer/src/pages/System/Tools/Base.tsx @@ -7,7 +7,7 @@ import { DATABASE_SELECT_SUCCESS, DATABASE_UPDATE_SUCCESS } from '@/constants/common.constants' -import { message, modal } from '@/util/common' +import { addExtraCssVariable, message, modal } from '@/util/common' import { utcToLocalTime } from '@/util/datetime' import { hasPermission } from '@/util/auth' import editorExtraLibs from '@/util/editorExtraLibs' @@ -1113,6 +1113,7 @@ const Base = () => { !hasPermission('system:tool:modify:base') } extraLibs={editorExtraLibs} + onEditorDidMount={(_, monaco) => addExtraCssVariable(monaco)} />
{ selectedFileName={selectedFileName} onSelectedFileChange={setSelectedFileName} extraLibs={editorExtraLibs} + onEditorDidMount={(_, monaco) => addExtraCssVariable(monaco)} /> diff --git a/src/renderer/src/pages/System/Tools/Template.tsx b/src/renderer/src/pages/System/Tools/Template.tsx index 2555e3e..2b57426 100644 --- a/src/renderer/src/pages/System/Tools/Template.tsx +++ b/src/renderer/src/pages/System/Tools/Template.tsx @@ -7,7 +7,7 @@ import { DATABASE_SELECT_SUCCESS, DATABASE_UPDATE_SUCCESS } from '@/constants/common.constants' -import { message, modal } from '@/util/common' +import { addExtraCssVariable, message, modal } from '@/util/common' import { utcToLocalTime } from '@/util/datetime' import { hasPermission } from '@/util/auth' import editorExtraLibs from '@/util/editorExtraLibs' @@ -1056,6 +1056,7 @@ const Template = () => { !hasPermission('system:tool:modify:template') } extraLibs={editorExtraLibs} + onEditorDidMount={(_, monaco) => addExtraCssVariable(monaco)} />
{ onChangeFileContent={handleOnChangeFileContent} onSelectedFileChange={setSelectedFileName} extraLibs={editorExtraLibs} + onEditorDidMount={(_, monaco) => addExtraCssVariable(monaco)} /> { selectedFileName={selectedFileName} onSelectedFileChange={setSelectedFileName} extraLibs={editorExtraLibs} + onEditorDidMount={(_, monaco) => addExtraCssVariable(monaco)} /> diff --git a/src/renderer/src/util/common.tsx b/src/renderer/src/util/common.tsx index 1ac7956..8219166 100644 --- a/src/renderer/src/util/common.tsx +++ b/src/renderer/src/util/common.tsx @@ -1,4 +1,10 @@ import { createRoot } from 'react-dom/client' +import { editor, languages, Position } from 'monaco-editor' +import { Monaco } from '@monaco-editor/react' +import { MessageInstance } from 'antd/es/message/interface' +import { HookAPI } from 'antd/es/modal/useModal' +import { NotificationInstance } from 'antd/es/notification/interface' +import { Theme, css } from 'antd-style' import { floor } from 'lodash' import { STORAGE_COLLAPSE_SIDEBAR_KEY, @@ -10,9 +16,6 @@ import { } from '@/constants/common.constants' import { getLocalStorage, setLocalStorage } from '@/util/browser' import FullscreenLoadingMask from '@/components/common/FullscreenLoadingMask' -import { MessageInstance } from 'antd/es/message/interface' -import { NotificationInstance } from 'antd/es/notification/interface' -import { HookAPI } from 'antd/es/modal/useModal' export type ThemeMode = typeof THEME_FOLLOW_SYSTEM | typeof THEME_LIGHT | typeof THEME_DARK @@ -241,3 +244,294 @@ export const getThemeMode = (): ThemeMode => { export const setThemeMode = (themeMode: ThemeMode) => { setLocalStorage(STORAGE_THEME_MODE_KEY, themeMode) } + +const cssColors = [ + 'blue', + 'purple', + 'cyan', + 'green', + 'magenta', + 'pink', + 'red', + 'orange', + 'yellow', + 'volcano', + 'geekblue', + 'gold', + 'lime' +].reduce((prev: string[], current) => { + let temp: string[] = [] + for (let i = 1; i <= 10; i++) { + temp = [...temp, `${current}${i}`] + } + return [...prev, current, ...temp] +}, []) + +const cssVariables: string[] = [ + ...cssColors, + 'colorPrimary', + 'colorSuccess', + 'colorWarning', + 'colorError', + 'colorInfo', + 'colorLink', + 'colorTextBase', + 'colorBgBase', + 'fontFamily', + 'fontFamilyCode', + 'fontSize', + 'lineWidth', + 'lineType', + 'motionUnit', + 'motionBase', + 'motionEaseOutCirc', + 'motionEaseInOutCirc', + 'motionEaseOut', + 'motionEaseInOut', + 'motionEaseOutBack', + 'motionEaseInBack', + 'motionEaseInQuint', + 'motionEaseOutQuint', + 'borderRadius', + 'sizeUnit', + 'sizeStep', + 'sizePopupArrow', + 'controlHeight', + 'zIndexBase', + 'zIndexPopupBase', + 'opacityImage', + 'wireframe', + 'motion', + 'colorLinkHover', + 'colorText', + 'colorTextSecondary', + 'colorTextTertiary', + 'colorTextQuaternary', + 'colorFill', + 'colorFillSecondary', + 'colorFillTertiary', + 'colorFillQuaternary', + 'colorBgSolid', + 'colorBgSolidHover', + 'colorBgSolidActive', + 'colorBgLayout', + 'colorBgContainer', + 'colorBgElevated', + 'colorBgSpotlight', + 'colorBgBlur', + 'colorBorder', + 'colorBorderSecondary', + 'colorPrimaryBg', + 'colorPrimaryBgHover', + 'colorPrimaryBorder', + 'colorPrimaryBorderHover', + 'colorPrimaryHover', + 'colorPrimaryActive', + 'colorPrimaryTextHover', + 'colorPrimaryText', + 'colorPrimaryTextActive', + 'colorSuccessBg', + 'colorSuccessBgHover', + 'colorSuccessBorder', + 'colorSuccessBorderHover', + 'colorSuccessHover', + 'colorSuccessActive', + 'colorSuccessTextHover', + 'colorSuccessText', + 'colorSuccessTextActive', + 'colorErrorBg', + 'colorErrorBgHover', + 'colorErrorBgFilledHover', + 'colorErrorBgActive', + 'colorErrorBorder', + 'colorErrorBorderHover', + 'colorErrorHover', + 'colorErrorActive', + 'colorErrorTextHover', + 'colorErrorText', + 'colorErrorTextActive', + 'colorWarningBg', + 'colorWarningBgHover', + 'colorWarningBorder', + 'colorWarningBorderHover', + 'colorWarningHover', + 'colorWarningActive', + 'colorWarningTextHover', + 'colorWarningText', + 'colorWarningTextActive', + 'colorInfoBg', + 'colorInfoBgHover', + 'colorInfoBorder', + 'colorInfoBorderHover', + 'colorInfoHover', + 'colorInfoActive', + 'colorInfoTextHover', + 'colorInfoText', + 'colorInfoTextActive', + 'colorLinkActive', + 'colorBgMask', + 'colorWhite', + 'fontSizeSM', + 'fontSizeLG', + 'fontSizeXL', + 'fontSizeHeading1', + 'fontSizeHeading2', + 'fontSizeHeading3', + 'fontSizeHeading4', + 'fontSizeHeading5', + 'lineHeight', + 'lineHeightLG', + 'lineHeightSM', + 'lineHeightHeading1', + 'lineHeightHeading2', + 'lineHeightHeading3', + 'lineHeightHeading4', + 'lineHeightHeading5', + 'sizeXXL', + 'sizeXL', + 'sizeLG', + 'sizeMD', + 'sizeMS', + 'size', + 'sizeSM', + 'sizeXS', + 'sizeXXS', + 'controlHeightSM', + 'controlHeightXS', + 'controlHeightLG', + 'motionDurationFast', + 'motionDurationMid', + 'motionDurationSlow', + 'lineWidthBold', + 'borderRadiusXS', + 'borderRadiusSM', + 'borderRadiusLG', + 'borderRadiusOuter', + 'colorFillContent', + 'colorFillContentHover', + 'colorFillAlter', + 'colorBgContainerDisabled', + 'colorBorderBg', + 'colorSplit', + 'colorTextPlaceholder', + 'colorTextDisabled', + 'colorTextHeading', + 'colorTextLabel', + 'colorTextDescription', + 'colorTextLightSolid', + 'colorHighlight', + 'colorBgTextHover', + 'colorBgTextActive', + 'colorIcon', + 'colorIconHover', + 'colorErrorOutline', + 'colorWarningOutline', + 'fontSizeIcon', + 'lineWidthFocus', + 'controlOutlineWidth', + 'controlInteractiveSize', + 'controlItemBgHover', + 'controlItemBgActive', + 'controlItemBgActiveHover', + 'controlItemBgActiveDisabled', + 'controlOutline', + 'fontWeightStrong', + 'opacityLoading', + 'linkDecoration', + 'linkHoverDecoration', + 'linkFocusDecoration', + 'controlPaddingHorizontal', + 'controlPaddingHorizontalSM', + 'paddingXXS', + 'paddingXS', + 'paddingSM', + 'padding', + 'paddingMD', + 'paddingLG', + 'paddingXL', + 'paddingContentHorizontalLG', + 'paddingContentVerticalLG', + 'paddingContentHorizontal', + 'paddingContentVertical', + 'paddingContentHorizontalSM', + 'paddingContentVerticalSM', + 'marginXXS', + 'marginXS', + 'marginSM', + 'margin', + 'marginMD', + 'marginLG', + 'marginXL', + 'marginXXL', + 'boxShadow', + 'boxShadowSecondary', + 'boxShadowTertiary', + 'screenXS', + 'screenXSMin', + 'screenXSMax', + 'screenSM', + 'screenSMMin', + 'screenSMMax', + 'screenMD', + 'screenMDMin', + 'screenMDMax', + 'screenLG', + 'screenLGMin', + 'screenLGMax', + 'screenXL', + 'screenXLMin', + 'screenXLMax', + 'screenXXL', + 'screenXXLMin' +] + +export const generateThemeCssVariable = (theme: Omit) => { + const cssContent = cssVariables + .map((variable) => `--${variable}: ${theme[variable]};`) + .join('\n') + + return css` + :root { + ${cssContent} + } + ` +} + +export const addExtraCssVariable = (monaco: Monaco) => { + monaco.languages.registerCompletionItemProvider('css', { + provideCompletionItems: ( + model: editor.ITextModel, + position: Position + ): languages.ProviderResult => { + const textUntilPosition = model.getValueInRange({ + startLineNumber: 1, + startColumn: 1, + endLineNumber: position.lineNumber, + endColumn: position.column + }) + if (!textUntilPosition.match(/var\(([^)]*)$/)) { + return { suggestions: [] } + } + + const word = model.getWordUntilPosition(position) + const range = new monaco.Range( + position.lineNumber, + word.startColumn, + position.lineNumber, + word.endColumn + ) + + return { + suggestions: cssVariables.map( + (variable): languages.CompletionItem => ({ + label: `--${variable}`, + kind: monaco.languages.CompletionItemKind.Variable, + insertText: `--${variable}`, + range, + detail: 'Oxygen Theme Variable' + }) + ) + } + } + }) +}