|  |  | @ -1,5 +1,6 @@ | 
			
		
	
		
			
				
					|  |  |  | import produce, { Draft } from 'immer'; | 
			
		
	
		
			
				
					|  |  |  | import { get, set, unset, remove, cloneDeep } from 'lodash-es'; | 
			
		
	
		
			
				
					|  |  |  | import { SchemaType, SchemaState, SchemaAction, KeyRoute } from '../model'; | 
			
		
	
		
			
				
					|  |  |  | import { SchemaType, SchemaItem, SchemaState, SchemaAction, KeyRoute } from '../model'; | 
			
		
	
		
			
				
					|  |  |  | import { defaultSchema, setAllFieldRequired } from '../utils/schema'; | 
			
		
	
		
			
				
					|  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  | let fieldNum = 0; | 
			
		
	
	
		
			
				
					|  |  | @ -8,169 +9,154 @@ 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 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<SchemaState>, 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<SchemaState>) => { | 
			
		
	
		
			
				
					|  |  |  |     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; | 
			
		
	
	
		
			
				
					|  |  | 
 |