UNPKG

@tomino/dynamic-form-semantic-ui

Version:

Semantic UI form renderer based on dynamic form generation

422 lines 15.7 kB
import { buildStore, setUndoManager, Schema } from '@tomino/dynamic-form'; import { UndoManager } from 'mst-middlewares'; import { types } from 'mobx-state-tree'; import { root, findParentSchema } from './editor_common'; import { elementSchema, schemaSchema, boundSchema } from './common_schemas'; import { toJS } from 'mobx'; import { schemaDatasetToJS } from '../helpers'; let undoSet = false; function mergeProp(schema, localTypes, sharedTypes) { let item = { ...schema }; if (item.properties) { item.properties = mergeProperties(item, localTypes, sharedTypes); } if (item.$import) { item.reference = sharedTypes.find(t => t.id === item.$import).id; } if (item.$ref) { item.reference = item.$ref.split('/')[2]; } return item; } function mergeProperties(schema, localTypes, sharedTypes) { let keys = Object.getOwnPropertyNames(schema.properties); let newProperties = {}; for (let key of keys) { let prop = mergeProp(schema.properties[key], localTypes, sharedTypes); newProperties[key] = mergeSchema(prop, localTypes, sharedTypes); } return newProperties; } function mergeSchema(schema, localTypes, sharedTypes) { let newSchema = { ...schema }; if (schema.properties) { newSchema.properties = mergeProperties(schema, localTypes, sharedTypes); } if (schema.items) { newSchema.items = mergeProp(newSchema.items, localTypes, sharedTypes); } return newSchema; } export function searchJsonSchema(func, schema) { let queue = [schema]; while (queue.length) { let current = queue.pop(); if (!current.properties) { continue; } for (let key of Object.getOwnPropertyNames(current.properties)) { let property = current.properties[key]; if (property.properties) { queue.push(property); } let result = func(property); if (result) { return result; } } } } export function searchSchema(schema, func) { let queue = [schema]; while (queue.length) { let current = queue.pop(); if (!current.properties) { continue; } for (let key of Array.from(current.properties.keys())) { let property = current.properties.get(key); if (property.properties) { queue.push(property); } let result = func(property); if (result) { return result; } } } } export function searchForm(form, func) { let queue = [form]; while (queue.length) { let current = queue.pop(); for (let element of current.elements) { if (element.elements) { queue.push(element); } let result = func(element); if (result) { return result; } } } } function assignArrayIds(schema, schemas) { let s = schema; s.imports = {}; searchJsonSchema(property => { if (property.$import) { let item = schemas.find(s => s.id === property.$import); s.imports[item.id] = item.schema; } }, schema); } function assignSource(props) { Object.getOwnPropertyNames(props).forEach(key => { props[key].control.props = { [props[key].schema.type === 'boolean' ? 'value' : 'value']: { source: key }, ...props[key].control.props }; }); } function buildProps(props, extraProps = {}) { assignSource(props); let schema = new Schema({ type: 'object', properties: { ...buildPropSchema(props), ...extraProps } }); return buildStore(schema, {}, false); } function buildPropSchema(props) { let result = {}; for (let key of Object.getOwnPropertyNames(props)) { result[key] = props[key].schema; } return result; } export function prepareStores(context) { let id = 0; let date = Date.now(); function generateId() { if (process.env.NODE_ENV === 'test') { return (id++).toString(); } return (date + id++).toString(); } let objectMap = {}; const mapModifiers = (self) => ({ afterCreate() { objectMap[self.uid] = self; }, beforeDestroy() { delete objectMap[self.uid]; } }); const referenceHandlers = { get(identifier) { return objectMap[identifier] || null; }, set(value) { return value.uid; } }; const props = {}; const propNames = {}; let childProps = { properties: { control: { type: 'string' }, label: boundSchema(), css: boundSchema(), className: boundSchema(), hidden: boundSchema(), editorLabel: { type: 'string' }, locked: { type: 'boolean' } } }; let childPropStore = buildStore(new Schema({ type: 'object', properties: {} }), {}, false); if (context.editorCatalogue) { for (let key of Object.getOwnPropertyNames(context.editorCatalogue.components)) { let c = context.editorCatalogue.components[key]; if (c.childProps) { assignSource(c.childProps); childProps = { properties: { ...childProps.properties, ...buildPropSchema(c.childProps) } }; } } childPropStore = buildStore(new Schema({ type: 'object', properties: childProps.properties }), {}, false); for (let key of Object.getOwnPropertyNames(context.editorCatalogue.components)) { let c = context.editorCatalogue.components[key]; if (c.props) { props[key] = buildProps(c.props, childProps.properties); propNames[key] = Object.keys(c.props).concat(Object.keys(childProps.properties)); } } } const elementStore = buildStore(elementSchema, {}, false) .props({ uid: types.optional(types.string, generateId), props: types.optional(types.union({ dispatcher: snapshot => { if (!snapshot || !snapshot.control) { return childPropStore; } return props[snapshot.control] || childPropStore; } }), {}), elements: types.optional(types.array(types.maybe(types.union(types.late(() => elementStore), types.null))), []), pages: types.optional(types.array(types.maybe(types.union(types.late(() => elementStore), types.null))), []), isSelected: types.optional(types.boolean, false) }) .actions(mapModifiers) .actions(() => ({ moveElement(from, to, index) { from.parent.detach(from); if (index != null && index >= 0) { to.insertRow('elements', index, toJS(from)); } else { to.addRow('elements', toJS(from)); } } })); const schemaStore = buildStore(schemaSchema, {}, false) .actions(mapModifiers) .props({ uid: types.optional(types.string, generateId), properties: types.maybe(types.map(types.late(() => schemaStore))), items: types.maybe(types.late(() => schemaStore)), reference: types.maybe(types.reference(types.late(() => schemaStore), { get(identifier, parent) { let root = parent.root().schema; if (!root) { return {}; } let key = Array.from(root.definitions.keys()).find(f => f === identifier); if (key) { return root.definitions.get(key); } key = Array.from(root.imports.keys()).find(f => f === identifier); if (key) { return root.imports.get(key); } throw new Error('Could not find reference to: ' + identifier); }, set(value) { let root = value.root().schema; if (!root) { return ''; } let key = Array.from(root.definitions.keys()).find(f => root.definitions.get(f) === value); if (key) { return key; } key = Array.from(root.imports.keys()).find(f => root.imports.get(f) === value); if (key) { return key; } throw new Error('WTF'); } })), definitions: types.optional(types.map(types.late(() => schemaStore)), {}), imports: types.optional(types.map(types.late(() => schemaStore)), {}), anyOf: types.optional(types.array(types.late(() => schemaStore)), []), allOf: types.optional(types.array(types.late(() => schemaStore)), []), oneOf: types.optional(types.array(types.late(() => schemaStore)), []), errorMessage: types.maybe(types.union(types.string, types.map(types.union(types.string, types.map(types.string))))) }) .volatile(self => ({ name() { let parent = self.parent; let key = Array.from(parent.properties.keys()).find(k => parent.properties.get(k) === self); if (key) { return key; } return 'unknownArgMessage'; } })) .actions(self => ({ changeType: (value, prev) => { const owner = self; owner.setValue('$ref', null); owner.setValue('$import', null); owner.setValue('reference', undefined); owner.setValue('type', value); if (['string', 'number', 'integer', 'boolean'].indexOf(value) >= 0) { owner.setValue('properties', undefined); owner.setValue('items', undefined); return; } if (value === 'array' && prev !== 'array') { owner.setValue('items', { type: 'object', properties: {} }); return; } if (value === 'object') { if (!owner.getValue('properties')) { owner.setValue('properties', {}); } return; } let elementRoot = root(owner); let localTypes = Array.from(elementRoot.definitions.keys()); if (localTypes.indexOf(value) >= 0) { owner.setValue('reference', value); owner.setValue('$ref', `#/definitions/${value}`); owner.setValue('type', 'object'); return; } let imports = Array.from(elementRoot.imports.keys()); if (imports.indexOf(value) >= 0) { owner.setValue('reference', value); owner.setValue('$import', value); owner.setValue('type', 'object'); return; } } })); const stateStore = types .model({ selectedForm: types.maybe(types.reference(elementStore, referenceHandlers)), selectedSchema: types.maybe(types.reference(schemaStore, referenceHandlers)), selectedSchemaName: types.maybe(types.string), selectedElement: types.maybe(types.reference(elementStore, referenceHandlers)), selectedLeftPane: types.optional(types.string, localStorage.getItem('CORPIX_LEFT') || 'pages') }) .actions(self => ({ setForm(element) { self.selectedSchema = undefined; self.selectedElement = element; self.selectedForm = element; }, setSchema(schema, name = '') { self.selectedSchema = schema; self.selectedSchemaName = name; self.selectedElement = undefined; }, setElement(element, schema) { self.selectedSchema = undefined; if (self.selectedElement) { self.selectedElement.setValue('isSelected', false); } self.selectedElement = element; self.selectedElement.setValue('isSelected', true); context.selectedParentSchema = findParentSchema(element, schema, context); }, changeLeftPane(view) { localStorage.setItem('CORPIX_LEFT', view); self.selectedLeftPane = view; } })) .actions(self => ({ deleteActiveSchema() { self.selectedSchema.parent.detach(self.selectedSchema); self.setSchema(undefined, undefined); }, deleteActiveElement() { self.selectedElement.parent.detach(self.selectedElement); self.selectedElement = undefined; } })); const projectStore = types .model({ form: elementStore, schema: schemaStore, state: stateStore, history: types.optional(UndoManager, {}) }) .actions(self => ({ renameSchema: (name, rootSchema) => { let selected = self.state.selectedSchema; let owner = selected.reference ? selected.reference : selected; let parent = owner.parent; let sourcePath = context.selectedSourcePath; let titleOnly = !sourcePath || (parent.definitions.has(sourcePath) && rootSchema === parent); let current = self.state.selectedSchema; if (titleOnly) { current.setValue('title', name); return; } if (parent.properties.has(name)) { current.setError('title', 'Element with this name already exists!'); return; } let newSourcePathParts = sourcePath.split('.'); newSourcePathParts.pop(); let newSourcePath = newSourcePathParts.concat(name).join('.'); context.selectedSourcePath = newSourcePath; searchForm(self.form, item => { for (let prop of propNames[item.control]) { let p = item.props[prop]; if (p && p.source && p.source.indexOf(sourcePath) >= 0) { p.setValue('source', p.source.replace(sourcePath, newSourcePath)); } } }); const data = schemaDatasetToJS(current); parent.properties.set(name, data); let newValue = parent.properties.get(name); newValue.setValue('title', name); self.state.setSchema(newValue, name); parent.detach(current); context.recomputeDataBounce(); } })); return function buildFormEditorDataset(form, schema) { assignArrayIds(schema, context.types); if (schema) { schema = mergeSchema(schema, schema.definitions, context.types); } else { schema = {}; } let s = projectStore.create({ form, schema, state: {} }); if (form) { const currentForm = localStorage.getItem('CORPIX_SELECTED_FORM'); if (currentForm && s.form.pages.some(p => p.uid === currentForm)) { s.state.setForm(s.form.pages.find(p => p.uid === currentForm)); } else { s.state.setForm(s.form); } } context.form = s.form; context.schema = s.schema; context.project = s; context.state = s.state; if (!undoSet) { setUndoManager(s); global.__state = s; undoSet = true; } return s; }; } //# sourceMappingURL=form_store.js.map