Group the asset panel by category, then by type
The asset panel listed one heading per type, 30 of them side by side. Now the types sit under five categories, and each type heading reads like its entry in the + menu. - ASSET_GROUPS replaces ASSET_GROUP_LABELS and gives every type a name and a category: Basics, Visuals, Logics, Physics or Audio. - The categories keep that fixed order. The groups in a category and the assets in a group stay sorted by name. - A category heading folds like a group heading. A project opens with every category open and every group folded, as before. - Selecting an asset unfolds its category and its group. Folding the category of the selected asset lets go of the selection. - The headings are singular now (Scene, Sprite, Sound FX). The old plural names only kept them apart from the + menu entries. - locate looks in the popup layer first. A click by text meant for a menu entry hit the heading of the same name, since the panel is drawn first. Below an open menu a click only closes the menu anyway. - New test hook __gm.assetCategories(). Tests: - Unit asset-groups: 6 green, counter-check (empty categories kept) 1 red - e2e 04: ALL PASSED, 63 checks (1 new). Counter-check with locate taking the first hit again: 2 red, the click on Behavior created no asset - Gate: CHECK EXIT 0, 261 files, 4040 tests - The category names got their final plural form after these runs. The unit test ran again on them (6 green), e2e 04 did not. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
8154569405
commit
5e09ba9a50
6 files changed
+239
-107
No files matched your search
+50
-13
@@ -348,29 +348,39 @@ export async function assets(t) {
|
||||
`${await assetColor('crate')} props=${JSON.stringify((await asset('crate')).props)}`,
|
||||
);
|
||||
|
||||
// 4g. The list is grouped by type (#176): a heading per type, groups and the assets in them
|
||||
// alphabetical, each heading with an icon of its own. A freshly opened project shows every
|
||||
// group folded – reloaded here, since the modules before have unfolded groups by selecting.
|
||||
// 4g. The list is grouped by category and in it by type (#176): a heading per category, under it
|
||||
// a heading per type, groups and the assets in them alphabetical, each type heading with an
|
||||
// icon of its own. A freshly opened project shows every category open and every group folded
|
||||
// – reloaded here, since the modules before have unfolded groups by selecting.
|
||||
await evaluate('__gm.state.loadProject(__gm.state.project, __gm.state.revision)');
|
||||
await sleep(100);
|
||||
const groups = await json('__gm.assetGroups()');
|
||||
const categories = await json('__gm.assetCategories()');
|
||||
const nameOf = Object.fromEntries(
|
||||
(await json('__gm.state.project.assets.map((a) => [a.id, a.name])')).map(([id, name]) => [id, name]),
|
||||
);
|
||||
const collator = new Intl.Collator('en', { numeric: true, sensitivity: 'base' });
|
||||
const sorted = (list) => list.every((v, i) => i === 0 || collator.compare(list[i - 1], v) <= 0);
|
||||
const ORDER = ['Basics', 'Visuals', 'Logics', 'Physics', 'Audio'];
|
||||
check(
|
||||
'the assets panel groups by type, groups and assets sorted by name',
|
||||
'the assets panel groups by category and type, groups and assets sorted by name',
|
||||
groups.length >= 4 &&
|
||||
sorted(groups.map((g) => g.label)) &&
|
||||
categories.length >= 2 &&
|
||||
sorted(categories.map((c) => ORDER.indexOf(c.category)).map(String)) &&
|
||||
categories.every((c) => sorted(groups.filter((g) => g.category === c.category).map((g) => g.label))) &&
|
||||
groups.every((g) => sorted(g.ids.map((id) => nameOf[id]))) &&
|
||||
groups.flatMap((g) => g.ids).length === Object.keys(nameOf).length,
|
||||
JSON.stringify(groups.map((g) => [g.label, g.ids.map((id) => nameOf[id])])),
|
||||
JSON.stringify({
|
||||
categories,
|
||||
groups: groups.map((g) => [g.category, g.label, g.ids.map((id) => nameOf[id])]),
|
||||
}),
|
||||
);
|
||||
check(
|
||||
'a loaded project shows every group folded',
|
||||
groups.every((g) => !g.expanded) && (await json("__gm.assetRowShown('crate')")) === null,
|
||||
JSON.stringify(groups.map((g) => [g.label, g.expanded])),
|
||||
'a loaded project shows every category open and every group folded',
|
||||
categories.every((c) => c.expanded) &&
|
||||
groups.every((g) => !g.expanded) &&
|
||||
(await json("__gm.assetRowShown('crate')")) === null,
|
||||
JSON.stringify({ categories, groups: groups.map((g) => [g.label, g.expanded]) }),
|
||||
);
|
||||
// Read in the drawn picture: every icon inks its 16 units in the muted colour, and no two alike.
|
||||
// Every pixel against the row's own background (`+`), since a 1.5 unit outline on half pixels is
|
||||
@@ -411,7 +421,7 @@ export async function assets(t) {
|
||||
Boolean(unfolded.heading) &&
|
||||
unfolded.heading.y < unfolded.crate.y &&
|
||||
unfolded.selected === 'cam' &&
|
||||
(await has('Sprites')),
|
||||
(await has('Sprite')),
|
||||
JSON.stringify(unfolded),
|
||||
);
|
||||
await shot('15c-asset-groups');
|
||||
@@ -442,6 +452,29 @@ export async function assets(t) {
|
||||
released.expanded === false && released.crate === null && released.selected === undefined,
|
||||
JSON.stringify(released),
|
||||
);
|
||||
// A category folds the same way: its type headings go, and selecting an asset in it opens it again.
|
||||
const visual = await json("__gm.assetRowShown('category:Visuals')");
|
||||
await click(visual.x, visual.y);
|
||||
const visualFolded = {
|
||||
category: (await json('__gm.assetCategories()')).find((c) => c.category === 'Visuals'),
|
||||
sprites: await json("__gm.assetRowShown('group:sprite')"),
|
||||
};
|
||||
await select({ kind: 'asset', assetId: 'crate' });
|
||||
const visualAgain = {
|
||||
category: (await json('__gm.assetCategories()')).find((c) => c.category === 'Visuals'),
|
||||
crate: await json("__gm.assetRowShown('crate')"),
|
||||
};
|
||||
check(
|
||||
'a click on a category heading folds its groups away, selecting an asset in it unfolds it',
|
||||
visualFolded.category?.expanded === false &&
|
||||
visualFolded.sprites === null &&
|
||||
visualAgain.category?.expanded === true &&
|
||||
Boolean(visualAgain.crate),
|
||||
JSON.stringify({ visualFolded, visualAgain }),
|
||||
);
|
||||
await select(null);
|
||||
const spritesAgain = await json("__gm.assetRow('group:sprite')");
|
||||
await click(spritesAgain.x, spritesAgain.y);
|
||||
|
||||
// 4h. The same icons elsewhere: in front of every type in the `+` menu, and at the right edge of
|
||||
// a scene object's row, where its type used to be spelled out.
|
||||
@@ -518,12 +551,16 @@ async function addMenuOnRightClick(t) {
|
||||
);
|
||||
await shot('11c-add-menu-on-right-click');
|
||||
const before = (await json('__gm.state.project.assets')).length;
|
||||
// The heading of the behaviors reads like the entry and is drawn first: the entry still wins.
|
||||
const behaviorWords = (await json("__gm.locateAll('Behavior')")).length;
|
||||
await clickText('Behavior');
|
||||
const fromMenu = await json('__gm.state.project.assets.at(-1)');
|
||||
check(
|
||||
'an entry of that menu creates the asset like the + button does',
|
||||
(await json('__gm.state.project.assets')).length === before + 1 && fromMenu.type === 'behavior',
|
||||
JSON.stringify(fromMenu && { type: fromMenu.type, name: fromMenu.name }),
|
||||
'an entry of that menu creates the asset like the + button does, even under a heading of the same word',
|
||||
(await json('__gm.state.project.assets')).length === before + 1 &&
|
||||
fromMenu.type === 'behavior' &&
|
||||
behaviorWords >= 2,
|
||||
JSON.stringify({ fromMenu: fromMenu && { type: fromMenu.type, name: fromMenu.name }, behaviorWords }),
|
||||
);
|
||||
await undo();
|
||||
await showTab('scene');
|
||||
|
||||
Reference in new issue
Block a user