import { Button, Collapse, DividerProps, Flex, Group, Modal, Stack, TitleOrder, TitleProps } from '@mantine/core'; import { DefaultNodeOptions, ObjectNode, Widget, WidgetField, WidgetPlugin, JsonNode, widget, Editor } from '@sagold/react-json-editor'; import { Icon } from '../../components/icon/Icon'; import { WidgetInputWrapper } from '../../components/widgetinputwrapper/WidgetInputWrapper'; import { useDisclosure } from '@mantine/hooks'; import { WidgetMenuItems } from '../../components/widgetmenu/WidgetMenu'; import { ArrayOptions } from '../arraywidget/ArrayWidget'; import { WidgetParentHeader } from '../../components/widgetheader/WidgetHeader'; import classNames from 'classnames'; import { ActionButton } from '../../components/actionbutton/ActionButton'; import { useSelect } from '../../features/selection'; export type ObjectOptions = 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 */ 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; /** if true will add a separator line to the header */ showTitleDivider?: boolean; selectable?: boolean; /** placement of title within the separator */ dividerProps?: Pick; /** Mantine Title Props */ titleProps?: TitleProps; /** hide all children */ hideChildren?: boolean; /** if false, will hide title. will hide complete title-header if no menu-actions are available */ showHeader?: boolean; /** @internal option for menu action items */ widgetMenuItems?: WidgetMenuItems; /** @internal */ widget?: string; }; export const ObjectWidget = widget>(({ node, options, editor }) => { 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 [contentOpened, contentToggle] = useDisclosure(!(options.collapsed ?? false)); const leftSection = options.collapsed != null && node.children.length > 0 && ( contentToggle.toggle()} /> ); const widgetMenuItems = getHeaderMenu(editor, node, options, jsonModal); const widgetHeader = WidgetParentHeader.isEmpty(options, widgetMenuItems) ? undefined : ( ); // evaluate menu actions const withHeaderMenu = options.readOnly !== true && widgetMenuItems.length > 0 && options.showHeaderMenu !== false; const withOptionalPropertiesInline = options.showInlineAddAction ?? (!withHeaderMenu && options.readOnly !== true && node.missingProperties.length > 0); const [selected, onSelect] = useSelect(node.pointer, options.selectable); return ( {node.children .filter((child) => !child.options.hidden && !options.hideChildren) .map((child) => ( ))} {withOptionalPropertiesInline && ( {node.missingProperties.map((name) => ( ))} )} ); }); function getHeaderMenu(editor: Editor, node: ObjectNode, options: ArrayOptions, jsonModal: { open: () => void }) { const widgetMenuItems: WidgetMenuItems = []; if (options.showEditJsonAction == true) { widgetMenuItems.push({ icon: 'edit', onClick: jsonModal.open, label: 'Edit Json' }); } if (editor.optionalProperties && node.optionalProperties.length > 0) { const actions = node.optionalProperties.map((property) => { const isMissing = node.missingProperties.includes(property); return { disabled: options.disabled || options.readOnly, icon: isMissing ? 'add' : 'delete', label: property, color: isMissing ? 'blue' : 'red', onClick: () => { if (isMissing) { editor.addValue(`${node.pointer}/${property}`); } else { editor.removeValue(`${node.pointer}/${property}`); } } }; }); if (widgetMenuItems.length > 0 && actions.length > 0) { widgetMenuItems.push('-'); } widgetMenuItems.push(...actions); } if (Array.isArray(options.widgetMenuItems)) { if (widgetMenuItems.length > 0) { widgetMenuItems.push('-'); } widgetMenuItems.push(...options.widgetMenuItems); } return widgetMenuItems; } type ObjectPropertyProps = { editor: Editor; node: JsonNode; options: Record; optionalProperties: string[]; showItemControls?: boolean; }; function ObjectProperty({ editor, node, options, optionalProperties, showItemControls = true }: ObjectPropertyProps) { return (
editor.removeValue(node.pointer) } ] : [] }} />
); } export const ObjectWidgetPlugin: WidgetPlugin = { id: 'object-widget', use: (node) => node.schema.type === 'object', Widget: ObjectWidget };