@forgeax/interface 0.4.1 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +51 -0
- package/dist/package/browser/ApplicationShell.js +1 -1
- package/dist/package/browser/appHostBootstrap.js +0 -2
- package/dist/package/browser/application.d.ts +0 -10
- package/dist/package/browser/application.js +0 -10
- package/dist/package/browser/components/CommandPalette/CommandPalette.js +1 -1
- package/dist/package/browser/core/app-shell/host.js +5 -2
- package/dist/package/browser/core/app-shell/types.d.ts +3 -1
- package/dist/package/browser/core/extensions/builtin-commands.js +1 -0
- package/dist/package/browser/core/extensions/trajectory.js +1 -1
- package/dist/package/browser/i18n/locales/en.json +0 -10
- package/dist/package/browser/i18n/locales/zh.json +0 -10
- package/dist/package/browser/lib/action-dom-discovery.js +1 -1
- package/dist/package/browser/lib/ai-intents.js +1 -1
- package/dist/package/browser/lib/builtin-actions.js +1 -1
- package/dist/package/browser/lib/global-shortcuts.d.ts +5 -74
- package/dist/package/browser/lib/global-shortcuts.js +29 -228
- package/dist/package/browser/lib/ui-action-highlight.js +1 -1
- package/dist/package/browser/lib/ui-bridge.js +1 -1
- package/dist/package/browser/lib/ui-trajectory.js +1 -1
- package/dist/package/browser/lib/vag-action-bridge.js +1 -1
- package/dist/package/node/ApplicationShell.js +1 -1
- package/dist/package/node/appHostBootstrap.js +0 -2
- package/dist/package/node/application.js +0 -10
- package/dist/package/node/components/CommandPalette/CommandPalette.js +1 -1
- package/dist/package/node/core/app-shell/host.js +5 -2
- package/dist/package/node/core/extensions/builtin-commands.js +1 -0
- package/dist/package/node/core/extensions/trajectory.js +1 -1
- package/dist/package/node/i18n/locales/en.json +0 -10
- package/dist/package/node/i18n/locales/zh.json +0 -10
- package/dist/package/node/lib/action-dom-discovery.js +1 -1
- package/dist/package/node/lib/ai-intents.js +1 -1
- package/dist/package/node/lib/builtin-actions.js +1 -1
- package/dist/package/node/lib/global-shortcuts.js +29 -228
- package/dist/package/node/lib/ui-action-highlight.js +1 -1
- package/dist/package/node/lib/ui-bridge.js +1 -1
- package/dist/package/node/lib/ui-trajectory.js +1 -1
- package/dist/package/node/lib/vag-action-bridge.js +1 -1
- package/package.json +2 -2
- package/dist/package/browser/core/extensions/editor-commands.d.ts +0 -2
- package/dist/package/browser/core/extensions/editor-commands.js +0 -182
- package/dist/package/browser/lib/action-registry.d.ts +0 -94
- package/dist/package/browser/lib/action-registry.js +0 -210
- package/dist/package/node/core/extensions/editor-commands.js +0 -182
- package/dist/package/node/lib/action-registry.js +0 -210
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* Ctrl+/ focus chat composer
|
|
11
11
|
* Ctrl+Shift+H open Settings → Changelog (was Ctrl+H before 2026-08-04 —
|
|
12
12
|
* UE-parity editor hide claimed Ctrl+H for "show all hidden")
|
|
13
|
-
* Esc
|
|
13
|
+
* Esc close current overlay (higher-priority owners may claim it)
|
|
14
14
|
* (Settings → Dashboard → Fullscreen)
|
|
15
15
|
*
|
|
16
16
|
* IME 安全:
|
|
@@ -77,16 +77,9 @@ function mod(e) {
|
|
|
77
77
|
}
|
|
78
78
|
// Is the scene editor the surface the user is currently looking at?
|
|
79
79
|
//
|
|
80
|
-
//
|
|
81
|
-
//
|
|
82
|
-
//
|
|
83
|
-
// not the foreground surface. Two facts, both interface-local (no editor
|
|
84
|
-
// import, no focus/DOM resolver — that larger redesign is ADR-0029 scope; this
|
|
85
|
-
// is its Phase 0 short-term mitigation):
|
|
86
|
-
// 1. an overlay (Dashboard / Settings) is covering the shell, or
|
|
87
|
-
// 2. the editor Page anchor is not currently visible.
|
|
88
|
-
// Read live at event time (cached snapshot — cheap) so switching tab / opening
|
|
89
|
-
// an overlay takes effect immediately.
|
|
80
|
+
// Contributions classified as edit use the product shell's existing surface
|
|
81
|
+
// identity and visibility policy. Read at event time so page/overlay changes
|
|
82
|
+
// take effect without rebuilding the host or listener.
|
|
90
83
|
export function isEditorSurfaceActive() {
|
|
91
84
|
if (useShellStore.getState().activeOverlay)
|
|
92
85
|
return false;
|
|
@@ -95,7 +88,6 @@ export function isEditorSurfaceActive() {
|
|
|
95
88
|
: document.querySelector('[data-surface-anchor="edit"]');
|
|
96
89
|
return Boolean(anchor && anchor.getClientRects().length > 0);
|
|
97
90
|
}
|
|
98
|
-
let routerDeps = null;
|
|
99
91
|
const transientKeydownHandlers = new Set();
|
|
100
92
|
export function registerGlobalKeydownHandler(handler) {
|
|
101
93
|
transientKeydownHandlers.add(handler);
|
|
@@ -108,204 +100,9 @@ export function dispatchGlobalKeydownHandlers(event) {
|
|
|
108
100
|
}
|
|
109
101
|
return false;
|
|
110
102
|
}
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
export function registerKeyboardRouterDeps(deps) {
|
|
115
|
-
routerDeps = deps;
|
|
116
|
-
}
|
|
117
|
-
/** Read the currently injected router deps. Returns null until the host has
|
|
118
|
-
* called registerKeyboardRouterDeps() at boot. Command-bus wrappers
|
|
119
|
-
* (editor-commands extension) resolve deps lazily via this getter so they
|
|
120
|
-
* stay editor-agnostic and boot-order-safe. */
|
|
121
|
-
export function getKeyboardRouterDeps() {
|
|
122
|
-
return routerDeps;
|
|
123
|
-
}
|
|
124
|
-
// Build the edit-domain shortcut list from injected deps. Pure dispatcher: every
|
|
125
|
-
// branch routes through a dep callback (which the editor maps onto gateway ops),
|
|
126
|
-
// so this file stays editor-agnostic. Three-layer guards (IME / typing-target /
|
|
127
|
-
// play-mode) are enforced by the host's onKey wrapper (isComposing / isTypingTarget)
|
|
128
|
-
// plus the per-op play-mode checks below.
|
|
129
|
-
function editShortcuts(deps) {
|
|
130
|
-
const routeCtrlD = () => {
|
|
131
|
-
const domain = deps.getLastSelectionDomain() ?? 'entity';
|
|
132
|
-
if (domain === 'asset') {
|
|
133
|
-
for (const a of deps.getAssetSelection())
|
|
134
|
-
deps.duplicateAsset(a.guid, a.packPath);
|
|
135
|
-
return true;
|
|
136
|
-
}
|
|
137
|
-
const ids = deps.getEntitySelection();
|
|
138
|
-
if (ids.length > 0) {
|
|
139
|
-
deps.duplicateEntities(ids);
|
|
140
|
-
return true;
|
|
141
|
-
}
|
|
142
|
-
return false;
|
|
143
|
-
};
|
|
144
|
-
// UE-parity editor hide (docs 2026-08-04-editor-hide-ue-parity-plan M2):
|
|
145
|
-
// H hides the selection, Ctrl+H shows every hidden entity, Shift+H hides
|
|
146
|
-
// the unselected (isolate). Entity-domain only; under Play the game keeps
|
|
147
|
-
// its keys (same guard as entity Delete).
|
|
148
|
-
const routeH = () => {
|
|
149
|
-
if (deps.isPlayMode())
|
|
150
|
-
return false;
|
|
151
|
-
const ids = deps.getEntitySelection();
|
|
152
|
-
if (ids.length === 0)
|
|
153
|
-
return false;
|
|
154
|
-
deps.hideEntities(ids);
|
|
155
|
-
return true;
|
|
156
|
-
};
|
|
157
|
-
const routeShiftH = () => {
|
|
158
|
-
if (deps.isPlayMode())
|
|
159
|
-
return false;
|
|
160
|
-
if (deps.getEntitySelection().length === 0)
|
|
161
|
-
return false;
|
|
162
|
-
deps.hideUnselected();
|
|
163
|
-
return true;
|
|
164
|
-
};
|
|
165
|
-
const routeCtrlH = () => {
|
|
166
|
-
if (deps.isPlayMode())
|
|
167
|
-
return false;
|
|
168
|
-
deps.showAllHidden();
|
|
169
|
-
return true;
|
|
170
|
-
};
|
|
171
|
-
// Viewport Game View toggle. Plain G is editor-only while not playing so a
|
|
172
|
-
// running game retains its gameplay binding; Shift+G remains the explicit
|
|
173
|
-
// Play shortcut and is also available in Edit Viewport.
|
|
174
|
-
const routeShiftG = () => {
|
|
175
|
-
deps.dispatch({ kind: 'setDisplay', display: deps.getDisplay() === 'game' ? 'scene' : 'game' }, 'human');
|
|
176
|
-
return true;
|
|
177
|
-
};
|
|
178
|
-
const routeEditorOwnedPlayKey = (e) => {
|
|
179
|
-
if (!e)
|
|
180
|
-
return true;
|
|
181
|
-
deps.handleViewportKeyDown(e);
|
|
182
|
-
return true;
|
|
183
|
-
};
|
|
184
|
-
const routeViewportInput = (e) => {
|
|
185
|
-
if (!e)
|
|
186
|
-
return false;
|
|
187
|
-
if (deps.getInputTarget() === 'game')
|
|
188
|
-
return false;
|
|
189
|
-
deps.handleViewportKeyDown(e);
|
|
190
|
-
return true;
|
|
191
|
-
};
|
|
192
|
-
return [
|
|
193
|
-
{
|
|
194
|
-
combo: 'Ctrl+D',
|
|
195
|
-
group: 'edit',
|
|
196
|
-
label: t('shortcuts.duplicateSelection'),
|
|
197
|
-
match: (e) => mod(e) && !e.shiftKey && !e.altKey
|
|
198
|
-
&& (e.code === 'KeyD' || safeKeyLower(e) === 'd'),
|
|
199
|
-
run: routeCtrlD,
|
|
200
|
-
},
|
|
201
|
-
{
|
|
202
|
-
combo: 'H',
|
|
203
|
-
group: 'edit',
|
|
204
|
-
label: t('shortcuts.hideSelected'),
|
|
205
|
-
match: (e) => !mod(e) && !e.shiftKey && !e.altKey && e.code === 'KeyH',
|
|
206
|
-
run: routeH,
|
|
207
|
-
},
|
|
208
|
-
{
|
|
209
|
-
combo: 'Shift+H',
|
|
210
|
-
group: 'edit',
|
|
211
|
-
label: t('shortcuts.hideUnselected'),
|
|
212
|
-
match: (e) => !mod(e) && e.shiftKey && !e.altKey && e.code === 'KeyH',
|
|
213
|
-
run: routeShiftH,
|
|
214
|
-
},
|
|
215
|
-
{
|
|
216
|
-
combo: 'Ctrl+H',
|
|
217
|
-
group: 'edit',
|
|
218
|
-
label: t('shortcuts.showAllHidden'),
|
|
219
|
-
match: (e) => mod(e) && !e.shiftKey && !e.altKey && e.code === 'KeyH',
|
|
220
|
-
run: routeCtrlH,
|
|
221
|
-
},
|
|
222
|
-
{
|
|
223
|
-
combo: 'Shift+G',
|
|
224
|
-
group: 'edit',
|
|
225
|
-
label: t('shortcuts.toggleViewportGameView'),
|
|
226
|
-
match: (e) => !mod(e) && e.shiftKey && !e.altKey
|
|
227
|
-
&& (e.key === 'g' || e.key === 'G'),
|
|
228
|
-
run: routeShiftG,
|
|
229
|
-
},
|
|
230
|
-
{
|
|
231
|
-
combo: 'G',
|
|
232
|
-
group: 'edit',
|
|
233
|
-
label: t('shortcuts.toggleViewportGameView'),
|
|
234
|
-
match: (e) => !mod(e) && !e.shiftKey && !e.altKey
|
|
235
|
-
&& !deps.isPlayMode() && deps.getInputTarget() !== 'game'
|
|
236
|
-
&& (e.key === 'g' || e.key === 'G'),
|
|
237
|
-
run: routeShiftG,
|
|
238
|
-
},
|
|
239
|
-
{
|
|
240
|
-
combo: 'Ctrl+Z',
|
|
241
|
-
group: 'edit',
|
|
242
|
-
label: t('shortcuts.undo'),
|
|
243
|
-
allowInInput: true,
|
|
244
|
-
match: (e) => mod(e) && !e.altKey && !e.shiftKey && (e.code === 'KeyZ' || safeKeyLower(e) === 'z'),
|
|
245
|
-
run: () => { deps.undo(); return true; },
|
|
246
|
-
},
|
|
247
|
-
{
|
|
248
|
-
combo: 'Ctrl+Shift+Z',
|
|
249
|
-
group: 'edit',
|
|
250
|
-
label: t('shortcuts.redo'),
|
|
251
|
-
allowInInput: true,
|
|
252
|
-
match: (e) => mod(e) && !e.altKey && e.shiftKey && (e.code === 'KeyZ' || safeKeyLower(e) === 'z'),
|
|
253
|
-
run: () => { deps.redo(); return true; },
|
|
254
|
-
},
|
|
255
|
-
{
|
|
256
|
-
combo: 'Ctrl+Y',
|
|
257
|
-
group: 'edit',
|
|
258
|
-
label: t('shortcuts.redo'),
|
|
259
|
-
allowInInput: true,
|
|
260
|
-
match: (e) => mod(e) && !e.altKey && !e.shiftKey && (e.code === 'KeyY' || safeKeyLower(e) === 'y'),
|
|
261
|
-
run: () => { deps.redo(); return true; },
|
|
262
|
-
},
|
|
263
|
-
{
|
|
264
|
-
combo: 'Ctrl+S',
|
|
265
|
-
group: 'edit',
|
|
266
|
-
label: t('shortcuts.save'),
|
|
267
|
-
// Must fire while typing in Input Map / MI fields (⌘/Ctrl+S is a document
|
|
268
|
-
// command, not a text-editing key). Without this, focus inside `.im-editor`
|
|
269
|
-
// makes isTypingTarget true and silently drops save.
|
|
270
|
-
allowInInput: true,
|
|
271
|
-
match: (e) => mod(e) && !e.altKey && !e.shiftKey && (e.code === 'KeyS' || safeKeyLower(e) === 's'),
|
|
272
|
-
run: () => { deps.save(); return true; },
|
|
273
|
-
},
|
|
274
|
-
{
|
|
275
|
-
combo: 'Viewport camera and fly input',
|
|
276
|
-
group: 'edit',
|
|
277
|
-
label: t('shortcuts.viewportNavigation'),
|
|
278
|
-
match: (e) => {
|
|
279
|
-
if (deps.getInputTarget() === 'game')
|
|
280
|
-
return false;
|
|
281
|
-
const key = safeKeyLower(e);
|
|
282
|
-
if (!key)
|
|
283
|
-
return false;
|
|
284
|
-
// UE-style view presets (Alt+G/H/J/K) route to the viewport handler,
|
|
285
|
-
// which owns the camera. All other Alt combos stay excluded here.
|
|
286
|
-
if (e.altKey) {
|
|
287
|
-
return !mod(e) && !e.shiftKey && ['g', 'h', 'j', 'k'].includes(key);
|
|
288
|
-
}
|
|
289
|
-
const plainCameraKey = !mod(e)
|
|
290
|
-
&& (['w', 'e', 'r', 'f', 'a', 's', 'd', 'q', 'v', 'z', 'c', 'escape', 'shift'].includes(key)
|
|
291
|
-
|| /^[1-9]$/.test(key));
|
|
292
|
-
const bookmarkKey = mod(e) && !e.shiftKey && /^[1-9]$/.test(key);
|
|
293
|
-
return plainCameraKey || bookmarkKey;
|
|
294
|
-
},
|
|
295
|
-
run: routeViewportInput,
|
|
296
|
-
},
|
|
297
|
-
{
|
|
298
|
-
combo: 'Play editor input shield',
|
|
299
|
-
group: 'edit',
|
|
300
|
-
label: t('shortcuts.shieldGameInput'),
|
|
301
|
-
match: () => deps.isPlayMode() && deps.getInputTarget() !== 'game',
|
|
302
|
-
run: routeEditorOwnedPlayKey,
|
|
303
|
-
},
|
|
304
|
-
];
|
|
305
|
-
}
|
|
306
|
-
// Build the shortcut registry. Each match() / run() is plain JS so we can
|
|
307
|
-
// drive them from a Settings table later (or a Command Palette).
|
|
308
|
-
export function buildShortcuts() {
|
|
103
|
+
// Build the Settings list in product order, not event-routing priority order.
|
|
104
|
+
// Exact duplicate descriptions share a row, but remain separate runtime owners.
|
|
105
|
+
export function buildShortcuts(contributions) {
|
|
309
106
|
const store = useShellStore.getState;
|
|
310
107
|
const shortcuts = [
|
|
311
108
|
// ── Layout (collapse / fullscreen) ──
|
|
@@ -394,13 +191,6 @@ export function buildShortcuts() {
|
|
|
394
191
|
allowInInput: true,
|
|
395
192
|
match: (e) => e.key === 'Escape' && !mod(e) && !e.shiftKey && !e.altKey,
|
|
396
193
|
run: () => {
|
|
397
|
-
// Escape is a Play-only viewport shortcut: stop the transient play
|
|
398
|
-
// session and return to edit mode. The listener stays in the single
|
|
399
|
-
// global router; outside Play this branch is inactive.
|
|
400
|
-
if (routerDeps?.isPlayMode()) {
|
|
401
|
-
routerDeps.dispatch({ kind: 'stop' }, 'human');
|
|
402
|
-
return true;
|
|
403
|
-
}
|
|
404
194
|
const s = store();
|
|
405
195
|
// Browser fullscreen exits automatically on Esc — but be defensive
|
|
406
196
|
// in case some browser swallows the event before reaching the native
|
|
@@ -460,18 +250,29 @@ export function buildShortcuts() {
|
|
|
460
250
|
},
|
|
461
251
|
},
|
|
462
252
|
];
|
|
463
|
-
// Inject the host editor's remaining edit-domain shortcuts when deps were
|
|
464
|
-
// registered at boot. Focus-owned F2/Delete/Mod+A live in host.keybindings.
|
|
465
|
-
if (routerDeps)
|
|
466
|
-
shortcuts.push(...editShortcuts(routerDeps));
|
|
467
253
|
shortcuts.push({
|
|
254
|
+
priority: -20,
|
|
468
255
|
combo: 'Ctrl+K',
|
|
469
256
|
group: 'general',
|
|
470
257
|
label: t('shortcuts.toggleCommandPalette'),
|
|
471
258
|
match: (e) => mod(e) && !e.altKey && !e.shiftKey && (e.code === 'KeyK' || safeKeyLower(e) === 'k'),
|
|
472
259
|
run: () => { toggleCommandPalette(); return true; },
|
|
473
260
|
});
|
|
474
|
-
|
|
261
|
+
const shell = shortcuts.map((shortcut) => ({ priority: 10, ...shortcut }));
|
|
262
|
+
const rowKey = ({ combo, group, label }) => JSON.stringify([combo, group, label]);
|
|
263
|
+
const seen = new Set(shell.map(rowKey));
|
|
264
|
+
const contributedRows = [];
|
|
265
|
+
for (const shortcut of contributions?.snapshot() ?? []) {
|
|
266
|
+
const key = rowKey(shortcut);
|
|
267
|
+
if (seen.has(key))
|
|
268
|
+
continue;
|
|
269
|
+
seen.add(key);
|
|
270
|
+
contributedRows.push(shortcut);
|
|
271
|
+
}
|
|
272
|
+
return [...shell.slice(0, -1), ...contributedRows, ...shell.slice(-1)];
|
|
273
|
+
}
|
|
274
|
+
function orderShortcuts(shell, contributions) {
|
|
275
|
+
return [...shell, ...(contributions?.snapshot() ?? [])].sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0));
|
|
475
276
|
}
|
|
476
277
|
/**
|
|
477
278
|
* Mount once at App root. Returns nothing — purely an effect.
|
|
@@ -481,9 +282,9 @@ export function buildShortcuts() {
|
|
|
481
282
|
* Ctrl+Shift+1/2/3 (which Chrome maps to tab switching only WITHOUT Shift,
|
|
482
283
|
* so we're safe, but we preventDefault anyway).
|
|
483
284
|
*/
|
|
484
|
-
export function useGlobalShortcuts(keybindings) {
|
|
285
|
+
export function useGlobalShortcuts(keybindings, contributions) {
|
|
485
286
|
useEffect(() => {
|
|
486
|
-
const
|
|
287
|
+
const shell = buildShortcuts();
|
|
487
288
|
const onKey = (e) => {
|
|
488
289
|
// 0. IME composing — bail. Never intercept Chinese pinyin chord.
|
|
489
290
|
if (isComposing(e))
|
|
@@ -499,11 +300,11 @@ export function useGlobalShortcuts(keybindings) {
|
|
|
499
300
|
// 2. The contextual resolver gets first refusal inside the ONE capture
|
|
500
301
|
// listener. A handled or disabled-but-claimed binding must not fall into
|
|
501
302
|
// legacy application shortcuts; passthrough/unclaimed continues below.
|
|
502
|
-
const contextual = keybindings
|
|
303
|
+
const contextual = keybindings.handle(e);
|
|
503
304
|
if (contextual?.status === 'handled' || contextual?.status === 'claimed-disabled')
|
|
504
305
|
return;
|
|
505
|
-
// 3.
|
|
506
|
-
for (const s of
|
|
306
|
+
// 3. Read live host contributions after contextual first refusal.
|
|
307
|
+
for (const s of orderShortcuts(shell, contributions)) {
|
|
507
308
|
// A focused preview owns camera/fly keys locally. Keep non-edit global
|
|
508
309
|
// shortcuts available, but never route preview input to the main editor.
|
|
509
310
|
if (shouldSkipGlobalShortcut(e, s))
|
|
@@ -532,7 +333,7 @@ export function useGlobalShortcuts(keybindings) {
|
|
|
532
333
|
onKeyDown: onKey,
|
|
533
334
|
});
|
|
534
335
|
return disposeKeydownObservation;
|
|
535
|
-
}, [keybindings]);
|
|
336
|
+
}, [keybindings, contributions]);
|
|
536
337
|
}
|
|
537
338
|
// macOS pretty-printing for shortcut combos shown in Settings.
|
|
538
339
|
// Returns the canonical UI string. When platform is omitted, auto-detects macOS.
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* 纯 DOM overlay,零 store 依赖,装不上(SSR)静默跳过;观测层绝不影响派发主流程。
|
|
8
8
|
*/
|
|
9
9
|
import { installCustomEventObservation } from '@forgeax/app-shell/react';
|
|
10
|
-
import { UI_ACTION_DISPATCH_EVENT, getAction } from '
|
|
10
|
+
import { UI_ACTION_DISPATCH_EVENT, getAction } from '@forgeax/app-shell/application';
|
|
11
11
|
const STYLE_ID = 'fx-ui-action-highlight-style';
|
|
12
12
|
const BADGE_ID = 'fx-ui-action-badge';
|
|
13
13
|
const HIGHLIGHT_CSS = `
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
*/
|
|
14
14
|
import { installWindowMessageObservation } from '@forgeax/app-shell/react';
|
|
15
15
|
import { getSessionClient, hasSessionClient } from '../store-parts/session-client';
|
|
16
|
-
import { buildManifest, dispatchAction, onRegistryChange, snapshotActions, snapshotState, } from '
|
|
16
|
+
import { buildManifest, dispatchAction, onRegistryChange, snapshotActions, snapshotState, } from '@forgeax/app-shell/application';
|
|
17
17
|
import { registerBuiltinActions } from './builtin-actions';
|
|
18
18
|
import { startActionDomDiscovery } from './action-dom-discovery';
|
|
19
19
|
import { installUiActionHighlight } from './ui-action-highlight';
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* 可序列化:entry 只含原始标量 / 浅裁剪后的 args,过得了 snapshotState 的 JSON 出墙。
|
|
10
10
|
* 隐私:credential 级 action 的 args 整体打码(不把密钥类入参喂进 AI 上下文)。 */
|
|
11
11
|
import { installCustomEventObservation } from '@forgeax/app-shell/react';
|
|
12
|
-
import { getAction, UI_ACTION_DISPATCH_EVENT } from '
|
|
12
|
+
import { getAction, UI_ACTION_DISPATCH_EVENT } from '@forgeax/app-shell/application';
|
|
13
13
|
// 环形缓冲容量 == `trajectory.read` 单次可拉上限(readTrajectory 用它收敛 limit)。
|
|
14
14
|
// 二者本就相等,故一个常量封两处:内存占用,以及 AI 一次拉取的**最坏上下文体积**
|
|
15
15
|
// (SNAPSHOT_TAIL 20 条随每次 ui_snapshot 出墙不受此限;这里管的是按需 read 的天花板)。
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
* 是 host 半边,协议以此为准)。
|
|
16
16
|
*/
|
|
17
17
|
import { installWindowMessageObservation } from '@forgeax/app-shell/react';
|
|
18
|
-
import { registerAction } from '
|
|
18
|
+
import { registerAction } from '@forgeax/app-shell/application';
|
|
19
19
|
import { isTrustedMessageOrigin } from './trustedOrigins';
|
|
20
20
|
const VALID_CAPS = new Set([
|
|
21
21
|
'read', 'write', 'delete', 'exec', 'network', 'credential', 'delegate', 'other',
|
|
@@ -25,7 +25,7 @@ import { isTauri } from './lib/platform/runtime.js';
|
|
|
25
25
|
|
|
26
26
|
export { ApplicationDetachedShell } from './ApplicationDetachedShell.js';
|
|
27
27
|
function KeyboardRouter({ runtime }) {
|
|
28
|
-
useGlobalShortcuts(runtime.host.keybindings);
|
|
28
|
+
useGlobalShortcuts(runtime.host.keybindings, runtime.host.shortcuts);
|
|
29
29
|
return null;
|
|
30
30
|
}
|
|
31
31
|
export function isApplicationOnboardingEnabled(onboarding) {
|
|
@@ -21,7 +21,6 @@ import { foundationStorageExtension } from './core/extensions/foundation-storage
|
|
|
21
21
|
import { builtinCommandsExtension } from './core/extensions/builtin-commands.js';
|
|
22
22
|
import { hostCommandsExtension } from './core/extensions/host-commands.js';
|
|
23
23
|
import { builtinMenusExtension } from './core/extensions/builtin-menus.js';
|
|
24
|
-
import { editorCommandsExtension } from './core/extensions/editor-commands.js';
|
|
25
24
|
import { panelsViewportExtension } from './core/extensions/panels-viewport.js';
|
|
26
25
|
import { panelsChatExtension } from './core/extensions/panels-chat.js';
|
|
27
26
|
import { chromeStatusBarExtension } from './core/extensions/chrome-statusbar.js';
|
|
@@ -108,7 +107,6 @@ export async function bootstrapAppHost(overrides = {}) {
|
|
|
108
107
|
builtinCommandsExtension,
|
|
109
108
|
hostCommandsExtension,
|
|
110
109
|
builtinMenusExtension,
|
|
111
|
-
editorCommandsExtension,
|
|
112
110
|
panelsViewportExtension,
|
|
113
111
|
panelsChatExtension,
|
|
114
112
|
chromeStatusBarExtension,
|
|
@@ -2,7 +2,6 @@ import { bootstrapAppHost, } from './appHostBootstrap.js';
|
|
|
2
2
|
import { bootStageAppMounted } from './boot/driver.js';
|
|
3
3
|
import { changeLanguage, initI18n } from './i18n/index.js';
|
|
4
4
|
import { useShellStore } from './store.js';
|
|
5
|
-
import { registerKeyboardRouterDeps, } from './lib/global-shortcuts.js';
|
|
6
5
|
/**
|
|
7
6
|
* Lets a product replace one shell overlay with its own destination. Interface
|
|
8
7
|
* owns observation, clearing and synchronous reentry; the product owns routing.
|
|
@@ -24,14 +23,6 @@ export function installApplicationOverlayRedirect(overlayId, onRedirect) {
|
|
|
24
23
|
}
|
|
25
24
|
});
|
|
26
25
|
}
|
|
27
|
-
/**
|
|
28
|
-
* Configures product-owned editor callbacks consumed by Interface's single
|
|
29
|
-
* keyboard router. Product assemblies use this public application boundary;
|
|
30
|
-
* the router implementation remains private to Interface.
|
|
31
|
-
*/
|
|
32
|
-
export function configureInterfaceKeyboardRouter(deps) {
|
|
33
|
-
registerKeyboardRouterDeps(deps);
|
|
34
|
-
}
|
|
35
26
|
/**
|
|
36
27
|
* Starts the shared Interface application runtime without selecting a product
|
|
37
28
|
* entry or product extensions. IDE and compatibility callers supply their own
|
|
@@ -61,4 +52,3 @@ export async function startInterfaceApplication(overrides = {}, options = {}) {
|
|
|
61
52
|
// Product boot injects the existing domain authority without importing store
|
|
62
53
|
// internals. This is the same configuration function, not another client registry.
|
|
63
54
|
export { configureStudioDomainClients, } from './store-parts/domain-clients.js';
|
|
64
|
-
export { dispatchAction, registerAction } from './lib/action-registry.js';
|
|
@@ -12,7 +12,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
12
12
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
13
13
|
import { Command } from 'cmdk';
|
|
14
14
|
import { createRestartableTimeoutTaskLifecycle } from '@forgeax/app-shell/react';
|
|
15
|
-
import { dispatchAction, getAction, snapshotActions, } from '
|
|
15
|
+
import { dispatchAction, getAction, snapshotActions, } from '@forgeax/app-shell/application';
|
|
16
16
|
import { setCommandPaletteOpen, useCommandPaletteOpen } from '../../lib/command-palette-store.js';
|
|
17
17
|
|
|
18
18
|
/** 从 action 的 JSON Schema 派生顶层参数清单(与 registry 的 validateArgs 同口径)。 */
|
|
@@ -13,8 +13,9 @@ import { derivePanelRenderers } from './derive-panel-renderers.js';
|
|
|
13
13
|
import { DEFAULT_PANEL_RENDERERS } from '../../components/DockShell/panelRenderers.js';
|
|
14
14
|
import { installPageNavigation } from '../page-navigation.js';
|
|
15
15
|
import { createContextualKeybindings } from '../contextual-keybindings.js';
|
|
16
|
+
import { createApplicationShortcutRegistry } from '@forgeax/app-shell/application';
|
|
16
17
|
const BUILT_IN_CAPS = [
|
|
17
|
-
'commands', 'keybindings', 'bus', 'storage', 'panels', 'panelActions', 'panelControls', 'contextKeys', 'pages',
|
|
18
|
+
'commands', 'keybindings', 'shortcuts', 'bus', 'storage', 'panels', 'panelActions', 'panelControls', 'contextKeys', 'pages',
|
|
18
19
|
'activities', 'resourceEditors',
|
|
19
20
|
];
|
|
20
21
|
export function createAppHost(deps = {}) {
|
|
@@ -23,6 +24,7 @@ export function createAppHost(deps = {}) {
|
|
|
23
24
|
for (const c of BUILT_IN_CAPS)
|
|
24
25
|
caps.add(c);
|
|
25
26
|
const commands = createCommandsRegistry();
|
|
27
|
+
const shortcuts = createApplicationShortcutRegistry();
|
|
26
28
|
const keybindings = createContextualKeybindings(commands, {
|
|
27
29
|
onCommandError(error, commandId) {
|
|
28
30
|
log.error(`[app-shell] keybinding command "${commandId}" failed`, error);
|
|
@@ -62,7 +64,7 @@ export function createAppHost(deps = {}) {
|
|
|
62
64
|
const extensionFields = {};
|
|
63
65
|
let activeSetup = null;
|
|
64
66
|
const base = {
|
|
65
|
-
commands, keybindings, bus, storage, contextKeys,
|
|
67
|
+
commands, keybindings, shortcuts, bus, storage, contextKeys,
|
|
66
68
|
get panels() { return panelsSnapshot(); },
|
|
67
69
|
panelActions,
|
|
68
70
|
panelControls,
|
|
@@ -181,6 +183,7 @@ export function createAppHost(deps = {}) {
|
|
|
181
183
|
removePageNavigation();
|
|
182
184
|
await pageSession.dispose();
|
|
183
185
|
keybindings.dispose();
|
|
186
|
+
shortcuts.dispose();
|
|
184
187
|
bus.destroy();
|
|
185
188
|
},
|
|
186
189
|
};
|
|
@@ -38,6 +38,7 @@ export const builtinCommandsExtension = {
|
|
|
38
38
|
registerTextEditCommand('text.cut', '剪切输入框选区', 'cut');
|
|
39
39
|
registerTextEditCommand('text.copy', '复制输入框选区', 'copy');
|
|
40
40
|
registerTextEditCommand('text.paste', '粘贴到输入框', 'paste');
|
|
41
|
+
registerTextEditCommand('text.selectAll', 'Select all focused text', 'selectAll');
|
|
41
42
|
cleanups.push(registerCommand({
|
|
42
43
|
id: 'app.panel.open',
|
|
43
44
|
title: 'Open (or focus) a dock panel by id',
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { registerAction, registerStateSlice } from '
|
|
1
|
+
import { registerAction, registerStateSlice } from '@forgeax/app-shell/application';
|
|
2
2
|
import { TRAJECTORY_MAX, clearTrajectory, readTrajectory, startTrajectoryRecording, } from '../../lib/ui-trajectory.js';
|
|
3
3
|
/** 随每次 ui_snapshot 内联出墙的尾部条数(压 token;更多靠 trajectory.read 拉)。 */
|
|
4
4
|
const SNAPSHOT_TAIL = 20;
|
|
@@ -1288,22 +1288,12 @@
|
|
|
1288
1288
|
"toggleDashboard": "Toggle Dashboard overview panel",
|
|
1289
1289
|
"toggleSettings": "Open / close Settings overlay",
|
|
1290
1290
|
"openChangelog": "Settings → About (changelog)",
|
|
1291
|
-
"hideSelected": "Hide selected entities (UE parity · editor-only, never affects the game)",
|
|
1292
|
-
"hideUnselected": "Hide unselected entities (isolate selection)",
|
|
1293
|
-
"showAllHidden": "Show all hidden entities",
|
|
1294
1291
|
"closeOverlay": "Close current overlay · browser fullscreen → game fullscreen → Settings → Dashboard in order",
|
|
1295
1292
|
"modePreview": "Switch to Preview mode",
|
|
1296
1293
|
"modePage": "Switch to Page mode",
|
|
1297
1294
|
"switchPageN": "Switch to extension {{n}} (Blender parity — indexed into extension list)",
|
|
1298
1295
|
"openExtensions": "Settings → Extensions (was the Bus mode tab)",
|
|
1299
1296
|
"focusComposer": "Focus the right ChatPanel composer",
|
|
1300
|
-
"duplicateSelection": "Duplicate selection",
|
|
1301
|
-
"toggleViewportGameView": "Toggle viewport Game View",
|
|
1302
|
-
"undo": "Undo",
|
|
1303
|
-
"redo": "Redo",
|
|
1304
|
-
"save": "Save",
|
|
1305
|
-
"viewportNavigation": "Viewport navigation and gizmo mode",
|
|
1306
|
-
"shieldGameInput": "Shield game input while editor-owned",
|
|
1307
1297
|
"toggleCommandPalette": "Toggle command palette"
|
|
1308
1298
|
},
|
|
1309
1299
|
"analytics": {
|
|
@@ -1288,22 +1288,12 @@
|
|
|
1288
1288
|
"toggleDashboard": "切换 Dashboard 总览面板",
|
|
1289
1289
|
"toggleSettings": "打开 / 关闭 Settings 浮层",
|
|
1290
1290
|
"openChangelog": "Settings → 关于(更新日志)",
|
|
1291
|
-
"hideSelected": "隐藏选中实体(UE 对标 · 编辑器临时隐藏,不影响游戏)",
|
|
1292
|
-
"hideUnselected": "隐藏未选中实体(孤立选择)",
|
|
1293
|
-
"showAllHidden": "显示所有隐藏实体",
|
|
1294
1291
|
"closeOverlay": "关闭当前浮层 · 浏览器全屏 → 游戏全屏 → Settings → Dashboard 依次撤回",
|
|
1295
1292
|
"modePreview": "切到 Preview 模式",
|
|
1296
1293
|
"modePage": "切到 Page 模式",
|
|
1297
1294
|
"switchPageN": "切到第 {{n}} 个 extension(Blender 风格 · 按列表索引)",
|
|
1298
1295
|
"openExtensions": "Settings → Extensions(原 Bus mode tab)",
|
|
1299
1296
|
"focusComposer": "聚焦右侧 ChatPanel 输入框",
|
|
1300
|
-
"duplicateSelection": "复制选中项",
|
|
1301
|
-
"toggleViewportGameView": "切换视口 Game View",
|
|
1302
|
-
"undo": "撤销",
|
|
1303
|
-
"redo": "重做",
|
|
1304
|
-
"save": "保存",
|
|
1305
|
-
"viewportNavigation": "视口导航与 Gizmo 模式",
|
|
1306
|
-
"shieldGameInput": "编辑器接管时屏蔽游戏输入",
|
|
1307
1297
|
"toggleCommandPalette": "切换命令面板"
|
|
1308
1298
|
},
|
|
1309
1299
|
"analytics": {
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* data-fx-capability 权限分级(8 类之一;缺省 'other'。危险按钮**务必**标
|
|
13
13
|
* delete/credential,编排层会据此弹确认卡)
|
|
14
14
|
*/
|
|
15
|
-
import { registerAction } from '
|
|
15
|
+
import { registerAction } from '@forgeax/app-shell/application';
|
|
16
16
|
const VALID_CAPS = new Set([
|
|
17
17
|
'read', 'write', 'delete', 'exec', 'network', 'credential', 'delegate', 'other',
|
|
18
18
|
]);
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* surface 口径(方案 §5):纯视图操作 'ui';背后是 server REST 的标 'both'(headless
|
|
9
9
|
* 等价路径 P1 接线,声明先行,UI run() 已经就是调同一 HTTP API,server 是行为 SSOT)。
|
|
10
10
|
*/
|
|
11
|
-
import { registerAction, registerStateSlice } from '
|
|
11
|
+
import { registerAction, registerStateSlice } from '@forgeax/app-shell/application';
|
|
12
12
|
import { getSessionClient } from '../store-parts/session-client.js';
|
|
13
13
|
import { useShellStore, tabLabel } from '../store.js';
|
|
14
14
|
import { openExtensionPage, resolveRegisteredOverlayId } from '../core/page-navigation.js';
|