The groups of the inspector had a small grey label as their title, in the same grid as a property row, so with many rows one group hardly stood apart from the next. Now every group has a band as its head, from edge to edge of the panel, and a click on it folds the group. - A band runs across the padding of its column in the header colour, with the title and a mark that says whether the group is open. The titles stay as they are, as answered in the issue. - A click on a band folds its group: the rows go, the band stays and the next band moves up against it. A control in the band (Open, + Variable, + Property) does its own job and folds nothing. - A placed object has a group SWITCHES under its name, asset and parent: Enabled, Keep on scene load and the switches of a behavior. An asset has SWITCHES and PROPERTIES, and a group without rows is left out. - The same bands head the tilemap panel, the Variables tab, the side of the Pixel art tab and the shape list of vector art. A list with a title (Layers, Frames, Shapes) folds with a click on its header, clear of its plus. - ui/sections.ts holds the rules as pure functions, SectionStack is the column and SectionHeader the band. Every title of the inspector goes through addTitle or sectionHeader in inspector/host.ts. Decided here: - A fold is kept for good, in localStorage of this browser. The issue asked whether for the session or for good and the answer left it open. - A fold is remembered by title, per panel. PROPERTIES folded once is folded on every object, asset and node. A number in a title does not count. - A behavior asset has all its rows under SWITCHES. A Variables asset and a state machine get a group SCOPE. - A greyed out control in a band takes the click too. Without that the click fell through to the band and folded the group. - The Curve tab has no column of this kind and is unchanged. Found on the way: - The inspector of a sprite is taller than the 800 pixel window of the checks now. The hooks that hand out a row scroll it into view first, and module 28 scrolls before it looks for a row by its text. Tests: - Gate: 202 files, 3008 tests (2982 + 26), exit 0 - e2e: no complete run for this state. Modules 28 to 61 passed alone (749 checks). A full run was cut off in the accounts module at 1492 passes without a failure, the modules after it and the smokes are missing. The sum of 1639 (1635 + 4) is reckoned, CI measures it. - 26 unit tests in tests/editor/sections.test.ts - Four e2e checks with a real mouse, three in module 02 and one in module 48 - 25 unit counter-probes all fail tests, run on stein2 - Screenshots of the bands looked at before the last build, none after it Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
69 lines
2.1 KiB
TypeScript
69 lines
2.1 KiB
TypeScript
/**
|
||
* Which groups of rows are folded (#290), kept in this browser: a group folded once stays folded
|
||
* for every selection that shows it, and after a reload. Best effort, like the saves – a browser
|
||
* that refuses `localStorage` forgets with the page.
|
||
*/
|
||
import { Theme } from '../ui/Theme';
|
||
|
||
const STORAGE_KEY = 'game-maker.folded-sections';
|
||
|
||
let folded: Set<string> | null = null;
|
||
|
||
function storage(): Pick<Storage, 'getItem' | 'setItem'> | null {
|
||
try {
|
||
return typeof window === 'undefined' ? null : window.localStorage;
|
||
} catch {
|
||
return null;
|
||
}
|
||
}
|
||
|
||
function read(): Set<string> {
|
||
try {
|
||
const stored: unknown = JSON.parse(storage()?.getItem(STORAGE_KEY) ?? '[]');
|
||
return new Set(Array.isArray(stored) ? stored.filter((key) => typeof key === 'string') : []);
|
||
} catch {
|
||
return new Set();
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Names a group: the panel it stands in and its title, numbers left out – "3 OBJECTS SELECTED" and
|
||
* "KEY AT FRAME 12" are the same group whatever they count.
|
||
*/
|
||
export function foldKey(scope: string, title: string): string {
|
||
return `${scope}:${title.replace(/\d+/g, '#')}`;
|
||
}
|
||
|
||
export function isFolded(key: string): boolean {
|
||
folded ??= read();
|
||
return folded.has(key);
|
||
}
|
||
|
||
export function setFolded(key: string, on: boolean): void {
|
||
folded ??= read();
|
||
if (on) folded.add(key);
|
||
else folded.delete(key);
|
||
try {
|
||
storage()?.setItem(STORAGE_KEY, JSON.stringify([...folded].sort()));
|
||
} catch {
|
||
// Kept for this page only.
|
||
}
|
||
}
|
||
|
||
/**
|
||
* What a list with a header of its own takes to fold like the groups of the column it stands in:
|
||
* the band reaches across the column's padding, and the fold is remembered under the list's title.
|
||
*/
|
||
export function listFold(
|
||
scope: string,
|
||
title: string,
|
||
): { bleed: number; collapsed: boolean; onFold: (collapsed: boolean) => void } {
|
||
const key = foldKey(scope, title.toUpperCase());
|
||
return { bleed: Theme.padding, collapsed: isFolded(key), onFold: (on) => setFolded(key, on) };
|
||
}
|
||
|
||
/** Forgets what was read, so the next question asks the browser again (tests). */
|
||
export function resetFolds(): void {
|
||
folded = null;
|
||
}
|