diff --git a/src/Adminive/Backend_Fields.tsx b/src/Adminive/Backend_Fields.tsx new file mode 100644 index 0000000..ecceacb --- /dev/null +++ b/src/Adminive/Backend_Fields.tsx @@ -0,0 +1,122 @@ +import React from "react"; +import {Checkbox, ColorPicker, Input, InputNumber, Select, Space} from "antd"; + +export type Backend_Field_Descriptor = { + name: string + value_type?: string + readable?: boolean + editable?: boolean + children?: Backend_Field_Descriptor[] + presentation?: { + label?: string + control?: string + options?: {label: string, value: unknown}[] + } +} + +export type Backend_Form_Section = { + title?: string + object_path?: string + descriptor_path?: string + fields: string[] +} + +export type Backend_Disabled_Rule = { + field: string + disabled_when: { + field: string + equals: unknown + } +} + +type Json_Object = Record + +export function get_path(root: Json_Object, path = ""): any { + if (!path) return root; + return path.split(".").reduce((value: any, key) => value?.[key], root); +} + +export function fields_at_path(fields: Backend_Field_Descriptor[], path = ""): Backend_Field_Descriptor[] { + if (!path) return fields; + let current = fields; + for (const name of path.split(".")) { + const field = current.find(item => item.name === name); + if (!field) return []; + current = field.children || []; + } + return current; +} + +function field_disabled(root: Json_Object, field: Backend_Field_Descriptor, rules: Backend_Disabled_Rule[]): boolean { + if (field.editable === false) return true; + const rule = rules.find(item => item.field === field.name); + return Boolean(rule && get_path(root, rule.disabled_when.field) === rule.disabled_when.equals); +} + +function Backend_Field_Control(props: { + descriptor: Backend_Field_Descriptor + value: any + disabled: boolean + onChange: (value: any) => void +}) { + const {descriptor, value, disabled, onChange} = props; + const control = descriptor.presentation?.control || descriptor.value_type || "text"; + const label = descriptor.presentation?.label || descriptor.name; + if (control === "boolean" || descriptor.value_type === "boolean") { + return onChange(event.target.checked)}>{label}; + } + if (control === "color") { + return {label} onChange(color.toHexString())}/>; + } + if (control === "select") { + return onChange(event.target.value)} style={{width: 420, maxWidth: "100%"}}/>; +} + +export function Backend_Fields(props: { + root: Json_Object + descriptor_fields: Backend_Field_Descriptor[] + section: Backend_Form_Section + rules?: Backend_Disabled_Rule[] + onChange?: (path: string, value: unknown, descriptor: Backend_Field_Descriptor) => void +}) { + const object = get_path(props.root, props.section.object_path) as Json_Object | undefined; + const descriptor_path = props.section.descriptor_path ?? props.section.object_path; + const available_fields = fields_at_path(props.descriptor_fields, descriptor_path); + const selected = props.section.fields + .map(name => available_fields.find(field => field.name === name)) + .filter((field): field is Backend_Field_Descriptor => Boolean(field && field.readable !== false)); + const update = (field: Backend_Field_Descriptor, value: unknown) => { + if (!object) return; + object[field.name] = value; + const path = [props.section.object_path, field.name].filter(Boolean).join("."); + props.onChange?.(path, value, field); + }; + const booleans = selected.filter(field => (field.presentation?.control || field.value_type) === "boolean"); + const controls = selected.filter(field => !booleans.includes(field)); + return ( + + {booleans.length > 0 && + {booleans.map(field => update(field, value)}/>) } + } + {controls.map(field => update(field, value)}/>) } + + ); +}