diff --git a/jsonforms-editor/src/core/dnd/types.ts b/jsonforms-editor/src/core/dnd/types.ts index f09dbbf..d4e8e3d 100644 --- a/jsonforms-editor/src/core/dnd/types.ts +++ b/jsonforms-editor/src/core/dnd/types.ts @@ -5,6 +5,8 @@ * https://github.com/eclipsesource/jsonforms-editor/blob/master/LICENSE * --------------------------------------------------------------------- */ +import { JsonSchema } from '@jsonforms/core'; + import { getArrayContainer, SchemaElement } from '../model'; import { containsControls, @@ -18,8 +20,27 @@ import { getHierarchy } from '../util/tree'; export const NEW_UI_SCHEMA_ELEMENT: 'newUiSchemaElement' = 'newUiSchemaElement'; export const MOVE_UI_SCHEMA_ELEMENT: 'moveUiSchemaElement' = 'moveUiSchemaElement'; +export const NEW_SCHEMA_ELEMENT: 'newSchemaElement' = 'newSchemaElement'; + +export type DndType = + | NewUISchemaElement + | MoveUISchemaElement + | NewSchemaElement; -export type DndType = NewUISchemaElement | MoveUISchemaElement; +export interface NewSchemaElement { + type: 'newSchemaElement'; + uiSchemaElement: EditorUISchemaElement; + schema: JsonSchema; +} + +const newSchemaElement = ( + uiSchemaElement: EditorUISchemaElement, + schema: JsonSchema +) => ({ + type: NEW_SCHEMA_ELEMENT, + uiSchemaElement, + schema, +}); export interface NewUISchemaElement { type: 'newUiSchemaElement'; @@ -51,7 +72,11 @@ const moveUISchemaElement = ( schema, }); -export const DndItems = { newUISchemaElement, moveUISchemaElement }; +export const DndItems = { + newUISchemaElement, + moveUISchemaElement, + newSchemaElement, +}; export const canDropIntoLayout = ( item: NewUISchemaElement, diff --git a/jsonforms-editor/src/core/model/actions.ts b/jsonforms-editor/src/core/model/actions.ts index c72c2f4..0b8f0a3 100644 --- a/jsonforms-editor/src/core/model/actions.ts +++ b/jsonforms-editor/src/core/model/actions.ts @@ -5,6 +5,8 @@ * https://github.com/eclipsesource/jsonforms-editor/blob/master/LICENSE * --------------------------------------------------------------------- */ +import { JsonSchema } from '@jsonforms/core'; + import { EditorUISchemaElement } from './uischema'; export type UiSchemaAction = AddUnscopedElementToLayout | UpdateUiSchemaElement; @@ -14,6 +16,7 @@ export type CombinedAction = | SetSchemaAction | SetSchemasAction | AddScopedElementToLayout + | AddSchemaElementToLayout | MoveUiSchemaElement | RemoveUiSchemaElement | AddDetail; @@ -26,6 +29,8 @@ export const SET_UISCHEMA: 'jsonforms-editor/SET_UISCHEMA' = 'jsonforms-editor/SET_UISCHEMA'; export const SET_SCHEMAS: 'jsonforms-editor/SET_SCHEMAS' = 'jsonforms-editor/SET_SCHEMAS'; +export const ADD_SCHEMA_ELEMENT_TO_LAYOUT: 'jsonforms-editor/ADD_SCHEMA_ELEMENT_TO_LAYOUT' = + 'jsonforms-editor/ADD_SCHEMA_ELEMENT_TO_LAYOUT'; export const ADD_SCOPED_ELEMENT_TO_LAYOUT: 'jsonforms-editor/ADD_SCOPED_ELEMENT_TO_LAYOUT' = 'jsonforms-editor/ADD_SCOPED_ELEMENT_TO_LAYOUT'; export const ADD_UNSCOPED_ELEMENT_TO_LAYOUT: 'jsonforms-editor/ADD_UNSCOPED_ELEMENT_TO_LAYOUT' = @@ -55,6 +60,14 @@ export interface SetSchemasAction { uiSchema: any; } +export interface AddSchemaElementToLayout { + type: 'jsonforms-editor/ADD_SCHEMA_ELEMENT_TO_LAYOUT'; + uiSchemaElement: EditorUISchemaElement; + layoutUUID: string; + schema: JsonSchema; + index: number; +} + export interface AddScopedElementToLayout { type: 'jsonforms-editor/ADD_SCOPED_ELEMENT_TO_LAYOUT'; uiSchemaElement: EditorUISchemaElement; @@ -111,6 +124,19 @@ const setSchemas = (schema: any, uiSchema: any) => ({ uiSchema, }); +const addSchemaElementToLayout = ( + uiSchemaElement: EditorUISchemaElement, + layoutUUID: string, + index: number, + schema: JsonSchema +) => ({ + type: ADD_SCHEMA_ELEMENT_TO_LAYOUT, + uiSchemaElement, + layoutUUID, + index, + schema, +}); + const addScopedElementToLayout = ( uiSchemaElement: EditorUISchemaElement, layoutUUID: string, @@ -177,4 +203,5 @@ export const Actions = { removeUiSchemaElement, updateUISchemaElement, addDetail, + addSchemaElementToLayout, }; diff --git a/jsonforms-editor/src/core/model/reducer.ts b/jsonforms-editor/src/core/model/reducer.ts index ce4fb56..1bbcea7 100644 --- a/jsonforms-editor/src/core/model/reducer.ts +++ b/jsonforms-editor/src/core/model/reducer.ts @@ -10,6 +10,7 @@ import { assign } from 'lodash'; import { withCloneTree, withCloneTrees } from '../util/clone'; import { findByUUID, + getPathString, getRoot, isUUIDError, linkElements, @@ -18,6 +19,7 @@ import { } from '../util/schemasUtil'; import { ADD_DETAIL, + ADD_SCHEMA_ELEMENT_TO_LAYOUT, ADD_SCOPED_ELEMENT_TO_LAYOUT, ADD_UNSCOPED_ELEMENT_TO_LAYOUT, CombinedAction, @@ -30,7 +32,13 @@ import { UiSchemaAction, UPDATE_UISCHEMA_ELEMENT, } from './actions'; -import { buildSchemaTree, cleanLinkedElements, SchemaElement } from './schema'; +import { + buildJsonSchema, + buildSchemaTree, + cleanLinkedElements, + isObjectElement, + SchemaElement, +} from './schema'; import { buildEditorUiSchemaTree, cleanUiSchemaLinks, @@ -118,6 +126,51 @@ export const combinedReducer = (state: EditorState, action: CombinedAction) => { buildSchemaTree(action.schema), buildEditorUiSchemaTree(action.uiSchema) ); + case ADD_SCHEMA_ELEMENT_TO_LAYOUT: + return withCloneTree( + state.uiSchema, + action.layoutUUID, + state, + (newUiSchema) => { + const newUIElement = action.uiSchemaElement; + newUIElement.parent = newUiSchema; + (newUiSchema as EditorLayout).elements.splice( + action.index, + 0, + newUIElement + ); + const currentJsonSchema = buildJsonSchema(state.schema!); + const maxPropNumber = + Object.keys(currentJsonSchema.properties!) + .filter((propName) => propName.startsWith('prop_')) + .map((propName) => Number.parseInt(propName.substr(5))) + .reduce( + (maxValue, propNumber) => Math.max(maxValue, propNumber), + 0 + ) + 1; + const propName = `prop_${maxPropNumber}`; + currentJsonSchema.properties![propName] = action.schema; + const newSchema = buildSchemaTree(currentJsonSchema); + if (!isObjectElement(newSchema)) { + return state; + } + + if ( + !linkElements(newUIElement, newSchema.properties.get(propName)!) + ) { + console.error('Could not add new UI element', newUIElement); + return state; + } + (newUIElement as any).scope = `#/${getPathString( + newSchema.properties.get(propName) + )}`; + + return { + schema: getRoot(newSchema as SchemaElement), + uiSchema: getRoot(newUiSchema), + }; + } + ); case ADD_SCOPED_ELEMENT_TO_LAYOUT: return withCloneTrees( state.uiSchema, @@ -364,6 +417,7 @@ export const editorReducer = (state: EditorState, action: EditorAction) => { case SET_SCHEMA: case SET_UISCHEMA: case SET_SCHEMAS: + case ADD_SCHEMA_ELEMENT_TO_LAYOUT: case ADD_SCOPED_ELEMENT_TO_LAYOUT: case MOVE_UISCHEMA_ELEMENT: case REMOVE_UISCHEMA_ELEMENT: diff --git a/jsonforms-editor/src/core/renderers/DroppableLayout.tsx b/jsonforms-editor/src/core/renderers/DroppableLayout.tsx index b8434dc..d0986a4 100644 --- a/jsonforms-editor/src/core/renderers/DroppableLayout.tsx +++ b/jsonforms-editor/src/core/renderers/DroppableLayout.tsx @@ -27,6 +27,7 @@ import { canMoveSchemaElementTo, MOVE_UI_SCHEMA_ELEMENT, MoveUISchemaElement, + NEW_SCHEMA_ELEMENT, NEW_UI_SCHEMA_ELEMENT, NewUISchemaElement, } from '../dnd'; @@ -125,10 +126,11 @@ const useDropPointStyles = makeStyles((theme) => ({ const DropPoint: React.FC = ({ layout, index }) => { const dispatch = useDispatch(); const rootSchema = useSchema(); - const [{ isOver, uiSchemaElement, schemaUUID }, drop] = useDrop({ - accept: [NEW_UI_SCHEMA_ELEMENT, MOVE_UI_SCHEMA_ELEMENT], + const [{ isOver, uiSchemaElement, schemaUUID, schema }, drop] = useDrop({ + accept: [NEW_UI_SCHEMA_ELEMENT, MOVE_UI_SCHEMA_ELEMENT, NEW_SCHEMA_ELEMENT], canDrop: (item, monitor) => { switch (item.type) { + case NEW_SCHEMA_ELEMENT: case NEW_UI_SCHEMA_ELEMENT: return canDropIntoLayout( item as NewUISchemaElement, @@ -149,6 +151,7 @@ const DropPoint: React.FC = ({ layout, index }) => { isOver: !!mon.isOver() && mon.canDrop(), uiSchemaElement: mon.getItem()?.uiSchemaElement, schemaUUID: mon.getItem()?.schemaUUID, + schema: mon.getItem()?.schema, }), drop: (item) => { switch (item.type) { @@ -180,6 +183,15 @@ const DropPoint: React.FC = ({ layout, index }) => { ) ); break; + case NEW_SCHEMA_ELEMENT: + dispatch( + Actions.addSchemaElementToLayout( + uiSchemaElement, + layout.uuid, + index, + schema + ) + ); } }, }); diff --git a/jsonforms-editor/src/palette-panel/components/CreateSchemaTree.tsx b/jsonforms-editor/src/palette-panel/components/CreateSchemaTree.tsx new file mode 100644 index 0000000..576f0e4 --- /dev/null +++ b/jsonforms-editor/src/palette-panel/components/CreateSchemaTree.tsx @@ -0,0 +1,80 @@ +/** + * --------------------------------------------------------------------- + * Copyright (c) 2020 EclipseSource Munich + * Licensed under MIT + * https://github.com/eclipsesource/jsonforms-editor/blob/master/LICENSE + * --------------------------------------------------------------------- + */ +import { JsonSchema } from '@jsonforms/core'; +import Typography from '@material-ui/core/Typography'; +import React from 'react'; +import { useDrag } from 'react-dnd'; +import { v4 as uuid } from 'uuid'; + +import { DndItems } from '../../core/dnd'; +import { StyledTreeItem, StyledTreeView } from './Tree'; + +interface CreateSchemaTreeProps { + schema: JsonSchema; + label: string; + icon?: React.ReactNode; +} + +const CreateSchemaTreeItem: React.FC = ({ + schema, + label, + icon, +}) => { + const uiSchemaElement = { + type: 'Control', + uuid: uuid(), + }; + const [{ isDragging }, drag] = useDrag({ + item: DndItems.newSchemaElement(uiSchemaElement, schema), + collect: (monitor) => ({ + isDragging: !!monitor.isDragging(), + }), + }); + if (!schema.type || Array.isArray(schema.type)) { + return <>; + } + return ( +
+ +
+ ); +}; + +export const CreateSchemaTree: React.FC<{}> = () => { + return ( +
+ + Create Schema + + + + + + + +
+ ); +}; diff --git a/jsonforms-editor/src/palette-panel/components/PalletePanel.tsx b/jsonforms-editor/src/palette-panel/components/PalletePanel.tsx index 50024d9..bdcfa46 100644 --- a/jsonforms-editor/src/palette-panel/components/PalletePanel.tsx +++ b/jsonforms-editor/src/palette-panel/components/PalletePanel.tsx @@ -19,6 +19,7 @@ import { Actions, SchemaElement, toPrintableObject } from '../../core/model'; import { buildDebugUISchema } from '../../core/model/uischema'; import { useExportSchema, useExportUiSchema } from '../../core/util/hooks'; import { env } from '../../env'; +import { CreateSchemaTree } from './CreateSchemaTree'; import { SchemaJson, UpdateResult } from './SchemaJson'; import { SchemaTreeView } from './SchemaTree'; import { UIElementsTree } from './UIElementsTree'; @@ -101,6 +102,7 @@ export const PalettePanel = () => { className={classes.uiElementsTree} elements={paletteService.getPaletteElements()} /> +