import { useMemo, useState, useCallback, type DragEvent } from 'react'; import { X, Plus, RotateCcw } from 'lucide-react'; import { Button } from '@/components/ui/button'; import { useDock } from 'officerdev'; import { useCapabilities } from 'hooks/useCapabilities'; import { CORE_DOCK_ITEMS, dockItemsFromPlugins, DEFAULT_DOCK_PATHS } from '@/Screens/Dashboard/Layout/Dock'; type DockPillProps = { label: string; path: string; color: string; visible: boolean; onAction: (path: string) => void; onDragStart: (ev: DragEvent, path: string) => void; onDropOnPill?: (draggedPath: string) => void; dropIndicator?: 'left' | 'right' | null; onDragOverPill?: (ev: DragEvent) => void; onDragLeavePill?: () => void; }; const DockPill = ({ label, path, color, visible, onAction, onDragStart, onDropOnPill, dropIndicator, onDragOverPill, onDragLeavePill, }: DockPillProps) => ( onDragStart(ev, path)} onDragOver={onDragOverPill} onDragLeave={onDragLeavePill} onDrop={ onDropOnPill ? (ev) => { ev.preventDefault(); ev.stopPropagation(); const p = ev.dataTransfer.getData('text/plain'); if (p) onDropOnPill(p); } : undefined } className={`relative inline-flex items-center gap-1 px-2.5 py-1 rounded-full text-xs font-medium cursor-grab active:cursor-grabbing select-none border border-duck-dark/15 dark:border-foreground/15 bg-background/60 text-duck-dark/80 dark:text-foreground/80 hover:bg-duck-dark/5 dark:hover:bg-foreground/5 transition-colors ${dropIndicator === 'left' ? 'ring-l-2 ring-duck-teal' : ''} ${dropIndicator === 'right' ? 'ring-r-2 ring-duck-teal' : ''}`} > {dropIndicator === 'left' && } {label} {dropIndicator === 'right' && } ); type DropZoneProps = { label: string; children: React.ReactNode; onDrop: (path: string) => void; }; const DropZone = ({ label, children, onDrop }: DropZoneProps) => { const [over, setOver] = useState(false); const handleDragOver = useCallback((ev: DragEvent) => { ev.preventDefault(); setOver(true); }, []); const handleDragLeave = useCallback(() => setOver(false), []); const handleDrop = useCallback( (ev: DragEvent) => { ev.preventDefault(); setOver(false); const path = ev.dataTransfer.getData('text/plain'); if (path) onDrop(path); }, [onDrop], ); return (
{label}
{children}
); }; export const DockSettings = () => { // Same composition as the dock itself. Offering a pin for an uninstalled feature would let someone // pin a tile that cannot appear, which reads as the setting being broken. const { plugins } = useCapabilities(); const allDockItems = useMemo(() => [...CORE_DOCK_ITEMS, ...dockItemsFromPlugins(plugins)], [plugins]); const { items, allItems, setItems, reset } = useDock(allDockItems, DEFAULT_DOCK_PATHS); const [dropTarget, setDropTarget] = useState<{ path: string; side: 'left' | 'right' } | null>(null); const visiblePaths = new Set(items.map((i) => i.to)); const hiddenItems = allItems.filter((i) => !visiblePaths.has(i.to)); const onDragStart = useCallback((ev: DragEvent, path: string) => { ev.dataTransfer.setData('text/plain', path); ev.dataTransfer.effectAllowed = 'move'; }, []); const addItem = useCallback( (path: string) => { if (visiblePaths.has(path)) return; setItems([...items.map((i) => i.to), path]); }, [items, visiblePaths, setItems], ); const removeItem = useCallback( (path: string) => { setItems(items.filter((i) => i.to !== path).map((i) => i.to)); }, [items, setItems], ); const insertAt = useCallback( (draggedPath: string, targetPath: string, side: 'left' | 'right') => { const paths = items.filter((i) => i.to !== draggedPath).map((i) => i.to); const targetIdx = paths.indexOf(targetPath); if (targetIdx === -1) return; const insertIdx = side === 'left' ? targetIdx : targetIdx + 1; paths.splice(insertIdx, 0, draggedPath); setItems(paths); setDropTarget(null); }, [items, setItems], ); const handlePillDragOver = useCallback((ev: DragEvent, pillPath: string) => { ev.preventDefault(); ev.dataTransfer.dropEffect = 'move'; const rect = (ev.currentTarget as HTMLElement).getBoundingClientRect(); const midX = rect.left + rect.width / 2; const side = ev.clientX < midX ? 'left' : 'right'; setDropTarget((prev) => (prev?.path === pillPath && prev?.side === side ? prev : { path: pillPath, side })); }, []); const handlePillDragLeave = useCallback(() => setDropTarget(null), []); const handlePillDrop = useCallback( (draggedPath: string, targetPath: string) => { if (draggedPath === targetPath) { setDropTarget(null); return; } const side = dropTarget?.path === targetPath ? dropTarget.side : 'right'; insertAt(draggedPath, targetPath, side); }, [dropTarget, insertAt], ); const onDropVisible = useCallback( (path: string) => { setDropTarget(null); if (visiblePaths.has(path)) return; addItem(path); }, [visiblePaths, addItem], ); const onDropHidden = useCallback( (path: string) => { setDropTarget(null); if (!visiblePaths.has(path)) return; removeItem(path); }, [visiblePaths, removeItem], ); return (
{items.length === 0 && ( Drag items here to show in dock )} {items.map((item) => ( handlePillDragOver(ev, item.to)} onDragLeavePill={handlePillDragLeave} onDropOnPill={(draggedPath) => handlePillDrop(draggedPath, item.to)} /> ))} {hiddenItems.length === 0 && ( All items visible )} {hiddenItems.map((item) => ( ))}
); };