'use client'; import { Note } from '@/lib/types'; import { NoteCard } from './note-card'; import { useState, useEffect, useRef, useCallback } from 'react'; import { NoteEditor } from './note-editor'; import { updateFullOrder } from '@/app/actions/notes'; import { useResizeObserver } from '@/hooks/use-resize-observer'; interface MasonryGridProps { notes: Note[]; } interface MasonryItemProps { note: Note; onEdit: (note: Note) => void; onResize: () => void; } function MasonryItem({ note, onEdit, onResize }: MasonryItemProps) { const resizeRef = useResizeObserver(() => { onResize(); }); return (
); } export function MasonryGrid({ notes }: MasonryGridProps) { const [editingNote, setEditingNote] = useState(null); const pinnedGridRef = useRef(null); const othersGridRef = useRef(null); const pinnedMuuri = useRef(null); const othersMuuri = useRef(null); const pinnedNotes = notes.filter(n => n.isPinned).sort((a, b) => a.order - b.order); const othersNotes = notes.filter(n => !n.isPinned).sort((a, b) => a.order - b.order); const handleDragEnd = async (grid: any) => { if (!grid) return; const items = grid.getItems(); const ids = items .map((item: any) => item.getElement()?.getAttribute('data-id')) .filter((id: any): id is string => !!id); try { await updateFullOrder(ids); } catch (error) { console.error('Failed to persist order:', error); } }; const refreshLayout = useCallback(() => { // Use requestAnimationFrame for smoother updates requestAnimationFrame(() => { if (pinnedMuuri.current) { pinnedMuuri.current.refreshItems().layout(); } if (othersMuuri.current) { othersMuuri.current.refreshItems().layout(); } }); }, []); useEffect(() => { let isMounted = true; const initMuuri = async () => { // Import web-animations-js polyfill await import('web-animations-js'); // Dynamic import of Muuri to avoid SSR window error const MuuriClass = (await import('muuri')).default; if (!isMounted) return; const layoutOptions = { dragEnabled: true, dragContainer: document.body, dragStartPredicate: { distance: 10, delay: 0, }, dragPlaceholder: { enabled: true, createElement: (item: any) => { const el = item.getElement().cloneNode(true); el.style.opacity = '0.5'; return el; }, }, dragAutoScroll: { targets: [window], speed: (item: any, target: any, intersection: any) => { return intersection * 20; }, }, }; if (pinnedGridRef.current && !pinnedMuuri.current && pinnedNotes.length > 0) { pinnedMuuri.current = new MuuriClass(pinnedGridRef.current, layoutOptions) .on('dragEnd', () => handleDragEnd(pinnedMuuri.current)); } if (othersGridRef.current && !othersMuuri.current && othersNotes.length > 0) { othersMuuri.current = new MuuriClass(othersGridRef.current, layoutOptions) .on('dragEnd', () => handleDragEnd(othersMuuri.current)); } }; initMuuri(); return () => { isMounted = false; pinnedMuuri.current?.destroy(); othersMuuri.current?.destroy(); pinnedMuuri.current = null; othersMuuri.current = null; }; }, [pinnedNotes.length, othersNotes.length]); // Synchronize items when notes change (e.g. searching, adding) useEffect(() => { if (pinnedMuuri.current) { pinnedMuuri.current.refreshItems().layout(); } if (othersMuuri.current) { othersMuuri.current.refreshItems().layout(); } }, [notes]); return (
{pinnedNotes.length > 0 && (

Pinned

{pinnedNotes.map(note => ( ))}
)} {othersNotes.length > 0 && (
{pinnedNotes.length > 0 && (

Others

)}
{othersNotes.map(note => ( ))}
)} {editingNote && ( setEditingNote(null)} /> )}
); }