Attempt to fix note resizing with React keys and Muuri sync
This commit is contained in:
@@ -20,12 +20,13 @@ interface MasonryItemProps {
|
||||
note: Note;
|
||||
onEdit: (note: Note, readOnly?: boolean) => void;
|
||||
onResize: () => void;
|
||||
onNoteSizeChange: (noteId: string, newSize: 'small' | 'medium' | 'large') => void;
|
||||
onDragStart?: (noteId: string) => void;
|
||||
onDragEnd?: () => void;
|
||||
isDragging?: boolean;
|
||||
}
|
||||
|
||||
const MasonryItem = memo(function MasonryItem({ note, onEdit, onResize, onDragStart, onDragEnd, isDragging }: MasonryItemProps) {
|
||||
const MasonryItem = memo(function MasonryItem({ note, onEdit, onResize, onNoteSizeChange, onDragStart, onDragEnd, isDragging }: MasonryItemProps) {
|
||||
const resizeRef = useResizeObserver(onResize);
|
||||
|
||||
return (
|
||||
@@ -43,13 +44,12 @@ const MasonryItem = memo(function MasonryItem({ note, onEdit, onResize, onDragSt
|
||||
onDragStart={onDragStart}
|
||||
onDragEnd={onDragEnd}
|
||||
isDragging={isDragging}
|
||||
onResize={onResize}
|
||||
onSizeChange={(newSize) => onNoteSizeChange(note.id, newSize)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}, (prev, next) => {
|
||||
// Custom comparison to avoid re-render on function prop changes if note data is same
|
||||
return prev.note.id === next.note.id && prev.isDragging === next.isDragging;
|
||||
});
|
||||
|
||||
export function MasonryGrid({ notes, onEdit }: MasonryGridProps) {
|
||||
@@ -57,6 +57,22 @@ export function MasonryGrid({ notes, onEdit }: MasonryGridProps) {
|
||||
const [editingNote, setEditingNote] = useState<{ note: Note; readOnly?: boolean } | null>(null);
|
||||
const { startDrag, endDrag, draggedNoteId } = useNotebookDrag();
|
||||
|
||||
// Local state for notes with dynamic size updates
|
||||
// This allows size changes to propagate immediately without waiting for server
|
||||
const [localNotes, setLocalNotes] = useState<Note[]>(notes);
|
||||
|
||||
// Sync localNotes when parent notes prop changes
|
||||
useEffect(() => {
|
||||
setLocalNotes(notes);
|
||||
}, [notes]);
|
||||
|
||||
// Callback for when a note's size changes - update local state immediately
|
||||
const handleNoteSizeChange = useCallback((noteId: string, newSize: 'small' | 'medium' | 'large') => {
|
||||
setLocalNotes(prevNotes =>
|
||||
prevNotes.map(n => n.id === noteId ? { ...n, size: newSize } : n)
|
||||
);
|
||||
}, []);
|
||||
|
||||
const handleEdit = useCallback((note: Note, readOnly?: boolean) => {
|
||||
if (onEdit) {
|
||||
onEdit(note, readOnly);
|
||||
@@ -70,14 +86,14 @@ export function MasonryGrid({ notes, onEdit }: MasonryGridProps) {
|
||||
const pinnedMuuri = useRef<any>(null);
|
||||
const othersMuuri = useRef<any>(null);
|
||||
|
||||
// Memoize filtered notes (order comes from array)
|
||||
// Memoize filtered notes from localNotes (which includes dynamic size updates)
|
||||
const pinnedNotes = useMemo(
|
||||
() => notes.filter(n => n.isPinned),
|
||||
[notes]
|
||||
() => localNotes.filter(n => n.isPinned),
|
||||
[localNotes]
|
||||
);
|
||||
const othersNotes = useMemo(
|
||||
() => notes.filter(n => !n.isPinned),
|
||||
[notes]
|
||||
() => localNotes.filter(n => !n.isPinned),
|
||||
[localNotes]
|
||||
);
|
||||
|
||||
const handleDragEnd = useCallback(async (grid: any) => {
|
||||
@@ -152,7 +168,7 @@ export function MasonryGrid({ notes, onEdit }: MasonryGridProps) {
|
||||
const columns = calculateColumns(containerWidth);
|
||||
const itemWidth = calculateItemWidth(containerWidth, columns);
|
||||
|
||||
console.log(`[Masonry] Container width: ${containerWidth}px, Columns: ${columns}, Item width: ${itemWidth}px`);
|
||||
|
||||
|
||||
const layoutOptions = {
|
||||
dragEnabled: true,
|
||||
@@ -279,6 +295,13 @@ export function MasonryGrid({ notes, onEdit }: MasonryGridProps) {
|
||||
requestAnimationFrame(() => {
|
||||
syncGridItems(pinnedMuuri.current, pinnedGridRef, pinnedNotes);
|
||||
syncGridItems(othersMuuri.current, othersGridRef, othersNotes);
|
||||
|
||||
// CRITICAL: Force a second layout after CSS transitions (padding/height changes) complete
|
||||
// NoteCard has a 200ms transition. We wait 300ms to be safe.
|
||||
setTimeout(() => {
|
||||
if (pinnedMuuri.current) pinnedMuuri.current.refreshItems().layout();
|
||||
if (othersMuuri.current) othersMuuri.current.refreshItems().layout();
|
||||
}, 300);
|
||||
});
|
||||
}, [pinnedNotes, othersNotes]); // Re-run when notes change
|
||||
|
||||
@@ -295,7 +318,7 @@ export function MasonryGrid({ notes, onEdit }: MasonryGridProps) {
|
||||
const containerWidth = entries[0]?.contentRect.width || window.innerWidth - 32;
|
||||
const columns = calculateColumns(containerWidth);
|
||||
|
||||
console.log(`[Masonry Resize] Width: ${containerWidth}px, Columns: ${columns}`);
|
||||
|
||||
|
||||
// Apply dimensions to both grids
|
||||
applyItemDimensions(pinnedMuuri.current, containerWidth);
|
||||
@@ -331,10 +354,11 @@ export function MasonryGrid({ notes, onEdit }: MasonryGridProps) {
|
||||
<div ref={pinnedGridRef} className="relative min-h-[100px]">
|
||||
{pinnedNotes.map(note => (
|
||||
<MasonryItem
|
||||
key={note.id}
|
||||
key={`${note.id}-${note.size}`}
|
||||
note={note}
|
||||
onEdit={handleEdit}
|
||||
onResize={refreshLayout}
|
||||
onNoteSizeChange={handleNoteSizeChange}
|
||||
onDragStart={startDrag}
|
||||
onDragEnd={endDrag}
|
||||
isDragging={draggedNoteId === note.id}
|
||||
@@ -352,10 +376,11 @@ export function MasonryGrid({ notes, onEdit }: MasonryGridProps) {
|
||||
<div ref={othersGridRef} className="relative min-h-[100px]">
|
||||
{othersNotes.map(note => (
|
||||
<MasonryItem
|
||||
key={note.id}
|
||||
key={`${note.id}-${note.size}`}
|
||||
note={note}
|
||||
onEdit={handleEdit}
|
||||
onResize={refreshLayout}
|
||||
onNoteSizeChange={handleNoteSizeChange}
|
||||
onDragStart={startDrag}
|
||||
onDragEnd={endDrag}
|
||||
isDragging={draggedNoteId === note.id}
|
||||
@@ -373,39 +398,6 @@ export function MasonryGrid({ notes, onEdit }: MasonryGridProps) {
|
||||
/>
|
||||
)}
|
||||
|
||||
<style jsx global>{`
|
||||
.masonry-container {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.masonry-item {
|
||||
display: block;
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.masonry-item.muuri-item-dragging {
|
||||
z-index: 3;
|
||||
opacity: 0.8;
|
||||
}
|
||||
.masonry-item.muuri-item-releasing {
|
||||
z-index: 2;
|
||||
}
|
||||
.masonry-item.muuri-item-hidden {
|
||||
z-index: 0;
|
||||
}
|
||||
.masonry-item-content {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
/* Ensure proper box-sizing for all elements in the grid */
|
||||
.masonry-item *,
|
||||
.masonry-item-content * {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
`}</style>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user