import React from 'react' import * as UiKit from '@/components/ui' import PlatformViewHost, { type PlatformViewName, } from '@/components/componentEditor/PlatformViewHost' import { useLocalization } from '@/utils/hooks/useLocalization' 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' const getTableDimension = (value: unknown, fallback: number) => Math.min(20, Math.max(1, Math.floor(Number(value) || fallback))) const resolveStaticLanguageKeys = ( value: unknown, translate: (key: string) => string, ): unknown => { if (typeof value === 'string') return value.startsWith('::') ? translate(value) : value if (Array.isArray(value)) return value.map((item) => resolveStaticLanguageKeys(item, translate)) if (value && typeof value === 'object') { return Object.fromEntries( Object.entries(value).map(([key, item]) => [ key, resolveStaticLanguageKeys(item, translate), ]), ) } return value } interface VisualCanvasProps { nodes: DesignerNode[] selectedId: string | null interactive?: boolean onSelect?: (id: string) => void onDropComponent?: (definitionName: string, parentId: string | null, slot?: string) => void onMove?: (id: string, direction: -1 | 1) => void onReorder?: (sourceId: string, targetId: string, placement: 'before' | 'after') => void onDuplicate?: (id: string) => void onDelete?: (id: string) => void onNodePropChange?: (id: string, propertyName: string, value: unknown) => 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', 'children', 'name', 'title', 'text', 'description'].find( (key) => record[key] !== undefined, ) const valueKey = ['value', 'eventKey', '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, onNodePropChange?: (id: string, propertyName: string, value: unknown) => void, translate: (key: string) => string = (key) => key, ) => { const props: Record = {} Object.entries(node.props).forEach(([key, value]) => { if (key === 'children' || key === 'html' || (value === '' && key !== 'value')) return if (key.startsWith('on') && typeof value === 'string') return props[key] = resolveStaticLanguageKeys(value, translate) }) 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 ( ['Select', 'AutoComplete', 'Menu', 'Dropdown', 'Pagination', 'Radio.Group'].includes(node.type) ) { const collectionProperty = ['Menu', 'Dropdown', 'Pagination', 'Radio.Group'].includes(node.type) ? '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 === 'Pagination') { const pages = props.items as Array> delete props.items if (pages.length) props.total = pages.length } if (node.type === 'Select' && 'value' in props) { const options = props.options as Array> const selectedValue = props.value props.value = props.isMulti ? options.filter( (option) => Array.isArray(selectedValue) && selectedValue.includes(option.value), ) : options.find((option) => option.value === selectedValue) || null } } const chainHandler = ( eventName: string, update: (...args: unknown[]) => void, ) => { const storedHandler = typeof props[eventName] === 'function' ? (props[eventName] as (...args: unknown[]) => void) : undefined props[eventName] = (...args: unknown[]) => { update(...args) storedHandler?.(...args) } } const updateProp = (propertyName: string, value: unknown) => onNodePropChange?.(node.id, propertyName, value) const eventValue = (value: unknown) => value && typeof value === 'object' && 'target' in value ? (value as { target?: { value?: unknown } }).target?.value : value if (node.type === 'Select') { chainHandler('onChange', (selected) => updateProp( 'value', Array.isArray(selected) ? selected.map((option) => option && typeof option === 'object' && 'value' in option ? (option as { value: unknown }).value : option, ) : selected && typeof selected === 'object' && 'value' in selected ? (selected as { value: unknown }).value : null, ), ) } else if (node.type === 'AutoComplete') { chainHandler('onInputChange', (value) => updateProp('value', value ?? '')) } else if (node.type === 'Menu') { chainHandler('onSelect', (value) => updateProp('defaultActiveKeys', [String(value ?? '')])) } else if (node.type === 'Dropdown') { chainHandler('onSelect', (value) => updateProp('activeKey', String(value ?? ''))) } else if (node.type === 'Radio.Group') { chainHandler('onChange', (value) => updateProp('value', value)) } else if (node.type === 'Pagination') { chainHandler('onChange', (value) => updateProp('currentPage', Number(value) || 1)) } else if (node.type === 'Steps') { chainHandler('onChange', (value) => updateProp('current', Number(value) || 0)) } else if (node.type === 'ImageViewer') { chainHandler('onIndexChange', (value) => updateProp('activeIndex', Number(value) || 0)) } else if (node.type === 'Upload') { chainHandler('onChange', (_files, fileList) => updateProp('fileList', Array.isArray(fileList) ? fileList : []), ) } else if (node.type === 'MenuItem') { chainHandler('onSelect', () => updateProp('isActive', true)) } else if ('checked' in props) { chainHandler('onChange', (value, originalEvent) => { const checked = originalEvent && typeof originalEvent === 'object' && 'target' in originalEvent ? Boolean( (originalEvent as { target?: { checked?: unknown } }).target?.checked, ) : value && typeof value === 'object' && 'target' in value ? Boolean((value as { target?: { checked?: unknown } }).target?.checked) : Boolean(value) updateProp('checked', checked) }) } else if ('value' in props) { chainHandler('onChange', (value) => updateProp('value', eventValue(value))) } props.key = node.id 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, translate: (key: string) => string, onNodePropChange?: (id: string, propertyName: string, value: unknown) => void, renderCustomComponent?: (name: string, props?: Record) => React.ReactNode, ) => { if (node.type === 'Spacer') { return (