import React, { useState, useCallback } from 'react'; interface LayerItem { id: string; name: string; type: string; visible: boolean; locked: boolean; isGroup: boolean; children?: LayerItem[]; } interface LayerPanelProps { layers: LayerItem[]; selectedIds: string[]; onSelect: (id: string) => void; onToggleVisible: (id: string) => void; onToggleLock: (id: string) => void; onReorder: (fromIndex: number, toIndex: number) => void; onDelete: (id: string) => void; onRename: (id: string, name: string) => void; onGroup: () => void; onUngroup: () => void; hasSelection: boolean; hasGroupSelection: boolean; } export default function LayerPanel({ layers, selectedIds, onSelect, onToggleVisible, onToggleLock, onReorder, onDelete, onRename, onGroup, onUngroup, hasSelection, hasGroupSelection, }: LayerPanelProps) { const [collapsed, setCollapsed] = useState(false); const [dragIdx, setDragIdx] = useState(null); const [editingId, setEditingId] = useState(null); const [editValue, setEditValue] = useState(''); const [expandedGroups, setExpandedGroups] = useState>(new Set()); const onDragStart = useCallback((idx: number) => setDragIdx(idx), []); const onDragOver = useCallback((e: React.DragEvent) => e.preventDefault(), []); const onDrop = useCallback((targetIdx: number) => { if (dragIdx !== null && dragIdx !== targetIdx) { onReorder(dragIdx, targetIdx); } setDragIdx(null); }, [dragIdx, onReorder]); const toggleGroupExpand = useCallback((id: string) => { setExpandedGroups((prev) => { const next = new Set(prev); if (next.has(id)) next.delete(id); else next.add(id); return next; }); }, []); const startRename = useCallback((id: string, currentName: string) => { setEditingId(id); setEditValue(currentName); }, []); const commitRename = useCallback(() => { if (editingId && editValue.trim()) { onRename(editingId, editValue.trim()); } setEditingId(null); setEditValue(''); }, [editingId, editValue, onRename]); if (collapsed) { return (
); } function getTypeIcon(type: string, isGroup: boolean) { if (isGroup) return '\u25B8'; // triangle if (type === 'image') return '\u{1F5BC}'; if (type === 'text' || type === 'i-text') return 'T'; if (type === 'path') return '\u270E'; if (type === 'video') return '\u25B6'; return '\u25C7'; } function renderLayer(layer: LayerItem, realIdx: number, depth = 0) { const selected = selectedIds.includes(layer.id); const isExpanded = expandedGroups.has(layer.id); const isEditing = editingId === layer.id; return (
onDragStart(realIdx)} onDragOver={onDragOver} onDrop={() => onDrop(realIdx)} onClick={() => onSelect(layer.id)} style={{ display: 'flex', alignItems: 'center', gap: '3px', padding: `3px 6px 3px ${6 + depth * 12}px`, cursor: 'pointer', background: selected ? 'rgba(74,158,255,0.1)' : dragIdx === realIdx ? '#1a1a1a' : 'transparent', borderBottom: '1px solid #151515', borderLeft: selected ? '2px solid #4a9eff' : '2px solid transparent', opacity: layer.visible ? 1 : 0.35, transition: 'background 0.1s', }} onMouseEnter={(e) => { if (!selected) (e.currentTarget as HTMLDivElement).style.background = '#161616'; }} onMouseLeave={(e) => { if (!selected) (e.currentTarget as HTMLDivElement).style.background = 'transparent'; }} > {/* Group expand/collapse */} {layer.isGroup ? ( ) : ( )} {/* Visibility toggle */} {/* Lock toggle */} {/* Type icon */} {getTypeIcon(layer.type, layer.isGroup)} {/* Name (editable) */} {isEditing ? ( setEditValue(e.target.value)} onBlur={commitRename} onKeyDown={(e) => { if (e.key === 'Enter') commitRename(); if (e.key === 'Escape') { setEditingId(null); setEditValue(''); } }} onClick={(e) => e.stopPropagation()} style={{ flex: 1, fontSize: '11px', color: '#ddd', background: '#0d0d0d', border: '1px solid #333', borderRadius: '3px', padding: '1px 4px', outline: 'none', minWidth: 0, }} /> ) : ( { e.stopPropagation(); startRename(layer.id, layer.name); }} style={{ flex: 1, fontSize: '11px', color: selected ? '#ccc' : '#888', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', cursor: 'text', }} title="Double-click to rename" > {layer.name} )} {/* Delete */}
{/* Group children */} {layer.isGroup && isExpanded && layer.children && ( layer.children.map((child, ci) => renderLayer(child, realIdx, depth + 1)) )}
); } return (
{/* Header */}
Layers
G+ G- setCollapsed(true)}>
{/* Layer list — reversed so top layer is first */}
{[...layers].reverse().map((layer, i) => { const realIdx = layers.length - 1 - i; return renderLayer(layer, realIdx); })} {layers.length === 0 && (
No objects
)}
); } function SmallBtn({ onClick, title, disabled, children }: { onClick: () => void; title: string; disabled?: boolean; children: React.ReactNode; }) { return ( ); }