import React from 'react' import * as UiKit from '@/components/ui' import PlatformViewHost, { type PlatformViewName, } from '@/components/componentEditor/PlatformViewHost' import { FaArrowDown, FaArrowUp, FaClone, FaGripVertical, FaTrash } from 'react-icons/fa' import { getDesignerValueByPath, normalizeDesignerKeyList, type DesignerBinding, type DesignerNode, } from './types' export const DESIGNER_DRAG_TYPE = 'application/x-sozsoft-designer' interface VisualCanvasProps { nodes: DesignerNode[] selectedId: string | null background: string interactive?: boolean onSelect?: (id: string) => void onDropComponent?: (definitionName: string, parentId: string | null) => void onMove?: (id: string, direction: -1 | 1) => void onDuplicate?: (id: string) => void onDelete?: (id: string) => void renderCustomComponent?: (name: string, props?: Record) => React.ReactNode dataValues?: Record } class PreviewBoundary extends React.Component< { name: string; resetKey: string; children: React.ReactNode }, { failed: boolean } > { state = { failed: false } static getDerivedStateFromError() { return { failed: true } } componentDidUpdate(previousProps: Readonly<{ resetKey: string }>) { if (this.state.failed && previousProps.resetKey !== this.props.resetKey) { this.setState({ failed: false }) } } render() { if (this.state.failed) { return (
{this.props.name} önizlemesi için ek veri veya alt bileşen gerekiyor.
) } return this.props.children } } const resolveUiComponent = (name: string): React.ElementType | null => { const parts = name.split('.') let component: unknown = (UiKit as Record)[parts[0]] for (const part of parts.slice(1)) { if ((typeof component !== 'object' && typeof component !== 'function') || !component) { return null } component = (component as Record)[part] } return component ? (component as React.ElementType) : null } const executeEvent = (script: string, event: unknown, node: DesignerNode) => { if (!script.trim()) return try { const run = new Function('event', 'component', 'props', script) run(event, node, node.props) } catch (error) { console.error(`Designer event error (${node.type}):`, error) } } const getBindingValue = ( binding: DesignerBinding, dataValues: Record, currentItem?: unknown, ) => { const path = binding.path.trim() if (currentItem !== undefined && (path === '$item' || path.startsWith('$item.'))) { return getDesignerValueByPath(currentItem, path === '$item' ? '' : path.slice(6)) } return getDesignerValueByPath(dataValues[binding.sourceId], path) } const toSelectOptions = ( value: unknown, labelPath = '', valuePath = '', ): Array> => { if (!Array.isArray(value)) return [] return value.map((item, index) => { if (item === null || typeof item !== 'object' || Array.isArray(item)) { return { label: String(item ?? ''), value: item ?? index } } const record = item as Record const primitiveKeys = Object.keys(record).filter( (key) => record[key] === null || ['string', 'number', 'boolean'].includes(typeof record[key]), ) const labelKey = ['label', 'name', 'title', 'text', 'description'].find( (key) => record[key] !== undefined, ) const valueKey = ['value', 'id', 'code', 'key'].find((key) => record[key] !== undefined) const labelValue = labelPath ? getDesignerValueByPath(record, labelPath) : labelKey ? record[labelKey] : record[primitiveKeys[0]] const optionValue = valuePath ? getDesignerValueByPath(record, valuePath) : valueKey ? record[valueKey] : record[primitiveKeys[1] || primitiveKeys[0]] return { ...record, label: String(labelValue ?? `Seçenek ${index + 1}`), value: optionValue ?? index, } }) } const getPreviewProps = ( node: DesignerNode, dataValues: Record, currentItem?: unknown, ) => { const props: Record = {} Object.entries(node.props).forEach(([key, value]) => { if (key === 'children' || key === 'html' || value === '') return if (key.startsWith('on') && typeof value === 'string') return props[key] = value }) Object.entries(node.events).forEach(([name, script]) => { if (!script.trim()) return props[name] = (...args: unknown[]) => { const event = node.type === 'Checkbox' && name === 'onChange' ? { checked: Boolean(args[0]), originalEvent: args[1], target: args[1] && typeof args[1] === 'object' && 'target' in args[1] ? (args[1] as { target: unknown }).target : undefined, } : args[0] executeEvent(script, event, node) } }) Object.entries(node.bindings || {}).forEach(([propertyName, binding]) => { if (propertyName !== 'children' && binding.sourceId) { props[propertyName] = getBindingValue(binding, dataValues, currentItem) } }) if (node.type === 'Select' || node.type === 'AutoComplete' || node.type === 'Menu') { const collectionProperty = node.type === 'Menu' ? 'items' : 'options' if (node.type === 'Select') { const legacyAliases: Record = { clearable: 'isClearable', disabled: 'isDisabled', multiple: 'isMulti', searchable: 'isSearchable', } Object.entries(legacyAliases).forEach(([legacyName, runtimeName]) => { if (props[runtimeName] === undefined && props[legacyName] !== undefined) { props[runtimeName] = props[legacyName] } delete props[legacyName] }) } const optionsBinding = node.bindings?.[collectionProperty] props[collectionProperty] = toSelectOptions( props[collectionProperty], optionsBinding?.labelPath, optionsBinding?.valuePath, ) if (node.type === 'Select' && 'value' in props) { const options = props.options as Array> props.defaultValue = options.find((option) => option.value === props.value) || null delete props.value } } if (node.type === 'Input' && 'value' in props) { props.defaultValue = props.value delete props.value } if (node.type === 'Checkbox' && 'checked' in props) { props.defaultChecked = Boolean(props.checked) delete props.checked } if ('value' in props && !props.onChange) props.onChange = () => undefined if ('checked' in props && !props.onChange) props.onChange = () => undefined props.key = JSON.stringify([node.props, node.bindings]) return props } const PlatformPlaceholder = ({ node }: { node: DesignerNode }) => (
S
{node.type.replace(/View$/, '')}
{String(node.props.listFormCode || 'Property panelinden List Form Code seçin')}
Platform görünümü
) const PLATFORM_VIEW_NAMES: Record = { ListView: 'List', DataGridView: 'Grid', TreeView: 'Tree', GanttView: 'GanttView', TodoBoard: 'TodoBoard', CardView: 'CardView', SchedulerView: 'SchedulerView', PivotView: 'Pivot', ChartView: 'Chart', } const GridColumnHeaders = ({ columns }: { columns: string[] }) => (
{columns.length ? (
{columns.map((column) => (
{column}
))}
) : (
Data panelinden gösterilecek sütunları seçin.
)}
) const getGridCellText = (value: unknown) => typeof value === 'object' && value !== null ? JSON.stringify(value) : String(value ?? '') || '—' const GridDataTablePreview = ({ borderlessRow = false, compact = false, hoverable = true, items, overflow = true, selectedColumns, }: { borderlessRow?: boolean compact?: boolean hoverable?: boolean items: unknown[] overflow?: boolean selectedColumns?: string[] }) => { const firstObject = items.find( (item): item is Record => Boolean(item) && typeof item === 'object' && !Array.isArray(item), ) const columns = selectedColumns ?? (firstObject ? Object.keys(firstObject) : ['value']) if (!columns.length) { return (
Preview için en az bir sütun seçin.
) } return (
{columns.map((column) => ( ))} {items.slice(0, 100).map((item, rowIndex) => ( {columns.map((column) => { const value = column === 'value' ? item : getDesignerValueByPath(item, column) const text = getGridCellText(value) return ( ) })} ))}
{column === 'value' ? 'Value' : column}
{text}
) } const renderElement = ( node: DesignerNode, children: React.ReactNode, dataValues: Record, currentItem: unknown, interactive: boolean, renderCustomComponent?: (name: string, props?: Record) => React.ReactNode, ) => { if (node.type === 'Spacer') { return