diff --git a/jsonforms-editor/src/core/api/paletteService.tsx b/jsonforms-editor/src/core/api/paletteService.tsx index 6076110..56ddb8b 100644 --- a/jsonforms-editor/src/core/api/paletteService.tsx +++ b/jsonforms-editor/src/core/api/paletteService.tsx @@ -8,9 +8,19 @@ import React from 'react'; -import { GroupIcon, HorizontalIcon, LabelIcon, VerticalIcon } from '../icons'; +import { + ControlIcon, + GroupIcon, + HorizontalIcon, + LabelIcon, + VerticalIcon, +} from '../icons'; import { EditorUISchemaElement } from '../model/uischema'; -import { createLabel, createLayout } from '../util/generators/uiSchema'; +import { + createEmptyControl, + createLabel, + createLayout, +} from '../util/generators/uiSchema'; export interface PaletteService { getPaletteElements(): PaletteElement[]; @@ -48,6 +58,12 @@ const paletteElements: PaletteElement[] = [ icon: , uiSchemaElementProvider: () => createLabel(), }, + { + type: 'Control', + label: 'Control', + icon: , + uiSchemaElementProvider: () => createEmptyControl(), + }, ]; export class DefaultPaletteService implements PaletteService { diff --git a/jsonforms-editor/src/core/components/EditableControl.tsx b/jsonforms-editor/src/core/components/EditableControl.tsx new file mode 100644 index 0000000..b3856ce --- /dev/null +++ b/jsonforms-editor/src/core/components/EditableControl.tsx @@ -0,0 +1,95 @@ +/** + * --------------------------------------------------------------------- + * Copyright (c) 2020 EclipseSource Munich + * Licensed under MIT + * https://github.com/eclipsesource/jsonforms-editor/blob/master/LICENSE + * --------------------------------------------------------------------- + */ + +import { Grid, TextField } from '@material-ui/core'; +import MenuItem from '@material-ui/core/MenuItem'; +import React from 'react'; + +import { useDispatch, useSchema } from '../context'; +import { + Actions, + getChildren, + getScope, + isArrayElement, + isObjectElement, + SchemaElement, +} from '../model'; +import { EditorControl } from '../model/uischema'; + +interface EditableControlProps { + uischema: EditorControl; +} +export const EditableControl: React.FC = ({ + uischema, +}) => { + const dispatch = useDispatch(); + const baseSchema = useSchema() as SchemaElement; + const handleLabelChange = (event: React.ChangeEvent) => { + dispatch( + Actions.updateUISchemaElement(uischema.uuid, { + label: event.target.value, + }) + ); + }; + const handleScopeChange = (event: React.ChangeEvent) => { + dispatch( + Actions.changeControlScope( + uischema.uuid, + (event.target.value as any).scope, + (event.target.value as any).uuid + ) + ); + }; + const scopes = getChildren(baseSchema) + .flatMap((child) => { + // if the child is the only item of an array, use its children instead + if ( + (isObjectElement(child) && + isArrayElement(child.parent) && + child.parent.items === child) || + isObjectElement(child) + ) { + return getChildren(child); + } + return [child]; + }) + .map((e) => ({ scope: `#${getScope(e)}`, uuid: e.uuid })); + const scopeValues = scopes.filter((s) => s.scope.endsWith(uischema.scope)); + const scopeValue = + scopeValues && scopeValues.length === 1 ? scopeValues[0] : ''; + return ( + + + + + + + {scopes.map((scope) => ( + + {scope.scope} + + ))} + + + + ); +}; diff --git a/jsonforms-editor/src/core/components/Layout.tsx b/jsonforms-editor/src/core/components/Layout.tsx index 6b1e929..32e4d7a 100644 --- a/jsonforms-editor/src/core/components/Layout.tsx +++ b/jsonforms-editor/src/core/components/Layout.tsx @@ -101,7 +101,7 @@ export const Layout: React.FC = ({ paletteTabs, children, }) => { - const [open, setOpen] = React.useState(true); + const [open, setOpen] = React.useState(false); const [selectedTabName, setSelectedTabName] = React.useState( paletteTabs ? paletteTabs[0].name : '' ); diff --git a/jsonforms-editor/src/core/components/index.ts b/jsonforms-editor/src/core/components/index.ts index d454b75..8472164 100644 --- a/jsonforms-editor/src/core/components/index.ts +++ b/jsonforms-editor/src/core/components/index.ts @@ -14,3 +14,4 @@ export * from './ErrorDialog'; export * from './OkCancelDialog'; export * from './ExportDialog'; export * from './ShowMoreLess'; +export * from './EditableControl'; diff --git a/jsonforms-editor/src/core/model/actions.ts b/jsonforms-editor/src/core/model/actions.ts index c72c2f4..6dfa957 100644 --- a/jsonforms-editor/src/core/model/actions.ts +++ b/jsonforms-editor/src/core/model/actions.ts @@ -16,7 +16,8 @@ export type CombinedAction = | AddScopedElementToLayout | MoveUiSchemaElement | RemoveUiSchemaElement - | AddDetail; + | AddDetail + | ChangeControlScope; export type EditorAction = UiSchemaAction | CombinedAction; @@ -38,6 +39,8 @@ export const UPDATE_UISCHEMA_ELEMENT: 'jsonforms-editor/UPDATE_UISCHEMA_ELEMENT' 'jsonforms-editor/UPDATE_UISCHEMA_ELEMENT'; export const ADD_DETAIL: 'jsonforms-editor/ADD_DETAIL' = 'jsonforms-editor/ADD_DETAIL'; +export const CHANGE_CONTROL_SCOPE: 'jsonforms-editor/CHANGE_CONTROL_SCOPE' = + 'jsonforms-editor/CHANGE_CONTROL_SCOPE'; export interface SetSchemaAction { type: 'jsonforms-editor/SET_SCHEMA'; @@ -89,6 +92,13 @@ export interface UpdateUiSchemaElement { changedProperties: { [key: string]: any }; } +export interface ChangeControlScope { + type: 'jsonforms-editor/CHANGE_CONTROL_SCOPE'; + elementUUID: string; + scope: string; + schemaUUID: string; +} + export interface AddDetail { type: 'jsonforms-editor/ADD_DETAIL'; uiSchemaElementId: string; @@ -167,6 +177,12 @@ const addDetail = ( detail, }); +const changeControlScope = ( + elementUUID: string, + scope: string, + schemaUUID: string +) => ({ type: CHANGE_CONTROL_SCOPE, elementUUID, scope, schemaUUID }); + export const Actions = { setSchema, setUiSchema, @@ -177,4 +193,5 @@ export const Actions = { removeUiSchemaElement, updateUISchemaElement, addDetail, + changeControlScope, }; diff --git a/jsonforms-editor/src/core/model/reducer.ts b/jsonforms-editor/src/core/model/reducer.ts index ce4fb56..1c90b17 100644 --- a/jsonforms-editor/src/core/model/reducer.ts +++ b/jsonforms-editor/src/core/model/reducer.ts @@ -20,6 +20,7 @@ import { ADD_DETAIL, ADD_SCOPED_ELEMENT_TO_LAYOUT, ADD_UNSCOPED_ELEMENT_TO_LAYOUT, + CHANGE_CONTROL_SCOPE, CombinedAction, EditorAction, MOVE_UISCHEMA_ELEMENT, @@ -118,6 +119,24 @@ export const combinedReducer = (state: EditorState, action: CombinedAction) => { buildSchemaTree(action.schema), buildEditorUiSchemaTree(action.uiSchema) ); + case CHANGE_CONTROL_SCOPE: + return withCloneTrees( + state.uiSchema, + action.elementUUID, + state.schema, + action.schemaUUID, + state, + (newUiSchema, newSchema) => { + if (!newSchema || !newUiSchema || !isEditorControl(newUiSchema)) + return state; + linkElements(newUiSchema, newSchema); + newUiSchema.scope = action.scope; + return { + schema: getRoot(newSchema), + uiSchema: getRoot(newUiSchema as EditorUISchemaElement), + }; + } + ); case ADD_SCOPED_ELEMENT_TO_LAYOUT: return withCloneTrees( state.uiSchema, @@ -361,6 +380,7 @@ export const editorReducer = (state: EditorState, action: EditorAction) => { schema: state.schema, uiSchema: uiSchemaReducer(state.uiSchema, action), }; + case CHANGE_CONTROL_SCOPE: case SET_SCHEMA: case SET_UISCHEMA: case SET_SCHEMAS: diff --git a/jsonforms-editor/src/core/renderers/DroppableArrayControl.tsx b/jsonforms-editor/src/core/renderers/DroppableArrayControl.tsx index 1c8c8ad..e7daa46 100644 --- a/jsonforms-editor/src/core/renderers/DroppableArrayControl.tsx +++ b/jsonforms-editor/src/core/renderers/DroppableArrayControl.tsx @@ -15,10 +15,11 @@ import { JsonFormsDispatch, withJsonFormsArrayControlProps, } from '@jsonforms/react'; -import { makeStyles, Typography } from '@material-ui/core'; +import { Grid, makeStyles, Typography } from '@material-ui/core'; import React, { useMemo } from 'react'; import { useDrop } from 'react-dnd'; +import { EditableControl } from '../components'; import { useDispatch, useSchema } from '../context'; import { canDropIntoScope, @@ -96,22 +97,28 @@ const DroppableArrayControl: React.FC = ({ return renderers && [...renderers, DroppableElementRegistration]; }, [renderers]); - if (!uischema.options?.detail) { - return ( - - Default array layout. Drag and drop an item here to customize array - layout. - - ); - } return ( - + + + + + + {!uischema.options?.detail ? ( + + Default array layout. Drag and drop an item here to customize array + layout. + + ) : ( + + )} + + ); }; diff --git a/jsonforms-editor/src/core/renderers/DroppableControl.tsx b/jsonforms-editor/src/core/renderers/DroppableControl.tsx new file mode 100644 index 0000000..644adc3 --- /dev/null +++ b/jsonforms-editor/src/core/renderers/DroppableControl.tsx @@ -0,0 +1,28 @@ +/** + * --------------------------------------------------------------------- + * Copyright (c) 2020 EclipseSource Munich + * Licensed under MIT + * https://github.com/eclipsesource/jsonforms-editor/blob/master/LICENSE + * --------------------------------------------------------------------- + */ + +import { ControlProps, isControl, rankWith } from '@jsonforms/core'; +import { withJsonFormsControlProps } from '@jsonforms/react'; +import React from 'react'; + +import { EditableControl } from '../components'; +import { EditorControl } from '../model/uischema'; + +interface DroppableControlProps extends ControlProps { + uischema: EditorControl; +} +const DroppableControl: React.FC = ({ uischema }) => { + return ; +}; + +export const DroppableControlRegistration = { + tester: rankWith(40, isControl), // less than DroppableElement + renderer: withJsonFormsControlProps( + DroppableControl as React.FC + ), +}; diff --git a/jsonforms-editor/src/core/renderers/DroppableGroupLayout.tsx b/jsonforms-editor/src/core/renderers/DroppableGroupLayout.tsx index 1530c81..d39d626 100644 --- a/jsonforms-editor/src/core/renderers/DroppableGroupLayout.tsx +++ b/jsonforms-editor/src/core/renderers/DroppableGroupLayout.tsx @@ -13,10 +13,13 @@ import { CardHeader, Grid, makeStyles, + TextField, Typography, } from '@material-ui/core'; import React from 'react'; +import { useDispatch } from '../context'; +import { Actions } from '../model'; import { EditorLayout } from '../model/uischema'; import { DroppableLayout } from './DroppableLayout'; @@ -39,6 +42,14 @@ const Group: React.FC = (props) => { const { uischema } = props; const groupLayout = uischema as GroupLayout & EditorLayout; const classes = useStyles(); + const dispatch = useDispatch(); + const handleLabelChange = (event: React.ChangeEvent) => { + dispatch( + Actions.updateUISchemaElement(groupLayout.uuid, { + label: event.target.value, + }) + ); + }; return ( = (props) => { )} > + diff --git a/jsonforms-editor/src/core/renderers/DroppableLabel.tsx b/jsonforms-editor/src/core/renderers/DroppableLabel.tsx new file mode 100644 index 0000000..8cbd679 --- /dev/null +++ b/jsonforms-editor/src/core/renderers/DroppableLabel.tsx @@ -0,0 +1,42 @@ +/** + * --------------------------------------------------------------------- + * Copyright (c) 2020 EclipseSource Munich + * Licensed under MIT + * https://github.com/eclipsesource/jsonforms-editor/blob/master/LICENSE + * --------------------------------------------------------------------- + */ +import { LabelElement, LayoutProps, rankWith, uiTypeIs } from '@jsonforms/core'; +import { withJsonFormsLayoutProps } from '@jsonforms/react'; +import { TextField } from '@material-ui/core'; +import React from 'react'; + +import { useDispatch } from '../context'; +import { Actions } from '../model'; +import { EditorUISchemaElement } from '../model/uischema'; + +const DroppableLabel: React.FC = (props) => { + const { uischema } = props; + const labelElement = uischema as LabelElement & EditorUISchemaElement; + const dispatch = useDispatch(); + const handleLabelChange = (event: React.ChangeEvent) => { + dispatch( + Actions.updateUISchemaElement(labelElement.uuid, { + text: event.target.value, + }) + ); + }; + return ( + + ); +}; + +export const DroppableLabelRegistration = { + tester: rankWith(45, uiTypeIs('Label')), + renderer: withJsonFormsLayoutProps(DroppableLabel), +}; diff --git a/jsonforms-editor/src/core/util/generators/uiSchema.ts b/jsonforms-editor/src/core/util/generators/uiSchema.ts index b0f55f9..6191520 100644 --- a/jsonforms-editor/src/core/util/generators/uiSchema.ts +++ b/jsonforms-editor/src/core/util/generators/uiSchema.ts @@ -44,3 +44,12 @@ export const createLabel = ( uuid: uuid(), } as LabelElement & EditorUISchemaElement; }; + +export const createEmptyControl = (): ControlElement & + EditorUISchemaElement => { + return { + type: 'Control', + scope: '', + uuid: uuid(), + } as ControlElement & EditorUISchemaElement; +}; diff --git a/jsonforms-editor/src/editor/index.ts b/jsonforms-editor/src/editor/index.ts index 9fead8e..6d94016 100644 --- a/jsonforms-editor/src/editor/index.ts +++ b/jsonforms-editor/src/editor/index.ts @@ -9,8 +9,10 @@ import { JsonFormsRendererRegistryEntry } from '@jsonforms/core'; import { materialRenderers } from '@jsonforms/material-renderers'; import { DroppableArrayControlRegistration } from '../core/renderers/DroppableArrayControl'; +import { DroppableControlRegistration } from '../core/renderers/DroppableControl'; import { DroppableElementRegistration } from '../core/renderers/DroppableElement'; import { DroppableGroupLayoutRegistration } from '../core/renderers/DroppableGroupLayout'; +import { DroppableLabelRegistration } from '../core/renderers/DroppableLabel'; import { DroppableHorizontalLayoutRegistration, DroppableVerticalLayoutRegistration, @@ -33,4 +35,6 @@ export const defaultEditorRenderers: JsonFormsRendererRegistryEntry[] = [ DroppableElementRegistration, DroppableGroupLayoutRegistration, DroppableArrayControlRegistration, + DroppableControlRegistration, + DroppableLabelRegistration, ];