diff --git a/src/JsonSchemaEditor/SchemaItem/AdvancedSettingModal.tsx b/src/JsonSchemaEditor/SchemaItem/AdvancedSettingModal.tsx new file mode 100644 index 0000000..fbf79b5 --- /dev/null +++ b/src/JsonSchemaEditor/SchemaItem/AdvancedSettingModal.tsx @@ -0,0 +1,460 @@ +import { DeleteOutlined, PlusOutlined } from '@ant-design/icons'; +import { + Button, + Col, + Form, + Input, + InputNumber, + Modal, + Row, + Select, + Switch, + theme, +} from 'antd'; +import _ from 'lodash'; +import React, { useCallback, useEffect, useRef, useState } from 'react'; +import MonacoEditor from '../MonacoEditor'; +import { SchemaTypes, StringFormat } from '../utils'; + +interface AdvancedSettingModalProps { + schema: any; + open?: boolean; + onOk?: (newSchema: any) => void; + onCancel?: () => void; +} + +export default (props: AdvancedSettingModalProps) => { + const { schema, open = false, onOk, onCancel } = props; + + const { token } = theme.useToken(); + const [advancedForm] = Form.useForm(); + const [formSchema, setFormSchema] = useState(); + const [advancedModal, setAdvancedModal] = useState(false); + const [isObject, setIsObject] = useState(false); + const [isArray, setIsArray] = useState(false); + const [isNumber, setIsNumber] = useState(false); + const [isBoolean, setIsBoolean] = useState(false); + const [isInteger, setIsInteger] = useState(false); + const [isString, setIsString] = useState(false); + const editorRef = useRef(null); + + useEffect(() => { + setFormSchema(schema); + }, [schema]); + + useEffect(() => { + setAdvancedModal(open); + }, [open]); + + const handleDebounce = useCallback( + _.debounce( + (callback) => { + if (typeof callback === 'function') { + callback(); + } else { + console.log('Provided argument is not a function'); + } + }, + 300, + { maxWait: 1000 }, + ), + [], + ); + + useEffect(() => { + return () => { + handleDebounce.cancel(); + }; + }, [handleDebounce]); + + useEffect(() => { + if (!advancedModal || !formSchema) { + return; + } + advancedForm.setFieldsValue(formSchema); + setIsObject(formSchema.type === 'object'); + setIsArray(formSchema.type === 'array'); + setIsNumber(formSchema.type === 'number'); + setIsBoolean(formSchema.type === 'boolean'); + setIsInteger(formSchema.type === 'integer'); + setIsString(formSchema.type === 'string'); + }, [advancedModal, formSchema]); + + function handleEditorDidMount(editor: any) { + editorRef.current = editor; + editorRef.current.setValue(JSON.stringify(schema, null, 2)); + } + + function onClose() { + if (onCancel) { + onCancel(); + } + setAdvancedModal(false); + } + + return ( + { + advancedForm + .validateFields() + .then((values) => onOk && onOk({ ...formSchema, ...values })) + .catch((errorInfo) => { + console.log('Failed:', errorInfo); + }) + .finally(() => onClose()); + }} + onCancel={onClose} + > +
{ + if (editorRef.current) { + editorRef.current.setValue( + JSON.stringify({ ...formSchema, ...allValues }, null, 2), + ); + } + }} + > + {!isObject && SchemaTypes.indexOf(formSchema?.type) !== -1 && ( +
+ 基本设置 +
+ )} + {(isString || isNumber || isInteger || isBoolean) && ( + + + 默认值: + + + + {isString && ( + + )} + {(isNumber || isInteger) && ( + + )} + {isBoolean && ( + + + + + + + 格式: + + + + + )} + {(isNumber || isInteger) && ( + + )} + + + + + + + + + )} + + + + )} +
+ Json Schema +
+ { + handleDebounce(() => { + if (value) { + try { + const editorSchema = JSON.parse(value); + setFormSchema(editorSchema); + } catch (e) {} + } + }); + }} + /> + +
+ ); +}; diff --git a/src/JsonSchemaEditor/SchemaItem/ImportModal.tsx b/src/JsonSchemaEditor/SchemaItem/ImportModal.tsx new file mode 100644 index 0000000..eaf982a --- /dev/null +++ b/src/JsonSchemaEditor/SchemaItem/ImportModal.tsx @@ -0,0 +1,126 @@ +import Ajv from 'ajv'; +import { message, Modal, Radio, Row } from 'antd'; +import { Draft07 } from 'json-schema-library'; +import React, { useEffect, useRef, useState } from 'react'; +import MonacoEditor from '../MonacoEditor'; +import { parseJsonStr, resolveJsonSchemaRef } from '../utils'; + +type ImportModalProps = { + open: boolean; + onOk?: (importValue: any) => void; + onCancel?: () => void; +}; + +const ImportModal = (props: ImportModalProps) => { + const { open, onOk, onCancel } = props; + const [messageApi, contextHolder] = message.useMessage(); + const [importValue, setImportValue] = useState(); + const [modalOpen, setModalOpen] = useState(); + const [importType, setImportType] = useState<'json' | 'json-schema'>('json'); + + const editorRef = useRef(null); + + function handleEditorDidMount(editor: any) { + editorRef.current = editor; + } + + useEffect(() => { + setModalOpen(open); + }, [open]); + + function onClose() { + setModalOpen(false); + setImportValue(undefined); + editorRef.current?.setValue(''); + if (onCancel) { + onCancel(); + } + } + + return ( + <> + {contextHolder} + { + if (!importValue || importValue.length === 0) { + messageApi.warning('请输入导入的 Json 数据'); + return; + } + const importObject = parseJsonStr(importValue); + if (!importObject) { + messageApi.error('导入的内容不是 Json 格式的数据'); + return; + } + let schema; + switch (importType) { + case 'json': + schema = new Draft07().createSchemaOf(importObject); + break; + case 'json-schema': + schema = await resolveJsonSchemaRef(importObject); + break; + } + if (!schema) { + messageApi.warning('导入的内容有误,请检查后重新导入'); + return; + } else { + const ajv = new Ajv({ allErrors: true }); + const validateSchema = ajv.getSchema( + 'http://json-schema.org/draft-07/schema#', + ); + if (validateSchema && !validateSchema(schema)) { + const errorContent = validateSchema.errors?.map( + (error, index) => { + const field = error.instancePath.split('/').pop() || 'root'; + const message = `${field} ${error.message}`; + return ( +
+ {`Error in field "${field}": ${message}`} +
+ ); + }, + ); + if ((errorContent?.length || 0) > 0) { + messageApi.error({ + content:
{errorContent}
, + }); + } + return; + } + } + if (onOk) { + onOk(schema); + } + onClose(); + }} + onCancel={onClose} + > + + setImportType(type.target.value)} + options={[ + { value: 'json', label: 'Json' }, + { value: 'json-schema', label: 'JsonSchema' }, + ]} + /> + + + setImportValue(value)} + /> + +
+ + ); +}; + +export default ImportModal; diff --git a/src/JsonSchemaEditor/SchemaItem/index.tsx b/src/JsonSchemaEditor/SchemaItem/index.tsx index dd786f6..303e6d9 100644 --- a/src/JsonSchemaEditor/SchemaItem/index.tsx +++ b/src/JsonSchemaEditor/SchemaItem/index.tsx @@ -6,36 +6,27 @@ import { PlusOutlined, SettingOutlined, } from '@ant-design/icons'; +import AdvancedSettingModal from '@quiet-front-end/json-schema-editor-antd/JsonSchemaEditor/SchemaItem/AdvancedSettingModal'; +import ImportModal from '@quiet-front-end/json-schema-editor-antd/JsonSchemaEditor/SchemaItem/ImportModal'; import { Button, Checkbox, Col, Dropdown, - Form, Input, - InputNumber, message, - Modal, - Radio, Row, Select, - Switch, theme, Tooltip, } from 'antd'; -import { Draft07 } from 'json-schema-library'; import _ from 'lodash'; -import React, { useCallback, useEffect, useRef, useState } from 'react'; -import MonacoEditor from '../MonacoEditor'; +import React, { useCallback, useEffect, useState } from 'react'; import { JSONSchema7 } from '../types'; import { getDefaultSchema, getPropertyIndex, - parseJsonStr, - resolveJsonSchemaRef, SchemaTypeOptions, - SchemaTypes, - StringFormat, } from '../utils'; type SchemaItemProps = { @@ -68,7 +59,6 @@ type SchemaItemProps = { function SchemaItem(props: SchemaItemProps) { const { token } = theme.useToken(); - const [advancedForm] = Form.useForm(); const [messageApi, contextHolder] = message.useMessage(); const { changeSchema, @@ -83,7 +73,6 @@ function SchemaItem(props: SchemaItemProps) { } = props; const [schema, setSchema] = useState(props.schema); - const [formSchema, setFormSchema] = useState(); const [propertyName, setPropertyName] = useState(props.propertyName); const [schemaTitle, setSchemaTitle] = useState(schema.title); const [schemaDescription, setSchemaDescription] = useState( @@ -98,15 +87,6 @@ function SchemaItem(props: SchemaItemProps) { const [expand, setExpand] = useState(true); const [advancedModal, setAdvancedModal] = useState(false); const [importModal, setImportModal] = useState(false); - const [importType, setImportType] = useState<'json' | 'json-schema'>('json'); - const [importValue, setImportValue] = useState(); - const [isObject, setIsObject] = useState(false); - const [isArray, setIsArray] = useState(false); - const [isNumber, setIsNumber] = useState(false); - const [isBoolean, setIsBoolean] = useState(false); - const [isInteger, setIsInteger] = useState(false); - const [isString, setIsString] = useState(false); - const editorRef = useRef(null); const isRoot = typeof propertyName === 'undefined'; useEffect(() => { @@ -142,23 +122,6 @@ function SchemaItem(props: SchemaItemProps) { }; }, [handleDebounce]); - useEffect(() => { - if (!advancedModal || !formSchema) { - return; - } - advancedForm.setFieldsValue(formSchema); - setIsObject(formSchema.type === 'object'); - setIsArray(formSchema.type === 'array'); - setIsNumber(formSchema.type === 'number'); - setIsBoolean(formSchema.type === 'boolean'); - setIsInteger(formSchema.type === 'integer'); - setIsString(formSchema.type === 'string'); - }, [advancedModal, formSchema]); - - function handleEditorDidMount(editor: any) { - editorRef.current = editor; - } - const schemaItems: any = schema.items; const addChildItems = !!( @@ -295,7 +258,6 @@ function SchemaItem(props: SchemaItemProps) { ) { return; } - setFormSchema(schema); setAdvancedModal(!advancedModal); }} /> @@ -423,456 +385,35 @@ function SchemaItem(props: SchemaItemProps) { handleAdvancedSettingClick={handleAdvancedSettingClick} /> )} - { + onOk={(newSchema) => { if (!changeSchema) { return; } if (isRoot || schema.type === 'object') { - changeSchema(namePath, { ...schema, ...formSchema }); - setAdvancedModal(!advancedModal); + changeSchema(namePath, { ...newSchema }); + setAdvancedModal(false); return; } - advancedForm - .validateFields() - .then((values) => { - changeSchema( - namePath, - { ...schema, ...formSchema, ...values }, - propertyName, - ); - setAdvancedModal(!advancedModal); - }) - .catch((errorInfo) => { - console.log('Failed:', errorInfo); - }); + changeSchema(namePath, { ...newSchema }, propertyName); + setAdvancedModal(false); }} - onCancel={() => setAdvancedModal(!advancedModal)} - > -
{ - if (editorRef.current) { - editorRef.current.setValue( - JSON.stringify({ ...formSchema, ...allValues }, null, 2), - ); - } - }} - > - {!isObject && SchemaTypes.indexOf(formSchema?.type) !== -1 && ( -
- 基本设置 -
- )} - {(isString || isNumber || isInteger || isBoolean) && ( - - - 默认值: - - - - {isString && ( - - )} - {(isNumber || isInteger) && ( - - )} - {isBoolean && ( - - - - - - - 格式: - - - - - )} - {(isNumber || isInteger) && ( - - )} - - - - - - - - - )} - - - - )} -
- Json Schema -
- { - handleDebounce(() => { - if (value) { - try { - const editorSchema = JSON.parse(value); - setFormSchema(editorSchema); - } catch (e) {} - } - }); - }} - /> - -
+ onCancel={() => setAdvancedModal(false)} + /> - { - if (!importValue || importValue.length === 0) { - messageApi.warning('请输入导入的 Json 数据'); - return; - } - const importObject = parseJsonStr(importValue); - if (!importObject) { - messageApi.error('导入的内容不是 Json 格式的数据'); - return; - } - let schema; - switch (importType) { - case 'json': - schema = new Draft07().createSchemaOf(importObject); - break; - case 'json-schema': - schema = await resolveJsonSchemaRef(importObject); - break; - } + onOk={(newSchema) => { if (changeSchema) { - changeSchema([], schema); - setImportModal(!importModal); - setImportValue(undefined); + changeSchema([], newSchema); + setImportModal(false); } }} - onCancel={() => setImportModal(!importModal)} - > - - setImportType(type.target.value)} - options={[ - { value: 'json', label: 'Json' }, - { value: 'json-schema', label: 'JsonSchema' }, - ]} - /> - - - setImportValue(value)} - /> - - + onCancel={() => setImportModal(false)} + /> ); } diff --git a/src/JsonSchemaEditor/index.tsx b/src/JsonSchemaEditor/index.tsx index 5aa3555..d017e91 100644 --- a/src/JsonSchemaEditor/index.tsx +++ b/src/JsonSchemaEditor/index.tsx @@ -130,6 +130,11 @@ const JsonSchemaEditor = forwardRef( parent[lastKey] = Object.fromEntries( Object.entries(current).map(([key, value]) => { if (key === oldKey) { + const requiredIndex = parent['required']?.indexOf(oldKey); + if (requiredIndex && requiredIndex !== -1) { + parent['required'].splice(requiredIndex, 1); + parent['required'].push(newKey); + } return [newKey, value]; } return [key, value];