import React, { useEffect, useState, useContext, useMemo, useRef } from 'react'; import { Table, Tabs, Button, message, Input, Checkbox, Modal, AutoComplete, Tooltip, Select } from 'antd'; import { ReloadOutlined, SaveOutlined, PlusOutlined, DeleteOutlined, MenuOutlined, FileTextOutlined } from '@ant-design/icons'; import { DndContext, closestCenter, KeyboardSensor, PointerSensor, useSensor, useSensors, DragOverlay } from '@dnd-kit/core'; import { arrayMove, SortableContext, sortableKeyboardCoordinates, verticalListSortingStrategy, useSortable } from '@dnd-kit/sortable'; import { CSS } from '@dnd-kit/utilities'; import { Resizable } from 'react-resizable'; import { TabData, ColumnDefinition, IndexDefinition, ForeignKeyDefinition, TriggerDefinition } from '../types'; import { useStore } from '../store'; import { DBGetColumns, DBGetIndexes, DBQuery, DBGetForeignKeys, DBGetTriggers, DBShowCreateTable } from '../../wailsjs/go/app/App'; // Need styles for react-resizable import 'react-resizable/css/styles.css'; interface EditableColumn extends ColumnDefinition { _key: string; isNew?: boolean; isAutoIncrement?: boolean; // Virtual field for UI } const COMMON_TYPES = [ { value: 'int' }, { value: 'varchar(255)' }, { value: 'text' }, { value: 'datetime' }, { value: 'tinyint(1)' }, { value: 'decimal(10,2)' }, { value: 'bigint' }, { value: 'json' }, ]; const COMMON_DEFAULTS = [ { value: 'CURRENT_TIMESTAMP' }, { value: 'NULL' }, { value: '0' }, { value: "''" }, ]; const CHARSETS = [ { label: 'utf8mb4 (Recommended)', value: 'utf8mb4' }, { label: 'utf8', value: 'utf8' }, { label: 'latin1', value: 'latin1' }, { label: 'ascii', value: 'ascii' }, ]; const COLLATIONS = { 'utf8mb4': [ { label: 'utf8mb4_unicode_ci (Default)', value: 'utf8mb4_unicode_ci' }, { label: 'utf8mb4_general_ci', value: 'utf8mb4_general_ci' }, { label: 'utf8mb4_bin', value: 'utf8mb4_bin' }, { label: 'utf8mb4_0900_ai_ci', value: 'utf8mb4_0900_ai_ci' }, ], 'utf8': [ { label: 'utf8_unicode_ci', value: 'utf8_unicode_ci' }, { label: 'utf8_general_ci', value: 'utf8_general_ci' }, { label: 'utf8_bin', value: 'utf8_bin' }, ] }; // --- Resizable Header Component --- const ResizableTitle = (props: any) => { const { onResize, width, ...restProps } = props; if (!width) { return ; } return ( { e.stopPropagation(); e.preventDefault(); }} onMouseDown={(e) => { e.stopPropagation(); e.preventDefault(); // Prevent text selection and focus hijacking }} style={{ position: 'absolute', right: -5, bottom: 0, top: 0, width: 10, cursor: 'col-resize', zIndex: 10 }} /> } onResize={onResize} draggableOpts={{ enableUserSelectHack: true }} > ); }; // --- Sortable Row Component --- interface RowProps extends React.HTMLAttributes { 'data-row-key': string; } const SortableRow = ({ children, ...props }: RowProps) => { const { attributes, listeners, setNodeRef, transform, transition, isDragging, } = useSortable({ id: props['data-row-key'], }); const style: React.CSSProperties = { ...props.style, transform: CSS.Transform.toString(transform), transition, cursor: 'move', ...(isDragging ? { position: 'relative', zIndex: 9999 } : {}), }; return ( {React.Children.map(children, child => { if ((child as React.ReactElement).key === 'sort') { return React.cloneElement(child as React.ReactElement, { children: ( ), }); } return child; })} ); }; const TableDesigner: React.FC<{ tab: TabData }> = ({ tab }) => { const isNewTable = !tab.tableName; const [columns, setColumns] = useState([]); const [originalColumns, setOriginalColumns] = useState([]); const [indexes, setIndexes] = useState([]); const [fks, setFks] = useState([]); const [triggers, setTriggers] = useState([]); const [ddl, setDdl] = useState(''); // New Table State const [newTableName, setNewTableName] = useState(''); const [charset, setCharset] = useState('utf8mb4'); const [collation, setCollation] = useState('utf8mb4_unicode_ci'); const [loading, setLoading] = useState(false); const [previewSql, setPreviewSql] = useState(''); const [isPreviewOpen, setIsPreviewOpen] = useState(false); const [activeKey, setActiveKey] = useState(tab.initialTab || "columns"); const connections = useStore(state => state.connections); const readOnly = !!tab.readOnly; const [tableHeight, setTableHeight] = useState(500); const containerRef = useRef(null); useEffect(() => { if (!containerRef.current) return; const resizeObserver = new ResizeObserver(entries => { for (let entry of entries) { const h = Math.max(200, entry.contentRect.height - 40); setTableHeight(h); } }); resizeObserver.observe(containerRef.current); return () => resizeObserver.disconnect(); }, [activeKey]); // Re-attach when tab switches // --- Resizable Columns State --- const [tableColumns, setTableColumns] = useState([]); const sensors = useSensors( useSensor(PointerSensor), useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates, }) ); useEffect(() => { if (tab.initialTab) { setActiveKey(tab.initialTab); } }, [tab.initialTab]); // Initial Columns Definition useEffect(() => { const initialCols = [ ...(readOnly ? [] : [{ key: 'sort', width: 40, render: () => , }]), { title: '名', dataIndex: 'name', key: 'name', width: 180, render: (text: string, record: EditableColumn) => readOnly ? text : ( handleColumnChange(record._key, 'name', e.target.value)} variant="borderless" /> ) }, { title: '类型', dataIndex: 'type', key: 'type', width: 150, render: (text: string, record: EditableColumn) => readOnly ? text : ( handleColumnChange(record._key, 'type', val)} style={{ width: '100%' }} variant="borderless" /> ) }, { title: '主键', dataIndex: 'key', key: 'key', width: 60, align: 'center', render: (text: string, record: EditableColumn) => ( handleColumnChange(record._key, 'key', e.target.checked ? 'PRI' : '')} /> ) }, { title: '自增', dataIndex: 'isAutoIncrement', key: 'isAutoIncrement', width: 60, align: 'center', render: (val: boolean, record: EditableColumn) => ( handleColumnChange(record._key, 'isAutoIncrement', e.target.checked)} /> ) }, { title: '不是 Null', dataIndex: 'nullable', key: 'nullable', width: 80, align: 'center', render: (text: string, record: EditableColumn) => ( handleColumnChange(record._key, 'nullable', e.target.checked ? 'NO' : 'YES')} /> ) }, { title: '默认', dataIndex: 'default', key: 'default', width: 180, // Increased default width render: (text: string, record: EditableColumn) => readOnly ? text : ( handleColumnChange(record._key, 'default', val)} style={{ width: '100%' }} variant="borderless" placeholder="NULL" /> ) }, { title: '注释', dataIndex: 'comment', key: 'comment', width: 200, render: (text: string, record: EditableColumn) => readOnly ? text : ( handleColumnChange(record._key, 'comment', e.target.value)} variant="borderless" /> ) }, ...(readOnly ? [] : [{ title: '操作', key: 'action', width: 60, render: (_: any, record: EditableColumn) => (