Asset panel: double-click opens the tab, single click only selects (#71)
game-maker / suite (push) Failing after 8m11s
game-maker / suite (push) Failing after 8m11s
One rule for every asset type: a click on an asset row selects it, a double-click (or Enter, or creating it via +) opens its tab. Selection alone still switches tabs only for nodes, frames and template objects. - state.openAsset(id) = select + 'open' event; the editor maps the asset type to its tab via TAB_OF_ASSET. The two special clauses for state machine and shader assets in the selection handler are gone. - Creating via + and importing a texture from the picker open the tab; paste/drop of an image only selects. - 3 unit tests (event order, re-open of a selected asset, selection never fires open), 14 e2e checks in step 4. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
1 parent
3da0ef1be2
commit
4d9c4982eb
7 files changed
+160
-15
No files matched your search
+28
-9
@@ -1,4 +1,5 @@
|
||||
import type { Application } from 'pixi.js';
|
||||
import type { AssetType } from '../core/model/AssetTypes';
|
||||
import { findAsset, findObjectHolder } from '../core/model/Project';
|
||||
import type { ProjectStore } from '../core/store/ProjectStore';
|
||||
import type { TextureStore } from '../core/store/TextureStore';
|
||||
@@ -52,6 +53,17 @@ const TAB_LABELS: Record<CentreTab, string> = {
|
||||
list: 'List',
|
||||
};
|
||||
|
||||
/** Where an opened asset goes; a placeable one (sprite, camera, text, collider) has no tab of its own. */
|
||||
const TAB_OF_ASSET: Partial<Record<AssetType, CentreTab>> = {
|
||||
scene: 'scene',
|
||||
behavior: 'graph',
|
||||
statemachine: 'statemachine',
|
||||
shader: 'shader',
|
||||
texture: 'texture',
|
||||
animation: 'animation',
|
||||
container: 'container',
|
||||
};
|
||||
|
||||
const MIN_LEFT_WIDTH = 160;
|
||||
const MAX_LEFT_WIDTH = 500;
|
||||
const MIN_RIGHT_WIDTH = 200;
|
||||
@@ -222,17 +234,18 @@ export class Editor implements PlayHost {
|
||||
});
|
||||
app.ticker.add((ticker) => this.tick(ticker.deltaMS / 1000));
|
||||
// A node, a frame and a template object exist only in their own tab, so selecting one
|
||||
// switches to it; selecting the behavior, texture or container asset itself does not. A state
|
||||
// machine or shader asset does switch (user's choice): its tab is the only place it shows anything.
|
||||
// switches to it. Selecting an asset never does: that is what opening it (double-click,
|
||||
// Enter, creating it) is for – see the `open` event below.
|
||||
state.on('selection', () => {
|
||||
const s = state.selection;
|
||||
const machine = state.selectedStateMachineId !== null;
|
||||
const shader = state.selectedShaderId !== null;
|
||||
if (s?.kind === 'node' && !machine && !shader && this.tab !== 'graph') this.showTab('graph');
|
||||
if ((s?.kind === 'node' || s?.kind === 'asset') && machine && this.tab !== 'statemachine')
|
||||
this.showTab('statemachine');
|
||||
if ((s?.kind === 'node' || s?.kind === 'asset') && shader && this.tab !== 'shader')
|
||||
this.showTab('shader');
|
||||
if (s?.kind === 'node') {
|
||||
const tab = state.selectedStateMachineId
|
||||
? 'statemachine'
|
||||
: state.selectedShaderId
|
||||
? 'shader'
|
||||
: 'graph';
|
||||
if (this.tab !== tab) this.showTab(tab);
|
||||
}
|
||||
if (s?.kind === 'frame' && this.tab !== 'texture') this.showTab('texture');
|
||||
if (
|
||||
s?.kind === 'object' &&
|
||||
@@ -241,6 +254,12 @@ export class Editor implements PlayHost {
|
||||
)
|
||||
this.showTab('container');
|
||||
});
|
||||
state.on('open', () => {
|
||||
const s = state.selection;
|
||||
const tab =
|
||||
s?.kind === 'asset' ? TAB_OF_ASSET[findAsset(state.project, s.assetId)?.type ?? 'sprite'] : undefined;
|
||||
if (tab) this.showTab(tab);
|
||||
});
|
||||
// Opening a list is a request to look at it, so this switches even when the slot is unchanged.
|
||||
state.on('list', () => {
|
||||
if (state.listSlot) this.showTab('list');
|
||||
|
||||
@@ -85,15 +85,14 @@ export class AssetPanel extends Panel {
|
||||
|
||||
private createAsset(type: AssetType, name = ASSET_TYPE_LABELS[type], props: PropertyBag = {}): void {
|
||||
const command = new CreateAssetCommand(type, nextAssetName(this.state.project, name), props);
|
||||
if (this.state.execute(command)) this.state.select({ kind: 'asset', assetId: command.asset.id });
|
||||
if (this.state.execute(command)) this.state.openAsset(command.asset.id);
|
||||
}
|
||||
|
||||
/** One undo step for the asset and its frames. */
|
||||
private createAnimation(texture: TextureAsset): void {
|
||||
const create = new CreateAssetCommand('animation', nextAssetName(this.state.project, texture.name));
|
||||
const frames = new AddAnimationFramesCommand(create.asset.id, framesOfTexture(texture));
|
||||
if (this.state.execute(oneStep(create.label, [create, frames])))
|
||||
this.state.select({ kind: 'asset', assetId: create.asset.id });
|
||||
if (this.state.execute(oneStep(create.label, [create, frames]))) this.state.openAsset(create.asset.id);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -108,10 +107,11 @@ export class AssetPanel extends Panel {
|
||||
openImagePicker({ state: this.state, textures: this.textures });
|
||||
}
|
||||
|
||||
/** Double-click / Enter on a scene makes it the active one. Placing is a drag, not a click. */
|
||||
/** Double-click / Enter opens the asset's tab; a scene also becomes the active one. Placing is a drag. */
|
||||
private activate(id: string): void {
|
||||
const asset = findAsset(this.state.project, id);
|
||||
if (asset?.type === 'scene') this.state.execute(new SetActiveSceneCommand(id));
|
||||
this.state.openAsset(id);
|
||||
}
|
||||
|
||||
/** Where an asset's row sits on screen, so a check can pick it up by id (test hook). */
|
||||
|
||||
@@ -83,6 +83,8 @@ export type EditorEvent =
|
||||
| 'selection'
|
||||
/** The List tab was pointed at another list (or at none). */
|
||||
| 'list'
|
||||
/** An asset was opened (double-click, Enter, or just created): its tab should show. */
|
||||
| 'open'
|
||||
| 'mode'
|
||||
| 'save'
|
||||
| 'notice'
|
||||
@@ -267,6 +269,16 @@ export class EditorState {
|
||||
this.emit('list');
|
||||
}
|
||||
|
||||
/**
|
||||
* Selects the asset and asks for its tab. Selecting alone never switches tabs (a click in the
|
||||
* Assets panel is a look at the inspector); this is the way a double-click or a fresh asset gets
|
||||
* there, and like `openList` it works without knowing the `Editor`.
|
||||
*/
|
||||
openAsset(assetId: string): void {
|
||||
this.select({ kind: 'asset', assetId });
|
||||
this.emit('open');
|
||||
}
|
||||
|
||||
select(selection: Selection): void {
|
||||
// Test hooks pass the short form `{ kind: 'object', holderId, objectId }` without `objectIds`.
|
||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
||||
|
||||
@@ -156,7 +156,9 @@ function adopt(
|
||||
*/
|
||||
export function importStoredImage(image: UploadedTexture, deps: TextureImportDeps): ImportOutcome {
|
||||
const outcome = adopt(image, 'Texture', image.file, deps);
|
||||
selectLast([outcome], deps);
|
||||
// Chosen in the picker, so this is "+ → Texture": the tab opens like for any created asset. A
|
||||
// pasted or dropped picture (`importImages`) only selects – it usually goes into the scene.
|
||||
if (outcome.assetId) deps.state.openAsset(outcome.assetId);
|
||||
deps.state.setNotice(
|
||||
outcome.error ??
|
||||
(outcome.reused ? 'That image is already in the project.' : 'Added the image to the project.'),
|
||||
|
||||
Reference in new issue
Block a user