@fps-games/editor 0.1.1-beta.5 → 0.1.1-beta.6
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/dist/local-editor-harness.d.ts +1 -0
- package/dist/local-editor-harness.d.ts.map +1 -1
- package/dist/local-editor-harness.js +73 -0
- package/dist/local-editor-harness.js.map +1 -1
- package/node_modules/@fps-games/editor-babylon/package.json +4 -4
- package/node_modules/@fps-games/editor-browser/dist/index.d.ts +2 -0
- package/node_modules/@fps-games/editor-browser/dist/index.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-browser/dist/index.js +1 -0
- package/node_modules/@fps-games/editor-browser/dist/index.js.map +1 -1
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-actions.d.ts +10 -0
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-actions.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-actions.js +142 -3
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-actions.js.map +1 -1
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-controller.d.ts +1 -0
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-controller.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-controller.js +82 -14
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-controller.js.map +1 -1
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-shortcuts.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-shortcuts.js +3 -0
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-shortcuts.js.map +1 -1
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-types.d.ts +16 -0
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-types.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-workbench.d.ts +35 -0
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-workbench.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-workbench.js +375 -1
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-workbench.js.map +1 -1
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui.d.ts +2 -2
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui.js +340 -15
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui.js.map +1 -1
- package/node_modules/@fps-games/editor-browser/package.json +2 -2
- package/node_modules/@fps-games/editor-core/package.json +2 -2
- package/node_modules/@fps-games/editor-forge-play/package.json +2 -2
- package/node_modules/@fps-games/editor-protocol/package.json +1 -1
- package/package.json +6 -6
|
@@ -2,7 +2,7 @@ import * as LocalEditorPanels from './local-editor-ui-panels';
|
|
|
2
2
|
import { createLocalEditorHierarchyController } from './local-editor-ui-hierarchy-controller';
|
|
3
3
|
import { createLocalEditorContextMenuController } from './local-editor-ui-context-menu';
|
|
4
4
|
import { DEFAULT_EDITOR_TRANSFORM_OPERATION_SETTINGS, DEFAULT_EDITOR_TRANSFORM_TOOL_DESCRIPTORS, } from '@fps-games/editor-core';
|
|
5
|
-
export { createLocalEditorHierarchyBlankMenu, createLocalEditorHierarchyDeleteShortcutAction, createLocalEditorHierarchyNodeMenu, } from './local-editor-ui-hierarchy-actions';
|
|
5
|
+
export { createLocalEditorHierarchyBlankMenu, createLocalEditorHierarchyCopyShortcutAction, createLocalEditorHierarchyDeleteShortcutAction, createLocalEditorHierarchyDuplicateShortcutAction, createLocalEditorHierarchyNodeMenu, createLocalEditorHierarchyPasteShortcutAction, } from './local-editor-ui-hierarchy-actions';
|
|
6
6
|
export { createLocalEditorHierarchyController, } from './local-editor-ui-hierarchy-controller';
|
|
7
7
|
export { canLocalEditorHierarchyNodeHaveChildren, createLocalEditorHierarchyTreeModel, isLocalEditorHierarchyNodeMovable, } from './local-editor-ui-hierarchy-tree';
|
|
8
8
|
export { createLocalEditorContextMenuController, } from './local-editor-ui-context-menu';
|
|
@@ -11,7 +11,7 @@ import { createLocalEditorPanelRegistry } from './local-editor-ui-panel-registry
|
|
|
11
11
|
import * as LocalEditorShared from './local-editor-ui-shared';
|
|
12
12
|
import { createShortcutHelpPanel as createLocalEditorShortcutHelpPanel } from './local-editor-ui-shortcuts';
|
|
13
13
|
import { applyLocalEditorTheme, ensureLocalEditorTheme, LOCAL_EDITOR_THEME_CLASS, normalizeLocalEditorThemeName, } from './local-editor-ui-theme';
|
|
14
|
-
import { createDefaultLocalEditorWorkbenchLayout, createLocalEditorWorkbench, createSceneHeaderToolbar, createWorkbenchPanelContent, } from './local-editor-ui-workbench';
|
|
14
|
+
import { createDefaultLocalEditorWorkbenchLayout, createLocalEditorWorkbenchLayoutController, createLocalEditorWorkbench, createSceneHeaderToolbar, createWorkbenchPanelContent, } from './local-editor-ui-workbench';
|
|
15
15
|
export { applyLocalEditorBrowserInspectorControlBinding, createLocalEditorBrowserInspectorControlRegistry, formatLocalEditorBrowserInspectorValue, resolveLocalEditorBrowserInspectorControlRegistration, } from './local-editor-ui-panels';
|
|
16
16
|
export { DEFAULT_LOCAL_EDITOR_THEME, LOCAL_EDITOR_THEME_CLASS, applyLocalEditorTheme, normalizeLocalEditorThemeName, } from './local-editor-ui-theme';
|
|
17
17
|
export { LOCAL_EDITOR_ICON_NAMES, createLocalEditorIcon, isLocalEditorIconName, resolveLocalEditorIconName, } from './local-editor-ui-icons';
|
|
@@ -116,6 +116,7 @@ export function createLocalEditorBrowserUi(options = {}) {
|
|
|
116
116
|
const doc = options.document ?? document;
|
|
117
117
|
const root = options.root ?? doc.body;
|
|
118
118
|
const callbacks = options.callbacks ?? {};
|
|
119
|
+
const localTestActionsEnabled = options.localTestActions === true;
|
|
119
120
|
ensureLocalEditorTheme(doc);
|
|
120
121
|
let activeTheme = normalizeLocalEditorThemeName(options.theme);
|
|
121
122
|
const inputRouter = createLocalEditorWorkbenchInputRouter(doc);
|
|
@@ -177,6 +178,38 @@ export function createLocalEditorBrowserUi(options = {}) {
|
|
|
177
178
|
const saveButton = LocalEditorShared.createButton(doc, '保存场景', { icon: 'save' });
|
|
178
179
|
const saveAndRunButton = LocalEditorShared.createButton(doc, '保存并运行', { icon: 'execute' });
|
|
179
180
|
const discardRunButton = LocalEditorShared.createButton(doc, '放弃并运行', { icon: 'discard' });
|
|
181
|
+
const localTestButton = LocalEditorShared.createButton(doc, '本地测试', { icon: 'execute' });
|
|
182
|
+
localTestButton.dataset.editorLocalTestToggle = 'true';
|
|
183
|
+
localTestButton.title = '打开本地测试操作';
|
|
184
|
+
const localTestMenu = doc.createElement('div');
|
|
185
|
+
localTestMenu.classList.add(LOCAL_EDITOR_THEME_CLASS);
|
|
186
|
+
localTestMenu.dataset.editorLocalTestMenu = 'true';
|
|
187
|
+
localTestMenu.style.cssText = [
|
|
188
|
+
'position:fixed',
|
|
189
|
+
'z-index:2147483641',
|
|
190
|
+
'display:none',
|
|
191
|
+
'flex-direction:column',
|
|
192
|
+
'gap:6px',
|
|
193
|
+
'min-width:150px',
|
|
194
|
+
'padding:8px',
|
|
195
|
+
'border:1px solid var(--fps-editor-border)',
|
|
196
|
+
'border-radius:3px',
|
|
197
|
+
'background:var(--fps-editor-panel)',
|
|
198
|
+
'box-shadow:var(--fps-editor-shadow-popover)',
|
|
199
|
+
'pointer-events:auto',
|
|
200
|
+
].join(';');
|
|
201
|
+
for (const button of [saveButton, saveAndRunButton, discardRunButton]) {
|
|
202
|
+
button.style.justifyContent = 'flex-start';
|
|
203
|
+
button.style.width = '100%';
|
|
204
|
+
localTestMenu.appendChild(button);
|
|
205
|
+
}
|
|
206
|
+
const localTestGroup = doc.createElement('div');
|
|
207
|
+
localTestGroup.style.cssText = [
|
|
208
|
+
`display:${localTestActionsEnabled ? 'flex' : 'none'}`,
|
|
209
|
+
'align-items:center',
|
|
210
|
+
'gap:4px',
|
|
211
|
+
].join(';');
|
|
212
|
+
localTestGroup.appendChild(localTestButton);
|
|
180
213
|
const undoButton = LocalEditorShared.createButton(doc, '撤销', { icon: 'undo' });
|
|
181
214
|
const redoButton = LocalEditorShared.createButton(doc, '重做', { icon: 'redo' });
|
|
182
215
|
let helpOpen = false;
|
|
@@ -198,6 +231,7 @@ export function createLocalEditorBrowserUi(options = {}) {
|
|
|
198
231
|
workbench.leftDock.appendChild(hierarchyPanel);
|
|
199
232
|
workbench.rightDock.appendChild(inspectorPanel);
|
|
200
233
|
root.appendChild(workbench.root);
|
|
234
|
+
const workbenchLayoutController = createLocalEditorWorkbenchLayoutController(doc, workbench);
|
|
201
235
|
const sceneToolOverlay = createSceneHeaderToolbar(doc);
|
|
202
236
|
const sceneTitle = doc.createElement('div');
|
|
203
237
|
sceneTitle.textContent = 'Preview';
|
|
@@ -215,6 +249,14 @@ export function createLocalEditorBrowserUi(options = {}) {
|
|
|
215
249
|
].join(';');
|
|
216
250
|
const sceneQuickActions = doc.createElement('div');
|
|
217
251
|
sceneQuickActions.style.cssText = 'display:flex;align-items:center;gap:4px';
|
|
252
|
+
const sceneUtilityActions = doc.createElement('div');
|
|
253
|
+
sceneUtilityActions.style.cssText = [
|
|
254
|
+
'display:flex',
|
|
255
|
+
'align-items:center',
|
|
256
|
+
'gap:4px',
|
|
257
|
+
'padding-left:8px',
|
|
258
|
+
'border-left:1px solid var(--fps-editor-divider)',
|
|
259
|
+
].join(';');
|
|
218
260
|
const toolGroup = doc.createElement('div');
|
|
219
261
|
toolGroup.style.cssText = [
|
|
220
262
|
'display:flex',
|
|
@@ -393,14 +435,12 @@ export function createLocalEditorBrowserUi(options = {}) {
|
|
|
393
435
|
themeToggleButton.style.padding = '5px 7px';
|
|
394
436
|
const sceneHelpButton = LocalEditorShared.createButton(doc, '快捷键', { icon: 'help' });
|
|
395
437
|
sceneHelpButton.style.padding = '5px 7px';
|
|
396
|
-
sceneQuickActions.appendChild(
|
|
397
|
-
sceneQuickActions.appendChild(saveAndRunButton);
|
|
398
|
-
sceneQuickActions.appendChild(discardRunButton);
|
|
438
|
+
sceneQuickActions.appendChild(localTestGroup);
|
|
399
439
|
sceneQuickActions.appendChild(undoButton);
|
|
400
440
|
sceneQuickActions.appendChild(redoButton);
|
|
401
441
|
sceneQuickActions.appendChild(dirtyBadge);
|
|
402
|
-
|
|
403
|
-
|
|
442
|
+
sceneUtilityActions.appendChild(themeToggleButton);
|
|
443
|
+
sceneUtilityActions.appendChild(sceneHelpButton);
|
|
404
444
|
const cameraPreviewGroup = doc.createElement('div');
|
|
405
445
|
cameraPreviewGroup.style.cssText = [
|
|
406
446
|
'display:flex',
|
|
@@ -413,8 +453,33 @@ export function createLocalEditorBrowserUi(options = {}) {
|
|
|
413
453
|
sceneCameraButton.dataset.sceneCameraPreviewToggle = 'true';
|
|
414
454
|
sceneCameraButton.title = '从 Main Camera 查看当前场景';
|
|
415
455
|
cameraPreviewGroup.appendChild(sceneCameraButton);
|
|
456
|
+
const toolbarOverflowButton = LocalEditorShared.createButton(doc, '更多', { icon: 'chevron-down' });
|
|
457
|
+
toolbarOverflowButton.dataset.editorToolbarOverflowToggle = 'true';
|
|
458
|
+
toolbarOverflowButton.title = '显示隐藏的工具栏命令';
|
|
459
|
+
toolbarOverflowButton.style.display = 'none';
|
|
460
|
+
const toolbarOverflowMenu = doc.createElement('div');
|
|
461
|
+
toolbarOverflowMenu.classList.add(LOCAL_EDITOR_THEME_CLASS);
|
|
462
|
+
toolbarOverflowMenu.dataset.editorToolbarOverflowMenu = 'true';
|
|
463
|
+
toolbarOverflowMenu.style.cssText = [
|
|
464
|
+
'position:fixed',
|
|
465
|
+
'z-index:2147483641',
|
|
466
|
+
'display:none',
|
|
467
|
+
'flex-direction:column',
|
|
468
|
+
'gap:6px',
|
|
469
|
+
'min-width:220px',
|
|
470
|
+
'max-width:min(420px, calc(100vw - 16px))',
|
|
471
|
+
'max-height:calc(100vh - 72px)',
|
|
472
|
+
'overflow:auto',
|
|
473
|
+
'padding:8px',
|
|
474
|
+
'border:1px solid var(--fps-editor-border)',
|
|
475
|
+
'border-radius:3px',
|
|
476
|
+
'background:var(--fps-editor-panel)',
|
|
477
|
+
'box-shadow:var(--fps-editor-shadow-popover)',
|
|
478
|
+
'pointer-events:auto',
|
|
479
|
+
].join(';');
|
|
416
480
|
sceneToolOverlay.appendChild(sceneTitle);
|
|
417
481
|
sceneToolOverlay.appendChild(sceneQuickActions);
|
|
482
|
+
sceneToolOverlay.appendChild(sceneUtilityActions);
|
|
418
483
|
sceneToolOverlay.appendChild(cameraPreviewGroup);
|
|
419
484
|
sceneToolOverlay.appendChild(toolGroup);
|
|
420
485
|
sceneToolOverlay.appendChild(spaceGroup);
|
|
@@ -425,7 +490,10 @@ export function createLocalEditorBrowserUi(options = {}) {
|
|
|
425
490
|
sceneToolOverlay.appendChild(sceneToolStatus);
|
|
426
491
|
sceneToolOverlay.appendChild(sceneMouseHint);
|
|
427
492
|
sceneToolOverlay.appendChild(status);
|
|
493
|
+
sceneToolOverlay.appendChild(toolbarOverflowButton);
|
|
428
494
|
workbench.sceneHeader.appendChild(sceneToolOverlay);
|
|
495
|
+
root.appendChild(localTestMenu);
|
|
496
|
+
root.appendChild(toolbarOverflowMenu);
|
|
429
497
|
const boxSelectionOverlay = doc.createElement('div');
|
|
430
498
|
boxSelectionOverlay.classList.add(LOCAL_EDITOR_THEME_CLASS);
|
|
431
499
|
boxSelectionOverlay.style.cssText = [
|
|
@@ -446,6 +514,8 @@ export function createLocalEditorBrowserUi(options = {}) {
|
|
|
446
514
|
applyLocalEditorTheme(workbench.root, activeTheme);
|
|
447
515
|
applyLocalEditorTheme(shortcutHelpPanel, activeTheme);
|
|
448
516
|
applyLocalEditorTheme(boxSelectionOverlay, activeTheme);
|
|
517
|
+
applyLocalEditorTheme(localTestMenu, activeTheme);
|
|
518
|
+
applyLocalEditorTheme(toolbarOverflowMenu, activeTheme);
|
|
449
519
|
contextMenu.setTheme?.(activeTheme);
|
|
450
520
|
}
|
|
451
521
|
function updateThemeToggleButton() {
|
|
@@ -459,12 +529,251 @@ export function createLocalEditorBrowserUi(options = {}) {
|
|
|
459
529
|
applyThemeToSurfaces();
|
|
460
530
|
updateThemeToggleButton();
|
|
461
531
|
}
|
|
532
|
+
let localTestMenuOpen = false;
|
|
533
|
+
function closeLocalTestMenu() {
|
|
534
|
+
localTestMenuOpen = false;
|
|
535
|
+
localTestMenu.style.display = 'none';
|
|
536
|
+
LocalEditorShared.applyButtonActiveState(localTestButton, false);
|
|
537
|
+
}
|
|
538
|
+
function openLocalTestMenu() {
|
|
539
|
+
if (!localTestActionsEnabled || localTestButton.style.display === 'none')
|
|
540
|
+
return;
|
|
541
|
+
closeToolbarOverflowMenu();
|
|
542
|
+
const win = doc.defaultView;
|
|
543
|
+
const rect = localTestButton.getBoundingClientRect();
|
|
544
|
+
const viewportWidth = win?.innerWidth ?? doc.documentElement.clientWidth;
|
|
545
|
+
localTestMenu.style.top = `${Math.max(8, rect.bottom + 5)}px`;
|
|
546
|
+
localTestMenu.style.right = `${Math.max(8, viewportWidth - rect.right)}px`;
|
|
547
|
+
localTestMenu.style.display = 'flex';
|
|
548
|
+
localTestMenuOpen = true;
|
|
549
|
+
LocalEditorShared.applyButtonActiveState(localTestButton, true);
|
|
550
|
+
}
|
|
551
|
+
const onLocalTestButtonClick = (event) => {
|
|
552
|
+
event.stopPropagation();
|
|
553
|
+
if (localTestMenuOpen)
|
|
554
|
+
closeLocalTestMenu();
|
|
555
|
+
else
|
|
556
|
+
openLocalTestMenu();
|
|
557
|
+
};
|
|
558
|
+
const toolbarOverflowStyleProperties = [
|
|
559
|
+
'alignSelf',
|
|
560
|
+
'borderLeft',
|
|
561
|
+
'display',
|
|
562
|
+
'flex',
|
|
563
|
+
'height',
|
|
564
|
+
'maxWidth',
|
|
565
|
+
'minHeight',
|
|
566
|
+
'minWidth',
|
|
567
|
+
'overflow',
|
|
568
|
+
'paddingLeft',
|
|
569
|
+
'textOverflow',
|
|
570
|
+
'whiteSpace',
|
|
571
|
+
'width',
|
|
572
|
+
];
|
|
573
|
+
const toolbarButtonOriginalPadding = new WeakMap();
|
|
574
|
+
const toolbarOverflowOriginalStyles = new WeakMap();
|
|
575
|
+
const toolbarOrder = [
|
|
576
|
+
sceneTitle,
|
|
577
|
+
sceneQuickActions,
|
|
578
|
+
sceneUtilityActions,
|
|
579
|
+
cameraPreviewGroup,
|
|
580
|
+
toolGroup,
|
|
581
|
+
spaceGroup,
|
|
582
|
+
handleGroup,
|
|
583
|
+
snapGroup,
|
|
584
|
+
placementGroup,
|
|
585
|
+
actionGroup,
|
|
586
|
+
sceneToolStatus,
|
|
587
|
+
sceneMouseHint,
|
|
588
|
+
status,
|
|
589
|
+
toolbarOverflowButton,
|
|
590
|
+
];
|
|
591
|
+
const toolbarOverflowItems = [
|
|
592
|
+
{ id: 'status', element: status, kind: 'text' },
|
|
593
|
+
{ id: 'mouse-hint', element: sceneMouseHint, kind: 'text' },
|
|
594
|
+
{ id: 'tool-status', element: sceneToolStatus, kind: 'text' },
|
|
595
|
+
{ id: 'transform-actions', element: actionGroup, kind: 'group' },
|
|
596
|
+
{ id: 'placement', element: placementGroup, kind: 'group' },
|
|
597
|
+
{ id: 'snap', element: snapGroup, kind: 'group' },
|
|
598
|
+
{ id: 'handles', element: handleGroup, kind: 'group' },
|
|
599
|
+
{ id: 'space', element: spaceGroup, kind: 'group' },
|
|
600
|
+
{ id: 'camera-preview', element: cameraPreviewGroup, kind: 'group' },
|
|
601
|
+
{ id: 'scene-utilities', element: sceneUtilityActions, kind: 'group' },
|
|
602
|
+
];
|
|
603
|
+
let toolbarOverflowOpen = false;
|
|
604
|
+
let toolbarOverflowRaf = null;
|
|
605
|
+
function restoreToolbarOverflowItemStyle(item) {
|
|
606
|
+
const original = toolbarOverflowOriginalStyles.get(item.element);
|
|
607
|
+
if (!original)
|
|
608
|
+
return;
|
|
609
|
+
for (const property of toolbarOverflowStyleProperties) {
|
|
610
|
+
item.element.style[property] = original[property] ?? '';
|
|
611
|
+
}
|
|
612
|
+
toolbarOverflowOriginalStyles.delete(item.element);
|
|
613
|
+
item.element.dataset.editorToolbarOverflowPlacement = 'toolbar';
|
|
614
|
+
}
|
|
615
|
+
function applyToolbarOverflowMenuStyle(item) {
|
|
616
|
+
if (!toolbarOverflowOriginalStyles.has(item.element)) {
|
|
617
|
+
const original = {};
|
|
618
|
+
for (const property of toolbarOverflowStyleProperties) {
|
|
619
|
+
original[property] = item.element.style[property];
|
|
620
|
+
}
|
|
621
|
+
toolbarOverflowOriginalStyles.set(item.element, original);
|
|
622
|
+
}
|
|
623
|
+
item.element.dataset.editorToolbarOverflowPlacement = 'menu';
|
|
624
|
+
item.element.style.alignSelf = 'stretch';
|
|
625
|
+
item.element.style.borderLeft = '0';
|
|
626
|
+
item.element.style.display = item.kind === 'group' ? 'flex' : 'block';
|
|
627
|
+
item.element.style.flex = '0 0 auto';
|
|
628
|
+
item.element.style.height = '';
|
|
629
|
+
item.element.style.maxWidth = 'none';
|
|
630
|
+
item.element.style.minHeight = '0';
|
|
631
|
+
item.element.style.minWidth = '0';
|
|
632
|
+
item.element.style.overflow = item.kind === 'group' ? 'visible' : 'hidden';
|
|
633
|
+
item.element.style.paddingLeft = '0';
|
|
634
|
+
item.element.style.textOverflow = item.kind === 'text' ? 'ellipsis' : '';
|
|
635
|
+
item.element.style.whiteSpace = item.kind === 'text' ? 'nowrap' : '';
|
|
636
|
+
item.element.style.width = '100%';
|
|
637
|
+
}
|
|
638
|
+
function setToolbarButtonCompact(button, compact) {
|
|
639
|
+
const label = button.querySelector('[data-editor-button-label]');
|
|
640
|
+
if (!label)
|
|
641
|
+
return;
|
|
642
|
+
if (!toolbarButtonOriginalPadding.has(button)) {
|
|
643
|
+
toolbarButtonOriginalPadding.set(button, button.style.padding);
|
|
644
|
+
}
|
|
645
|
+
const accessibleLabel = button.dataset.editorToolbarLabel || label.textContent || button.title;
|
|
646
|
+
button.dataset.editorToolbarLabel = accessibleLabel;
|
|
647
|
+
button.setAttribute('aria-label', accessibleLabel);
|
|
648
|
+
if (!button.title)
|
|
649
|
+
button.title = accessibleLabel;
|
|
650
|
+
button.style.padding = compact ? '4px 6px' : toolbarButtonOriginalPadding.get(button) ?? '';
|
|
651
|
+
label.style.display = compact ? 'inline-block' : '';
|
|
652
|
+
label.style.width = compact ? '0' : '';
|
|
653
|
+
label.style.maxWidth = compact ? '0' : '';
|
|
654
|
+
label.style.opacity = compact ? '0' : '';
|
|
655
|
+
label.style.marginLeft = compact ? '-5px' : '';
|
|
656
|
+
}
|
|
657
|
+
function setToolbarCompact(compact) {
|
|
658
|
+
for (const button of Array.from(sceneToolOverlay.querySelectorAll('button'))) {
|
|
659
|
+
setToolbarButtonCompact(button, compact);
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
function closeToolbarOverflowMenu() {
|
|
663
|
+
toolbarOverflowOpen = false;
|
|
664
|
+
toolbarOverflowMenu.style.display = 'none';
|
|
665
|
+
LocalEditorShared.applyButtonActiveState(toolbarOverflowButton, false);
|
|
666
|
+
}
|
|
667
|
+
function openToolbarOverflowMenu() {
|
|
668
|
+
if (toolbarOverflowMenu.childElementCount === 0)
|
|
669
|
+
return;
|
|
670
|
+
closeLocalTestMenu();
|
|
671
|
+
const win = doc.defaultView;
|
|
672
|
+
const rect = toolbarOverflowButton.getBoundingClientRect();
|
|
673
|
+
const viewportWidth = win?.innerWidth ?? doc.documentElement.clientWidth;
|
|
674
|
+
toolbarOverflowMenu.style.top = `${Math.max(8, rect.bottom + 5)}px`;
|
|
675
|
+
toolbarOverflowMenu.style.right = `${Math.max(8, viewportWidth - rect.right)}px`;
|
|
676
|
+
toolbarOverflowMenu.style.display = 'flex';
|
|
677
|
+
toolbarOverflowOpen = true;
|
|
678
|
+
LocalEditorShared.applyButtonActiveState(toolbarOverflowButton, true);
|
|
679
|
+
}
|
|
680
|
+
function resetToolbarOverflowLayout() {
|
|
681
|
+
setToolbarCompact(false);
|
|
682
|
+
for (const item of toolbarOverflowItems)
|
|
683
|
+
restoreToolbarOverflowItemStyle(item);
|
|
684
|
+
for (const item of toolbarOrder) {
|
|
685
|
+
item.dataset.editorToolbarOverflowed = 'false';
|
|
686
|
+
sceneToolOverlay.appendChild(item);
|
|
687
|
+
}
|
|
688
|
+
LocalEditorShared.clearElement(toolbarOverflowMenu);
|
|
689
|
+
toolbarOverflowButton.style.display = 'none';
|
|
690
|
+
sceneToolOverlay.style.overflowX = 'hidden';
|
|
691
|
+
closeToolbarOverflowMenu();
|
|
692
|
+
}
|
|
693
|
+
function layoutToolbarOverflow() {
|
|
694
|
+
toolbarOverflowRaf = null;
|
|
695
|
+
resetToolbarOverflowLayout();
|
|
696
|
+
if (sceneToolOverlay.style.display === 'none')
|
|
697
|
+
return;
|
|
698
|
+
const availableWidth = sceneToolOverlay.clientWidth;
|
|
699
|
+
if (availableWidth <= 0)
|
|
700
|
+
return;
|
|
701
|
+
const isOverflowing = () => sceneToolOverlay.scrollWidth > availableWidth + 1;
|
|
702
|
+
if (!isOverflowing())
|
|
703
|
+
return;
|
|
704
|
+
toolbarOverflowButton.style.display = 'inline-flex';
|
|
705
|
+
for (const item of toolbarOverflowItems) {
|
|
706
|
+
if (!isOverflowing())
|
|
707
|
+
break;
|
|
708
|
+
if (item.element.style.display === 'none')
|
|
709
|
+
continue;
|
|
710
|
+
item.element.dataset.editorToolbarOverflowed = 'true';
|
|
711
|
+
applyToolbarOverflowMenuStyle(item);
|
|
712
|
+
toolbarOverflowMenu.appendChild(item.element);
|
|
713
|
+
}
|
|
714
|
+
if (toolbarOverflowMenu.childElementCount === 0) {
|
|
715
|
+
toolbarOverflowButton.style.display = 'none';
|
|
716
|
+
}
|
|
717
|
+
if (isOverflowing()) {
|
|
718
|
+
setToolbarCompact(true);
|
|
719
|
+
}
|
|
720
|
+
sceneToolOverlay.style.overflowX = isOverflowing() ? 'auto' : 'hidden';
|
|
721
|
+
}
|
|
722
|
+
function scheduleToolbarOverflowLayout() {
|
|
723
|
+
const win = doc.defaultView;
|
|
724
|
+
if (!win) {
|
|
725
|
+
layoutToolbarOverflow();
|
|
726
|
+
return;
|
|
727
|
+
}
|
|
728
|
+
if (toolbarOverflowRaf != null)
|
|
729
|
+
win.cancelAnimationFrame(toolbarOverflowRaf);
|
|
730
|
+
toolbarOverflowRaf = win.requestAnimationFrame(layoutToolbarOverflow);
|
|
731
|
+
}
|
|
732
|
+
const onToolbarOverflowButtonClick = (event) => {
|
|
733
|
+
event.stopPropagation();
|
|
734
|
+
if (toolbarOverflowOpen)
|
|
735
|
+
closeToolbarOverflowMenu();
|
|
736
|
+
else
|
|
737
|
+
openToolbarOverflowMenu();
|
|
738
|
+
};
|
|
739
|
+
const onToolbarOverflowPointerDown = (event) => {
|
|
740
|
+
const target = event.target instanceof HTMLElement ? event.target : null;
|
|
741
|
+
if (!target)
|
|
742
|
+
return;
|
|
743
|
+
if (target.closest('[data-editor-local-test-menu]') || target.closest('[data-editor-local-test-toggle]'))
|
|
744
|
+
return;
|
|
745
|
+
if (target.closest('[data-editor-toolbar-overflow-menu]') || target.closest('[data-editor-toolbar-overflow-toggle]'))
|
|
746
|
+
return;
|
|
747
|
+
closeLocalTestMenu();
|
|
748
|
+
closeToolbarOverflowMenu();
|
|
749
|
+
};
|
|
750
|
+
const ResizeObserverCtor = doc.defaultView?.ResizeObserver;
|
|
751
|
+
const toolbarResizeObserver = ResizeObserverCtor
|
|
752
|
+
? new ResizeObserverCtor(() => scheduleToolbarOverflowLayout())
|
|
753
|
+
: null;
|
|
754
|
+
toolbarOverflowButton.addEventListener('click', onToolbarOverflowButtonClick);
|
|
755
|
+
localTestButton.addEventListener('click', onLocalTestButtonClick);
|
|
756
|
+
doc.addEventListener('pointerdown', onToolbarOverflowPointerDown);
|
|
757
|
+
toolbarResizeObserver?.observe(sceneToolOverlay);
|
|
758
|
+
doc.defaultView?.addEventListener('resize', scheduleToolbarOverflowLayout);
|
|
462
759
|
applyThemeToSurfaces();
|
|
463
760
|
updateThemeToggleButton();
|
|
464
761
|
enterEditorButton.addEventListener('click', () => callbacks.onEnterEditor?.());
|
|
465
|
-
saveButton.addEventListener('click', () =>
|
|
466
|
-
|
|
467
|
-
|
|
762
|
+
saveButton.addEventListener('click', () => {
|
|
763
|
+
closeLocalTestMenu();
|
|
764
|
+
if (localTestActionsEnabled)
|
|
765
|
+
callbacks.onSaveScene?.();
|
|
766
|
+
});
|
|
767
|
+
saveAndRunButton.addEventListener('click', () => {
|
|
768
|
+
closeLocalTestMenu();
|
|
769
|
+
if (localTestActionsEnabled)
|
|
770
|
+
callbacks.onSaveAndRunGame?.();
|
|
771
|
+
});
|
|
772
|
+
discardRunButton.addEventListener('click', () => {
|
|
773
|
+
closeLocalTestMenu();
|
|
774
|
+
if (localTestActionsEnabled)
|
|
775
|
+
callbacks.onDiscardAndRunGame?.();
|
|
776
|
+
});
|
|
468
777
|
undoButton.addEventListener('click', () => callbacks.onUndo?.());
|
|
469
778
|
redoButton.addEventListener('click', () => callbacks.onRedo?.());
|
|
470
779
|
sceneCameraButton.addEventListener('click', () => {
|
|
@@ -624,7 +933,7 @@ export function createLocalEditorBrowserUi(options = {}) {
|
|
|
624
933
|
const primaryModifier = event.metaKey || event.ctrlKey;
|
|
625
934
|
const handleDocumentShortcut = inputRouter.shouldHandleDocumentShortcut(event);
|
|
626
935
|
const handleGlobalShortcut = inputRouter.shouldHandleGlobalShortcut(event);
|
|
627
|
-
if (handleDocumentShortcut && primaryModifier && key === 's') {
|
|
936
|
+
if (handleDocumentShortcut && localTestActionsEnabled && primaryModifier && key === 's') {
|
|
628
937
|
event.preventDefault();
|
|
629
938
|
callbacks.onSaveScene?.();
|
|
630
939
|
return;
|
|
@@ -642,8 +951,7 @@ export function createLocalEditorBrowserUi(options = {}) {
|
|
|
642
951
|
callbacks.onRedo?.();
|
|
643
952
|
return;
|
|
644
953
|
}
|
|
645
|
-
if (handleGlobalShortcut && (
|
|
646
|
-
hierarchyController.handleDeleteShortcut(event);
|
|
954
|
+
if (handleGlobalShortcut && hierarchyController.handleEditShortcut(event)) {
|
|
647
955
|
return;
|
|
648
956
|
}
|
|
649
957
|
if (!handleGlobalShortcut || event.metaKey || event.ctrlKey || event.altKey)
|
|
@@ -767,12 +1075,16 @@ export function createLocalEditorBrowserUi(options = {}) {
|
|
|
767
1075
|
const disabled = state.busy;
|
|
768
1076
|
if (!inEditor || disabled)
|
|
769
1077
|
contextMenu.close();
|
|
770
|
-
hostChrome.style.display = inEditor ? '
|
|
1078
|
+
hostChrome.style.display = !inEditor && localTestActionsEnabled ? 'flex' : 'none';
|
|
771
1079
|
enterEditorButton.disabled = disabled;
|
|
772
1080
|
for (const button of [saveButton, saveAndRunButton, discardRunButton, undoButton, redoButton, sceneHelpButton, sceneCameraButton]) {
|
|
773
|
-
button.style.display = '';
|
|
1081
|
+
button.style.display = 'inline-flex';
|
|
774
1082
|
button.disabled = disabled;
|
|
775
1083
|
}
|
|
1084
|
+
localTestGroup.style.display = inEditor && localTestActionsEnabled ? 'flex' : 'none';
|
|
1085
|
+
localTestButton.disabled = disabled;
|
|
1086
|
+
if (!inEditor || !localTestActionsEnabled)
|
|
1087
|
+
closeLocalTestMenu();
|
|
776
1088
|
if (!inEditor)
|
|
777
1089
|
helpOpen = false;
|
|
778
1090
|
inputRouter.setModalOpen(inEditor && helpOpen);
|
|
@@ -864,6 +1176,7 @@ export function createLocalEditorBrowserUi(options = {}) {
|
|
|
864
1176
|
else {
|
|
865
1177
|
boxSelectionOverlay.style.display = 'none';
|
|
866
1178
|
}
|
|
1179
|
+
scheduleToolbarOverflowLayout();
|
|
867
1180
|
if (!inEditor)
|
|
868
1181
|
return;
|
|
869
1182
|
const hierarchyDescriptor = panelRegistry.getActivePanel('left');
|
|
@@ -886,11 +1199,23 @@ export function createLocalEditorBrowserUi(options = {}) {
|
|
|
886
1199
|
return activeTheme;
|
|
887
1200
|
},
|
|
888
1201
|
dispose() {
|
|
1202
|
+
if (toolbarOverflowRaf != null) {
|
|
1203
|
+
doc.defaultView?.cancelAnimationFrame(toolbarOverflowRaf);
|
|
1204
|
+
toolbarOverflowRaf = null;
|
|
1205
|
+
}
|
|
1206
|
+
toolbarResizeObserver?.disconnect();
|
|
1207
|
+
toolbarOverflowButton.removeEventListener('click', onToolbarOverflowButtonClick);
|
|
1208
|
+
localTestButton.removeEventListener('click', onLocalTestButtonClick);
|
|
1209
|
+
doc.removeEventListener('pointerdown', onToolbarOverflowPointerDown);
|
|
1210
|
+
doc.defaultView?.removeEventListener('resize', scheduleToolbarOverflowLayout);
|
|
889
1211
|
hostChrome.remove();
|
|
890
1212
|
workbench.root.remove();
|
|
1213
|
+
localTestMenu.remove();
|
|
1214
|
+
toolbarOverflowMenu.remove();
|
|
891
1215
|
boxSelectionOverlay.remove();
|
|
892
1216
|
shortcutHelpPanel.remove();
|
|
893
1217
|
hierarchyController.dispose();
|
|
1218
|
+
workbenchLayoutController.dispose();
|
|
894
1219
|
contextMenu.dispose();
|
|
895
1220
|
inputRouter.dispose();
|
|
896
1221
|
doc.removeEventListener('keydown', onKeyDown);
|