viewer-panels: read-only boots panels open-collapsed; kicad-style layer rows
- Read-only sessions boot BOTH panels open as collapsed headers (a stored per-browser choice wins); inspector's default anchor moves to the top-left, layers keep the top-right stack under the FAB. - Layer rows follow KiCad's Appearance-pane order: color swatch, eye toggle, layer name (name click = set active). - read-only spec: boot-default assertions (open + collapsed + top-left anchor) in the lock test, chevron expansion in the panels test. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011gJ3M1RpeZLeNUUj8jKC4h
This commit is contained in:
parent
acc76f65ec
commit
3df3468d36
4 changed files with 90 additions and 30 deletions
|
|
@ -78,7 +78,17 @@ export function parseLayersState(json: string): LayersState | null {
|
|||
}
|
||||
}
|
||||
|
||||
export function LayerPanel({ mod, onClose }: { mod: LayersModule; onClose: () => void }) {
|
||||
export function LayerPanel({
|
||||
mod,
|
||||
defaultCollapsed,
|
||||
onClose,
|
||||
}: {
|
||||
mod: LayersModule;
|
||||
/** Boot state when no per-browser choice is stored (read-only sessions
|
||||
* start as a collapsed header — viewer-panels). */
|
||||
defaultCollapsed?: boolean;
|
||||
onClose: () => void;
|
||||
}) {
|
||||
const rootRef = React.useRef<HTMLDivElement | null>(null);
|
||||
const drag = useDraggablePanel({
|
||||
storageKey: PANEL_POS_KEY,
|
||||
|
|
@ -87,10 +97,12 @@ export function LayerPanel({ mod, onClose }: { mod: LayersModule; onClose: () =>
|
|||
});
|
||||
const [collapsed, setCollapsedState] = React.useState<boolean>(() => {
|
||||
try {
|
||||
return localStorage.getItem(PANEL_COLLAPSED_KEY) === "1";
|
||||
const stored = localStorage.getItem(PANEL_COLLAPSED_KEY);
|
||||
if (stored !== null) return stored === "1";
|
||||
} catch {
|
||||
return false;
|
||||
/* private mode */
|
||||
}
|
||||
return defaultCollapsed === true;
|
||||
});
|
||||
const setCollapsed = (v: boolean) => {
|
||||
setCollapsedState(v);
|
||||
|
|
@ -201,22 +213,12 @@ export function LayerPanel({ mod, onClose }: { mod: LayersModule; onClose: () =>
|
|||
state.active === l.id ? "bg-sky-500/10 dark:bg-sky-400/10" : ""
|
||||
}`}
|
||||
>
|
||||
{/* Row body sets the ACTIVE layer (the wx Appearance pane's
|
||||
click semantics); the eye toggles visibility. */}
|
||||
<button
|
||||
data-testid="layer-activate"
|
||||
className="flex min-w-0 flex-1 items-center gap-2 text-left hover:text-sky-600 dark:hover:text-sky-300"
|
||||
title={`Make ${l.name} the active layer`}
|
||||
onClick={() => setActive(l.id)}
|
||||
>
|
||||
<span
|
||||
className="h-3 w-3 shrink-0 rounded-sm ring-1 ring-inset ring-black/20 dark:ring-white/25"
|
||||
style={l.color ? { backgroundColor: l.color } : undefined}
|
||||
/>
|
||||
<span className={`truncate ${state.active === l.id ? "font-semibold" : ""}`}>
|
||||
{l.name}
|
||||
</span>
|
||||
</button>
|
||||
{/* KiCad Appearance-pane row order: color swatch, eye, name.
|
||||
The name sets the ACTIVE layer; the eye toggles visibility. */}
|
||||
<span
|
||||
className="h-3 w-3 shrink-0 rounded-sm ring-1 ring-inset ring-black/20 dark:ring-white/25"
|
||||
style={l.color ? { backgroundColor: l.color } : undefined}
|
||||
/>
|
||||
<button
|
||||
data-testid="layer-visibility"
|
||||
aria-pressed={l.visible}
|
||||
|
|
@ -230,6 +232,16 @@ export function LayerPanel({ mod, onClose }: { mod: LayersModule; onClose: () =>
|
|||
>
|
||||
{l.visible ? <Eye size={13} /> : <EyeOff size={13} />}
|
||||
</button>
|
||||
<button
|
||||
data-testid="layer-activate"
|
||||
className="min-w-0 flex-1 text-left hover:text-sky-600 dark:hover:text-sky-300"
|
||||
title={`Make ${l.name} the active layer`}
|
||||
onClick={() => setActive(l.id)}
|
||||
>
|
||||
<span className={`block truncate ${state.active === l.id ? "font-semibold" : ""}`}>
|
||||
{l.name}
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -37,12 +37,16 @@ const MAX_ITEMS = 20;
|
|||
|
||||
export function SelectionInspector({
|
||||
doc,
|
||||
defaultCollapsed,
|
||||
onClose,
|
||||
}: {
|
||||
/** The bound collab doc (pcbnew: the board room; eeschema: the ACTIVE
|
||||
* sheet's room). Null when no doc room is bound (?collab=0) — the panel
|
||||
* then shows selection counts only. */
|
||||
doc: Y.Doc | null;
|
||||
/** Boot state when no per-browser choice is stored (read-only sessions
|
||||
* start as a collapsed header — viewer-panels). */
|
||||
defaultCollapsed?: boolean;
|
||||
onClose: () => void;
|
||||
}) {
|
||||
const rootRef = React.useRef<HTMLDivElement | null>(null);
|
||||
|
|
@ -53,10 +57,12 @@ export function SelectionInspector({
|
|||
});
|
||||
const [collapsed, setCollapsedState] = React.useState<boolean>(() => {
|
||||
try {
|
||||
return localStorage.getItem(PANEL_COLLAPSED_KEY) === "1";
|
||||
const stored = localStorage.getItem(PANEL_COLLAPSED_KEY);
|
||||
if (stored !== null) return stored === "1";
|
||||
} catch {
|
||||
return false;
|
||||
/* private mode */
|
||||
}
|
||||
return defaultCollapsed === true;
|
||||
});
|
||||
const setCollapsed = (v: boolean) => {
|
||||
setCollapsedState(v);
|
||||
|
|
@ -103,11 +109,12 @@ export function SelectionInspector({
|
|||
|
||||
const count = selection.uuids.length;
|
||||
|
||||
// Default anchor: below the overlay-menu FAB, clear of the layer panel's
|
||||
// default (right 12 / top 56, w-64) — stack under it.
|
||||
// Default anchor: TOP-LEFT (the layer panel owns the top-right stack under
|
||||
// the overlay-menu FAB). Clear of the transient status chip (left-3 top-3,
|
||||
// pointer-events-none anyway).
|
||||
const style: React.CSSProperties = drag.pos
|
||||
? { left: drag.pos.x, top: drag.pos.y }
|
||||
: { right: 12, top: 96 };
|
||||
: { left: 12, top: 12 };
|
||||
|
||||
return (
|
||||
<div
|
||||
|
|
|
|||
|
|
@ -1182,12 +1182,17 @@ export function WasmTool({
|
|||
// Read-only sessions never bind presence, so the inspector's selection
|
||||
// store is fed by this minimal local handler (+ the C++ input hooks).
|
||||
const localSelectionRef = React.useRef<{ destroy(): void } | null>(null);
|
||||
// Read-only sessions boot with BOTH panels open as collapsed headers
|
||||
// (viewer-panels): discoverable without a trip through the menu; a stored
|
||||
// per-browser choice wins over the default.
|
||||
const [layersOpen, setLayersOpenState] = React.useState<boolean>(() => {
|
||||
try {
|
||||
return localStorage.getItem(LAYERS_OPEN_KEY) === "1";
|
||||
const stored = localStorage.getItem(LAYERS_OPEN_KEY);
|
||||
if (stored !== null) return stored === "1";
|
||||
} catch {
|
||||
return false;
|
||||
/* private mode */
|
||||
}
|
||||
return readOnly === true;
|
||||
});
|
||||
const setLayersOpen = React.useCallback((v: boolean) => {
|
||||
setLayersOpenState(v);
|
||||
|
|
@ -1199,10 +1204,12 @@ export function WasmTool({
|
|||
}, []);
|
||||
const [inspectorOpen, setInspectorOpenState] = React.useState<boolean>(() => {
|
||||
try {
|
||||
return localStorage.getItem(INSPECTOR_OPEN_KEY) === "1";
|
||||
const stored = localStorage.getItem(INSPECTOR_OPEN_KEY);
|
||||
if (stored !== null) return stored === "1";
|
||||
} catch {
|
||||
return false;
|
||||
/* private mode */
|
||||
}
|
||||
return readOnly === true;
|
||||
});
|
||||
const setInspectorOpen = React.useCallback((v: boolean) => {
|
||||
setInspectorOpenState(v);
|
||||
|
|
@ -2767,13 +2774,21 @@ export function WasmTool({
|
|||
inspector for canvas-only sessions — the React stand-ins for the wx
|
||||
Appearance/Properties panes that kicadSetChrome(false) hides. */}
|
||||
{ready && effectiveChromeHidden && layersOpen && layersMod && (
|
||||
<LayerPanel mod={layersMod} onClose={() => setLayersOpen(false)} />
|
||||
<LayerPanel
|
||||
mod={layersMod}
|
||||
defaultCollapsed={readOnly}
|
||||
onClose={() => setLayersOpen(false)}
|
||||
/>
|
||||
)}
|
||||
{ready &&
|
||||
effectiveChromeHidden &&
|
||||
inspectorOpen &&
|
||||
(tool === "pcbnew" || tool === "eeschema") && (
|
||||
<SelectionInspector doc={panelDoc} onClose={() => setInspectorOpen(false)} />
|
||||
<SelectionInspector
|
||||
doc={panelDoc}
|
||||
defaultCollapsed={readOnly}
|
||||
onClose={() => setInspectorOpen(false)}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* DEV: presence style tuner (VITE_PRESENCE_TUNER=1). */}
|
||||
|
|
|
|||
Loading…
Reference in a new issue