import { useState } from 'react';
import { ChevronDown, ChevronRight, Plus } from 'lucide-react';
import { Button, useToast } from '@vertesia/ui/core';
import { useUITranslation } from '../../../i18n/index.js';
import { ManagedSchema, SchemaNode } from '../ManagedSchema.js';
import { TypeNames } from '../type-signature.js';
import { Editable } from './Editable.js';
import { EditableSchemaProperty, getEditableSchemaProperty } from './EditableSchemaProperty.js';
import { PropertyEditor } from './PropertyEditor.js';
import { PropertyViewer } from './PropertyViewer.js';
// do not exit edit mode when user is clicking inside the type suggestion popup
function skipClickOutside(e: MouseEvent) {
const target = e.target as HTMLElement;
return !!(target.closest && target.closest('.schema-type-suggest-popup'));
};
interface SchemaTreeProps {
schema: ManagedSchema;
readonly?: boolean;
}
export function SchemaEditor({ schema, readonly = false }: SchemaTreeProps) {
return (
{
schema.children.map(prop => {
return renderProperty(prop, readonly)
})
}
{
!readonly ? : null
}
);
}
function renderProperty(node: SchemaNode, readonly: boolean) {
return (
node.isParent ?
:
);
}
interface SimpleItemProps {
node: SchemaNode;
readonly: boolean
}
function SimpleItem({ node, readonly }: SimpleItemProps) {
return (
);
}
interface ParentItemProps {
property: SchemaNode;
readonly: boolean;
}
function ParentItem({ property, readonly }: ParentItemProps) {
const [isOpen, setOpen] = useState(true);
const Icon = isOpen ? ChevronDown : ChevronRight;
return (
{
isOpen &&
{
(property.children || []).map(prop => renderProperty(prop, readonly))
}
{
!readonly ? : null
}
}
);
}
export function validatePropertyName(propertyName: string): string | undefined {
if (!propertyName) {
return 'Name is required';
}
if (/^[a-zA-Z0-9_]+[?]?$/.test(propertyName)) {
return undefined; // valid
}
return 'Only letters, numbers, underscores or question mark are allowed (a-zA-Z0-9_?)';
}
interface PropertyTitleBarProps {
property: SchemaNode;
readonly: boolean;
}
function PropertyTitleBar({ property, readonly }: PropertyTitleBarProps) {
const { t } = useUITranslation();
const toast = useToast();
const onChange = (value: EditableSchemaProperty) => {
try {
if (value.description && typeof value.description !== 'string') {
value.description = undefined;
}
const update = property.getUpdateFromNameAndTypeSignature(value.name, value.type);
if (property.update({ ...update, description: value.description })) {
property.reloadTree();
}
} catch (err: any) {
toast({
status: 'error',
title: t('widgets.schema.invalidPropertyDeclaration'),
description: err.message,
duration: 9000
})
return false;
}
return true;
}
const isNew = property.resetIsNew();
const editableProp = getEditableSchemaProperty(property);
return (
{
property.remove()
property.reloadTree();
}}
editor={PropertyEditor}
viewer={PropertyViewer}
outlineOnHover isEditing={isNew}
skipClickOutside={skipClickOutside}
readonly={readonly}
onValidate={(property) => validatePropertyName(property.name)}
/>
);
}
interface AddPropertyButtonProps {
parent: SchemaNode;
}
function AddPropertyButton({ parent }: AddPropertyButtonProps) {
const add = () => {
const name = parent.findAvailableChildName("new_property_");
const child = parent.addChild(name, { isObject: false, isArray: false, isNullable: false, name: TypeNames.string }, true);
child.isNew = true;
parent.reloadTree();
}
return (
)
}