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
};