jobs: persist the panel layout across reloads
The page backed WorkspaceLayout with local useState, so onLayoutChange (fired on every resize) only updated ephemeral state — pane sizes reset to the default split on reload. Back it with useDashboardState under screens/jobs, like the other workspace routes, with a structural guard that falls back to the default when a persisted layout's panel ids no longer match PANEL_COMPONENTS. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -15,6 +15,7 @@ import {
|
|||||||
import { WorkspaceLayout } from 'officerdev';
|
import { WorkspaceLayout } from 'officerdev';
|
||||||
import type { LayoutNode, PanelComponents } from 'officerdev';
|
import type { LayoutNode, PanelComponents } from 'officerdev';
|
||||||
import { useClient } from 'hooks/useClient';
|
import { useClient } from 'hooks/useClient';
|
||||||
|
import { useDashboardState } from 'state/useDashboardState';
|
||||||
import { Card } from '@/components/Card';
|
import { Card } from '@/components/Card';
|
||||||
import { ScriptJobDetail } from './ScriptJobDetail';
|
import { ScriptJobDetail } from './ScriptJobDetail';
|
||||||
import { DownloadJobDetail } from './DownloadJobDetail';
|
import { DownloadJobDetail } from './DownloadJobDetail';
|
||||||
@@ -312,12 +313,29 @@ const PANEL_COMPONENTS: PanelComponents = {
|
|||||||
'job-detail': JobDetailPanel,
|
'job-detail': JobDetailPanel,
|
||||||
};
|
};
|
||||||
|
|
||||||
// One page for /jobs and /jobs/:id — master (list) + detail, resizable like /chat.
|
// Guard the persisted layout against a stale shape (e.g. panel ids changed in a later build): the panels
|
||||||
|
// render from PANEL_COMPONENTS by id, so a layout whose panel ids don't match ours would render blanks.
|
||||||
|
// If it doesn't line up exactly, fall back to the default rather than trust the saved node.
|
||||||
|
const collectPanelIds = (node: LayoutNode, acc: Set<string>): Set<string> => {
|
||||||
|
if (node.type === 'panel') acc.add(node.id);
|
||||||
|
else for (const child of node.children) collectPanelIds(child.node, acc);
|
||||||
|
return acc;
|
||||||
|
};
|
||||||
|
|
||||||
|
const matchesPanelSet = (node: LayoutNode): boolean => {
|
||||||
|
const ids = collectPanelIds(node, new Set<string>());
|
||||||
|
const expected = Object.keys(PANEL_COMPONENTS);
|
||||||
|
return ids.size === expected.length && expected.every((id) => ids.has(id));
|
||||||
|
};
|
||||||
|
|
||||||
|
// One page for /jobs and /jobs/:id — master (list) + detail, resizable like /chat. The layout is
|
||||||
|
// persisted per-user via useDashboardState (screens/ namespace), so pane sizes survive reloads.
|
||||||
export const JobsPage = () => {
|
export const JobsPage = () => {
|
||||||
const [layout, setLayout] = useState<LayoutNode>(JOBS_LAYOUT);
|
const { value, setValue } = useDashboardState<LayoutNode>('screens/jobs', JOBS_LAYOUT);
|
||||||
|
const layout = matchesPanelSet(value) ? value : JOBS_LAYOUT;
|
||||||
return (
|
return (
|
||||||
<div className="h-full w-full">
|
<div className="h-full w-full">
|
||||||
<WorkspaceLayout layout={layout} onLayoutChange={setLayout} components={PANEL_COMPONENTS} noHeader />
|
<WorkspaceLayout layout={layout} onLayoutChange={setValue} components={PANEL_COMPONENTS} noHeader />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user