Pick a new asset from category submenus in the + menu
The + menu was one long list in two groups, placeable types and library ones. That split is no longer wanted. The menu now lists the categories of the asset panel, and each opens its types beside it, so a new asset is filed where the panel shows it. - Menu learns submenus: an entry with submenu instead of onSelect opens a second Menu right of the first, or left of it where the canvas ends. Its first entry lines up with the row that opened it. - A submenu opens on hover or click. Resting on another entry swaps or closes it after 150 ms, so a pointer cutting across the rows on its way into a submenu does not lose it. - The row of an open submenu stays lit, and a triangle pointing right marks every such row. - A submenu has no backdrop and no Escape of its own. Popup takes a backdrop option for that. The first menu's backdrop and Escape close the whole stack, and picking an entry does the same. - assetMenuCategories replaces assetMenuGroups and takes categories and their order from ASSET_GROUPS. The types are sorted by label. - New test hooks __gm.menuSubmenus() and __gm.addMenuCategory(). pickAssetType opens the category first, so the modules that pick a type through it need no change of their own. Modules 04, 10, 47, 48, 53 and 54 open the category themselves where they read the menu. Tests: - Unit asset-menu: 4 green - e2e 04: ALL PASSED, 63 checks. New: categories in panel order, each type once and sorted, submenu placement, hover swaps the submenu, picking closes menu and submenu - Gate: CHECK EXIT 0, 261 files, 4040 tests - The other modules that use the + menu did not run here Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
5e09ba9a50
commit
a848cf8469
16 files changed
+320
-131
No files matched your search
@@ -37,7 +37,7 @@ import {
|
||||
} from '../../ui';
|
||||
import { type AssetCategory, ASSET_GROUPS, groupAssets } from './assetGroups';
|
||||
import { ASSET_ICON_SVG } from './assetIcons';
|
||||
import { assetMenuGroups } from './assetMenu';
|
||||
import { assetMenuCategories } from './assetMenu';
|
||||
import { TOOLBAR_ICON_SVG } from './toolbarIcons';
|
||||
import { activateAsset, confirmDeleteAsset } from '../actions';
|
||||
import { openReferenceItems } from '../objectMenu';
|
||||
@@ -125,16 +125,16 @@ export class AssetPanel extends Panel {
|
||||
return this.list.pingState();
|
||||
}
|
||||
|
||||
/** One entry per asset type, the groups of `assetMenuGroups` kept apart by a line. */
|
||||
/** One entry per category of the panel, each opening the types in it (`assetMenuCategories`). */
|
||||
private addMenuEntries(): MenuEntry[] {
|
||||
const entry = (type: AssetType): MenuEntry => ({
|
||||
label: ASSET_TYPE_LABELS[type],
|
||||
icon: ASSET_ICON_SVG[type],
|
||||
onSelect: () => this.addAsset(type),
|
||||
});
|
||||
return assetMenuGroups().flatMap((group, index) =>
|
||||
index === 0 ? group.map(entry) : [{ separator: true }, ...group.map(entry)],
|
||||
);
|
||||
return assetMenuCategories().map(({ category, types }) => ({
|
||||
label: category,
|
||||
submenu: types.map((type) => ({
|
||||
label: ASSET_TYPE_LABELS[type],
|
||||
icon: ASSET_ICON_SVG[type],
|
||||
onSelect: () => this.addAsset(type),
|
||||
})),
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,17 +1,18 @@
|
||||
import { ASSET_TYPE_LABELS, ASSET_TYPES, type AssetType, isPlaceableType } from '../../core/model/AssetTypes';
|
||||
import { ASSET_TYPE_LABELS, ASSET_TYPES, type AssetType } from '../../core/model/AssetTypes';
|
||||
import { type AssetCategory, ASSET_CATEGORIES, ASSET_GROUPS } from './assetGroups';
|
||||
|
||||
/**
|
||||
* The `+` menu in two alphabetical groups: what goes into a scene, then what only lives in the
|
||||
* library. At fifteen types declaration order is nothing anyone can search by eye, and the group
|
||||
* answers the question that comes right after "which type?" – whether the thing will be placed.
|
||||
* The `+` menu: one entry per category of the asset panel, in the panel's order, each opening the
|
||||
* types of that category sorted by label. The menu and the panel file a type under the same
|
||||
* category, so what you made is found where you would look for it.
|
||||
*
|
||||
* Ordered here and not in `ASSET_TYPES`: that array is also the serialisation and validation order,
|
||||
* and at least one check reads a list derived from it.
|
||||
*/
|
||||
export function assetMenuGroups(): AssetType[][] {
|
||||
export function assetMenuCategories(): { category: AssetCategory; types: AssetType[] }[] {
|
||||
const byLabel = (a: AssetType, b: AssetType) => ASSET_TYPE_LABELS[a].localeCompare(ASSET_TYPE_LABELS[b]);
|
||||
return [
|
||||
ASSET_TYPES.filter(isPlaceableType).sort(byLabel),
|
||||
ASSET_TYPES.filter((type) => !isPlaceableType(type)).sort(byLabel),
|
||||
];
|
||||
return ASSET_CATEGORIES.map((category) => ({
|
||||
category,
|
||||
types: ASSET_TYPES.filter((type) => ASSET_GROUPS[type].category === category).sort(byLabel),
|
||||
})).filter((entry) => entry.types.length > 0);
|
||||
}
|
||||
Reference in new issue
Block a user