diff --git a/.dumi/pages/theme-editor/components/JSONEditor.tsx b/.dumi/pages/theme-editor/components/JSONEditor.tsx new file mode 100644 index 000000000000..28a71ee33b0e --- /dev/null +++ b/.dumi/pages/theme-editor/components/JSONEditor.tsx @@ -0,0 +1,33 @@ +import { JSONEditor as Editor, Mode, type JSONEditorPropsOptional } from 'vanilla-jsoneditor'; +import React from 'react'; + +const JSONEditor = (props: JSONEditorPropsOptional) => { + const refContainer = React.useRef(null); + const refEditor = React.useRef(null); + + React.useEffect(() => { + refEditor.current = new Editor({ + target: refContainer.current, + props: { + mode: Mode.text, + }, + }); + + return () => { + if (refEditor.current) { + refEditor.current.destroy(); + refEditor.current = null; + } + }; + }, []); + + React.useEffect(() => { + if (refEditor.current) { + refEditor.current.updateProps(props); + } + }, [props]); + + return
; +}; + +export default JSONEditor; diff --git a/.dumi/pages/theme-editor/components/utils.tsx b/.dumi/pages/theme-editor/components/utils.tsx new file mode 100644 index 000000000000..afc5450e5fbd --- /dev/null +++ b/.dumi/pages/theme-editor/components/utils.tsx @@ -0,0 +1,4 @@ +/* eslint-disable import/prefer-default-export */ +export function isObject(target: any) { + return Object.prototype.toString.call(target) === '[object Object]'; +} diff --git a/.dumi/pages/theme-editor/index.tsx b/.dumi/pages/theme-editor/index.tsx index bf34cfb4f64e..c651513a5547 100644 --- a/.dumi/pages/theme-editor/index.tsx +++ b/.dumi/pages/theme-editor/index.tsx @@ -1,28 +1,36 @@ -import React, { useEffect } from 'react'; +import React, { useCallback, useEffect, useState } from 'react'; import { enUS, zhCN, ThemeEditor } from 'antd-token-previewer'; import { Button, ConfigProvider, message, Modal, Typography } from 'antd'; import type { ThemeConfig } from 'antd/es/config-provider/context'; import { Helmet } from 'dumi'; import { css } from '@emotion/react'; -import CopyToClipboard from 'react-copy-to-clipboard'; -import { CopyOutlined } from '@ant-design/icons'; +import { EditOutlined } from '@ant-design/icons'; +import type { JSONContent, TextContent } from 'vanilla-jsoneditor'; import useLocale from '../../hooks/useLocale'; +import JSONEditor from './components/JSONEditor'; +import { isObject } from './components/utils'; const locales = { cn: { title: '主题编辑器', save: '保存', reset: '重置', - export: '导出', - exportDesc: '将下面的 JSON 对象复制到 ConfigProvider 的 theme 属性中即可。', + edit: '代码', + editModelTitle: '编辑主题配置', + editTitle: '在下方编辑你的主题 JSON 即可', + editJsonContentTypeError: '主题 JSON 格式错误', + editSuccessfully: '编辑成功', saveSuccessfully: '保存成功', }, en: { title: 'Theme Editor', save: 'Save', reset: 'Reset', - export: 'Export', - exportDesc: 'Copy the following JSON object to the theme prop of ConfigProvider.', + edit: 'Code', + editModelTitle: 'edit Theme Config', + editTitle: 'Edit your theme JSON below', + editJsonContentTypeError: 'The theme of the JSON format is incorrect', + editSuccessfully: 'Edited successfully', saveSuccessfully: 'Saved successfully', }, }; @@ -42,11 +50,17 @@ const ANT_DESIGN_V5_THEME_EDITOR_THEME = 'ant-design-v5-theme-editor-theme'; const CustomTheme = () => { const [messageApi, contextHolder] = message.useMessage(); - const [modalApi, modalContextHolder] = Modal.useModal(); const [locale, lang] = useLocale(locales); const [theme, setTheme] = React.useState({}); + const [editModelOpen, setEditModelOpen] = useState(false); + const [editThemeFormatRight, setEditThemeFormatRight] = useState(true); + const [themeConfigContent, setThemeConfigContent] = useState({ + text: '{}', + json: undefined, + }); + useEffect(() => { const storedConfig = localStorage.getItem(ANT_DESIGN_V5_THEME_EDITOR_THEME); if (storedConfig) { @@ -54,6 +68,14 @@ const CustomTheme = () => { } }, []); + useEffect(() => { + if (editModelOpen === true) return; + setThemeConfigContent({ + json: theme as any, + text: undefined, + }); + }, [theme, editModelOpen]); + const styles = useStyle(); const handleSave = () => { @@ -61,48 +83,47 @@ const CustomTheme = () => { messageApi.success(locale.saveSuccessfully); }; - const onCopy = (text: string, result: boolean) => { - if (result) { - messageApi.success('Copy theme config successfully!'); - } else { - messageApi.error('Copy failed, please try again.'); - } + const handleReset = () => { + setTheme({}); }; - const handleOutput = () => { - modalApi.info({ - title: locale.export, - width: 600, - content: ( -
-
{locale.exportDesc}
-
-            
-              
-
- ), - }); + const handleEditConfig = () => { + setEditModelOpen(true); }; - const handleReset = () => { - setTheme({}); + const editModelClose = useCallback(() => { + setEditModelOpen(false); + }, [themeConfigContent]); + + const handleEditConfigChange = (newcontent, preContent, status) => { + setThemeConfigContent(newcontent); + if ( + Array.isArray(status.contentErrors.validationErrors) && + status.contentErrors.validationErrors.length === 0 + ) { + setEditThemeFormatRight(true); + } else { + setEditThemeFormatRight(false); + } }; + const editSave = useCallback(() => { + if (!editThemeFormatRight) { + message.error(locale.editJsonContentTypeError); + return; + } + const themeConfig = themeConfigContent.text + ? JSON.parse(themeConfigContent.text) + : themeConfigContent.json; + if (!isObject(themeConfig)) { + message.error(locale.editJsonContentTypeError); + return; + } + setTheme(themeConfig); + editModelClose(); + messageApi.success(locale.editSuccessfully); + }, [themeConfigContent]); + return (
@@ -110,15 +131,31 @@ const CustomTheme = () => { {contextHolder} - {modalContextHolder}
{locale.title}
-