mirror of
https://github.com/olehomelchenko/astrolabe.git
synced 2026-08-08 02:02:33 +00:00
Add pane show/hide toggle strip with persisted visibility (M6, §01A)
A persistent left-rail toolbar (APG toolbar + roving tabindex) toggles the library / editor / preview panes; a hidden pane frees its space and the rest redistribute proportionally to their remembered widths. Visibility persists to astrolabe:ux-prefs (separate key from widths, so a hidden pane keeps its width). Adds the positional pane-* glyph sub-family to Icon and a Datasets shortcut button in the strip.
This commit is contained in:
@@ -1,5 +1,10 @@
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import { loadPanelLayout, savePanelLayout } from './ux-prefs';
|
||||
import {
|
||||
loadPanelLayout,
|
||||
loadPaneVisibility,
|
||||
savePanelLayout,
|
||||
savePaneVisibility,
|
||||
} from './ux-prefs';
|
||||
|
||||
const KEY = 'astrolabe:ux-prefs';
|
||||
|
||||
@@ -61,3 +66,35 @@ describe('ux-prefs · panelLayout', () => {
|
||||
expect(stored.panelLayout.libraryWidth).toBe(260);
|
||||
});
|
||||
});
|
||||
|
||||
describe('ux-prefs · paneVisibility', () => {
|
||||
beforeEach(() => vi.stubGlobal('localStorage', makeStorageStub()));
|
||||
|
||||
it('round-trips persisted visibility; absent flags stay undefined', () => {
|
||||
savePaneVisibility({ library: false, editor: true });
|
||||
expect(loadPaneVisibility()).toEqual({ library: false, editor: true, preview: undefined });
|
||||
});
|
||||
|
||||
it('surfaces only booleans — junk in storage falls back to undefined', () => {
|
||||
localStorage.setItem(
|
||||
KEY,
|
||||
JSON.stringify({ paneVisibility: { library: 'nope', preview: false } }),
|
||||
);
|
||||
expect(loadPaneVisibility()).toEqual({
|
||||
library: undefined,
|
||||
editor: undefined,
|
||||
preview: false,
|
||||
});
|
||||
});
|
||||
|
||||
it('preserves panelLayout when writing visibility (separate keys)', () => {
|
||||
savePanelLayout({ libraryWidth: 300 });
|
||||
savePaneVisibility({ editor: false });
|
||||
const stored = JSON.parse(localStorage.getItem(KEY)!) as {
|
||||
panelLayout: { libraryWidth: number };
|
||||
paneVisibility: { editor: boolean };
|
||||
};
|
||||
expect(stored.panelLayout.libraryWidth).toBe(300);
|
||||
expect(stored.paneVisibility.editor).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -6,9 +6,10 @@
|
||||
* record. Its own key, `astrolabe:ux-prefs`, holds the snippet sort preference
|
||||
* (lands with M5/M6) and the panel layout (per-pane widths + visibility).
|
||||
*
|
||||
* This slice persists the panel **widths** only; visibility joins it when the
|
||||
* toggle strip lands. Read-with-fallback + write-through merge, the same
|
||||
* contract as the settings adapter, so adding fields later upgrades cleanly.
|
||||
* This slice persists the panel **widths** and per-pane **visibility** (spec §01A).
|
||||
* Read-with-fallback + write-through merge, the same contract as the settings
|
||||
* adapter, so adding fields later upgrades cleanly. Widths and visibility are kept
|
||||
* as separate keys: a hidden pane retains its remembered width.
|
||||
*
|
||||
* Per the architecture rule this is one of the only modules that may touch
|
||||
* `localStorage`; everything else goes through these typed functions.
|
||||
@@ -22,8 +23,16 @@ export interface PanelLayout {
|
||||
previewWidth?: number;
|
||||
}
|
||||
|
||||
/** Per-pane visibility. Optional — a missing field falls back to "shown". */
|
||||
export interface PaneVisibilityPref {
|
||||
library?: boolean;
|
||||
editor?: boolean;
|
||||
preview?: boolean;
|
||||
}
|
||||
|
||||
interface StoredPrefs {
|
||||
panelLayout?: PanelLayout;
|
||||
paneVisibility?: PaneVisibilityPref;
|
||||
[k: string]: unknown;
|
||||
}
|
||||
|
||||
@@ -76,3 +85,24 @@ export function savePanelLayout(layout: PanelLayout): void {
|
||||
const current = readRaw();
|
||||
writeRaw({ ...current, panelLayout: { ...current.panelLayout, ...layout } });
|
||||
}
|
||||
|
||||
/** A boolean, or undefined — guards against junk in storage. */
|
||||
function bool(v: unknown): boolean | undefined {
|
||||
return typeof v === 'boolean' ? v : undefined;
|
||||
}
|
||||
|
||||
/** The persisted per-pane visibility, with only valid booleans surfaced. */
|
||||
export function loadPaneVisibility(): PaneVisibilityPref {
|
||||
const stored = readRaw().paneVisibility ?? {};
|
||||
return {
|
||||
library: bool(stored.library),
|
||||
editor: bool(stored.editor),
|
||||
preview: bool(stored.preview),
|
||||
};
|
||||
}
|
||||
|
||||
/** Persist per-pane visibility, preserving every other key already in the record. */
|
||||
export function savePaneVisibility(visibility: PaneVisibilityPref): void {
|
||||
const current = readRaw();
|
||||
writeRaw({ ...current, paneVisibility: { ...current.paneVisibility, ...visibility } });
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user