import { Button, Collapse, DividerProps, Flex, Group, Modal, Table, TitleOrder, TitleProps } from '@mantine/core'; import { widget, WidgetPlugin, ArrayNode, DefaultNodeOptions, Widget, WidgetField, JsonNode, Editor } from '@sagold/react-json-editor'; import { Icon } from '../../components/icon/Icon'; import { useDraggableItems, SortableOptions } from '../../features/dragndrop/useDraggableItems'; import { useContext, useRef } from 'react'; import { useDisclosure } from '@mantine/hooks'; import { WidgetInputWrapper } from '../../components/widgetinputwrapper/WidgetInputWrapper'; import { WidgetMenu, WidgetMenuItems } from '../../components/widgetmenu/WidgetMenu'; import { WidgetParentHeader } from '../../components/widgetheader/WidgetHeader'; import { ActionButton } from '../../components/actionbutton/ActionButton'; import { SchemaNode } from 'json-schema-library'; import { SelectionContext } from '../../features/selection'; // for comparison https://github.com/sueddeutsche/editron/blob/master/src/editors/arrayeditor/index.ts // and https://github.com/sueddeutsche/editron/blob/master/src/editors/arrayeditor/ArrayItem.ts const DRAG_HANDLE_COLUMN = ( drag_indicator ); /** * JSON Schema array options * * @example * { * "type": "array", * "x-options": { * "showEditJsonAction": false * } * } */ export type ArrayOptions = DefaultNodeOptions & { /** if set, will add an accordion in the given toggle state */ collapsed?: boolean; /** * if set, will add an edit-json action to edit, copy and paste json-data for this location * * @story ArrayWidget.Options */ showEditJsonAction?: boolean; /** Is set internally to true to add a delete option for this object. */ isOptional?: boolean; /** set to true to show inline button at the end of the array to add another item */ showInlineAddAction?: boolean; /** set to false to deactivate array menu */ showHeaderMenu?: boolean; /** set to false to deactivate any array item-controls */ showItemControls?: boolean; /** set to true to inline description */ descriptionInline?: boolean; /** set to `{ enabled: true }` for dragndrop */ sortable?: SortableOptions; /** if true will add a separator line to the header */ showTitleDivider?: boolean; /** placement of title within the separator */ dividerProps?: Pick; /** Mantine Title Props */ titleProps?: TitleProps; selectable?: boolean; /** if false, will hide title. will hide complete title-header if no menu-actions are available */ showHeader?: boolean; /** @internal */ widgetMenuItems?: WidgetMenuItems; /** @internal */ widget?: string; }; export const ArrayWidget = widget>(({ editor, node, options }) => { const [isJsonModalOpen, jsonModal] = useDisclosure(false); const depth = Math.min(6, node.pointer.split('/').length); const order = options.titleProps?.order ?? ((depth === 1 ? 1 : 2) as TitleOrder); const childOptions = { titleProps: { order: Math.min(6, order + 1) }, disabled: options.disabled, readOnly: options.readOnly }; const ref = useRef(null); const { sortableEnabled } = useDraggableItems( editor, { pointer: node.pointer, disabled: options.disabled, readOnly: options.readOnly, sortable: options.sortable }, ref ); const withActions = options.readOnly !== true && options.showItemControls !== false; const insertOptions = editor.getArrayAddOptions(node); const addAction = insertOptions.length === 1 ? ( { const insertOptions = editor.getArrayAddOptions(node); if (insertOptions?.[0]?.schema) { editor.appendItem(node, insertOptions[0].schema); } }} /> ) : ( ({ icon: 'add', color: 'blue', label: snode.schema.title, onClick: () => editor.appendItem(node, snode.schema) }))} /> ); const [contentOpened, contentToggle] = useDisclosure(!(options.collapsed ?? false)); const leftSection = options.collapsed != null && ( contentToggle.toggle()} /> ); const { selected } = useContext(SelectionContext); if (options.canAddItem == null) { throw new Error('canAddItem is null'); } const canAddItem = options.canAddItem!; const widgetMenuItems = getArrayHeaderMenu(editor, node, options, insertOptions, jsonModal, canAddItem); // const rightSection = options.showHeaderMenu !== false const widgetHeader = WidgetParentHeader.isEmpty(options, widgetMenuItems) ? undefined : ( ); return ( {node.children .filter((child) => !child.options.hidden) .map((child) => ( {sortableEnabled && DRAG_HANDLE_COLUMN} ))}
{contentOpened && canAddItem && options.readOnly !== true && ( {options.showInlineAddAction !== false && addAction} )}
); }); /** * @returns list of array actions to be placed in menu */ function getArrayHeaderMenu( editor: Editor, node: ArrayNode, options: ArrayOptions, insertOptions: SchemaNode[], jsonModal: { open: () => void }, isAddEnabled: boolean ) { const widgetMenuItems: WidgetMenuItems = []; if (options.showEditJsonAction === true) { widgetMenuItems.push({ icon: 'edit', onClick: jsonModal.open, label: 'Edit Json' }); } if (insertOptions.length === 1) { if (widgetMenuItems.length > 0) { widgetMenuItems.push('-'); } widgetMenuItems.push({ icon: 'add', color: 'blue', disabled: !isAddEnabled, onClick: () => editor.appendItem(node, insertOptions[0]?.schema), label: 'Add Item' }); } if (insertOptions.length > 1) { const items = insertOptions.map((snode) => ({ disabled: !isAddEnabled, icon: 'add', color: 'blue', onClick: () => editor.appendItem(node, snode.schema), label: snode.schema.title ?? '' })); if (widgetMenuItems.length > 0) { widgetMenuItems.push('-'); } widgetMenuItems.push(...items); } if (Array.isArray(options.widgetMenuItems) && options.widgetMenuItems.length > 0) { if (widgetMenuItems.length > 0) { widgetMenuItems.push('-'); } widgetMenuItems.push(...options.widgetMenuItems); } return widgetMenuItems; } function getArrayItemMenu(editor: Editor, parentNode: ArrayNode, child: JsonNode, options: ArrayOptions) { const schemaNode = parentNode.schemaNode; const isInfixedArray = Array.isArray(schemaNode.prefixItems) && schemaNode.prefixItems.length > parseInt(child.property); if (isInfixedArray) { return []; } if (options.canRemoveItem == null) { throw new Error('canRemoveItems is null'); } const canRemoveItems = options.canRemoveItem; const menuItems: WidgetMenuItems = [ { label: 'move up', icon: 'keyboard_arrow_up', closeMenuOnClick: false, disabled: options.readOnly || options.disabled || child.property === '0', onClick: () => editor.moveItem(child.pointer, parseInt(child.property) - 1) }, { label: 'move down', icon: 'keyboard_arrow_down', closeMenuOnClick: false, disabled: options.readOnly || options.disabled || child.property === `${parentNode.children.length - 1}`, onClick: () => editor.moveItem(child.pointer, parseInt(child.property) + 1) }, { label: 'delete item', icon: 'delete', color: 'red', disabled: !canRemoveItems || options.readOnly || options.disabled, onClick: () => editor.removeValue(child.pointer) } ]; return menuItems; } export const ArrayWidgetPlugin: WidgetPlugin = { id: 'array-widget', use: (node) => node.type === 'array', Widget: ArrayWidget };