persistence in panels

This commit is contained in:
2026-02-23 02:26:52 +00:00
parent 81256a1dc9
commit af789471dc
2 changed files with 101 additions and 73 deletions
@@ -1,4 +1,4 @@
import { useState, useCallback, useEffect } from 'react';
import { useState, useCallback, useEffect, useRef, type ComponentRef } from 'react';
import { flushSync } from 'react-dom';
import { ResizablePanel, ResizablePanelGroup, ResizableHandle } from '@/components/ui/resizable';
import type { LayoutNode, WorkspaceState, EphemeralPanels, PanelComponents } from './types';
@@ -111,43 +111,22 @@ export const WorkspaceView = ({ workspace, cwd = '~', root, initialFilePath, def
return () => window.removeEventListener('keydown', onKeyDown);
}, [swapSourceId, dragSourceId, maximizedPanelId, setMaximizedAnimated]);
const ephemeralPanelRef = useRef<ComponentRef<typeof ResizablePanel>>(null);
const isEphemeralOpen = !!ephemeral;
useEffect(() => {
const panel = ephemeralPanelRef.current;
if (!panel) return;
if (isEphemeralOpen) {
panel.resize(100 - (ephemeral.defaultBaseSize ?? 40));
} else {
panel.collapse();
}
}, [isEphemeralOpen, ephemeral?.defaultBaseSize]);
if (!workspace.isLoaded) return null;
const baseRenderer = (
<WorkspaceRenderer
layout={layout}
registry={registry}
components={components}
interactive
onSetApp={handleSetApp}
onSplit={handleSplit}
onRemove={handleRemove}
onResized={handleResized}
/>
);
const content = ephemeral ? (
<ResizablePanelGroup direction="horizontal" className="h-full w-full">
<ResizablePanel defaultSize={ephemeral.defaultBaseSize ?? 40} minSize={15}>
{baseRenderer}
</ResizablePanel>
<ResizableHandle className="bg-transparent after:bg-transparent" />
<ResizablePanel defaultSize={100 - (ephemeral.defaultBaseSize ?? 40)} minSize={15}>
<WorkspaceRenderer
layout={ephemeral.layout}
registry={registry}
components={ephemeral.components}
onSetApp={noop}
onSplit={noop}
onRemove={noop}
onResized={noop}
/>
</ResizablePanel>
</ResizablePanelGroup>
) : (
baseRenderer
);
return (
<WorkspaceProvider
value={{
@@ -167,7 +146,40 @@ export const WorkspaceView = ({ workspace, cwd = '~', root, initialFilePath, def
transitioningPanelId,
}}
>
{content}
<ResizablePanelGroup direction="horizontal" className="h-full w-full">
<ResizablePanel defaultSize={100} minSize={15}>
<WorkspaceRenderer
layout={layout}
registry={registry}
components={components}
interactive
onSetApp={handleSetApp}
onSplit={handleSplit}
onRemove={handleRemove}
onResized={handleResized}
/>
</ResizablePanel>
<ResizableHandle className="bg-transparent after:bg-transparent" disabled={!isEphemeralOpen} />
<ResizablePanel
ref={ephemeralPanelRef}
collapsible
collapsedSize={0}
defaultSize={0}
minSize={15}
>
{isEphemeralOpen ? (
<WorkspaceRenderer
layout={ephemeral.layout}
registry={registry}
components={ephemeral.components}
onSetApp={noop}
onSplit={noop}
onRemove={noop}
onResized={noop}
/>
) : null}
</ResizablePanel>
</ResizablePanelGroup>
</WorkspaceProvider>
);
};
@@ -1,3 +1,4 @@
import { useCallback, useMemo } from 'react';
import { useSearchParams } from 'react-router';
import type { EphemeralPanels } from '../../components/Workspace';
import { FileViewerHeader, FileViewerBody } from '../../apps/FileViewer';
@@ -17,42 +18,57 @@ export const useFileViewerPanels = (): EphemeralPanels | null => {
? viewerWithEphemeralLayout
: singleViewerLayout;
const components = {
'files-viewer': {
provider: ViewerProvider,
header: FileViewerHeader,
component: FileViewerBody,
onClose: () => setSearchParams({}),
},
'files-ephemeral': {
provider: EphemeralProvider,
header: FileViewerHeader,
component: FileViewerBody,
onClose: () =>
setSearchParams((prev) => {
const next = new URLSearchParams(prev);
next.delete('ephemeral');
next.delete('ephemeralRoot');
next.delete('ephemeral2');
next.delete('ephemeral2Root');
next.delete('ephemeral2Auto');
return next;
}),
},
'files-ephemeral2': {
provider: Ephemeral2Provider,
header: FileViewerHeader,
component: FileViewerBody,
onClose: () =>
setSearchParams((prev) => {
const next = new URLSearchParams(prev);
next.delete('ephemeral2');
next.delete('ephemeral2Root');
next.delete('ephemeral2Auto');
return next;
}),
},
};
const onCloseViewer = useCallback(() => setSearchParams({}), [setSearchParams]);
const onCloseEphemeral = useCallback(
() =>
setSearchParams((prev) => {
const next = new URLSearchParams(prev);
next.delete('ephemeral');
next.delete('ephemeralRoot');
next.delete('ephemeral2');
next.delete('ephemeral2Root');
next.delete('ephemeral2Auto');
return next;
}),
[setSearchParams],
);
const onCloseEphemeral2 = useCallback(
() =>
setSearchParams((prev) => {
const next = new URLSearchParams(prev);
next.delete('ephemeral2');
next.delete('ephemeral2Root');
next.delete('ephemeral2Auto');
return next;
}),
[setSearchParams],
);
const components = useMemo(
() => ({
'files-viewer': {
provider: ViewerProvider,
header: FileViewerHeader,
component: FileViewerBody,
onClose: onCloseViewer,
},
'files-ephemeral': {
provider: EphemeralProvider,
header: FileViewerHeader,
component: FileViewerBody,
onClose: onCloseEphemeral,
},
'files-ephemeral2': {
provider: Ephemeral2Provider,
header: FileViewerHeader,
component: FileViewerBody,
onClose: onCloseEphemeral2,
},
}),
[onCloseViewer, onCloseEphemeral, onCloseEphemeral2],
);
if (!viewPath) return null;
return { layout, components, defaultBaseSize: 40 };