- Graph: nodes.ts split into nodes/<category>/<Type>.ts (62 files) with shared.ts factories and index.ts registry; NodeContext/values/NodeRegistry separated, writeTyped() replaces duplicated typed writes - Comments: one style (JSDoc on non-obvious exports, // for decisions and traps), wrong/stale comments fixed or removed, ids.ts now real 10 base-36 chars - Core: chainOf, shared worldPosition/editorPosition core, commands/require.ts, commands/naming.ts, util/list.ts, setBagValue/toPosition/propDef; dead code removed (isBehaviorAsset, SCENE_OBJECT_SCHEMA, CommandStack.subscribe, Input.axis, setMode, titleText, World.worldPosition, unused ctor params) - Editor: Inspector split into inspector/ sections, App.ts into Editor.ts + debugHook.ts + shortcuts.ts, Viewport into items.ts + symbols.ts, shared "Add collider" action, unified type labels/swatches - UI toolkit: Widget.trackHover/onPointerRelease, drawControlBox, math helpers, Popup.openBelow, Menu/Dialog/ColorPicker/ListRow in own files, named Theme sizes - Tools: seed-demo/smoke-demo and demo-check project removed, tools/lib (harness, projects, builders), e2e.mjs as runner over tools/e2e/*.mjs sections - Tests: tests/helpers.ts replaces copied fixtures and NodeContext doubles Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
61 lines
2.0 KiB
TypeScript
61 lines
2.0 KiB
TypeScript
import { DeleteAssetCommand } from '../core/commands/assetCommands';
|
|
import { RemoveNodeCommand } from '../core/commands/graphCommands';
|
|
import { findAsset } from '../core/model/Project';
|
|
import { Dialog, ui } from '../ui';
|
|
import { createContainer, removeObjects } from './actions';
|
|
import type { EditorState } from './state';
|
|
|
|
export interface ShortcutActions {
|
|
play(): void;
|
|
stop(): void;
|
|
}
|
|
|
|
/**
|
|
* Global keyboard shortcuts (see README "Keyboard"). Text fields keep their own key handling;
|
|
* these only run when no focused widget consumed the key.
|
|
*/
|
|
export function installShortcuts(state: EditorState, actions: ShortcutActions): void {
|
|
ui().focus.globalHandlers.push((event) => {
|
|
const mod = event.ctrlKey || event.metaKey;
|
|
const key = event.key.toLowerCase();
|
|
if (event.key === 'Escape' && state.mode === 'play') {
|
|
actions.stop();
|
|
return true;
|
|
}
|
|
if (mod && key === 'z') {
|
|
if (event.shiftKey) state.redo();
|
|
else state.undo();
|
|
return true;
|
|
}
|
|
if (mod && key === 'y') {
|
|
state.redo();
|
|
return true;
|
|
}
|
|
if (mod && key === 'g') {
|
|
const s = state.selection;
|
|
if (s?.kind === 'object') createContainer(state, s.sceneId, s.objectIds);
|
|
return true;
|
|
}
|
|
if (event.key === 'Delete' || event.key === 'Backspace') {
|
|
const s = state.selection;
|
|
if (s?.kind === 'object') removeObjects(state, s.sceneId, s.objectIds);
|
|
else if (s?.kind === 'node') state.execute(new RemoveNodeCommand(s.assetId, s.nodeId));
|
|
else if (s?.kind === 'asset') {
|
|
const asset = findAsset(state.project, s.assetId);
|
|
if (asset) {
|
|
new Dialog(`Delete "${asset.name}"?`, 'Placed instances are removed as well.', [
|
|
{ label: 'Cancel' },
|
|
{ label: 'Delete', danger: true, onClick: () => state.execute(new DeleteAssetCommand(s.assetId)) },
|
|
]).openCentered();
|
|
}
|
|
}
|
|
return s !== null;
|
|
}
|
|
if (mod && event.key === 'Enter' && state.mode === 'edit') {
|
|
actions.play();
|
|
return true;
|
|
}
|
|
return false;
|
|
});
|
|
}
|