Feat(Playground): Support load global js and css variables

This commit is contained in:
2024-11-03 12:04:05 +08:00
parent e2fe240877
commit a33a405a07
14 changed files with 1704 additions and 39 deletions

View File

@@ -2,7 +2,6 @@ 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'
@@ -12,15 +11,18 @@ interface RenderProps {
iframeKey: string
compiledCode: string
mobileMode?: boolean
globalJsVariables?: Record<string, unknown>
globalCssVariables?: string
}
interface IMessage {
type: 'LOADED' | 'ERROR' | 'UPDATE' | 'DONE' | 'THEME'
type: 'LOADED' | 'ERROR' | 'UPDATE' | 'DONE' | 'GLOBAL_VARIABLES'
msg: string
data: {
compiledCode?: string
zoom?: number
themeSrc?: string
globalJsVariables?: Record<string, unknown>
globalCssVariables?: string
}
}
@@ -30,7 +32,13 @@ const getIframeUrl = (iframeRaw: string) => {
const iframeUrl = getIframeUrl(iframeRaw)
const Render = ({ iframeKey, compiledCode, mobileMode = false }: RenderProps) => {
const Render = ({
iframeKey,
compiledCode,
mobileMode = false,
globalJsVariables,
globalCssVariables
}: RenderProps) => {
const { styles, theme } = useStyles()
const { isDarkMode } = useContext(AppContext)
const iframeRef = useRef<HTMLIFrameElement>(null)
@@ -59,11 +67,11 @@ const Render = ({ iframeKey, compiledCode, mobileMode = false }: RenderProps) =>
setIsRotate(!isRotate)
}
const loadTheme = () => {
const loadGlobalVariables = () => {
iframeRef.current?.contentWindow?.postMessage(
{
type: 'THEME',
data: { themeSrc: generateThemeCssVariable(theme).styles }
type: 'GLOBAL_VARIABLES',
data: { globalJsVariables, globalCssVariables }
} as IMessage,
'*'
)
@@ -80,15 +88,15 @@ const Render = ({ iframeKey, compiledCode, mobileMode = false }: RenderProps) =>
} as IMessage,
'*'
)
loadTheme()
loadGlobalVariables()
}, [isLoaded, compiledCode])
useEffect(() => {
if (!isLoaded) {
return
}
loadTheme()
}, [isLoaded, isDarkMode])
loadGlobalVariables()
}, [isLoaded, globalJsVariables, globalCssVariables])
return mobileMode ? (
<>

View File

@@ -4,14 +4,14 @@
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Preview</title>
<style id="theme-variable"></style>
<style id="global-css-variable"></style>
</head>
<body>
<script>
window.addEventListener("message", ({ data }) => {
if (data?.type === "UPDATE") {
// Record old styles that need to be removed
const appStyleElement = document.querySelectorAll("style:not(:is(style[id$=\"_oxygen_base_style.css\"], style[id=\"theme-variable\"]))") || [];
const appStyleElement = document.querySelectorAll("style:not(:is(style[id$=\"_oxygen_base_style.css\"], style[id=\"global-css-variable\"]))") || [];
// Remove old app
const appSrcElement = document.querySelector("#appSrc");
@@ -37,8 +37,11 @@
URL.revokeObjectURL(oldSrc);
}
if (data?.type === "THEME") {
document.querySelector("#theme-variable").textContent = data.data.themeSrc;
if (data?.type === "GLOBAL_VARIABLES") {
document.querySelector("#global-css-variable").textContent = data.data.globalCssVariables;
for (const key in data.data.globalJsVariables) {
globalThis[key] = data.data.globalJsVariables[key]
}
}
});
</script>

View File

@@ -11,6 +11,8 @@ interface PreviewProps {
preExpansionCode?: string
postExpansionCode?: string
mobileMode?: boolean
globalJsVariables?: Record<string, unknown>
globalCssVariables?: string
}
const Preview = ({
@@ -20,7 +22,9 @@ const Preview = ({
entryPoint,
preExpansionCode = '',
postExpansionCode = '',
mobileMode = false
mobileMode = false,
globalJsVariables,
globalCssVariables
}: PreviewProps) => {
const { styles } = useStyles()
const [errorMsg, setErrorMsg] = useState('')
@@ -44,7 +48,13 @@ const Preview = ({
return (
<div className={styles.root}>
<Render iframeKey={iframeKey} compiledCode={compiledCode} mobileMode={mobileMode} />
<Render
iframeKey={iframeKey}
compiledCode={compiledCode}
mobileMode={mobileMode}
globalJsVariables={globalJsVariables}
globalCssVariables={globalCssVariables}
/>
{errorMsg && <div className={styles.errorMessage}>{errorMsg}</div>}
</div>
)

View File

@@ -13,6 +13,8 @@ interface OutputProps {
preExpansionCode?: string
postExpansionCode?: string
mobileMode?: boolean
globalJsVariables?: Record<string, unknown>
globalCssVariables?: string
}
const Output = ({
@@ -23,7 +25,9 @@ const Output = ({
entryPoint,
preExpansionCode,
postExpansionCode,
mobileMode = false
mobileMode = false,
globalJsVariables,
globalCssVariables
}: OutputProps) => {
const [selectedTab, setSelectedTab] = useState('Preview')
@@ -47,6 +51,8 @@ const Output = ({
preExpansionCode={preExpansionCode}
postExpansionCode={postExpansionCode}
mobileMode={mobileMode}
globalJsVariables={globalJsVariables}
globalCssVariables={globalCssVariables}
/>
)}
{selectedTab === 'Transform' && (

View File

@@ -7,7 +7,7 @@ import {
DATABASE_SELECT_SUCCESS,
DATABASE_UPDATE_SUCCESS
} from '@/constants/common.constants'
import { addExtraCssVariable, message, modal } from '@/util/common'
import { addExtraCssVariables, message, modal } from '@/util/common'
import { utcToLocalTime } from '@/util/datetime'
import { hasPermission } from '@/util/auth'
import editorExtraLibs from '@/util/editorExtraLibs'
@@ -1113,7 +1113,7 @@ const Base = () => {
!hasPermission('system:tool:modify:base')
}
extraLibs={editorExtraLibs}
onEditorDidMount={(_, monaco) => addExtraCssVariable(monaco)}
onEditorDidMount={(_, monaco) => addExtraCssVariables(monaco)}
/>
<div
className={styles.closeEditorBtn}

View File

@@ -2,7 +2,7 @@ import Draggable from 'react-draggable'
import Icon from '@ant-design/icons'
import useStyles from '@/assets/css/pages/system/tools/code.style'
import { DATABASE_NO_RECORD_FOUND, DATABASE_SELECT_SUCCESS } from '@/constants/common.constants'
import { message, modal, checkDesktop, addExtraCssVariable } from '@/util/common'
import { message, modal, checkDesktop, addExtraCssVariables } from '@/util/common'
import { navigateToExecute, navigateToRepository } from '@/util/navigation'
import editorExtraLibs from '@/util/editorExtraLibs'
import { r_sys_tool_get_one } from '@/services/system'
@@ -93,7 +93,7 @@ const Code = () => {
selectedFileName={selectedFileName}
onSelectedFileChange={setSelectedFileName}
extraLibs={editorExtraLibs}
onEditorDidMount={(_, monaco) => addExtraCssVariable(monaco)}
onEditorDidMount={(_, monaco) => addExtraCssVariables(monaco)}
/>
</Card>

View File

@@ -1,8 +1,14 @@
import useStyles from '@/assets/css/pages/system/tools/execute.style'
import { DATABASE_NO_RECORD_FOUND, DATABASE_SELECT_SUCCESS } from '@/constants/common.constants'
import { checkDesktop, message } from '@/util/common'
import {
checkDesktop,
generateThemeCssVariables,
message,
removeUselessAttributes
} from '@/util/common'
import { navigateToTools } from '@/util/navigation'
import { r_sys_tool_get_one } from '@/services/system'
import { AppContext } from '@/App'
import FitFullscreen from '@/components/common/FitFullscreen'
import Card from '@/components/common/Card'
import Playground from '@/components/Playground'
@@ -11,7 +17,8 @@ import { IImportMap } from '@/components/Playground/shared'
import { base64ToFiles, base64ToStr, IMPORT_MAP_FILE_NAME } from '@/components/Playground/files'
const Execute = () => {
const { styles } = useStyles()
const { styles, theme } = useStyles()
const { isDarkMode } = useContext(AppContext)
const navigate = useNavigate()
const { id } = useParams()
const [isLoading, setIsLoading] = useState(false)
@@ -94,6 +101,10 @@ const Execute = () => {
iframeKey={`${id}`}
compiledCode={compiledCode}
mobileMode={isMobileMode}
globalJsVariables={{
OxygenTheme: { ...removeUselessAttributes(theme), isDarkMode }
}}
globalCssVariables={generateThemeCssVariables(theme).styles}
/>
</Card>
</FitFullscreen>

View File

@@ -7,7 +7,7 @@ import {
DATABASE_SELECT_SUCCESS,
DATABASE_UPDATE_SUCCESS
} from '@/constants/common.constants'
import { addExtraCssVariable, message, modal } from '@/util/common'
import { addExtraCssVariables, message, modal } from '@/util/common'
import { utcToLocalTime } from '@/util/datetime'
import { hasPermission } from '@/util/auth'
import editorExtraLibs from '@/util/editorExtraLibs'
@@ -1056,7 +1056,7 @@ const Template = () => {
!hasPermission('system:tool:modify:template')
}
extraLibs={editorExtraLibs}
onEditorDidMount={(_, monaco) => addExtraCssVariable(monaco)}
onEditorDidMount={(_, monaco) => addExtraCssVariables(monaco)}
/>
<div
className={styles.closeEditorBtn}

View File

@@ -5,7 +5,7 @@ import {
DATABASE_INSERT_SUCCESS,
DATABASE_SELECT_SUCCESS
} from '@/constants/common.constants'
import { message } from '@/util/common'
import { generateThemeCssVariables, message, removeUselessAttributes } from '@/util/common'
import { navigateToEdit } from '@/util/navigation'
import {
r_tool_category_get,
@@ -13,6 +13,7 @@ import {
r_tool_template_get,
r_tool_template_get_one
} from '@/services/tool'
import { AppContext } from '@/App'
import compiler from '@/components/Playground/compiler'
import { IImportMap } from '@/components/Playground/shared'
import { base64ToFiles, base64ToStr, IMPORT_MAP_FILE_NAME } from '@/components/Playground/files'
@@ -23,7 +24,8 @@ import HideScrollbar from '@/components/common/HideScrollbar'
import Playground from '@/components/Playground'
const Create = () => {
const { styles } = useStyles()
const { styles, theme } = useStyles()
const { isDarkMode } = useContext(AppContext)
const navigate = useNavigate()
const [form] = AntdForm.useForm<ToolCreateParam>()
const formValues = AntdForm.useWatch([], form)
@@ -380,6 +382,10 @@ const Create = () => {
iframeKey={previewTemplate}
compiledCode={compiledCode}
mobileMode={formValues.platform === 'ANDROID'}
globalJsVariables={{
OxygenTheme: { ...removeUselessAttributes(theme), isDarkMode }
}}
globalCssVariables={generateThemeCssVariables(theme).styles}
/>
) : (
<span className={styles.noPreview}></span>

View File

@@ -8,7 +8,12 @@ import {
TOOL_HAS_BEEN_PUBLISHED,
TOOL_UNDER_REVIEW
} from '@/constants/common.constants'
import { addExtraCssVariable, message } from '@/util/common'
import {
addExtraCssVariables,
generateThemeCssVariables,
message,
removeUselessAttributes
} from '@/util/common'
import { navigateToRepository } from '@/util/navigation'
import editorExtraLibs from '@/util/editorExtraLibs'
import { r_tool_category_get, r_tool_detail, r_tool_update } from '@/services/tool'
@@ -28,7 +33,7 @@ import LoadingMask from '@/components/common/LoadingMask'
import Card from '@/components/common/Card'
const Edit = () => {
const { styles } = useStyles()
const { styles, theme } = useStyles()
const { isDarkMode } = useContext(AppContext)
const blocker = useBlocker(
({ currentLocation, nextLocation }) =>
@@ -457,7 +462,7 @@ const Edit = () => {
onChangeFileContent={handleOnChangeFileContent}
onSelectedFileChange={setSelectedFileName}
extraLibs={editorExtraLibs}
onEditorDidMount={(_, monaco) => addExtraCssVariable(monaco)}
onEditorDidMount={(_, monaco) => addExtraCssVariables(monaco)}
/>
<Playground.Output
isDarkMode={isDarkMode}
@@ -467,6 +472,10 @@ const Edit = () => {
entryPoint={entryPoint}
postExpansionCode={baseDist}
mobileMode={toolData?.platform === 'ANDROID'}
globalJsVariables={{
OxygenTheme: { ...removeUselessAttributes(theme), isDarkMode }
}}
globalCssVariables={generateThemeCssVariables(theme).styles}
/>
</LoadingMask>
{isShowDraggableMask && <div className={styles.draggableMask} />}

View File

@@ -1,6 +1,6 @@
import useStyles from '@/assets/css/pages/tools/source.style'
import { DATABASE_NO_RECORD_FOUND, DATABASE_SELECT_SUCCESS } from '@/constants/common.constants'
import { addExtraCssVariable, message } from '@/util/common'
import { addExtraCssVariables, message } from '@/util/common'
import { getLoginStatus } from '@/util/auth'
import { navigateToRepository, navigateToSource } from '@/util/navigation'
import editorExtraLibs from '@/util/editorExtraLibs'
@@ -98,7 +98,7 @@ const Source = () => {
selectedFileName={selectedFileName}
onSelectedFileChange={setSelectedFileName}
extraLibs={editorExtraLibs}
onEditorDidMount={(_, monaco) => addExtraCssVariable(monaco)}
onEditorDidMount={(_, monaco) => addExtraCssVariables(monaco)}
/>
</Card>
</FitFullscreen>

View File

@@ -1,6 +1,11 @@
import useStyles from '@/assets/css/pages/tools/view.style'
import { DATABASE_NO_RECORD_FOUND, DATABASE_SELECT_SUCCESS } from '@/constants/common.constants'
import { checkDesktop, message } from '@/util/common'
import {
checkDesktop,
generateThemeCssVariables,
message,
removeUselessAttributes
} from '@/util/common'
import { getLoginStatus } from '@/util/auth'
import {
navigateToInstall,
@@ -9,6 +14,7 @@ import {
navigateToView
} from '@/util/navigation'
import { l_tool_detail, r_tool_detail } from '@/services/tool'
import { AppContext } from '@/App'
import compiler from '@/components/Playground/compiler'
import { IImportMap } from '@/components/Playground/shared'
import { base64ToFiles, base64ToStr, IMPORT_MAP_FILE_NAME } from '@/components/Playground/files'
@@ -17,7 +23,8 @@ import Playground from '@/components/Playground'
import Card from '@/components/common/Card'
const View = () => {
const { styles } = useStyles()
const { styles, theme } = useStyles()
const { isDarkMode } = useContext(AppContext)
const navigate = useNavigate()
const { username, toolId, ver } = useParams()
const [searchParams] = useSearchParams({
@@ -157,6 +164,10 @@ const View = () => {
iframeKey={`${username}:${toolId}:${ver}`}
compiledCode={compiledCode}
mobileMode={isMobileMode}
globalJsVariables={{
OxygenTheme: { ...removeUselessAttributes(theme), isDarkMode }
}}
globalCssVariables={generateThemeCssVariables(theme).styles}
/>
</Card>
</FitFullscreen>

View File

@@ -4,7 +4,7 @@ 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 { css, AntdToken, Theme } from 'antd-style'
import { floor } from 'lodash'
import {
STORAGE_COLLAPSE_SIDEBAR_KEY,
@@ -300,8 +300,6 @@ const cssVariables: string[] = [
'zIndexBase',
'zIndexPopupBase',
'opacityImage',
'wireframe',
'motion',
'colorLinkHover',
'colorText',
'colorTextSecondary',
@@ -485,7 +483,7 @@ const cssVariables: string[] = [
'screenXXLMin'
]
export const generateThemeCssVariable = (theme: Omit<Theme, 'prefixCls'>) => {
export const generateThemeCssVariables = (theme: AntdToken) => {
const cssContent = cssVariables
.map((variable) => `--${variable}: ${theme[variable]};`)
.join('\n')
@@ -497,7 +495,7 @@ export const generateThemeCssVariable = (theme: Omit<Theme, 'prefixCls'>) => {
`
}
export const addExtraCssVariable = (monaco: Monaco) => {
export const addExtraCssVariables = (monaco: Monaco) => {
monaco.languages.registerCompletionItemProvider('css', {
provideCompletionItems: (
model: editor.ITextModel,
@@ -535,3 +533,25 @@ export const addExtraCssVariable = (monaco: Monaco) => {
}
})
}
export const removeUselessAttributes = (theme: Omit<Theme, 'prefixCls'>) => {
const {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-expect-error
Tree,
appearance,
browserPrefers,
isDarkMode,
setAppearance,
setThemeMode,
stylish,
themeMode,
wireframe,
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-expect-error
_tokenKey,
...result
} = theme
return result
}

File diff suppressed because it is too large Load Diff