feat: click-drag drawing, layer tree, settings modal, UI improvements
- interaction: click-drag drawing for 2-point tools (line, rect, circle, dimension, leader) - interaction: preview rendering during drag with setPreviewElement - interaction: handleDrawDown only sets phase=drawing on first point - CanvasArea: sync activeLayerId to LayerManager - CanvasArea: callback refresh useEffect prevents stale closures - App.tsx: functional setElements updates for handleElementCreated/Deleted/Modified - LayerPanel: tree structure with sub-layers (onReorder, onAddSubLayer) - TreeView: drag-and-drop reordering support - RightSidebar: onReorder, onAddSubLayer, onUpdateElement props - PropertiesPanel: onUpdateElement prop - SettingsModal: personal/password/language/theme/users/plugins/AI tabs - styles.css: extensive UI styling additions - types: updated RightSidebarProps, CanvasAreaProps, LayerPanelProps
This commit is contained in:
@@ -6,6 +6,7 @@ interface TreeViewProps {
|
||||
selectedId?: string | null;
|
||||
onSelect: (id: string) => void;
|
||||
onToggle?: (id: string) => void;
|
||||
onReorder?: (draggedId: string, targetId: string, position: 'before' | 'after' | 'inside') => void;
|
||||
renderIcon?: (node: TreeNode) => React.ReactNode;
|
||||
renderActions?: (node: TreeNode) => React.ReactNode;
|
||||
renderDetail?: (node: TreeNode) => React.ReactNode;
|
||||
@@ -17,12 +18,16 @@ const TreeView: React.FC<TreeViewProps> = ({
|
||||
selectedId,
|
||||
onSelect,
|
||||
onToggle,
|
||||
onReorder,
|
||||
renderIcon,
|
||||
renderActions,
|
||||
renderDetail,
|
||||
draggable = false,
|
||||
}) => {
|
||||
const [expandedSet, setExpandedSet] = useState<Set<string>>(new Set());
|
||||
const [draggedId, setDraggedId] = useState<string | null>(null);
|
||||
const [dragOverId, setDragOverId] = useState<string | null>(null);
|
||||
const [dragPosition, setDragPosition] = useState<'before' | 'after' | 'inside'>('before');
|
||||
|
||||
const toggleExpand = (id: string) => {
|
||||
setExpandedSet((prev) => {
|
||||
@@ -34,18 +39,75 @@ const TreeView: React.FC<TreeViewProps> = ({
|
||||
onToggle?.(id);
|
||||
};
|
||||
|
||||
const handleDragStart = (e: React.DragEvent, id: string) => {
|
||||
if (!draggable || !onReorder) return;
|
||||
setDraggedId(id);
|
||||
e.dataTransfer.effectAllowed = 'move';
|
||||
e.dataTransfer.setData('text/plain', id);
|
||||
};
|
||||
|
||||
const handleDragOver = (e: React.DragEvent, id: string) => {
|
||||
if (!draggable || !onReorder || !draggedId || draggedId === id) return;
|
||||
e.preventDefault();
|
||||
e.dataTransfer.dropEffect = 'move';
|
||||
const rect = e.currentTarget.getBoundingClientRect();
|
||||
const offset = e.clientY - rect.top;
|
||||
const third = rect.height / 3;
|
||||
if (offset < third) {
|
||||
setDragPosition('before');
|
||||
} else if (offset > third * 2) {
|
||||
setDragPosition('after');
|
||||
} else {
|
||||
setDragPosition('inside');
|
||||
}
|
||||
setDragOverId(id);
|
||||
};
|
||||
|
||||
const handleDragLeave = (_e: React.DragEvent, id: string) => {
|
||||
if (!draggable || !onReorder) return;
|
||||
if (dragOverId === id) {
|
||||
setDragOverId(null);
|
||||
}
|
||||
};
|
||||
|
||||
const handleDrop = (e: React.DragEvent, id: string) => {
|
||||
if (!draggable || !onReorder || !draggedId || draggedId === id) return;
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
onReorder(draggedId, id, dragPosition);
|
||||
setDraggedId(null);
|
||||
setDragOverId(null);
|
||||
};
|
||||
|
||||
const handleDragEnd = () => {
|
||||
setDraggedId(null);
|
||||
setDragOverId(null);
|
||||
};
|
||||
|
||||
const renderNode = (node: TreeNode, level: number): React.ReactNode => {
|
||||
const hasChildren = node.children && node.children.length > 0;
|
||||
const isExpanded = expandedSet.has(node.id) || node.expanded;
|
||||
const isSelected = selectedId === node.id;
|
||||
const isDragOver = dragOverId === node.id;
|
||||
const isDragging = draggedId === node.id;
|
||||
|
||||
let dragClass = '';
|
||||
if (isDragOver && dragPosition === 'before') dragClass = ' tree-drag-before';
|
||||
if (isDragOver && dragPosition === 'after') dragClass = ' tree-drag-after';
|
||||
if (isDragOver && dragPosition === 'inside') dragClass = ' tree-drag-inside';
|
||||
|
||||
return (
|
||||
<React.Fragment key={node.id}>
|
||||
<div
|
||||
className={`tree-node${isSelected ? ' active' : ''}`}
|
||||
className={`tree-node${isSelected ? ' active' : ''}${dragClass}${isDragging ? ' tree-dragging' : ''}`}
|
||||
style={{ paddingLeft: `${level * 16 + 8}px` }}
|
||||
onClick={() => onSelect(node.id)}
|
||||
draggable={draggable}
|
||||
onDragStart={(e) => handleDragStart(e, node.id)}
|
||||
onDragOver={(e) => handleDragOver(e, node.id)}
|
||||
onDragLeave={(e) => handleDragLeave(e, node.id)}
|
||||
onDrop={(e) => handleDrop(e, node.id)}
|
||||
onDragEnd={handleDragEnd}
|
||||
>
|
||||
{hasChildren && (
|
||||
<button
|
||||
|
||||
Reference in New Issue
Block a user