Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { produce } from 'immer';
import type { StateCreator } from 'zustand/vanilla';
import { WorkspaceStore } from '../../store';
import { ConfigState, initConfigState } from './initialState';
import { ConfigState, nextPanelLeftLayout } from './initialState';

export interface ConfigAction {
togglePanelRight: (show?: boolean) => void;
Expand All @@ -23,9 +23,10 @@ export const createConfigAction: StateCreator<WorkspaceStore, [['zustand/devtool
togglePanelLeft: () => {
set(
produce((state: ConfigState) => {
const show = state.layout.panelLeftWidth === 0;
state.layout.panelLeft = show;
state.layout.panelLeftWidth = show ? initConfigState.layout.panelLeftWidth : 0;
const next = nextPanelLeftLayout(state.layout);
state.layout.panelLeft = next.panelLeft;
state.layout.panelLeftWidth = next.panelLeftWidth;
state.layout.lastPanelLeftWidth = next.lastPanelLeftWidth;
}),
);
},
Expand All @@ -34,6 +35,9 @@ export const createConfigAction: StateCreator<WorkspaceStore, [['zustand/devtool
produce((state: ConfigState) => {
state.layout.panelLeftWidth = width;
state.layout.panelLeft = width > 0;
if (width > 0) {
state.layout.lastPanelLeftWidth = width;
}
}),
);
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ export interface ConfigState {
panelLeftWidth: number;
panelRight: boolean;
panelRightWidth: number;
/** Last non-zero left panel width, so expanding restores the user's width. */
lastPanelLeftWidth: number;
};
}

Expand All @@ -13,5 +15,33 @@ export const initConfigState: ConfigState = {
panelRight: true,
panelLeftWidth: 260,
panelRightWidth: 300,
lastPanelLeftWidth: 260,
},
};

export interface PanelLeftToggleLayout {
panelLeft: boolean;
panelLeftWidth: number;
lastPanelLeftWidth: number;
}

/**
* Collapsing must not destroy the user's custom width, and expanding must
* restore it instead of resetting to the default 260px.
*/
export function nextPanelLeftLayout(layout: ConfigState['layout']): PanelLeftToggleLayout {
if (layout.panelLeftWidth > 0) {
return {
panelLeft: false,
panelLeftWidth: 0,
lastPanelLeftWidth: layout.panelLeftWidth,
};
}
const remembered =
layout.lastPanelLeftWidth > 0 ? layout.lastPanelLeftWidth : initConfigState.layout.panelLeftWidth;
return {
panelLeft: true,
panelLeftWidth: remembered,
lastPanelLeftWidth: remembered,
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import assert from 'node:assert';
import { initConfigState, nextPanelLeftLayout } from './initialState';

const collapsedAfterCollapse = nextPanelLeftLayout({
...initConfigState.layout,
panelLeftWidth: 420,
});

assert.equal(collapsedAfterCollapse.panelLeft, false, 'collapsing hides the left panel');
assert.equal(collapsedAfterCollapse.panelLeftWidth, 0, 'collapsing zeroes the live width');
assert.equal(
collapsedAfterCollapse.lastPanelLeftWidth,
420,
'collapsing must remember the custom width instead of destroying it',
);

const restoredAfterExpand = nextPanelLeftLayout({
...initConfigState.layout,
panelLeft: false,
panelLeftWidth: 0,
lastPanelLeftWidth: 420,
});

assert.equal(restoredAfterExpand.panelLeft, true, 'expanding shows the left panel');
assert.equal(
restoredAfterExpand.panelLeftWidth,
420,
'expanding must restore the remembered custom width, not the 260px default',
);

const defaultWhenNothingRemembered = nextPanelLeftLayout({
...initConfigState.layout,
panelLeft: false,
panelLeftWidth: 0,
lastPanelLeftWidth: 0,
});

assert.equal(
defaultWhenNothingRemembered.panelLeftWidth,
initConfigState.layout.panelLeftWidth,
'expanding with nothing remembered falls back to the default width',
);

console.log('panel left layout tests passed');
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ const persistableLayout = getPersistableWorkspaceLayout({
panelLeftWidth: 260,
panelRight: circularPanelState,
panelRightWidth: 300,
lastPanelLeftWidth: 420,
} as any);

assert.equal(persistableLayout.panelRight, false, 'non-boolean panel state must not reach persisted storage');
Expand All @@ -121,9 +122,10 @@ assert.doesNotThrow(

const migratedClosedLeftPanelLayout = getPersistableWorkspaceLayout({
panelLeft: false,
panelLeftWidth: 260,
panelLeftWidth: 0,
panelRight: false,
panelRightWidth: 300,
lastPanelLeftWidth: 420,
});

assert.equal(
Expand All @@ -132,12 +134,19 @@ assert.equal(
'legacy closed-left-panel state must remain closed after width-based layout migration',
);

assert.equal(
migratedClosedLeftPanelLayout.lastPanelLeftWidth,
420,
'a collapsed left panel must keep its remembered width in persisted storage',
);

const hydratedLegacyLayout = getHydratedWorkspaceLayout(
{
panelLeft: true,
panelLeftWidth: 240,
panelRight: false,
panelRightWidth: 300,
lastPanelLeftWidth: 240,
},
{
panelLeft: false,
Expand All @@ -152,10 +161,33 @@ assert.deepEqual(
{
panelLeft: false,
panelLeftWidth: 0,
lastPanelLeftWidth: 240,
panelRight: false,
panelRightWidth: 300,
},
'hydration must normalize legacy and malformed panel values before the workspace renders',
);

const legacyHydrationWithoutRememberedWidth = getHydratedWorkspaceLayout(
{
panelLeft: true,
panelLeftWidth: 240,
panelRight: false,
panelRightWidth: 300,
lastPanelLeftWidth: 240,
},
{
panelLeft: false,
panelLeftWidth: 0,
panelRight: false,
panelRightWidth: 300,
},
);

assert.equal(
legacyHydrationWithoutRememberedWidth.lastPanelLeftWidth,
240,
'legacy persisted layouts without a remembered width fall back to the current width',
);

console.log('workspace tab persistence tests passed');
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,12 @@ export function getPersistableWorkspaceLayout(layout: ConfigState['layout']): Co
? Math.max(0, layout.panelLeftWidth)
: initConfigState.layout.panelLeftWidth;
const panelLeftWidth = layout.panelLeft === false ? 0 : normalizedPanelLeftWidth;
const rememberedPanelLeftWidth =
typeof layout.lastPanelLeftWidth === 'number' && Number.isFinite(layout.lastPanelLeftWidth) && layout.lastPanelLeftWidth > 0
? layout.lastPanelLeftWidth
: normalizedPanelLeftWidth > 0
? normalizedPanelLeftWidth
: initConfigState.layout.panelLeftWidth;
const panelRightWidth =
typeof layout.panelRightWidth === 'number' && Number.isFinite(layout.panelRightWidth)
? Math.max(0, layout.panelRightWidth)
Expand All @@ -52,6 +58,7 @@ export function getPersistableWorkspaceLayout(layout: ConfigState['layout']): Co
return {
panelLeft: panelLeftWidth > 0,
panelLeftWidth,
lastPanelLeftWidth: rememberedPanelLeftWidth,
panelRight: typeof layout.panelRight === 'boolean' ? layout.panelRight : false,
panelRightWidth,
};
Expand Down
Loading