diff --git a/packages/json-schema-editor/package-lock.json b/packages/json-schema-editor/package-lock.json index 43b5fa7..0433955 100644 --- a/packages/json-schema-editor/package-lock.json +++ b/packages/json-schema-editor/package-lock.json @@ -619,6 +619,11 @@ "@babel/runtime": "^7.3.1" } }, + "immer": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/immer/-/immer-6.0.5.tgz", + "integrity": "sha512-Q2wd90qrgFieIpLzAO2q9NLEdmyp/sr76Ml4Vm5peUKgyTa2CQa3ey8zuzwSKOlKH7grCeGBGUcLLVCVW1aguA==" + }, "import-fresh": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.2.1.tgz", diff --git a/packages/json-schema-editor/package.json b/packages/json-schema-editor/package.json index 58de1d3..be69ca1 100644 --- a/packages/json-schema-editor/package.json +++ b/packages/json-schema-editor/package.json @@ -59,6 +59,7 @@ "gitHead": "60b114ff262513544204f33a9bbcb6220d6c1c5d", "dependencies": { "i18next": "^19.4.4", + "immer": "^6.0.5", "lodash-es": "^4.17.15", "react-i18next": "^11.4.0", "react-tracked": "^1.3.0" diff --git a/packages/json-schema-editor/src/components/field-input.tsx b/packages/json-schema-editor/src/components/field-input.tsx index 4721761..4e6b9b6 100644 --- a/packages/json-schema-editor/src/components/field-input.tsx +++ b/packages/json-schema-editor/src/components/field-input.tsx @@ -15,7 +15,8 @@ const CustomInput = styled(Input)` } `; -function FieldInput({ value: initValue, changeField, addonAfter }: FieldInputProps): JSX.Element { +function FieldInput(props: FieldInputProps, ref: React.Ref): JSX.Element { + const { value: initValue, changeField, addonAfter } = props; const [value, setValue] = React.useState(initValue); const handleChange = (e: React.ChangeEvent): void => { @@ -37,6 +38,7 @@ function FieldInput({ value: initValue, changeField, addonAfter }: FieldInputPro return ( (null); + const { t } = useTranslation(); const schemaDispatch = useSchemaDispatch(); const setUIState = useSetUIState(); @@ -46,10 +49,15 @@ function SchemaFormItem({ }; const toggleRequired = (): void => { + if (!fieldRef.current) return; + + const newField = fieldRef.current.state.value; + const fieldKeyRoute = keyRoute.slice(0, -1).concat(newField); + if (required) { - schemaDispatch({ type: 'REMOVE_REQUIRED', keyRoute }); + schemaDispatch({ type: 'REMOVE_REQUIRED', keyRoute: fieldKeyRoute }); } else { - schemaDispatch({ type: 'ADD_REQUIRED', keyRoute }); + schemaDispatch({ type: 'ADD_REQUIRED', keyRoute: fieldKeyRoute }); } }; @@ -66,9 +74,7 @@ function SchemaFormItem({ }; const changeField = (value: string): void => { - setTimeout(() => { - schemaDispatch({ type: 'CHANGE_FIELD', keyRoute, field: value }); - }, 500); + schemaDispatch({ type: 'CHANGE_FIELD', keyRoute, field: value }); }; const changeType = (value: SchemaType): void => { @@ -94,6 +100,7 @@ function SchemaFormItem({ { return schema; }; -const addRequired = (state: SchemaState, keyRoute: KeyRoute): SchemaState => { - const schema = cloneDeep(state); - - const field = keyRoute[keyRoute.length - 1]; +const addRequired = (state: SchemaState, keyRoute: KeyRoute): void => { + const field = keyRoute.slice(-1)[0]; const requiredKeyRoute = keyRoute.slice(0, -2).concat('required'); - const required = get(schema, requiredKeyRoute) || []; + const required = get(state, requiredKeyRoute) || []; required.push(field); - set(schema, requiredKeyRoute, required); - - return schema; + set(state, requiredKeyRoute, required); }; -const removeRequired = (state: SchemaState, keyRoute: KeyRoute): SchemaState => { - const schema = cloneDeep(state); - - const field = keyRoute[keyRoute.length - 1]; +const removeRequired = (state: SchemaState, keyRoute: KeyRoute): void => { + const field = keyRoute.slice(-1)[0]; const requiredKeyRoute = keyRoute.slice(0, -2).concat('required'); - const required = get(schema, requiredKeyRoute) || []; + const required = get(state, requiredKeyRoute) || []; remove(required, (item) => item === field); - set(schema, requiredKeyRoute, required); - - return schema; + set(state, requiredKeyRoute, required); }; -const toggleAllChecked = (state: SchemaState, checked: boolean): SchemaState => { - const schema = cloneDeep(state); - - setAllFieldRequired(schema, checked); - - return schema; +const toggleAllChecked = (state: SchemaState, checked: boolean): void => { + setAllFieldRequired(state, checked); }; -const addChildField = (state: SchemaState, keyRoute: KeyRoute): SchemaState => { +const addChildField = (state: Draft, keyRoute: KeyRoute): void => { fieldNum += 1; const newField = `field_${fieldNum}`; - let schema = cloneDeep(state); // add new field - string const newKeyRoute = keyRoute.concat(newField); - set(schema, newKeyRoute, defaultSchema.string); + set(state, newKeyRoute, defaultSchema.string); // add new filed to its parent's required - schema = addRequired(schema, newKeyRoute); - - return schema; + addRequired(state, newKeyRoute); }; -const addSiblingField = (state: SchemaState, keyRoute: KeyRoute): SchemaState => { +const addSiblingField = (state: SchemaState, keyRoute: KeyRoute): void => { 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); + set(state, newKeyRoute, defaultSchema.string); // add new filed to its parent's required - schema = addRequired(schema, newKeyRoute); - - return schema; + addRequired(state, newKeyRoute); }; -const removeField = (state: SchemaState, keyRoute: KeyRoute): SchemaState => { - let schema = cloneDeep(state); - +const removeField = (state: SchemaState, keyRoute: KeyRoute): void => { // remove field - unset(schema, keyRoute); + unset(state, keyRoute); // remove filed from its parent's required - schema = removeRequired(schema, keyRoute); - - return schema; + removeRequired(state, keyRoute); }; -const changeField = (state: SchemaState, keyRoute: KeyRoute, field: string): SchemaState => { - let schema = cloneDeep(state); +const changeField = (state: SchemaState, keyRoute: KeyRoute, newField: string): void => { + const oldField = keyRoute.slice(-1)[0]; + const parentKeyRoute = keyRoute.slice(0, -1); + const properties = get(state, parentKeyRoute); + const newProperties: { [key: string]: SchemaItem } = {}; + + // keep the order of fields + + // eslint-disable-next-line no-restricted-syntax + for (const field in properties) { + if (field === oldField) { + newProperties[newField] = properties[field]; + } else { + newProperties[field] = properties[field]; + } + } - // temp old field's value and remove old field - const temp = get(schema, keyRoute); - unset(schema, keyRoute); + set(state, parentKeyRoute, newProperties); // 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); + removeRequired(state, keyRoute); // add new filed to its parent's required - schema = addRequired(schema, fieldKeyRoute); - - return schema; + const fieldKeyRoute = parentKeyRoute.concat(newField); + addRequired(state, fieldKeyRoute); }; -const changeType = (state: SchemaState, keyRoute: KeyRoute, fieldType: SchemaType): SchemaState => { - const schema = cloneDeep(state); - +const changeType = (state: SchemaState, keyRoute: KeyRoute, fieldType: SchemaType): void => { // set field new value - set(schema, keyRoute, defaultSchema[fieldType]); - - return schema; + set(state, keyRoute, defaultSchema[fieldType]); }; -const changeTitle = (state: SchemaState, keyRoute: KeyRoute, title: string): SchemaState => { - const schema = cloneDeep(state); - +const changeTitle = (state: SchemaState, keyRoute: KeyRoute, title: string): void => { // point to title and set new value const titleKeyRoute = keyRoute.concat('title'); - set(schema, titleKeyRoute, title); - - return schema; + set(state, titleKeyRoute, title); }; -const changeDesc = (state: SchemaState, keyRoute: KeyRoute, desc: string): SchemaState => { - const schema = cloneDeep(state); - +const changeDesc = (state: SchemaState, keyRoute: KeyRoute, desc: string): void => { // point to desc and set new value const descKeyRoute = keyRoute.concat('description'); - set(schema, descKeyRoute, desc); - - return schema; + set(state, descKeyRoute, desc); }; -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; - } -}; +const reducer = (state: SchemaState, action: SchemaAction): SchemaState => + produce(state, (draft: Draft) => { + switch (action.type) { + case 'SET_SCHEMA': + setSchema(action.schema); + break; + + case 'ADD_CHILD_FIELD': + addChildField(draft, action.keyRoute); + break; + + case 'ADD_SIBLING_FIELD': + addSiblingField(draft, action.keyRoute); + break; + + case 'REMOVE_FIELD': + removeField(draft, action.keyRoute); + break; + + case 'TOGGLE_ALL_CHECKED': + toggleAllChecked(draft, action.checked); + break; + + case 'ADD_REQUIRED': + addRequired(draft, action.keyRoute); + break; + + case 'REMOVE_REQUIRED': + removeRequired(draft, action.keyRoute); + break; + + case 'CHANGE_FIELD': + changeField(draft, action.keyRoute, action.field); + break; + + case 'CHANGE_TYPE': + changeType(draft, action.keyRoute, action.fieldType); + break; + + case 'CHANGE_TITLE': + changeTitle(draft, action.keyRoute, action.title); + break; + + case 'CHANGE_DESC': + changeDesc(draft, action.keyRoute, action.desc); + break; + + default: + break; + } + }); export default reducer;