From 385bdd6734235bb6f04d4d8820afc1e752c67b79 Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Mon, 19 Jan 2026 01:25:31 -0800 Subject: [PATCH] Add panel size persistence on board save. - Add registerLayoutGetter to BoardContext for collecting sizes on save - Use GroupImperativeHandle and groupRef prop for react-resizable-panels - Add id props to Panels for layout mapping by panel id - Collect row and column sizes via getLayout() only when saving - Restore saved sizes via defaultSize prop on Panels Co-Authored-By: Claude Opus 4.5 --- src/app/(main)/boards/BoardProvider.tsx | 29 ++++++-- src/app/(main)/boards/[boardId]/BoardBody.tsx | 71 +++++++++++++++++-- 2 files changed, 89 insertions(+), 11 deletions(-) diff --git a/src/app/(main)/boards/BoardProvider.tsx b/src/app/(main)/boards/BoardProvider.tsx index 783f92ef..1d133166 100644 --- a/src/app/(main)/boards/BoardProvider.tsx +++ b/src/app/(main)/boards/BoardProvider.tsx @@ -1,15 +1,18 @@ 'use client'; import { Loading, useToast } from '@umami/react-zen'; -import { createContext, type ReactNode, useCallback, useEffect, useState } from 'react'; +import { createContext, type ReactNode, useCallback, useEffect, useRef, useState } from 'react'; import { useApi, useMessages, useModified, useNavigation } from '@/components/hooks'; import { useBoardQuery } from '@/components/hooks/queries/useBoardQuery'; -import type { Board } from '@/lib/types'; +import type { Board, BoardParameters } from '@/lib/types'; + +export type LayoutGetter = () => Partial | null; export interface BoardContextValue { board: Partial; updateBoard: (data: Partial) => void; saveBoard: () => Promise; isPending: boolean; + registerLayoutGetter: (getter: LayoutGetter) => void; } export const BoardContext = createContext(null); @@ -29,6 +32,11 @@ export function BoardProvider({ boardId, children }: { boardId?: string; childre const { router, renderUrl } = useNavigation(); const [board, setBoard] = useState>(data ?? defaultBoard); + const layoutGetterRef = useRef(null); + + const registerLayoutGetter = useCallback((getter: LayoutGetter) => { + layoutGetterRef.current = getter; + }, []); useEffect(() => { if (data) { @@ -51,7 +59,18 @@ export function BoardProvider({ boardId, children }: { boardId?: string; childre const saveBoard = useCallback(async () => { const defaultName = formatMessage(labels.untitled); - const result = await mutateAsync({ ...board, name: board.name || defaultName }); + + // Get current layout sizes from BoardBody if registered + const layoutData = layoutGetterRef.current?.(); + console.log('layoutData from getter:', layoutData); + const parameters = layoutData ? { ...board.parameters, ...layoutData } : board.parameters; + console.log('parameters to save:', parameters); + + const result = await mutateAsync({ + ...board, + name: board.name || defaultName, + parameters, + }); toast(formatMessage(messages.saved)); touch('boards'); @@ -80,7 +99,9 @@ export function BoardProvider({ boardId, children }: { boardId?: string; childre } return ( - + {children} ); diff --git a/src/app/(main)/boards/[boardId]/BoardBody.tsx b/src/app/(main)/boards/[boardId]/BoardBody.tsx index 43921963..c85fbbac 100644 --- a/src/app/(main)/boards/[boardId]/BoardBody.tsx +++ b/src/app/(main)/boards/[boardId]/BoardBody.tsx @@ -1,7 +1,7 @@ import { Box, Button, Column, Icon, Row, Tooltip, TooltipTrigger } from '@umami/react-zen'; import { produce } from 'immer'; -import { Fragment, type ReactElement } from 'react'; -import { Group, Panel, Separator } from 'react-resizable-panels'; +import { Fragment, type ReactElement, useEffect, useRef } from 'react'; +import { Group, type GroupImperativeHandle, Panel, Separator } from 'react-resizable-panels'; import { v4 as uuid } from 'uuid'; import { useBoard } from '@/components/hooks'; import { ChevronDown, Minus, Plus, X } from '@/components/icons'; @@ -21,7 +21,52 @@ const BUTTON_ROW_HEIGHT = 60; const MAX_COLUMNS = 4; export function BoardBody() { - const { board, updateBoard, saveBoard, isPending } = useBoard(); + const { board, updateBoard, saveBoard, isPending, registerLayoutGetter } = useBoard(); + const rowGroupRef = useRef(null); + const columnGroupRefs = useRef>(new Map()); + + // Register a function to get current layout sizes on save + useEffect(() => { + registerLayoutGetter(() => { + const rows = board?.parameters?.rows; + console.log('Layout getter called, rows:', rows); + console.log('rowGroupRef.current:', rowGroupRef.current); + console.log('columnGroupRefs.current:', columnGroupRefs.current); + + if (!rows?.length) return null; + + const rowLayout = rowGroupRef.current?.getLayout(); + console.log('rowLayout:', rowLayout); + + const updatedRows = rows.map(row => { + const columnGroupRef = columnGroupRefs.current.get(row.id); + const columnLayout = columnGroupRef?.getLayout(); + console.log(`Row ${row.id} columnLayout:`, columnLayout); + + const updatedColumns = row.columns.map(col => ({ + ...col, + size: columnLayout?.[col.id], + })); + + return { + ...row, + size: rowLayout?.[row.id], + columns: updatedColumns, + }; + }); + + console.log('updatedRows:', updatedRows); + return { rows: updatedRows }; + }); + }, [registerLayoutGetter, board?.parameters?.rows]); + + const registerColumnGroupRef = (rowId: string, ref: GroupImperativeHandle | null) => { + if (ref) { + columnGroupRefs.current.set(rowId, ref); + } else { + columnGroupRefs.current.delete(rowId); + } + }; console.log({ board }); @@ -82,10 +127,15 @@ export function BoardBody() { const minHeight = (rows?.length || 1) * MAX_ROW_HEIGHT + BUTTON_ROW_HEIGHT; return ( - + {rows.map((row, index) => ( - + {index < rows?.length - 1 && } @@ -123,6 +174,7 @@ function BoardRow({ onRemove, onMoveUp, onMoveDown, + onRegisterRef, }: { rowId: string; rowIndex: number; @@ -131,9 +183,14 @@ function BoardRow({ onRemove?: (id: string) => void; onMoveUp?: (id: string) => void; onMoveDown?: (id: string) => void; + onRegisterRef?: (rowId: string, ref: GroupImperativeHandle | null) => void; }) { const { board, updateBoard } = useBoard(); + const handleGroupRef = (ref: GroupImperativeHandle | null) => { + onRegisterRef?.(rowId, ref); + }; + const handleAddColumn = () => { updateBoard({ parameters: produce(board.parameters, draft => { @@ -160,10 +217,10 @@ function BoardRow({ }; return ( - + {columns?.map((column, index) => ( - + {index < columns?.length - 1 && }