import React, { useEffect, useRef, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { GripVertical } from 'lucide-react'; import { useDragDrop, useDragSource } from '../../contexts/DragDropContext'; import { useArtistLayoutStore, type ArtistSectionConfig, type ArtistSectionId } from '../../store/artistLayoutStore'; const ARTIST_SECTION_LABEL_KEYS: Record = { bio: 'settings.artistLayoutBio', topTracks: 'settings.artistLayoutTopTracks', similar: 'settings.artistLayoutSimilar', albums: 'settings.artistLayoutAlbums', featured: 'settings.artistLayoutFeatured', }; type ArtistDropTarget = { idx: number; before: boolean } | null; function ArtistSectionGripHandle({ idx, label }: { idx: number; label: string }) { const { t } = useTranslation(); const { onMouseDown } = useDragSource(() => ({ data: JSON.stringify({ type: 'artist_section_reorder', index: idx }), label, })); return ( ); } export function ArtistLayoutCustomizer() { const { t } = useTranslation(); const sections = useArtistLayoutStore(s => s.sections); const setSections = useArtistLayoutStore(s => s.setSections); const toggleSection = useArtistLayoutStore(s => s.toggleSection); const { isDragging: isPsyDragging } = useDragDrop(); const [containerEl, setContainerEl] = useState(null); const [dropTarget, setDropTarget] = useState(null); const dropTargetRef = useRef(null); const sectionsRef = useRef(sections); sectionsRef.current = sections; useEffect(() => { if (!isPsyDragging) { dropTargetRef.current = null; setDropTarget(null); } }, [isPsyDragging]); useEffect(() => { if (!containerEl) return; const onPsyDrop = (e: Event) => { const detail = (e as CustomEvent).detail; if (!detail?.data) return; let parsed: { type?: string; index?: number }; try { parsed = JSON.parse(detail.data as string); } catch { return; } if (parsed.type !== 'artist_section_reorder' || parsed.index == null) return; const fromIdx = parsed.index; const target = dropTargetRef.current; dropTargetRef.current = null; setDropTarget(null); if (!target) return; const insertBefore = target.before ? target.idx : target.idx + 1; if (insertBefore === fromIdx || insertBefore === fromIdx + 1) return; const next = [...sectionsRef.current]; const [moved] = next.splice(fromIdx, 1); next.splice(insertBefore > fromIdx ? insertBefore - 1 : insertBefore, 0, moved); setSections(next); }; containerEl.addEventListener('psy-drop', onPsyDrop); return () => containerEl.removeEventListener('psy-drop', onPsyDrop); }, [containerEl, setSections]); const handleMouseMove = (e: React.MouseEvent) => { if (!isPsyDragging || !containerEl) return; const rows = containerEl.querySelectorAll('[data-artist-idx]'); let target: ArtistDropTarget = null; for (const row of rows) { const rect = row.getBoundingClientRect(); const idx = Number(row.dataset.artistIdx); if (e.clientY < rect.top + rect.height / 2) { target = { idx, before: true }; break; } target = { idx, before: false }; } dropTargetRef.current = target; setDropTarget(target); }; return ( <>

{t('settings.artistLayoutDesc')}

{sections.map((section: ArtistSectionConfig, i) => { const label = t(ARTIST_SECTION_LABEL_KEYS[section.id]); const isBefore = isPsyDragging && dropTarget?.idx === i && dropTarget.before; const isAfter = isPsyDragging && dropTarget?.idx === i && !dropTarget.before; return (
{label}
); })}
); }