Compare commits

...

12 Commits

26 changed files with 2598 additions and 328 deletions

228
package-lock.json generated
View File

@@ -33,6 +33,7 @@
"@types/react-dom": "^18.3.1",
"@typescript/ata": "^0.9.7",
"@vitejs/plugin-react": "^4.3.3",
"@xyflow/react": "^12.3.2",
"antd": "^5.21.6",
"antd-style": "^3.7.1",
"axios": "^1.7.7",
@@ -3288,6 +3289,55 @@
"@types/responselike": "^1.0.0"
}
},
"node_modules/@types/d3-color": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz",
"integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==",
"dev": true
},
"node_modules/@types/d3-drag": {
"version": "3.0.7",
"resolved": "https://registry.npmjs.org/@types/d3-drag/-/d3-drag-3.0.7.tgz",
"integrity": "sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==",
"dev": true,
"dependencies": {
"@types/d3-selection": "*"
}
},
"node_modules/@types/d3-interpolate": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz",
"integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==",
"dev": true,
"dependencies": {
"@types/d3-color": "*"
}
},
"node_modules/@types/d3-selection": {
"version": "3.0.11",
"resolved": "https://registry.npmjs.org/@types/d3-selection/-/d3-selection-3.0.11.tgz",
"integrity": "sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w==",
"dev": true
},
"node_modules/@types/d3-transition": {
"version": "3.0.9",
"resolved": "https://registry.npmjs.org/@types/d3-transition/-/d3-transition-3.0.9.tgz",
"integrity": "sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg==",
"dev": true,
"dependencies": {
"@types/d3-selection": "*"
}
},
"node_modules/@types/d3-zoom": {
"version": "3.0.8",
"resolved": "https://registry.npmjs.org/@types/d3-zoom/-/d3-zoom-3.0.8.tgz",
"integrity": "sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==",
"dev": true,
"dependencies": {
"@types/d3-interpolate": "*",
"@types/d3-selection": "*"
}
},
"node_modules/@types/debug": {
"version": "4.1.12",
"resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz",
@@ -3736,6 +3786,36 @@
"node": ">=10.0.0"
}
},
"node_modules/@xyflow/react": {
"version": "12.3.3",
"resolved": "https://registry.npmjs.org/@xyflow/react/-/react-12.3.3.tgz",
"integrity": "sha512-hKwnY68fijc393/KCCCyJUi+vESPnY0Vht0Brbny8feMpp/iHtTNGbpwzQtSDmbvvnnIwIAWrxPZPgiUQmK8Kw==",
"dev": true,
"dependencies": {
"@xyflow/system": "0.0.44",
"classcat": "^5.0.3",
"zustand": "^4.4.0"
},
"peerDependencies": {
"react": ">=17",
"react-dom": ">=17"
}
},
"node_modules/@xyflow/system": {
"version": "0.0.44",
"resolved": "https://registry.npmjs.org/@xyflow/system/-/system-0.0.44.tgz",
"integrity": "sha512-hKHtH8hUVKmCCXbTdEYUWNqRkcSBwYxdzZhIxpJst60AEnlobfphNu8eAOJArEJJl+MrjidvY5K/BOzYUcwCug==",
"dev": true,
"dependencies": {
"@types/d3-drag": "^3.0.7",
"@types/d3-selection": "^3.0.10",
"@types/d3-transition": "^3.0.8",
"@types/d3-zoom": "^3.0.8",
"d3-drag": "^3.0.0",
"d3-selection": "^3.0.0",
"d3-zoom": "^3.0.0"
}
},
"node_modules/7zip-bin": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/7zip-bin/-/7zip-bin-5.2.0.tgz",
@@ -4764,6 +4844,12 @@
"node": ">=8"
}
},
"node_modules/classcat": {
"version": "5.0.5",
"resolved": "https://registry.npmjs.org/classcat/-/classcat-5.0.5.tgz",
"integrity": "sha512-JhZUT7JFcQy/EzW605k/ktHtncoo9vnyW/2GspNYwFlN1C/WmjuV/xtS04e9SOkL2sTdw0VAZ2UGCcQ9lR6p6w==",
"dev": true
},
"node_modules/classnames": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz",
@@ -5242,6 +5328,111 @@
"dev": true,
"license": "MIT"
},
"node_modules/d3-color": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz",
"integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==",
"dev": true,
"engines": {
"node": ">=12"
}
},
"node_modules/d3-dispatch": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz",
"integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==",
"dev": true,
"engines": {
"node": ">=12"
}
},
"node_modules/d3-drag": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz",
"integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==",
"dev": true,
"dependencies": {
"d3-dispatch": "1 - 3",
"d3-selection": "3"
},
"engines": {
"node": ">=12"
}
},
"node_modules/d3-ease": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz",
"integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==",
"dev": true,
"engines": {
"node": ">=12"
}
},
"node_modules/d3-interpolate": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz",
"integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==",
"dev": true,
"dependencies": {
"d3-color": "1 - 3"
},
"engines": {
"node": ">=12"
}
},
"node_modules/d3-selection": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz",
"integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==",
"dev": true,
"engines": {
"node": ">=12"
}
},
"node_modules/d3-timer": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz",
"integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==",
"dev": true,
"engines": {
"node": ">=12"
}
},
"node_modules/d3-transition": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz",
"integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==",
"dev": true,
"dependencies": {
"d3-color": "1 - 3",
"d3-dispatch": "1 - 3",
"d3-ease": "1 - 3",
"d3-interpolate": "1 - 3",
"d3-timer": "1 - 3"
},
"engines": {
"node": ">=12"
},
"peerDependencies": {
"d3-selection": "2 - 3"
}
},
"node_modules/d3-zoom": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-3.0.0.tgz",
"integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==",
"dev": true,
"dependencies": {
"d3-dispatch": "1 - 3",
"d3-drag": "2 - 3",
"d3-interpolate": "1 - 3",
"d3-selection": "2 - 3",
"d3-transition": "2 - 3"
},
"engines": {
"node": ">=12"
}
},
"node_modules/data-urls": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/data-urls/-/data-urls-5.0.0.tgz",
@@ -11596,6 +11787,15 @@
"react": ">= 16.x"
}
},
"node_modules/use-sync-external-store": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.2.tgz",
"integrity": "sha512-PElTlVMwpblvbNqQ82d2n6RjStvdSoNe9FG28kNfz3WiXilJm4DdNkEzRhCZuIDwY8U08WVihhGR5iRqAwfDiw==",
"dev": true,
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0 || ^18.0.0"
}
},
"node_modules/utf8-byte-length": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/utf8-byte-length/-/utf8-byte-length-1.0.5.tgz",
@@ -12055,6 +12255,34 @@
"dev": true,
"license": "0BSD"
},
"node_modules/zustand": {
"version": "4.5.5",
"resolved": "https://registry.npmjs.org/zustand/-/zustand-4.5.5.tgz",
"integrity": "sha512-+0PALYNJNgK6hldkgDq2vLrw5f6g/jCInz52n9RTpropGgeAf/ioFUCdtsjCqu4gNhW9D01rUQBROoRjdzyn2Q==",
"dev": true,
"dependencies": {
"use-sync-external-store": "1.2.2"
},
"engines": {
"node": ">=12.7.0"
},
"peerDependencies": {
"@types/react": ">=16.8",
"immer": ">=9.0.6",
"react": ">=16.8"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"immer": {
"optional": true
},
"react": {
"optional": true
}
}
},
"node_modules/zwitch": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz",

View File

@@ -54,6 +54,7 @@
"@types/react-dom": "^18.3.1",
"@typescript/ata": "^0.9.7",
"@vitejs/plugin-react": "^4.3.3",
"@xyflow/react": "^12.3.2",
"antd": "^5.21.6",
"antd-style": "^3.7.1",
"axios": "^1.7.7",

View File

@@ -4,6 +4,7 @@ import { electronApp, optimizer, is } from '@electron-toolkit/utils'
import icon from '../../build/icon.ico?asset'
import path from 'node:path'
import url from 'node:url'
import fs from 'fs'
let mainWindow: BrowserWindow
@@ -105,8 +106,19 @@ void app.whenReady().then(() => {
protocol.handle('local', (request) => {
const { host } = new URL(request.url)
if (host === 'oxygen.fatweb.top') {
const filePath = request.url.slice('local://oxygen.fatweb.top/'.length)
return net.fetch(url.pathToFileURL(filePath).toString())
let filePath = request.url.slice('local://oxygen.fatweb.top/'.length)
if (fs.existsSync(filePath)) {
return net.fetch(url.pathToFileURL(filePath).toString())
}
filePath = join(join(__dirname, '../renderer'), filePath)
if (fs.existsSync(filePath)) {
return net.fetch(url.pathToFileURL(filePath).toString())
}
return net.fetch(
url.pathToFileURL(join(__dirname, '../renderer/index.html')).toString()
)
} else {
const filePath = request.url.slice('local://'.length)
return net.fetch(url.pathToFileURL(filePath).toString())

View File

@@ -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<editor.IStandaloneCodeEditor>()
@@ -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)
}

View File

@@ -3,7 +3,6 @@ import { createStyles } from 'antd-style'
export default createStyles(({ token }) => ({
root: {
position: 'relative',
width: '100%',
height: '100%'
},

View File

@@ -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 && <div className={styles.errorMessage}>{errorMsg}</div>}
</FlexBox>

View File

@@ -1,256 +1,144 @@
import { ChangeEvent } from 'react'
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 useStyles from '@/components/Playground/Output/Preview/render.style'
import iframeRaw from '@/components/Playground/Output/Preview/iframe.html?raw'
import HideScrollbar from '@/components/common/HideScrollbar'
import devices, { DeviceName } from '@/components/Playground/Output/Preview/devices'
import Simulation, { SimulationData } from '@/components/Playground/Output/Preview/Simulation'
interface RenderProps {
iframeKey: string
compiledCode: string
mobileMode?: boolean
globalJsVariables?: Record<string, unknown>
globalCssVariables?: string
}
interface IMessage {
type: 'LOADED' | 'ERROR' | 'UPDATE' | 'DONE' | 'SCALE'
type: 'LOADED' | 'ERROR' | 'UPDATE' | 'DONE' | 'GLOBAL_VARIABLES'
msg: string
data: {
compiledCode?: string
zoom?: number
globalJsVariables?: Record<string, unknown>
globalCssVariables?: string
}
}
interface IDevice {
name: string
width: number
height: number
}
const getIframeUrl = (iframeRaw: string) => {
const shimsUrl = '//unpkg.com/es-module-shims/dist/es-module-shims.js'
// 判断浏览器是否支持esm 不支持esm就引入es-module-shims
const newIframeRaw =
typeof import.meta === 'undefined'
? iframeRaw.replace(
'<!-- es-module-shims -->',
`<script async src="${shimsUrl}"></script>`
)
: iframeRaw
return URL.createObjectURL(new Blob([newIframeRaw], { type: 'text/html' }))
return URL.createObjectURL(new Blob([iframeRaw], { type: 'text/html' }))
}
const iframeUrl = getIframeUrl(iframeRaw)
const Render = ({ iframeKey, compiledCode, mobileMode = false }: RenderProps) => {
const { styles, theme, cx } = useStyles()
const Render = ({
iframeKey,
compiledCode,
mobileMode = false,
globalJsVariables,
globalCssVariables
}: RenderProps) => {
const { styles, theme } = useStyles()
const { isDarkMode } = useContext(AppContext)
const iframeRef = useRef<HTMLIFrameElement>(null)
const [isLoaded, setIsLoaded] = useState(false)
const [selectedDevice, setSelectedDevice] = useState('Pixel 7')
const [zoom, setZoom] = useState(1)
const [selectedDevice, setSelectedDevice] = useState<DeviceName>('Pixel 7')
const [isRotate, setIsRotate] = useState(false)
const devices: IDevice[] = [
{
name: 'iPhone SE',
width: 375,
height: 667
},
{
name: 'iPhone XR',
width: 414,
height: 896
},
{
name: 'iPhone 12 Pro',
width: 390,
height: 844
},
{
name: 'iPhone 14 Pro Max',
width: 430,
height: 932
},
{
name: 'Pixel 7',
width: 412,
height: 915
},
{
name: 'Samsung Galaxy S8+',
width: 360,
height: 740
},
{
name: 'Samsung Galaxy S20 Ultra',
width: 412,
height: 915
},
{
name: 'iPad Mini',
width: 768,
height: 1024
},
{
name: 'iPad Air',
width: 820,
height: 1180
},
{
name: 'iPad Pro',
width: 1024,
height: 1366
},
{
name: 'Surface Pro 7',
width: 912,
height: 1368
},
{
name: 'Surface Duo',
width: 540,
height: 720
},
{
name: 'Galaxy Fold',
width: 280,
height: 653
},
{
name: 'Asus Zenbook Fold',
width: 853,
height: 1280
},
{
name: 'Samsung Galaxy A51/71',
width: 412,
height: 914
},
{
name: 'Nest Hub',
width: 1024,
height: 600
},
{
name: 'Nest Hub Max',
width: 1280,
height: 800
}
]
const handleOnChangeDevice = (e: ChangeEvent<HTMLSelectElement>) => {
setSelectedDevice(e.target.value)
}
const handleOnChangeZoom = (e: ChangeEvent<HTMLInputElement>) => {
setZoom(Number(e.target.value))
}
const [nodes, setNodes] = useState<Node<SimulationData>[]>([])
const handleOnRotateDevice = () => {
setIsRotate(!isRotate)
}
const loadGlobalVariables = () => {
iframeRef.current?.contentWindow?.postMessage(
{
type: 'GLOBAL_VARIABLES',
data: { globalJsVariables, globalCssVariables }
} as IMessage,
'*'
)
}
useEffect(() => {
if (isLoaded) {
iframeRef.current?.contentWindow?.postMessage(
{
type: 'UPDATE',
data: { compiledCode }
} as IMessage,
'*'
)
if (!isLoaded) {
return
}
iframeRef.current?.contentWindow?.postMessage(
{
type: 'UPDATE',
data: { compiledCode }
} as IMessage,
'*'
)
loadGlobalVariables()
}, [isLoaded, compiledCode])
useEffect(() => {
if (isLoaded) {
iframeRef.current?.contentWindow?.postMessage(
{
type: 'SCALE',
data: { zoom }
} as IMessage,
'*'
)
if (!isLoaded) {
return
}
}, [isLoaded, zoom])
loadGlobalVariables()
}, [isLoaded, globalJsVariables, globalCssVariables])
useEffect(() => {
setNodes([
{
id: 'device',
type: 'simulation',
position: { x: 0, y: 0 },
data: {
deviceWidth: devices[selectedDevice].width,
deviceHeight: devices[selectedDevice].height,
isRotate,
iframeKey,
iframeRef,
iframeUrl,
setIsLoaded
}
}
])
}, [selectedDevice, isRotate, iframeKey, iframeRef, iframeUrl, setIsLoaded])
return mobileMode ? (
<>
<HideScrollbar
className={styles.mobileModeRoot}
isShowVerticalScrollbar
isShowHorizontalScrollbar
autoHideWaitingTime={1000}
<ReactFlow
colorMode={isDarkMode ? 'dark' : 'light'}
nodeTypes={{ simulation: Simulation }}
nodes={nodes}
proOptions={{ hideAttribution: true }}
fitView
>
<div className={styles.mobileModeContent} style={{ zoom }}>
<div className={cx(styles.device, isRotate ? styles.rotate : '')}>
<div
className={cx(
styles.deviceHeader,
isRotate ? styles.rotatedDeviceHeader : ''
)}
<Background bgColor={theme.colorBgLayout} />
<MiniMap bgColor={theme.colorBgMask} zoomStep={1} pannable zoomable />
<Controls />
<Panel>
<AntdSpace>
<AntdSelect
size={'small'}
options={Object.values(devices).map((item) => ({
label: item.name,
value: item.name
}))}
value={selectedDevice}
onChange={setSelectedDevice}
/>
<div
className={cx(
styles.deviceContent,
isRotate ? styles.rotatedDeviceContent : ''
)}
style={{
width: isRotate
? (devices.find((value) => value.name === selectedDevice)
?.height ?? 915)
: (devices.find((value) => value.name === selectedDevice)
?.width ?? 412),
height: isRotate
? (devices.find((value) => value.name === selectedDevice)
?.width ?? 412)
: (devices.find((value) => value.name === selectedDevice)
?.height ?? 915)
}}
>
<iframe
className={styles.renderRoot}
key={iframeKey}
ref={iframeRef}
src={iframeUrl}
onLoad={() => setIsLoaded(true)}
sandbox="allow-downloads allow-forms allow-modals allow-scripts"
allow={'clipboard-read; clipboard-write'}
/>
</div>
<div
className={cx(
styles.deviceFooter,
isRotate ? styles.rotatedDeviceFooter : ''
)}
<AntdButton
size={'small'}
title={'旋转屏幕'}
onClick={handleOnRotateDevice}
icon={
<Icon
component={
isRotate ? IconOxygenRotateRight : IconOxygenRotateLeft
}
/>
}
/>
</div>
</div>
</HideScrollbar>
<div className={styles.switchDevice}>
<IconOxygenMobile fill={theme.colorText} />
<select value={selectedDevice} onChange={handleOnChangeDevice}>
{devices.map((value) => (
<option value={value.name}>{value.name}</option>
))}
</select>
<div title={'旋转屏幕'} onClick={handleOnRotateDevice}>
{isRotate ? (
<IconOxygenRotateRight fill={theme.colorText} />
) : (
<IconOxygenRotateLeft fill={theme.colorText} />
)}
</div>
</div>
<div className={styles.switchZoom}>
<IconOxygenZoom fill={theme.colorText} />
<input
type={'range'}
min={0.5}
max={2}
step={0.1}
value={zoom}
onChange={handleOnChangeZoom}
/>
</div>
</AntdSpace>
</Panel>
</ReactFlow>
</>
) : (
<iframe

View File

@@ -0,0 +1,52 @@
import { Dispatch, RefObject, SetStateAction } from 'react'
import { Node, NodeProps } from '@xyflow/react'
import useStyles from '@/components/Playground/Output/Preview/simulation.style'
export type SimulationData = {
deviceWidth: number
deviceHeight: number
isRotate: boolean
iframeKey: string
iframeRef: RefObject<HTMLIFrameElement>
iframeUrl: string
setIsLoaded: Dispatch<SetStateAction<boolean>>
}
export type SimulationNode = Node<SimulationData, 'simulation'>
const Simulation = ({ data }: NodeProps<SimulationNode>) => {
const { styles, cx } = useStyles()
return (
<div className={cx(styles.device, data.isRotate ? styles.rotate : '')}>
<div
className={cx(styles.deviceHeader, data.isRotate ? styles.rotatedDeviceHeader : '')}
/>
<div
className={cx(
styles.deviceContent,
data.isRotate ? styles.rotatedDeviceContent : ''
)}
style={{
width: data.isRotate ? data.deviceHeight : data.deviceWidth,
height: data.isRotate ? data.deviceWidth : data.deviceHeight
}}
>
<iframe
className={styles.renderRoot}
key={data.iframeKey}
ref={data.iframeRef}
src={data.iframeUrl}
onLoad={() => data.setIsLoaded(true)}
sandbox={'allow-downloads allow-forms allow-modals allow-scripts'}
allow={'clipboard-read; clipboard-write'}
/>
</div>
<div
className={cx(styles.deviceFooter, data.isRotate ? styles.rotatedDeviceFooter : '')}
/>
</div>
)
}
export default Simulation

View File

@@ -0,0 +1,114 @@
export type DeviceName =
| 'iPhone SE'
| 'iPhone XR'
| 'iPhone 12 Pro'
| 'iPhone 14 Pro Max'
| 'Pixel 7'
| 'Samsung Galaxy S8+'
| 'Samsung Galaxy S20 Ultra'
| 'iPad Mini'
| 'iPad Air'
| 'iPad Pro'
| 'Surface Pro 7'
| 'Surface Duo'
| 'Galaxy Fold'
| 'Asus Zenbook Fold'
| 'Samsung Galaxy A51/71'
| 'Nest Hub'
| 'Nest Hub Max'
export interface IDevice {
name: DeviceName
width: number
height: number
}
const devices: Record<DeviceName, IDevice> = {
'iPhone SE': {
name: 'iPhone SE',
width: 375,
height: 667
},
'iPhone XR': {
name: 'iPhone XR',
width: 414,
height: 896
},
'iPhone 12 Pro': {
name: 'iPhone 12 Pro',
width: 390,
height: 844
},
'iPhone 14 Pro Max': {
name: 'iPhone 14 Pro Max',
width: 430,
height: 932
},
'Pixel 7': {
name: 'Pixel 7',
width: 412,
height: 915
},
'Samsung Galaxy S8+': {
name: 'Samsung Galaxy S8+',
width: 360,
height: 740
},
'Samsung Galaxy S20 Ultra': {
name: 'Samsung Galaxy S20 Ultra',
width: 412,
height: 915
},
'iPad Mini': {
name: 'iPad Mini',
width: 768,
height: 1024
},
'iPad Air': {
name: 'iPad Air',
width: 820,
height: 1180
},
'iPad Pro': {
name: 'iPad Pro',
width: 1024,
height: 1366
},
'Surface Pro 7': {
name: 'Surface Pro 7',
width: 912,
height: 1368
},
'Surface Duo': {
name: 'Surface Duo',
width: 540,
height: 720
},
'Galaxy Fold': {
name: 'Galaxy Fold',
width: 280,
height: 653
},
'Asus Zenbook Fold': {
name: 'Asus Zenbook Fold',
width: 853,
height: 1280
},
'Samsung Galaxy A51/71': {
name: 'Samsung Galaxy A51/71',
width: 412,
height: 914
},
'Nest Hub': {
name: 'Nest Hub',
width: 1024,
height: 600
},
'Nest Hub Max': {
name: 'Nest Hub Max',
width: 1280,
height: 800
}
}
export default devices

View File

@@ -4,14 +4,14 @@
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Preview</title>
<!-- es-module-shims -->
<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(style[id$=\"_oxygen_base_style.css\"])") || [];
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 === "SCALE") {
document.getElementById("root").style.zoom = data.data.zoom;
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

@@ -1,84 +1,10 @@
import { createStyles } from 'antd-style'
export default createStyles(({ token }) => ({
export default createStyles(() => ({
renderRoot: {
border: 'none',
height: '100%',
width: '100%',
flex: 1
},
mobileModeRoot: {
backgroundColor: token.colorBgLayout
},
mobileModeContent: {
padding: 80
},
device: {
display: 'flex',
flexDirection: 'column',
backgroundColor: token.colorBgElevated,
width: 'fit-content',
margin: '0 auto',
borderRadius: 40
},
rotate: {
flexDirection: 'row'
},
deviceHeader: {
margin: '20px auto',
width: 60,
height: 10,
borderRadius: 5,
backgroundColor: token.colorBgMask
},
rotatedDeviceHeader: {
margin: 'auto 20px',
width: 10,
height: 60
},
deviceContent: {
margin: '0 10px',
backgroundColor: token.colorBgLayout
},
rotatedDeviceContent: {
margin: '10px 0'
},
deviceFooter: {
margin: '20px auto',
width: 40,
height: 40,
borderRadius: '50%',
backgroundColor: token.colorBgMask
},
rotatedDeviceFooter: {
margin: 'auto 20px'
},
switchDevice: {
display: 'flex',
position: 'absolute',
top: 10,
left: 10,
alignItems: 'center',
gap: 4
},
switchZoom: {
display: 'flex',
position: 'absolute',
top: 10,
right: 10,
alignItems: 'center',
gap: 4
}
}))

View File

@@ -0,0 +1,58 @@
import { createStyles } from 'antd-style'
export default createStyles(({ token }) => ({
renderRoot: {
border: `1px solid ${token.colorBorder}`,
height: '100%',
width: '100%',
flex: 1
},
device: {
display: 'flex',
flexDirection: 'column',
backgroundColor: token.colorBgElevated,
width: 'fit-content',
margin: '0 auto',
borderRadius: 40
},
rotate: {
flexDirection: 'row'
},
deviceHeader: {
margin: '20px auto',
width: 60,
height: 10,
borderRadius: 5,
backgroundColor: token.colorBgMask
},
rotatedDeviceHeader: {
margin: 'auto 20px',
width: 10,
height: 60
},
deviceContent: {
margin: '0 10px',
backgroundColor: token.colorBgLayout
},
rotatedDeviceContent: {
margin: '10px 0'
},
deviceFooter: {
margin: '20px auto',
width: 40,
height: 40,
borderRadius: '50%',
backgroundColor: token.colorBgMask
},
rotatedDeviceFooter: {
margin: 'auto 20px'
}
}))

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

@@ -262,7 +262,9 @@ const SignIn = () => {
<FlexBox direction={'horizontal'} className={styles.addition}>
<a
onClick={() => {
navigateToRoot(navigate)
setTimeout(() => {
navigateToRoot(navigate)
})
}}
>

View File

@@ -7,7 +7,7 @@ import {
DATABASE_SELECT_SUCCESS,
DATABASE_UPDATE_SUCCESS
} from '@/constants/common.constants'
import { 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,6 +1113,7 @@ const Base = () => {
!hasPermission('system:tool:modify:base')
}
extraLibs={editorExtraLibs}
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 } 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'
@@ -64,8 +64,9 @@ const Code = () => {
render(response.data!)
break
case DATABASE_NO_RECORD_FOUND:
void message.error('未找到指定工具')
navigateToRepository(navigate)
message.error('未找到指定工具').then(() => {
navigateToRepository(navigate)
})
break
default:
void message.error('获取工具信息失败,请稍后重试')
@@ -92,6 +93,7 @@ const Code = () => {
selectedFileName={selectedFileName}
onSelectedFileChange={setSelectedFileName}
extraLibs={editorExtraLibs}
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 { 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,14 +17,29 @@ 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)
const [compiledCode, setCompiledCode] = useState('')
const [isMobileMode, setIsMobileMode] = useState(false)
const render = (toolVo: ToolVo) => {
try {
switch (toolVo.platform) {
case 'ANDROID':
setIsMobileMode(true)
break
case 'DESKTOP':
if (!checkDesktop()) {
message.warning('此应用需要桌面端环境,请在桌面端打开').then(() => {
navigateToTools(navigate)
})
return
}
}
const baseDist = base64ToStr(toolVo.base.dist.data!)
const files = base64ToFiles(toolVo.source.data!)
const importMap = JSON.parse(files[IMPORT_MAP_FILE_NAME].value) as IImportMap
@@ -55,8 +76,9 @@ const Execute = () => {
render(response.data!)
break
case DATABASE_NO_RECORD_FOUND:
void message.error('未找到指定工具')
navigateToTools(navigate)
message.error('未找到指定工具').then(() => {
navigateToTools(navigate)
})
break
default:
void message.error('获取工具信息失败,请稍后重试')
@@ -75,7 +97,15 @@ const Execute = () => {
return (
<FitFullscreen className={styles.root}>
<Card className={styles.rootBox}>
<Playground.Output.Preview.Render iframeKey={`${id}`} compiledCode={compiledCode} />
<Playground.Output.Preview.Render
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 { 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,6 +1056,7 @@ const Template = () => {
!hasPermission('system:tool:modify:template')
}
extraLibs={editorExtraLibs}
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 { 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,6 +462,7 @@ const Edit = () => {
onChangeFileContent={handleOnChangeFileContent}
onSelectedFileChange={setSelectedFileName}
extraLibs={editorExtraLibs}
onEditorDidMount={(_, monaco) => addExtraCssVariables(monaco)}
/>
<Playground.Output
isDarkMode={isDarkMode}
@@ -466,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 { 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,6 +98,7 @@ const Source = () => {
selectedFileName={selectedFileName}
onSelectedFileChange={setSelectedFileName}
extraLibs={editorExtraLibs}
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 { 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({
@@ -25,10 +32,21 @@ const View = () => {
})
const [isLoading, setIsLoading] = useState(false)
const [compiledCode, setCompiledCode] = useState('')
const [isAndroid, setIsAndroid] = useState(false)
const [isMobileMode, setIsMobileMode] = useState(false)
const render = (toolVo: ToolVo) => {
setIsAndroid(toolVo.platform === 'ANDROID')
switch (toolVo.platform) {
case 'ANDROID':
setIsMobileMode(true)
break
case 'DESKTOP':
if (!checkDesktop()) {
message.warning('此应用需要桌面端环境,请在桌面端打开').then(() => {
navigateToRepository(navigate)
})
return
}
}
if (username === '!') {
try {
const baseDist = base64ToStr(toolVo.base.dist.data!)
@@ -145,7 +163,11 @@ const View = () => {
<Playground.Output.Preview.Render
iframeKey={`${username}:${toolId}:${ver}`}
compiledCode={compiledCode}
mobileMode={isAndroid}
mobileMode={isMobileMode}
globalJsVariables={{
OxygenTheme: { ...removeUselessAttributes(theme), isDarkMode }
}}
globalCssVariables={generateThemeCssVariables(theme).styles}
/>
</Card>
</FitFullscreen>

View File

@@ -29,11 +29,6 @@ export const tools: RouteJsonObject[] = [
icon: lazy(() => import('~icons/oxygen/installed')),
menu: true
},
{
path: '',
absolutePath: '/',
element: <Navigate to="/store" replace />
},
{
path: 'store/:username',
absolutePath: '/store',
@@ -92,7 +87,7 @@ export const tools: RouteJsonObject[] = [
{
path: '*',
absolutePath: '*',
element: <Navigate to="/" replace />
element: <Navigate to="/store" replace />
}
]

View File

@@ -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 { css, AntdToken, Theme } 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
@@ -215,7 +218,6 @@ const substringByByte = (str: string, start: number, length: number) => {
}
export const omitTextByByte = (text: string, length: number) => {
console.log(getByteLength(text))
if (getByteLength(text) <= length) {
return text
}
@@ -242,3 +244,314 @@ 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',
'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 generateThemeCssVariables = (theme: AntdToken) => {
const cssContent = cssVariables
.map((variable) => `--${variable}: ${theme[variable]};`)
.join('\n')
return css`
:root {
${cssContent}
}
`
}
export const addExtraCssVariables = (monaco: Monaco) => {
monaco.languages.registerCompletionItemProvider('css', {
provideCompletionItems: (
model: editor.ITextModel,
position: Position
): languages.ProviderResult<languages.CompletionList> => {
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'
})
)
}
}
})
}
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