From 150dd8ba388a495e69aaeba2f9df17696f441640 Mon Sep 17 00:00:00 2001 From: suanmei Date: Wed, 13 May 2020 18:25:56 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20json-schema-editor=20=E5=B8=B8=E7=94=A8?= =?UTF-8?q?=20field=20=E7=B1=BB=E5=9E=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit array 类型还未实现 --- packages/json-schema-editor/package-lock.json | 20 ++ packages/json-schema-editor/package.json | 2 + .../src/components/add-node.tsx | 35 ++++ .../src/components/field-input.tsx | 49 +++++ .../src/components/layout.tsx | 28 ++- .../src/components/schema-form.tsx | 44 +++-- .../{editor-head.tsx => schema-head.tsx} | 41 ++-- .../src/components/schema-item.tsx | 152 +++++++++++++++ .../src/i18n/translations/en.json | 8 +- .../src/i18n/translations/zh.json | 8 +- packages/json-schema-editor/src/index.tsx | 13 +- .../json-schema-editor/src/model/index.ts | 1 + .../json-schema-editor/src/model/schema.ts | 45 +++++ packages/json-schema-editor/src/model/ui.ts | 12 +- .../json-schema-editor/src/reducer/schema.ts | 176 ++++++++++++++++++ .../json-schema-editor/src/store/schema.ts | 23 +++ packages/json-schema-editor/src/store/ui.ts | 9 +- .../src/utils/schema-json.ts | 0 .../json-schema-editor/src/utils/schema.ts | 50 +++++ .../src/utils/styles/antd.ts | 9 + .../src/utils/styles/common.ts | 26 ++- .../src/utils/styles/global.ts | 9 - 22 files changed, 686 insertions(+), 74 deletions(-) create mode 100644 packages/json-schema-editor/src/components/add-node.tsx create mode 100644 packages/json-schema-editor/src/components/field-input.tsx rename packages/json-schema-editor/src/components/{editor-head.tsx => schema-head.tsx} (69%) create mode 100644 packages/json-schema-editor/src/components/schema-item.tsx create mode 100644 packages/json-schema-editor/src/model/schema.ts create mode 100644 packages/json-schema-editor/src/reducer/schema.ts create mode 100644 packages/json-schema-editor/src/store/schema.ts delete mode 100644 packages/json-schema-editor/src/utils/schema-json.ts create mode 100644 packages/json-schema-editor/src/utils/schema.ts diff --git a/packages/json-schema-editor/package-lock.json b/packages/json-schema-editor/package-lock.json index 33be3f0..31d382d 100644 --- a/packages/json-schema-editor/package-lock.json +++ b/packages/json-schema-editor/package-lock.json @@ -231,6 +231,21 @@ "hoist-non-react-statics": "^3.3.0" } }, + "@types/lodash": { + "version": "4.14.150", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.150.tgz", + "integrity": "sha512-kMNLM5JBcasgYscD9x/Gvr6lTAv2NVgsKtet/hm93qMyf/D1pt+7jeEZklKJKxMVmXjxbRVQQGfqDSfipYCO6w==", + "dev": true + }, + "@types/lodash-es": { + "version": "4.17.3", + "resolved": "https://registry.npmjs.org/@types/lodash-es/-/lodash-es-4.17.3.tgz", + "integrity": "sha512-iHI0i7ZAL1qepz1Y7f3EKg/zUMDwDfTzitx+AlHhJJvXwenP682ZyGbgPSc5Ej3eEAKVbNWKFuwOadCj5vBbYQ==", + "dev": true, + "requires": { + "@types/lodash": "*" + } + }, "@types/parse-json": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", @@ -659,6 +674,11 @@ "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", "dev": true }, + "lodash-es": { + "version": "4.17.15", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.15.tgz", + "integrity": "sha512-rlrc3yU3+JNOpZ9zj5pQtxnx2THmvRykwL4Xlxoa8I9lHBlVbbyPhgyPMioxVZ4NqyxaVVtaJnzsyOidQIhyyQ==" + }, "loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", diff --git a/packages/json-schema-editor/package.json b/packages/json-schema-editor/package.json index b536058..861f1d1 100644 --- a/packages/json-schema-editor/package.json +++ b/packages/json-schema-editor/package.json @@ -40,6 +40,7 @@ "@ant-design/icons": "^4.1.0", "@emotion/core": "^10.0.28", "@emotion/styled": "^10.0.27", + "@types/lodash-es": "^4.17.3", "@types/react": "^16.9.34", "@types/react-dom": "^16.9.7", "antd": "^4.1.5", @@ -58,6 +59,7 @@ "gitHead": "60b114ff262513544204f33a9bbcb6220d6c1c5d", "dependencies": { "i18next": "^19.4.4", + "lodash-es": "^4.17.15", "react-i18next": "^11.4.0", "react-tracked": "^1.3.0" } diff --git a/packages/json-schema-editor/src/components/add-node.tsx b/packages/json-schema-editor/src/components/add-node.tsx new file mode 100644 index 0000000..ea0960e --- /dev/null +++ b/packages/json-schema-editor/src/components/add-node.tsx @@ -0,0 +1,35 @@ +import * as React from 'react'; +import { Menu, Dropdown } from 'antd'; +import { useTranslation } from 'react-i18next'; +import { PlusIcon } from '../utils/styles/common'; + +interface AddNodeProps { + addChildField: () => void; + addSiblingField: () => void; +} + +function AddNodeIcon({ addChildField, addSiblingField }: AddNodeProps): JSX.Element { + const { t } = useTranslation(); + const menu = ( + + +
+ {t('child_node')} +
+
+ +
+ {t('sibling_node')} +
+
+
+ ); + + return ( + + + + ); +} + +export default AddNodeIcon; diff --git a/packages/json-schema-editor/src/components/field-input.tsx b/packages/json-schema-editor/src/components/field-input.tsx new file mode 100644 index 0000000..4721761 --- /dev/null +++ b/packages/json-schema-editor/src/components/field-input.tsx @@ -0,0 +1,49 @@ +import * as React from 'react'; +import styled from '@emotion/styled'; +import { Input } from 'antd'; + +interface FieldInputProps { + value: string; + changeField: (value: string) => void; + addonAfter: JSX.Element; +} + +const CustomInput = styled(Input)` + .ant-input-group-addon { + background: transparent; + border: none; + } +`; + +function FieldInput({ value: initValue, changeField, addonAfter }: FieldInputProps): JSX.Element { + const [value, setValue] = React.useState(initValue); + + const handleChange = (e: React.ChangeEvent): void => { + setValue(e.target.value); + }; + + const handleBlur = (e: React.ChangeEvent): void => { + if (e.target.value !== initValue) { + changeField(e.target.value); + } + }; + + const handleKeyUp = (e: React.KeyboardEvent): void => { + const { value: newValue } = e.target as HTMLInputElement; + if (e.keyCode === 13 && newValue !== initValue) { + changeField(newValue); + } + }; + + return ( + + ); +} + +export default FieldInput; diff --git a/packages/json-schema-editor/src/components/layout.tsx b/packages/json-schema-editor/src/components/layout.tsx index 83cf598..5d42109 100644 --- a/packages/json-schema-editor/src/components/layout.tsx +++ b/packages/json-schema-editor/src/components/layout.tsx @@ -1,15 +1,35 @@ /** @jsx jsx */ +import { useEffect } from 'react'; import { jsx, css } from '@emotion/core'; import { Button } from 'antd'; import { useTranslation } from 'react-i18next'; -import EditorHead from './editor-head'; +import EditorHead from './schema-head'; import SchemaForm from './schema-form'; import { useUIState } from '../store/ui'; +import { useSchemaState } from '../store/schema'; +import { SchemaState } from '../model'; -function Layout(): JSX.Element { +let temp: SchemaState | null = null; + +interface AA { + onChange: (value: SchemaState) => void; +} + +function Layout({ onChange }: AA): JSX.Element { const { t } = useTranslation(); + const uiState = useUIState(); - const { formVisible } = uiState; + const key = 'properties'; + const visible = uiState[key] !== false; + + const schemaState = useSchemaState(); + + useEffect(() => { + if (temp !== schemaState) { + temp = schemaState; + onChange(schemaState); + } + }); return (
- {formVisible && } + {visible && }
); } diff --git a/packages/json-schema-editor/src/components/schema-form.tsx b/packages/json-schema-editor/src/components/schema-form.tsx index 26f82fd..d197db9 100644 --- a/packages/json-schema-editor/src/components/schema-form.tsx +++ b/packages/json-schema-editor/src/components/schema-form.tsx @@ -1,22 +1,40 @@ /** @jsx jsx */ -import { useEffect, memo, useRef } from 'react'; import { jsx } from '@emotion/core'; -import { useUIState } from '../store/ui'; +import { isUndefined } from 'lodash-es'; +import SchemaItem from './schema-item'; +import { ObjectSchemaItem, KeyRoute } from '../model'; + +interface SchemaFormProps { + data: ObjectSchemaItem; + keyRoute: KeyRoute; +} + +function SchemaForm({ data, keyRoute }: SchemaFormProps): JSX.Element { + const { required, properties } = data; -function SchemaForm(): JSX.Element { - const uiState = useUIState(); - const { formVisible } = uiState; - const renders = useRef(1); - useEffect(() => { - console.log(11111); - }); - renders.current += 1; return (
- {formVisible ? 3 : 0} - (renders:{renders.current}) + {Object.keys(properties).map((field) => { + const itemData = properties[field]; + const isRequired = required && required.includes(field); + const fieldKeyRoute = keyRoute.concat(field); + + return ( + + {itemData.type === 'object' && !isUndefined(itemData.properties) ? ( + + ) : null} + + ); + })}
); } -export default memo(SchemaForm); +export default SchemaForm; diff --git a/packages/json-schema-editor/src/components/editor-head.tsx b/packages/json-schema-editor/src/components/schema-head.tsx similarity index 69% rename from packages/json-schema-editor/src/components/editor-head.tsx rename to packages/json-schema-editor/src/components/schema-head.tsx index b4a42b0..fc1b62a 100644 --- a/packages/json-schema-editor/src/components/editor-head.tsx +++ b/packages/json-schema-editor/src/components/schema-head.tsx @@ -1,9 +1,12 @@ /** @jsx jsx */ -import { jsx, css } from '@emotion/core'; +import { useState } from 'react'; +import { jsx } from '@emotion/core'; import styled from '@emotion/styled'; import { Row, Col, Tooltip, Input, Checkbox, Select } from 'antd'; +import { CheckboxChangeEvent } from 'antd/lib/checkbox'; import { useTranslation } from 'react-i18next'; import { useUIState, useSetUIState } from '../store/ui'; +import { useSchemaDispatch } from '../store/schema'; import { CaretDownIcon, CaretRightIcon, @@ -23,26 +26,35 @@ const FieldInput = styled(Input)` function EditorHead(): JSX.Element { const { t } = useTranslation(); - const uiState = useUIState(); + const schemaDispatch = useSchemaDispatch(); const setUIState = useSetUIState(); - const { formVisible } = uiState; + const uiState = useUIState(); + const key = 'properties'; + const visible = uiState[key]; + + const [checked, setChecked] = useState(false); const toggleFormVisible = (): void => { - setUIState((prev) => ({ ...prev, formVisible: !formVisible })); + setUIState((prev) => ({ ...prev, [key]: !visible })); + }; + + const addChildField = (): void => { + schemaDispatch({ type: 'ADD_CHILD_FIELD', keyRoute: ['properties'] }); + }; + + const toggleAllChecked = (e: CheckboxChangeEvent): void => { + setChecked(e.target.checked); + schemaDispatch({ type: 'TOGGLE_ALL_CHECKED', checked: e.target.checked }); }; return ( - + - {formVisible ? ( + {visible ? ( ) : ( @@ -54,12 +66,7 @@ function EditorHead(): JSX.Element { value="root" addonAfter={ - { - // code - }} - /> + } /> @@ -82,7 +89,7 @@ function EditorHead(): JSX.Element { - + diff --git a/packages/json-schema-editor/src/components/schema-item.tsx b/packages/json-schema-editor/src/components/schema-item.tsx new file mode 100644 index 0000000..5915323 --- /dev/null +++ b/packages/json-schema-editor/src/components/schema-item.tsx @@ -0,0 +1,152 @@ +/** @jsx jsx */ +import { jsx } from '@emotion/core'; +import { Row, Col, Tooltip, Input, Checkbox, Select } from 'antd'; +import { useTranslation } from 'react-i18next'; +import { useUIState, useSetUIState } from '../store/ui'; +import { useSchemaDispatch } from '../store/schema'; +import { schemaType } from '../utils/schema'; +import { SchemaType, SchemaItem, KeyRoute } from '../model'; +import FieldInput from './field-input'; +import AddNode from './add-node'; +import { + CaretDownIcon, + CaretRightIcon, + EditIcon, + SettingIcon, + PlusIcon, + CloseIcon, +} from '../utils/styles/common'; + +const { Option } = Select; + +interface SchemaItemProps { + field: string; + data: SchemaItem; + required: boolean; + keyRoute: KeyRoute; + children: React.ReactChild | null; +} + +function SchemaFormItem({ + field, + data, + required, + keyRoute, + children, +}: SchemaItemProps): JSX.Element { + const { t } = useTranslation(); + const schemaDispatch = useSchemaDispatch(); + const setUIState = useSetUIState(); + const uiState = useUIState(); + const key = keyRoute.join('-'); + const visible = uiState[key] !== false; + + const toggleFormVisible = (): void => { + setUIState((prev) => ({ ...prev, [key]: !visible })); + }; + + const toggleRequired = (): void => { + if (required) { + schemaDispatch({ type: 'REMOVE_REQUIRED', keyRoute }); + } else { + schemaDispatch({ type: 'ADD_REQUIRED', keyRoute }); + } + }; + + const addChildField = (): void => { + schemaDispatch({ type: 'ADD_CHILD_FIELD', keyRoute: keyRoute.concat('properties') }); + }; + + const addSiblingField = (): void => { + schemaDispatch({ type: 'ADD_SIBLING_FIELD', keyRoute }); + }; + + const removeField = (): void => { + schemaDispatch({ type: 'REMOVE_FIELD', keyRoute }); + }; + + const changeField = (value: string): void => { + setTimeout(() => { + schemaDispatch({ type: 'CHANGE_FIELD', keyRoute, field: value }); + }, 500); + }; + + const changeType = (value: SchemaType): void => { + schemaDispatch({ type: 'CHANGE_TYPE', keyRoute, fieldType: value }); + }; + + const changeTitle = (e: React.ChangeEvent): void => { + schemaDispatch({ type: 'CHANGE_TITLE', keyRoute, title: e.target.value }); + }; + + const changeDesc = (e: React.ChangeEvent): void => { + schemaDispatch({ type: 'CHANGE_DESC', keyRoute, desc: e.target.value }); + }; + + return ( +
+ + + + + {data.type === 'object' && visible && } + {data.type === 'object' && !visible && } + + + + + + } + /> + + + + + + + + } + /> + + + } + /> + + + + + + + {data.type === 'object' ? ( + + ) : ( + + + + )} + + + {visible && children ?
{children}
: null} +
+ ); +} + +export default SchemaFormItem; diff --git a/packages/json-schema-editor/src/i18n/translations/en.json b/packages/json-schema-editor/src/i18n/translations/en.json index efe7e44..bd8c5b1 100644 --- a/packages/json-schema-editor/src/i18n/translations/en.json +++ b/packages/json-schema-editor/src/i18n/translations/en.json @@ -2,7 +2,13 @@ "translation": { "import_json": "Import JSON", "select_all": "Select All", + "required": "required", + "title": "Title", + "description": "Description", "setting": "Advanced Setting", - "add_child_node": "Add child node" + "child_node": "Child node", + "sibling_node": "Sibling node", + "add_child_node": "Add child node", + "add_sibling_node": "Add sibling node" } } diff --git a/packages/json-schema-editor/src/i18n/translations/zh.json b/packages/json-schema-editor/src/i18n/translations/zh.json index 40a9e1b..0333d19 100644 --- a/packages/json-schema-editor/src/i18n/translations/zh.json +++ b/packages/json-schema-editor/src/i18n/translations/zh.json @@ -2,7 +2,13 @@ "translation": { "import_json": "导入 JSON", "select_all": "全选", + "required": "必选", + "title": "标题", + "description": "描述", "setting": "高级设置", - "add_child_node": "添加子节点" + "child_node": "子节点", + "sibling_node": "兄弟节点", + "add_child_node": "添加子节点", + "add_sibling_node": "添加兄弟节点" } } diff --git a/packages/json-schema-editor/src/index.tsx b/packages/json-schema-editor/src/index.tsx index 32c0711..1341f8a 100644 --- a/packages/json-schema-editor/src/index.tsx +++ b/packages/json-schema-editor/src/index.tsx @@ -4,14 +4,19 @@ import { Global, jsx } from '@emotion/core'; import { useTranslation } from 'react-i18next'; import Layout from './components/layout'; import { UIProvider } from './store/ui'; +import { SchemaProvider } from './store/schema'; import globalStyles from './utils/styles/global'; +import { SchemaState } from './model'; +import './utils/styles/antd'; import './i18n'; interface EditorProps { lang?: string; + data: SchemaState; + onChange: (value: SchemaState) => void; } -function JsonSchemaEditor({ lang }: EditorProps): JSX.Element { +function JsonSchemaEditor({ lang, data, onChange }: EditorProps): JSX.Element { const { i18n } = useTranslation(); useEffect(() => { @@ -21,8 +26,10 @@ function JsonSchemaEditor({ lang }: EditorProps): JSX.Element { return ( - - + + + + ); } diff --git a/packages/json-schema-editor/src/model/index.ts b/packages/json-schema-editor/src/model/index.ts index 5ecdd1f..2cfc7d9 100644 --- a/packages/json-schema-editor/src/model/index.ts +++ b/packages/json-schema-editor/src/model/index.ts @@ -1 +1,2 @@ export * from './ui'; +export * from './schema'; diff --git a/packages/json-schema-editor/src/model/schema.ts b/packages/json-schema-editor/src/model/schema.ts new file mode 100644 index 0000000..be9c86d --- /dev/null +++ b/packages/json-schema-editor/src/model/schema.ts @@ -0,0 +1,45 @@ +export type SchemaType = 'string' | 'number' | 'object' | 'array' | 'boolean' | 'integer'; + +type basicSchemaType = 'string' | 'number' | 'boolean' | 'integer'; + +interface BasicSchemaItem { + title?: string; + description?: string; + type: basicSchemaType; +} + +export interface ObjectSchemaItem { + title?: string; + description?: string; + type: 'object'; + properties: { + [field: string]: SchemaItem; + }; + required: string[]; +} + +export interface ArraySchemaItem { + title?: string; + description?: string; + type: 'array'; + items: SchemaItem; +} + +export type SchemaItem = BasicSchemaItem | ObjectSchemaItem | ArraySchemaItem; + +export type SchemaState = ObjectSchemaItem; + +export type KeyRoute = (string | number)[]; + +export type SchemaAction = + | { type: 'SET_SCHEMA'; schema: SchemaState } + | { type: 'ADD_CHILD_FIELD'; keyRoute: KeyRoute } + | { type: 'ADD_SIBLING_FIELD'; keyRoute: KeyRoute } + | { type: 'REMOVE_FIELD'; keyRoute: KeyRoute } + | { type: 'TOGGLE_ALL_CHECKED'; checked: boolean } + | { type: 'ADD_REQUIRED'; keyRoute: KeyRoute } + | { type: 'REMOVE_REQUIRED'; keyRoute: KeyRoute } + | { type: 'CHANGE_FIELD'; keyRoute: KeyRoute; field: string } + | { type: 'CHANGE_TYPE'; keyRoute: KeyRoute; fieldType: SchemaType } + | { type: 'CHANGE_TITLE'; keyRoute: KeyRoute; title: string } + | { type: 'CHANGE_DESC'; keyRoute: KeyRoute; desc: string }; diff --git a/packages/json-schema-editor/src/model/ui.ts b/packages/json-schema-editor/src/model/ui.ts index f3cd2e2..01aa659 100644 --- a/packages/json-schema-editor/src/model/ui.ts +++ b/packages/json-schema-editor/src/model/ui.ts @@ -1,13 +1,3 @@ export interface UIState { - formVisible: boolean; - aaa: boolean; -} - -export interface UIAction { - type: string; -} - -export interface UIContext { - uiState: UIState; - uiDispatch: React.Dispatch; + [key: string]: boolean; } diff --git a/packages/json-schema-editor/src/reducer/schema.ts b/packages/json-schema-editor/src/reducer/schema.ts new file mode 100644 index 0000000..f1986a3 --- /dev/null +++ b/packages/json-schema-editor/src/reducer/schema.ts @@ -0,0 +1,176 @@ +import { get, set, unset, remove, cloneDeep } from 'lodash-es'; +import { SchemaType, SchemaState, SchemaAction, KeyRoute } from '../model'; +import { defaultSchema, setAllFieldRequired } from '../utils/schema'; + +let fieldNum = 0; + +const setSchema = (schema: SchemaState): SchemaState => { + return schema; +}; + +const addRequired = (state: SchemaState, keyRoute: KeyRoute): SchemaState => { + const schema = cloneDeep(state); + + const field = keyRoute[keyRoute.length - 1]; + const requiredKeyRoute = keyRoute.slice(0, -2).concat('required'); + const required = get(schema, requiredKeyRoute) || []; + required.push(field); + set(schema, requiredKeyRoute, required); + + return schema; +}; + +const removeRequired = (state: SchemaState, keyRoute: KeyRoute): SchemaState => { + const schema = cloneDeep(state); + + const field = keyRoute[keyRoute.length - 1]; + const requiredKeyRoute = keyRoute.slice(0, -2).concat('required'); + const required = get(schema, requiredKeyRoute) || []; + remove(required, (item) => item === field); + set(schema, requiredKeyRoute, required); + + return schema; +}; + +const toggleAllChecked = (state: SchemaState, checked: boolean): SchemaState => { + const schema = cloneDeep(state); + + setAllFieldRequired(schema, checked); + + return schema; +}; + +const addChildField = (state: SchemaState, keyRoute: KeyRoute): SchemaState => { + fieldNum += 1; + + const newField = `field_${fieldNum}`; + let schema = cloneDeep(state); + + // add new field - string + const newKeyRoute = keyRoute.concat(newField); + set(schema, newKeyRoute, defaultSchema.string); + + // add new filed to its parent's required + schema = addRequired(schema, newKeyRoute); + + return schema; +}; + +const addSiblingField = (state: SchemaState, keyRoute: KeyRoute): SchemaState => { + fieldNum += 1; + + const newField = `field_${fieldNum}`; + let schema = cloneDeep(state); + + // add new field - string + const newKeyRoute = keyRoute.slice(0, -1).concat(newField); + set(schema, newKeyRoute, defaultSchema.string); + + // add new filed to its parent's required + schema = addRequired(schema, newKeyRoute); + + return schema; +}; + +const removeField = (state: SchemaState, keyRoute: KeyRoute): SchemaState => { + let schema = cloneDeep(state); + + // remove field + unset(schema, keyRoute); + + // remove filed from its parent's required + schema = removeRequired(schema, keyRoute); + + return schema; +}; + +const changeField = (state: SchemaState, keyRoute: KeyRoute, field: string): SchemaState => { + let schema = cloneDeep(state); + + // temp old field's value and remove old field + const temp = get(schema, keyRoute); + unset(schema, keyRoute); + + // remove old filed from its parent's required + schema = removeRequired(schema, keyRoute); + + // point to new field and add temp to it + const fieldKeyRoute = keyRoute.slice(0, -1).concat(field); + set(schema, fieldKeyRoute, temp); + + // add new filed to its parent's required + schema = addRequired(schema, fieldKeyRoute); + + return schema; +}; + +const changeType = (state: SchemaState, keyRoute: KeyRoute, fieldType: SchemaType): SchemaState => { + const schema = cloneDeep(state); + + // set field new value + set(schema, keyRoute, defaultSchema[fieldType]); + + return schema; +}; + +const changeTitle = (state: SchemaState, keyRoute: KeyRoute, title: string): SchemaState => { + const schema = cloneDeep(state); + + // point to title and set new value + const titleKeyRoute = keyRoute.concat('title'); + set(schema, titleKeyRoute, title); + + return schema; +}; + +const changeDesc = (state: SchemaState, keyRoute: KeyRoute, desc: string): SchemaState => { + const schema = cloneDeep(state); + + // point to desc and set new value + const descKeyRoute = keyRoute.concat('description'); + set(schema, descKeyRoute, desc); + + return schema; +}; + +const reducer = (state: SchemaState, action: SchemaAction): SchemaState => { + switch (action.type) { + case 'SET_SCHEMA': + return setSchema(action.schema); + + case 'ADD_CHILD_FIELD': + return addChildField(state, action.keyRoute); + + case 'ADD_SIBLING_FIELD': + return addSiblingField(state, action.keyRoute); + + case 'REMOVE_FIELD': + return removeField(state, action.keyRoute); + + case 'TOGGLE_ALL_CHECKED': + return toggleAllChecked(state, action.checked); + + case 'ADD_REQUIRED': + return addRequired(state, action.keyRoute); + + case 'REMOVE_REQUIRED': + return removeRequired(state, action.keyRoute); + + case 'CHANGE_FIELD': + return changeField(state, action.keyRoute, action.field); + + case 'CHANGE_TYPE': + return changeType(state, action.keyRoute, action.fieldType); + + case 'CHANGE_TITLE': + return changeTitle(state, action.keyRoute, action.title); + + case 'CHANGE_DESC': + return changeDesc(state, action.keyRoute, action.desc); + + default: + return state; + } +}; + +export default reducer; diff --git a/packages/json-schema-editor/src/store/schema.ts b/packages/json-schema-editor/src/store/schema.ts new file mode 100644 index 0000000..7c1a268 --- /dev/null +++ b/packages/json-schema-editor/src/store/schema.ts @@ -0,0 +1,23 @@ +import { Dispatch, useReducer } from 'react'; +import { createContainer } from 'react-tracked'; +import { SchemaState, SchemaAction } from '../model'; +import reducer from '../reducer/schema'; + +interface StoreProps { + initialData: SchemaState; +} + +const defaultData: SchemaState = { + type: 'object', + properties: {}, + required: [], +}; + +const useValue = ({ initialData }: StoreProps): [SchemaState, Dispatch] => + useReducer(reducer, initialData || defaultData); + +export const { + Provider: SchemaProvider, + useTrackedState: useSchemaState, + useUpdate: useSchemaDispatch, +} = createContainer(useValue); diff --git a/packages/json-schema-editor/src/store/ui.ts b/packages/json-schema-editor/src/store/ui.ts index 5731067..4a16b50 100644 --- a/packages/json-schema-editor/src/store/ui.ts +++ b/packages/json-schema-editor/src/store/ui.ts @@ -1,13 +1,10 @@ import { useState, Dispatch, SetStateAction } from 'react'; import { createContainer } from 'react-tracked'; +import { UIState } from '../model'; -export type State = { - formVisible: boolean; -}; +const initialData = { properties: true }; -const initialData = { formVisible: true }; - -const useValue = (): [State, Dispatch>] => useState(initialData); +const useValue = (): [UIState, Dispatch>] => useState(initialData); export const { Provider: UIProvider, diff --git a/packages/json-schema-editor/src/utils/schema-json.ts b/packages/json-schema-editor/src/utils/schema-json.ts deleted file mode 100644 index e69de29..0000000 diff --git a/packages/json-schema-editor/src/utils/schema.ts b/packages/json-schema-editor/src/utils/schema.ts new file mode 100644 index 0000000..d94685b --- /dev/null +++ b/packages/json-schema-editor/src/utils/schema.ts @@ -0,0 +1,50 @@ +import { SchemaItem } from '../model'; + +export const defaultSchema = { + string: { + type: 'string', + }, + number: { + type: 'number', + }, + array: { + type: 'array', + items: { + type: 'string', + }, + }, + object: { + type: 'object', + properties: {}, + }, + boolean: { + type: 'boolean', + }, + integer: { + type: 'integer', + }, +}; + +export const schemaType = ['string', 'number', 'array', 'object', 'boolean', 'integer']; + +export const setAllFieldRequired = (schema: SchemaItem, checked: boolean): void => { + if (schema.type === 'object') { + const { properties } = schema; + const fields = Object.keys(properties); + + if (checked) { + schema.required = fields; + } else { + delete schema.required; + } + + fields.forEach((field) => { + const childSchema = properties[field]; + if (['object', 'array'].includes(childSchema.type)) { + setAllFieldRequired(childSchema, checked); + } + }); + } else if (schema.type === 'array') { + setAllFieldRequired(schema.items, checked); + } +}; diff --git a/packages/json-schema-editor/src/utils/styles/antd.ts b/packages/json-schema-editor/src/utils/styles/antd.ts index e69de29..dd7db3a 100644 --- a/packages/json-schema-editor/src/utils/styles/antd.ts +++ b/packages/json-schema-editor/src/utils/styles/antd.ts @@ -0,0 +1,9 @@ +import 'antd/lib/row/style'; +import 'antd/lib/col/style'; +import 'antd/lib/menu/style'; +import 'antd/lib/input/style'; +import 'antd/lib/select/style'; +import 'antd/lib/button/style'; +import 'antd/lib/tooltip/style'; +import 'antd/lib/dropdown/style'; +import 'antd/lib/checkbox/style'; diff --git a/packages/json-schema-editor/src/utils/styles/common.ts b/packages/json-schema-editor/src/utils/styles/common.ts index 75d7ae0..2e8a3b7 100644 --- a/packages/json-schema-editor/src/utils/styles/common.ts +++ b/packages/json-schema-editor/src/utils/styles/common.ts @@ -6,13 +6,15 @@ import { EditOutlined, SettingOutlined, PlusOutlined, + CloseOutlined, } from '@ant-design/icons'; -const cursorStyle = css` +const iconStyle = css` + vertical-align: middle; cursor: pointer; `; -const IconStyle = css` +const actionStyle = css` margin: 0 5px; svg { font-size: 14px; @@ -20,25 +22,31 @@ const IconStyle = css` `; export const CaretDownIcon = styled(CaretDownOutlined)` - ${cursorStyle} + ${iconStyle} `; export const CaretRightIcon = styled(CaretRightOutlined)` - ${cursorStyle} + ${iconStyle} `; export const EditIcon = styled(EditOutlined)` - ${cursorStyle} + ${iconStyle} `; export const SettingIcon = styled(SettingOutlined)` color: #00a854; - ${cursorStyle} - ${IconStyle} + ${iconStyle} + ${actionStyle} `; export const PlusIcon = styled(PlusOutlined)` color: #2395f1; - ${cursorStyle} - ${IconStyle} + ${iconStyle} + ${actionStyle} +`; + +export const CloseIcon = styled(CloseOutlined)` + color: #ff561b; + ${iconStyle} + ${actionStyle} `; diff --git a/packages/json-schema-editor/src/utils/styles/global.ts b/packages/json-schema-editor/src/utils/styles/global.ts index 48d4ce2..e2a61a0 100644 --- a/packages/json-schema-editor/src/utils/styles/global.ts +++ b/packages/json-schema-editor/src/utils/styles/global.ts @@ -25,15 +25,6 @@ const globalStyles = css` width: 80%; text-align: left; } - - .editor-icon { - cursor: pointer; - - svg { - margin: 0 5px; - font-size: 14px; - } - } } `;