@fps-games/editor 0.1.2-beta.2 → 0.1.3-beta.2
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 +39 -7
- package/dist/local-editor-harness.d.ts.map +1 -1
- package/dist/local-editor-harness.js +379 -119
- package/dist/local-editor-harness.js.map +1 -1
- package/node_modules/@fps-games/babylon-renderer/dist/index.d.ts +5 -0
- package/node_modules/@fps-games/babylon-renderer/dist/index.d.ts.map +1 -0
- package/node_modules/@fps-games/babylon-renderer/dist/index.js +5 -0
- package/node_modules/@fps-games/babylon-renderer/dist/index.js.map +1 -0
- package/node_modules/@fps-games/babylon-renderer/dist/lighting/directional-light-angles.d.ts +17 -0
- package/node_modules/@fps-games/babylon-renderer/dist/lighting/directional-light-angles.d.ts.map +1 -0
- package/node_modules/@fps-games/babylon-renderer/dist/lighting/directional-light-angles.js +64 -0
- package/node_modules/@fps-games/babylon-renderer/dist/lighting/directional-light-angles.js.map +1 -0
- package/node_modules/@fps-games/babylon-renderer/dist/shadows/planar-shadow-shaders.d.ts +7 -0
- package/node_modules/@fps-games/babylon-renderer/dist/shadows/planar-shadow-shaders.d.ts.map +1 -0
- package/node_modules/@fps-games/babylon-renderer/dist/shadows/planar-shadow-shaders.js +176 -0
- package/node_modules/@fps-games/babylon-renderer/dist/shadows/planar-shadow-shaders.js.map +1 -0
- package/node_modules/@fps-games/babylon-renderer/dist/shadows/planar-shadow-system.d.ts +5 -0
- package/node_modules/@fps-games/babylon-renderer/dist/shadows/planar-shadow-system.d.ts.map +1 -0
- package/node_modules/@fps-games/babylon-renderer/dist/shadows/planar-shadow-system.js +837 -0
- package/node_modules/@fps-games/babylon-renderer/dist/shadows/planar-shadow-system.js.map +1 -0
- package/node_modules/@fps-games/babylon-renderer/dist/shadows/types.d.ts +96 -0
- package/node_modules/@fps-games/babylon-renderer/dist/shadows/types.d.ts.map +1 -0
- package/node_modules/@fps-games/babylon-renderer/dist/shadows/types.js +45 -0
- package/node_modules/@fps-games/babylon-renderer/dist/shadows/types.js.map +1 -0
- package/node_modules/@fps-games/babylon-renderer/package.json +20 -0
- package/node_modules/@fps-games/editor-babylon/dist/editor-sky.d.ts +2 -2
- package/node_modules/@fps-games/editor-babylon/dist/editor-sky.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-babylon/dist/editor-sky.js +31 -15
- package/node_modules/@fps-games/editor-babylon/dist/editor-sky.js.map +1 -1
- package/node_modules/@fps-games/editor-babylon/dist/editor-world.d.ts +10 -0
- package/node_modules/@fps-games/editor-babylon/dist/editor-world.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-babylon/dist/editor-world.js +31 -9
- package/node_modules/@fps-games/editor-babylon/dist/editor-world.js.map +1 -1
- package/node_modules/@fps-games/editor-babylon/dist/index.d.ts +1 -0
- package/node_modules/@fps-games/editor-babylon/dist/index.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-babylon/dist/index.js +1 -0
- package/node_modules/@fps-games/editor-babylon/dist/index.js.map +1 -1
- package/node_modules/@fps-games/editor-babylon/dist/infinite-grid.d.ts +23 -0
- package/node_modules/@fps-games/editor-babylon/dist/infinite-grid.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-babylon/dist/infinite-grid.js +34 -4
- package/node_modules/@fps-games/editor-babylon/dist/infinite-grid.js.map +1 -1
- package/node_modules/@fps-games/editor-babylon/dist/material-property-adapter.d.ts +3 -1
- package/node_modules/@fps-games/editor-babylon/dist/material-property-adapter.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-babylon/dist/material-property-adapter.js +166 -0
- package/node_modules/@fps-games/editor-babylon/dist/material-property-adapter.js.map +1 -1
- package/node_modules/@fps-games/editor-babylon/dist/projection.d.ts +12 -1
- package/node_modules/@fps-games/editor-babylon/dist/projection.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-babylon/dist/projection.js +231 -32
- package/node_modules/@fps-games/editor-babylon/dist/projection.js.map +1 -1
- package/node_modules/@fps-games/editor-babylon/dist/shadow-preview-controller.d.ts +22 -0
- package/node_modules/@fps-games/editor-babylon/dist/shadow-preview-controller.d.ts.map +1 -0
- package/node_modules/@fps-games/editor-babylon/dist/shadow-preview-controller.js +71 -0
- package/node_modules/@fps-games/editor-babylon/dist/shadow-preview-controller.js.map +1 -0
- package/node_modules/@fps-games/editor-babylon/dist/types.d.ts +2 -1
- package/node_modules/@fps-games/editor-babylon/dist/types.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-babylon/package.json +5 -4
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-panel-registry.d.ts +3 -1
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-panel-registry.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-panel-registry.js +7 -0
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-panel-registry.js.map +1 -1
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-panels.d.ts +8 -3
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-panels.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-panels.js +645 -20
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-panels.js.map +1 -1
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-types.d.ts +147 -3
- 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 +6 -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 +1 -0
- 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 -4
- 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 +1060 -86
- 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/dist/index.d.ts +61 -0
- package/node_modules/@fps-games/editor-protocol/dist/index.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-protocol/dist/index.js +157 -0
- package/node_modules/@fps-games/editor-protocol/dist/index.js.map +1 -1
- package/node_modules/@fps-games/editor-protocol/package.json +1 -1
- package/package.json +8 -6
|
@@ -16,33 +16,15 @@ import { createShortcutHelpPanel as createLocalEditorShortcutHelpPanel } from '.
|
|
|
16
16
|
import { applyLocalEditorTheme, ensureLocalEditorTheme, LOCAL_EDITOR_THEME_CLASS, normalizeLocalEditorThemeName, } from './local-editor-ui-theme';
|
|
17
17
|
import { createDefaultLocalEditorWorkbenchLayout, createLocalEditorWorkbenchLayoutController, createLocalEditorWorkbench, createSceneHeaderToolbar, createWorkbenchPanelContent, } from './local-editor-ui-workbench';
|
|
18
18
|
import { createLocalEditorIcon } from './local-editor-ui-icons';
|
|
19
|
-
export { applyLocalEditorBrowserInspectorControlBinding, createLocalEditorBrowserInspectorControlRegistry, formatLocalEditorBrowserInspectorNumberValue, formatLocalEditorBrowserInspectorValue, resolveLocalEditorBrowserInspectorSectionStatus, resolveLocalEditorBrowserInspectorControlRegistration, } from './local-editor-ui-panels';
|
|
19
|
+
export { applyLocalEditorBrowserInspectorControlBinding, createLocalEditorBrowserInspectorControlRegistry, formatLocalEditorBrowserInspectorNumberValue, formatLocalEditorBrowserInspectorValue, parseLocalEditorBrowserInspectorNumberValue, resolveLocalEditorBrowserInspectorSectionStatus, resolveLocalEditorBrowserInspectorControlRegistration, } from './local-editor-ui-panels';
|
|
20
20
|
export { DEFAULT_LOCAL_EDITOR_THEME, LOCAL_EDITOR_THEME_CLASS, applyLocalEditorTheme, normalizeLocalEditorThemeName, } from './local-editor-ui-theme';
|
|
21
21
|
export { LOCAL_EDITOR_ICON_NAMES, createLocalEditorIcon, isLocalEditorIconName, resolveLocalEditorIconName, } from './local-editor-ui-icons';
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
const normalized = value.trim();
|
|
25
|
-
if (normalized === '')
|
|
26
|
-
return mode === 'final' ? 0 : null;
|
|
27
|
-
if (normalized === '-'
|
|
28
|
-
|| normalized === '+'
|
|
29
|
-
|| normalized === '.'
|
|
30
|
-
|| normalized === '-.'
|
|
31
|
-
|| normalized === '+.') {
|
|
32
|
-
return null;
|
|
33
|
-
}
|
|
34
|
-
if (mode === 'live' && (/[.]$/.test(normalized) || /e[+-]?$/i.test(normalized)))
|
|
35
|
-
return null;
|
|
36
|
-
if (!/^[+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:e[+-]?\d+)?$/i.test(normalized))
|
|
37
|
-
return null;
|
|
38
|
-
const numeric = Number(normalized);
|
|
39
|
-
return Number.isFinite(numeric) ? numeric : null;
|
|
40
|
-
}
|
|
41
|
-
function readInspectorInputValue(input, mode = 'final') {
|
|
22
|
+
const PENDING_NUMERIC_PROPERTY_INPUT = Symbol('pending-numeric-property-input');
|
|
23
|
+
function readInspectorInputValue(input, source) {
|
|
42
24
|
const control = input.dataset.serializedControl;
|
|
43
25
|
const valueType = input.dataset.serializedValueType;
|
|
44
26
|
if ((control === 'vec2' || control === 'vec3') && input instanceof HTMLInputElement) {
|
|
45
|
-
return readInspectorVectorInputValue(input,
|
|
27
|
+
return readInspectorVectorInputValue(input, source) ?? PENDING_NUMERIC_PROPERTY_INPUT;
|
|
46
28
|
}
|
|
47
29
|
if (control === 'enum' && input instanceof HTMLSelectElement) {
|
|
48
30
|
const option = input.selectedOptions.item(0);
|
|
@@ -61,7 +43,9 @@ function readInspectorInputValue(input, mode = 'final') {
|
|
|
61
43
|
if (control === 'boolean' || valueType === 'boolean')
|
|
62
44
|
return input.value === 'true';
|
|
63
45
|
if (control === 'number' || valueType === 'number') {
|
|
64
|
-
|
|
46
|
+
const mode = source === 'input' && readInspectorCommitMode(input) === 'live' ? 'live' : 'final';
|
|
47
|
+
return LocalEditorPanels.parseLocalEditorBrowserInspectorNumberValue(input.value, mode)
|
|
48
|
+
?? PENDING_NUMERIC_PROPERTY_INPUT;
|
|
65
49
|
}
|
|
66
50
|
if (control === 'color' && input instanceof HTMLInputElement && input.type === 'color') {
|
|
67
51
|
const value = input.value.replace('#', '');
|
|
@@ -169,9 +153,10 @@ function setToolbarButtonIcon(doc, button, icon) {
|
|
|
169
153
|
else
|
|
170
154
|
button.prepend(nextIcon);
|
|
171
155
|
}
|
|
172
|
-
function readInspectorVectorInputValue(input,
|
|
156
|
+
function readInspectorVectorInputValue(input, source) {
|
|
173
157
|
const wrapper = input.closest('[data-inspector-vector-control]');
|
|
174
158
|
const values = {};
|
|
159
|
+
const mode = source === 'input' && readInspectorCommitMode(input) === 'live' ? 'live' : 'final';
|
|
175
160
|
const fields = wrapper
|
|
176
161
|
? Array.from(wrapper.querySelectorAll('input[data-serialized-vector-axis]'))
|
|
177
162
|
: [input];
|
|
@@ -179,21 +164,21 @@ function readInspectorVectorInputValue(input, mode) {
|
|
|
179
164
|
const axis = field.dataset.serializedVectorAxis;
|
|
180
165
|
if (!axis)
|
|
181
166
|
continue;
|
|
182
|
-
const numeric = parseLocalEditorBrowserInspectorNumberValue(field.value, mode);
|
|
167
|
+
const numeric = LocalEditorPanels.parseLocalEditorBrowserInspectorNumberValue(field.value, mode);
|
|
183
168
|
if (numeric == null)
|
|
184
|
-
return
|
|
169
|
+
return null;
|
|
185
170
|
values[axis] = numeric;
|
|
186
171
|
}
|
|
187
172
|
return values;
|
|
188
173
|
}
|
|
189
|
-
function createInspectorPropertyInput(input, source
|
|
174
|
+
function createInspectorPropertyInput(input, source) {
|
|
190
175
|
if (!input.dataset.serializedPath || !input.dataset.serializedTargetId)
|
|
191
176
|
return null;
|
|
192
177
|
const targetIds = input.dataset.serializedTargetIds
|
|
193
178
|
? input.dataset.serializedTargetIds.split(',').filter(Boolean)
|
|
194
179
|
: undefined;
|
|
195
|
-
const value = readInspectorInputValue(input,
|
|
196
|
-
if (value ===
|
|
180
|
+
const value = readInspectorInputValue(input, source);
|
|
181
|
+
if (value === PENDING_NUMERIC_PROPERTY_INPUT)
|
|
197
182
|
return null;
|
|
198
183
|
return {
|
|
199
184
|
targetId: input.dataset.serializedTargetId,
|
|
@@ -207,16 +192,63 @@ function createInspectorPropertyInput(input, source, numberParseMode = 'final')
|
|
|
207
192
|
source: input.dataset.serializedEditSource ?? source,
|
|
208
193
|
};
|
|
209
194
|
}
|
|
210
|
-
function
|
|
211
|
-
const control = input.dataset.
|
|
212
|
-
const valueType = input.dataset.
|
|
213
|
-
if (
|
|
214
|
-
return
|
|
215
|
-
|
|
195
|
+
function readRenderingInputValue(input, source) {
|
|
196
|
+
const control = input.dataset.renderingControl;
|
|
197
|
+
const valueType = input.dataset.renderingValueType;
|
|
198
|
+
if (input instanceof HTMLInputElement && input.type === 'checkbox')
|
|
199
|
+
return input.checked;
|
|
200
|
+
if (input instanceof HTMLInputElement && input.type === 'color') {
|
|
201
|
+
const value = input.value.replace('#', '');
|
|
202
|
+
const numeric = Number.parseInt(value, 16);
|
|
203
|
+
if (!Number.isFinite(numeric))
|
|
204
|
+
return null;
|
|
205
|
+
return {
|
|
206
|
+
r: ((numeric >> 16) & 255) / 255,
|
|
207
|
+
g: ((numeric >> 8) & 255) / 255,
|
|
208
|
+
b: (numeric & 255) / 255,
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
if (control === 'number' || valueType === 'number') {
|
|
212
|
+
const mode = source === 'input' && readRenderingCommitMode(input) === 'live' ? 'live' : 'final';
|
|
213
|
+
return LocalEditorPanels.parseLocalEditorBrowserInspectorNumberValue(input.value, mode)
|
|
214
|
+
?? PENDING_NUMERIC_PROPERTY_INPUT;
|
|
215
|
+
}
|
|
216
|
+
if (control === 'string-list' || valueType === 'string-list') {
|
|
217
|
+
return input.value
|
|
218
|
+
.split(/\r?\n/)
|
|
219
|
+
.map(value => value.trim())
|
|
220
|
+
.filter(Boolean);
|
|
221
|
+
}
|
|
222
|
+
if (control === 'boolean' || valueType === 'boolean')
|
|
223
|
+
return input.value === 'true';
|
|
224
|
+
return input.value;
|
|
225
|
+
}
|
|
226
|
+
function createRenderingPropertyInput(input, source) {
|
|
227
|
+
const sectionId = input.dataset.renderingSectionId;
|
|
228
|
+
const systemId = input.dataset.renderingSystemId;
|
|
229
|
+
const path = input.dataset.renderingPath;
|
|
230
|
+
if (!sectionId || !systemId || !path)
|
|
231
|
+
return null;
|
|
232
|
+
const value = readRenderingInputValue(input, source);
|
|
233
|
+
if (value === PENDING_NUMERIC_PROPERTY_INPUT)
|
|
234
|
+
return null;
|
|
235
|
+
return {
|
|
236
|
+
sectionId,
|
|
237
|
+
systemId,
|
|
238
|
+
path,
|
|
239
|
+
value,
|
|
240
|
+
control: input.dataset.renderingControl,
|
|
241
|
+
valueType: input.dataset.renderingValueType,
|
|
242
|
+
commitMode: input.dataset.renderingCommitMode,
|
|
243
|
+
source,
|
|
244
|
+
};
|
|
216
245
|
}
|
|
217
246
|
function readInspectorCommitMode(input) {
|
|
218
247
|
return input.dataset.serializedCommitMode ?? 'live';
|
|
219
248
|
}
|
|
249
|
+
function readRenderingCommitMode(input) {
|
|
250
|
+
return input.dataset.renderingCommitMode ?? 'live';
|
|
251
|
+
}
|
|
220
252
|
function readInspectorImmediateSource(input) {
|
|
221
253
|
if (input instanceof HTMLSelectElement)
|
|
222
254
|
return 'select';
|
|
@@ -226,6 +258,15 @@ function readInspectorImmediateSource(input) {
|
|
|
226
258
|
return 'color';
|
|
227
259
|
return 'input';
|
|
228
260
|
}
|
|
261
|
+
function readRenderingImmediateSource(input) {
|
|
262
|
+
if (input instanceof HTMLInputElement && input.type === 'checkbox')
|
|
263
|
+
return 'toggle';
|
|
264
|
+
if (input instanceof HTMLInputElement && input.type === 'color')
|
|
265
|
+
return 'color';
|
|
266
|
+
if (input instanceof HTMLTextAreaElement)
|
|
267
|
+
return 'list';
|
|
268
|
+
return 'input';
|
|
269
|
+
}
|
|
229
270
|
function createSceneFrameRateOverlay(doc) {
|
|
230
271
|
const root = doc.createElement('div');
|
|
231
272
|
root.classList.add(LOCAL_EDITOR_THEME_CLASS);
|
|
@@ -726,6 +767,170 @@ function renderMeasurementOverlay(overlay, state) {
|
|
|
726
767
|
function formatSvgNumber(value) {
|
|
727
768
|
return Number.isFinite(value) ? value.toFixed(2) : '0';
|
|
728
769
|
}
|
|
770
|
+
function isLocalEditorAssetBrowserTab(value) {
|
|
771
|
+
return value === 'all' || value === 'models' || value === 'materials' || value === 'textures';
|
|
772
|
+
}
|
|
773
|
+
function createAssetBrowserMenuItems(state, asset) {
|
|
774
|
+
const assetKind = resolveBrowserAssetKind(asset);
|
|
775
|
+
const placeable = asset.placeable !== false && asset.disabled !== true;
|
|
776
|
+
const activeId = state.activeId;
|
|
777
|
+
const items = [];
|
|
778
|
+
if (assetKind === 'material') {
|
|
779
|
+
items.push({
|
|
780
|
+
id: 'asset.edit-material',
|
|
781
|
+
label: '编辑材质球',
|
|
782
|
+
disabled: !asset.material,
|
|
783
|
+
disabledReason: '该材质资产缺少可编辑参数。',
|
|
784
|
+
});
|
|
785
|
+
}
|
|
786
|
+
else if (assetKind === 'texture' && asset.preview) {
|
|
787
|
+
items.push({
|
|
788
|
+
id: 'asset.preview',
|
|
789
|
+
label: '查看贴图预览',
|
|
790
|
+
});
|
|
791
|
+
}
|
|
792
|
+
if (assetKind === 'material') {
|
|
793
|
+
items.push({
|
|
794
|
+
id: 'asset.apply-material',
|
|
795
|
+
label: '应用到当前 GameObject',
|
|
796
|
+
separatorBefore: true,
|
|
797
|
+
disabled: !activeId,
|
|
798
|
+
disabledReason: '请先在 Hierarchy 或 Scene View 中选择一个 GameObject。',
|
|
799
|
+
});
|
|
800
|
+
}
|
|
801
|
+
else if (assetKind === 'texture') {
|
|
802
|
+
items.push({
|
|
803
|
+
id: 'asset.add-to-scene',
|
|
804
|
+
label: '作为地贴添加到场景',
|
|
805
|
+
separatorBefore: asset.preview ? true : undefined,
|
|
806
|
+
disabled: !placeable,
|
|
807
|
+
disabledReason: '该贴图当前不可放置。',
|
|
808
|
+
});
|
|
809
|
+
items.push({
|
|
810
|
+
id: 'asset.place',
|
|
811
|
+
label: '进入地面放置模式',
|
|
812
|
+
disabled: !placeable,
|
|
813
|
+
disabledReason: '该贴图当前不可放置。',
|
|
814
|
+
});
|
|
815
|
+
items.push({
|
|
816
|
+
id: 'asset.use-base-texture',
|
|
817
|
+
label: '用作当前材质 Base 图',
|
|
818
|
+
disabled: true,
|
|
819
|
+
disabledReason: 'Base 图材质字段将在下一步接入。',
|
|
820
|
+
separatorBefore: true,
|
|
821
|
+
});
|
|
822
|
+
items.push({
|
|
823
|
+
id: 'asset.use-emission-mask',
|
|
824
|
+
label: '用作当前材质自发光遮罩',
|
|
825
|
+
disabled: true,
|
|
826
|
+
disabledReason: '自发光遮罩绑定将在下一步接入。',
|
|
827
|
+
});
|
|
828
|
+
}
|
|
829
|
+
else {
|
|
830
|
+
items.push({
|
|
831
|
+
id: 'asset.add-to-scene',
|
|
832
|
+
label: '添加到场景',
|
|
833
|
+
disabled: !placeable,
|
|
834
|
+
disabledReason: '该模型当前不可放置。',
|
|
835
|
+
});
|
|
836
|
+
items.push({
|
|
837
|
+
id: 'asset.place',
|
|
838
|
+
label: '进入放置模式',
|
|
839
|
+
disabled: !placeable,
|
|
840
|
+
disabledReason: '该模型当前不可放置。',
|
|
841
|
+
});
|
|
842
|
+
items.push({
|
|
843
|
+
id: 'asset.add-as-child',
|
|
844
|
+
label: '添加为当前节点子对象',
|
|
845
|
+
disabled: true,
|
|
846
|
+
disabledReason: '子对象添加动作将在资产 patch 中接入。',
|
|
847
|
+
separatorBefore: true,
|
|
848
|
+
});
|
|
849
|
+
items.push({
|
|
850
|
+
id: 'asset.replace-selected-model',
|
|
851
|
+
label: '替换当前模型',
|
|
852
|
+
disabled: true,
|
|
853
|
+
disabledReason: '模型替换动作将在资产 patch 中接入。',
|
|
854
|
+
});
|
|
855
|
+
}
|
|
856
|
+
items.push({
|
|
857
|
+
id: 'asset.copy-id',
|
|
858
|
+
label: '复制 Asset ID',
|
|
859
|
+
separatorBefore: true,
|
|
860
|
+
});
|
|
861
|
+
return items;
|
|
862
|
+
}
|
|
863
|
+
function resolveBrowserAssetKind(asset) {
|
|
864
|
+
const kind = `${asset.kind ?? ''}`.trim().toLowerCase();
|
|
865
|
+
const id = `${asset.id ?? ''}`.trim().toLowerCase();
|
|
866
|
+
const assetId = `${asset.assetId ?? ''}`.trim().toLowerCase();
|
|
867
|
+
if (kind === 'material' || id.startsWith('material:') || assetId.startsWith('mat_'))
|
|
868
|
+
return 'material';
|
|
869
|
+
if (kind === 'texture' || kind === 'image' || id.startsWith('texture:') || assetId.startsWith('texture_'))
|
|
870
|
+
return 'texture';
|
|
871
|
+
return 'model';
|
|
872
|
+
}
|
|
873
|
+
function createAssetBrowserActionInput(actionId, asset) {
|
|
874
|
+
return {
|
|
875
|
+
actionId,
|
|
876
|
+
assetId: asset.assetId ?? asset.id,
|
|
877
|
+
browserAssetId: asset.id,
|
|
878
|
+
assetKind: asset.kind,
|
|
879
|
+
};
|
|
880
|
+
}
|
|
881
|
+
function copyTextToClipboard(win, text) {
|
|
882
|
+
void win?.navigator?.clipboard?.writeText?.(text);
|
|
883
|
+
}
|
|
884
|
+
function readInspectorAssetPickerOptions(property) {
|
|
885
|
+
const title = readControlOptionString(property, 'pickerTitle') || property.label;
|
|
886
|
+
const rawCandidates = property.controlOptions?.candidates;
|
|
887
|
+
const candidates = Array.isArray(rawCandidates)
|
|
888
|
+
? rawCandidates.flatMap(readInspectorAssetPickerOption)
|
|
889
|
+
: [];
|
|
890
|
+
return { title, candidates };
|
|
891
|
+
}
|
|
892
|
+
function readInspectorAssetPickerOption(value) {
|
|
893
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
894
|
+
return [];
|
|
895
|
+
const record = value;
|
|
896
|
+
const label = typeof record.label === 'string' ? record.label : '';
|
|
897
|
+
const optionValue = typeof record.value === 'string' ? record.value : '';
|
|
898
|
+
if (!label && !optionValue)
|
|
899
|
+
return [];
|
|
900
|
+
return [{
|
|
901
|
+
label: label || optionValue,
|
|
902
|
+
value: optionValue,
|
|
903
|
+
...(typeof record.meta === 'string' ? { meta: record.meta } : {}),
|
|
904
|
+
...(typeof record.kind === 'string' ? { kind: record.kind } : {}),
|
|
905
|
+
...(isAssetPreview(record.preview) ? { preview: record.preview } : {}),
|
|
906
|
+
}];
|
|
907
|
+
}
|
|
908
|
+
function readControlOptionString(property, key) {
|
|
909
|
+
const value = property.controlOptions?.[key];
|
|
910
|
+
return typeof value === 'string' ? value : '';
|
|
911
|
+
}
|
|
912
|
+
function isAssetPreview(value) {
|
|
913
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
914
|
+
return false;
|
|
915
|
+
const record = value;
|
|
916
|
+
if (record.kind === 'image')
|
|
917
|
+
return typeof record.url === 'string' && record.url.length > 0;
|
|
918
|
+
return record.kind === 'material-sphere';
|
|
919
|
+
}
|
|
920
|
+
function findInspectorPropertyForInput(state, input) {
|
|
921
|
+
const path = input.dataset.serializedPath;
|
|
922
|
+
if (!path)
|
|
923
|
+
return null;
|
|
924
|
+
const objects = [state.inspectorObject, state.inspectorMultiObject].filter(Boolean);
|
|
925
|
+
for (const object of objects) {
|
|
926
|
+
for (const section of object.sections) {
|
|
927
|
+
const property = section.properties.find(candidate => candidate.path === path);
|
|
928
|
+
if (property)
|
|
929
|
+
return property;
|
|
930
|
+
}
|
|
931
|
+
}
|
|
932
|
+
return null;
|
|
933
|
+
}
|
|
729
934
|
export function createLocalEditorBrowserUi(options = {}) {
|
|
730
935
|
const doc = options.document ?? document;
|
|
731
936
|
const root = options.root ?? doc.body;
|
|
@@ -745,6 +950,7 @@ export function createLocalEditorBrowserUi(options = {}) {
|
|
|
745
950
|
});
|
|
746
951
|
let currentState = null;
|
|
747
952
|
let inspectorFilter = '';
|
|
953
|
+
let assetBrowserTab = 'all';
|
|
748
954
|
const workbenchLayout = createDefaultLocalEditorWorkbenchLayout();
|
|
749
955
|
const panelRegistry = createLocalEditorPanelRegistry(workbenchLayout);
|
|
750
956
|
const hostChrome = doc.createElement('div');
|
|
@@ -836,15 +1042,26 @@ export function createLocalEditorBrowserUi(options = {}) {
|
|
|
836
1042
|
const undoButton = createToolbarIconButton(doc, '撤销', 'undo', '撤销');
|
|
837
1043
|
const redoButton = createToolbarIconButton(doc, '重做', 'redo', '重做');
|
|
838
1044
|
let helpOpen = false;
|
|
1045
|
+
let assetPreviewOpen = false;
|
|
1046
|
+
let previewedAsset = null;
|
|
1047
|
+
let assetPreviewMode = 'preview';
|
|
1048
|
+
let assetPickerOpen = false;
|
|
1049
|
+
let assetPickerFilter = '';
|
|
1050
|
+
let assetPickerInput = null;
|
|
1051
|
+
let assetPickerProperty = null;
|
|
839
1052
|
const editorStatusButton = createToolbarIconButton(doc, '编辑器状态', 'status', '编辑器状态');
|
|
840
1053
|
editorStatusButton.dataset.editorStatusButton = 'true';
|
|
841
1054
|
editorStatusButton.style.cursor = 'help';
|
|
842
1055
|
const workbench = createLocalEditorWorkbench(doc);
|
|
843
1056
|
const hierarchyPanel = createWorkbenchPanelContent(doc);
|
|
844
1057
|
const assetPanel = workbench.bottomDock;
|
|
1058
|
+
const rightDockTabs = doc.createElement('div');
|
|
845
1059
|
const inspectorPanel = createWorkbenchPanelContent(doc);
|
|
1060
|
+
const renderingPanel = createWorkbenchPanelContent(doc);
|
|
846
1061
|
workbench.leftDock.appendChild(hierarchyPanel);
|
|
1062
|
+
workbench.rightDock.appendChild(rightDockTabs);
|
|
847
1063
|
workbench.rightDock.appendChild(inspectorPanel);
|
|
1064
|
+
workbench.rightDock.appendChild(renderingPanel);
|
|
848
1065
|
root.appendChild(workbench.root);
|
|
849
1066
|
tooltipSurfaces.add(workbench.root);
|
|
850
1067
|
const workbenchLayoutController = createLocalEditorWorkbenchLayoutController(doc, workbench);
|
|
@@ -1123,17 +1340,17 @@ export function createLocalEditorBrowserUi(options = {}) {
|
|
|
1123
1340
|
sceneUtilityActions.appendChild(gridToggleButton);
|
|
1124
1341
|
sceneUtilityActions.appendChild(themeToggleButton);
|
|
1125
1342
|
sceneUtilityActions.appendChild(sceneHelpButton);
|
|
1126
|
-
const
|
|
1127
|
-
|
|
1343
|
+
const cameraPreviewGroup = doc.createElement('div');
|
|
1344
|
+
cameraPreviewGroup.style.cssText = [
|
|
1128
1345
|
'display:flex',
|
|
1129
1346
|
'align-items:center',
|
|
1130
1347
|
'gap:4px',
|
|
1131
1348
|
'padding-left:8px',
|
|
1132
1349
|
'border-left:1px solid var(--fps-editor-divider)',
|
|
1133
1350
|
].join(';');
|
|
1134
|
-
const
|
|
1135
|
-
|
|
1136
|
-
|
|
1351
|
+
const sceneCameraButton = createToolbarIconButton(doc, '从 Main Camera 查看场景', 'camera');
|
|
1352
|
+
sceneCameraButton.dataset.sceneCameraPreviewToggle = 'true';
|
|
1353
|
+
cameraPreviewGroup.appendChild(sceneCameraButton);
|
|
1137
1354
|
const viewportToolsGroup = doc.createElement('div');
|
|
1138
1355
|
viewportToolsGroup.style.cssText = [
|
|
1139
1356
|
'display:flex',
|
|
@@ -1238,7 +1455,7 @@ export function createLocalEditorBrowserUi(options = {}) {
|
|
|
1238
1455
|
sceneToolOverlay.appendChild(actionGroup);
|
|
1239
1456
|
sceneToolOverlay.appendChild(viewportToolsGroup);
|
|
1240
1457
|
sceneToolOverlay.appendChild(sceneUtilityActions);
|
|
1241
|
-
sceneToolOverlay.appendChild(
|
|
1458
|
+
sceneToolOverlay.appendChild(cameraPreviewGroup);
|
|
1242
1459
|
sceneToolOverlay.appendChild(editorStatusButton);
|
|
1243
1460
|
sceneToolOverlay.appendChild(toolbarOverflowButton);
|
|
1244
1461
|
workbench.sceneHeader.appendChild(sceneToolOverlay);
|
|
@@ -1275,6 +1492,164 @@ export function createLocalEditorBrowserUi(options = {}) {
|
|
|
1275
1492
|
shortcutHelpPanel.classList.add(LOCAL_EDITOR_THEME_CLASS);
|
|
1276
1493
|
root.appendChild(shortcutHelpPanel);
|
|
1277
1494
|
tooltipSurfaces.add(shortcutHelpPanel);
|
|
1495
|
+
const assetPreviewModal = doc.createElement('div');
|
|
1496
|
+
assetPreviewModal.classList.add(LOCAL_EDITOR_THEME_CLASS);
|
|
1497
|
+
assetPreviewModal.dataset.editorAssetPreviewModal = 'true';
|
|
1498
|
+
assetPreviewModal.dataset.editorWorkbenchRegion = 'modal';
|
|
1499
|
+
assetPreviewModal.style.cssText = [
|
|
1500
|
+
'position:fixed',
|
|
1501
|
+
'inset:0',
|
|
1502
|
+
'z-index:2147483642',
|
|
1503
|
+
'display:none',
|
|
1504
|
+
'align-items:center',
|
|
1505
|
+
'justify-content:center',
|
|
1506
|
+
'padding:32px',
|
|
1507
|
+
'background:rgba(0,0,0,0.45)',
|
|
1508
|
+
'font-family:var(--fps-editor-font)',
|
|
1509
|
+
'color:var(--fps-editor-text)',
|
|
1510
|
+
'pointer-events:auto',
|
|
1511
|
+
].join(';');
|
|
1512
|
+
const assetPreviewDialog = doc.createElement('div');
|
|
1513
|
+
assetPreviewDialog.setAttribute('role', 'dialog');
|
|
1514
|
+
assetPreviewDialog.setAttribute('aria-modal', 'true');
|
|
1515
|
+
assetPreviewDialog.setAttribute('aria-label', '资产预览');
|
|
1516
|
+
assetPreviewDialog.style.cssText = [
|
|
1517
|
+
'width:min(420px,calc(100vw - 64px))',
|
|
1518
|
+
'max-height:calc(100vh - 64px)',
|
|
1519
|
+
'display:flex',
|
|
1520
|
+
'flex-direction:column',
|
|
1521
|
+
'gap:12px',
|
|
1522
|
+
'padding:14px',
|
|
1523
|
+
'border:1px solid var(--fps-editor-border)',
|
|
1524
|
+
'border-radius:4px',
|
|
1525
|
+
'background:var(--fps-editor-panel)',
|
|
1526
|
+
'box-shadow:var(--fps-editor-shadow-modal)',
|
|
1527
|
+
'overflow:auto',
|
|
1528
|
+
].join(';');
|
|
1529
|
+
const assetPreviewHeader = doc.createElement('div');
|
|
1530
|
+
assetPreviewHeader.style.cssText = 'display:flex;align-items:center;justify-content:space-between;gap:12px';
|
|
1531
|
+
const assetPreviewTitle = doc.createElement('h2');
|
|
1532
|
+
assetPreviewTitle.style.cssText = [
|
|
1533
|
+
'min-width:0',
|
|
1534
|
+
'margin:0',
|
|
1535
|
+
'overflow:hidden',
|
|
1536
|
+
'text-overflow:ellipsis',
|
|
1537
|
+
'white-space:nowrap',
|
|
1538
|
+
'font-size:13px',
|
|
1539
|
+
'font-weight:900',
|
|
1540
|
+
'color:var(--fps-editor-text-strong)',
|
|
1541
|
+
].join(';');
|
|
1542
|
+
const assetPreviewCloseButton = LocalEditorShared.createButton(doc, '关闭');
|
|
1543
|
+
assetPreviewCloseButton.dataset.editorAssetPreviewClose = 'true';
|
|
1544
|
+
assetPreviewHeader.appendChild(assetPreviewTitle);
|
|
1545
|
+
assetPreviewHeader.appendChild(assetPreviewCloseButton);
|
|
1546
|
+
const assetPreviewBody = doc.createElement('div');
|
|
1547
|
+
assetPreviewBody.style.cssText = [
|
|
1548
|
+
'display:flex',
|
|
1549
|
+
'align-items:center',
|
|
1550
|
+
'justify-content:center',
|
|
1551
|
+
'min-height:240px',
|
|
1552
|
+
'border:1px solid var(--fps-editor-divider)',
|
|
1553
|
+
'border-radius:4px',
|
|
1554
|
+
'background:var(--fps-editor-panel-soft)',
|
|
1555
|
+
].join(';');
|
|
1556
|
+
const assetPreviewMeta = doc.createElement('div');
|
|
1557
|
+
assetPreviewMeta.style.cssText = [
|
|
1558
|
+
'min-height:16px',
|
|
1559
|
+
'overflow:hidden',
|
|
1560
|
+
'text-overflow:ellipsis',
|
|
1561
|
+
'white-space:nowrap',
|
|
1562
|
+
'color:var(--fps-editor-muted)',
|
|
1563
|
+
'font-size:11px',
|
|
1564
|
+
'font-weight:800',
|
|
1565
|
+
].join(';');
|
|
1566
|
+
assetPreviewDialog.appendChild(assetPreviewHeader);
|
|
1567
|
+
assetPreviewDialog.appendChild(assetPreviewBody);
|
|
1568
|
+
assetPreviewDialog.appendChild(assetPreviewMeta);
|
|
1569
|
+
assetPreviewModal.appendChild(assetPreviewDialog);
|
|
1570
|
+
root.appendChild(assetPreviewModal);
|
|
1571
|
+
tooltipSurfaces.add(assetPreviewModal);
|
|
1572
|
+
const assetPickerModal = doc.createElement('div');
|
|
1573
|
+
assetPickerModal.classList.add(LOCAL_EDITOR_THEME_CLASS);
|
|
1574
|
+
assetPickerModal.dataset.editorAssetPickerModal = 'true';
|
|
1575
|
+
assetPickerModal.dataset.editorWorkbenchRegion = 'modal';
|
|
1576
|
+
assetPickerModal.style.cssText = [
|
|
1577
|
+
'position:fixed',
|
|
1578
|
+
'inset:0',
|
|
1579
|
+
'z-index:2147483642',
|
|
1580
|
+
'display:none',
|
|
1581
|
+
'align-items:center',
|
|
1582
|
+
'justify-content:center',
|
|
1583
|
+
'padding:32px',
|
|
1584
|
+
'background:rgba(0,0,0,0.45)',
|
|
1585
|
+
'font-family:var(--fps-editor-font)',
|
|
1586
|
+
'color:var(--fps-editor-text)',
|
|
1587
|
+
'pointer-events:auto',
|
|
1588
|
+
].join(';');
|
|
1589
|
+
const assetPickerDialog = doc.createElement('div');
|
|
1590
|
+
assetPickerDialog.setAttribute('role', 'dialog');
|
|
1591
|
+
assetPickerDialog.setAttribute('aria-modal', 'true');
|
|
1592
|
+
assetPickerDialog.setAttribute('aria-label', '资产选择');
|
|
1593
|
+
assetPickerDialog.style.cssText = [
|
|
1594
|
+
'width:min(620px,calc(100vw - 64px))',
|
|
1595
|
+
'max-height:calc(100vh - 64px)',
|
|
1596
|
+
'display:flex',
|
|
1597
|
+
'flex-direction:column',
|
|
1598
|
+
'gap:10px',
|
|
1599
|
+
'padding:14px',
|
|
1600
|
+
'border:1px solid var(--fps-editor-border)',
|
|
1601
|
+
'border-radius:4px',
|
|
1602
|
+
'background:var(--fps-editor-panel)',
|
|
1603
|
+
'box-shadow:var(--fps-editor-shadow-modal)',
|
|
1604
|
+
'overflow:hidden',
|
|
1605
|
+
].join(';');
|
|
1606
|
+
const assetPickerHeader = doc.createElement('div');
|
|
1607
|
+
assetPickerHeader.style.cssText = 'display:flex;align-items:center;justify-content:space-between;gap:12px';
|
|
1608
|
+
const assetPickerTitle = doc.createElement('h2');
|
|
1609
|
+
assetPickerTitle.style.cssText = [
|
|
1610
|
+
'min-width:0',
|
|
1611
|
+
'margin:0',
|
|
1612
|
+
'overflow:hidden',
|
|
1613
|
+
'text-overflow:ellipsis',
|
|
1614
|
+
'white-space:nowrap',
|
|
1615
|
+
'font-size:13px',
|
|
1616
|
+
'font-weight:900',
|
|
1617
|
+
'color:var(--fps-editor-text-strong)',
|
|
1618
|
+
].join(';');
|
|
1619
|
+
const assetPickerCloseButton = LocalEditorShared.createButton(doc, '关闭');
|
|
1620
|
+
assetPickerCloseButton.dataset.editorAssetPickerClose = 'true';
|
|
1621
|
+
assetPickerHeader.appendChild(assetPickerTitle);
|
|
1622
|
+
assetPickerHeader.appendChild(assetPickerCloseButton);
|
|
1623
|
+
const assetPickerSearch = doc.createElement('input');
|
|
1624
|
+
assetPickerSearch.type = 'search';
|
|
1625
|
+
assetPickerSearch.dataset.editorAssetPickerSearch = 'true';
|
|
1626
|
+
assetPickerSearch.placeholder = '搜索资产';
|
|
1627
|
+
assetPickerSearch.style.cssText = [
|
|
1628
|
+
'width:100%',
|
|
1629
|
+
'height:30px',
|
|
1630
|
+
'border:1px solid var(--fps-editor-border)',
|
|
1631
|
+
'border-radius:3px',
|
|
1632
|
+
'background:var(--fps-editor-field)',
|
|
1633
|
+
'color:var(--fps-editor-text)',
|
|
1634
|
+
'font-size:12px',
|
|
1635
|
+
'padding:0 8px',
|
|
1636
|
+
].join(';');
|
|
1637
|
+
const assetPickerList = doc.createElement('div');
|
|
1638
|
+
assetPickerList.style.cssText = [
|
|
1639
|
+
'min-height:180px',
|
|
1640
|
+
'max-height:min(430px,calc(100vh - 196px))',
|
|
1641
|
+
'overflow:auto',
|
|
1642
|
+
'display:grid',
|
|
1643
|
+
'grid-template-columns:repeat(auto-fill,minmax(176px,1fr))',
|
|
1644
|
+
'gap:7px',
|
|
1645
|
+
'padding-right:2px',
|
|
1646
|
+
].join(';');
|
|
1647
|
+
assetPickerDialog.appendChild(assetPickerHeader);
|
|
1648
|
+
assetPickerDialog.appendChild(assetPickerSearch);
|
|
1649
|
+
assetPickerDialog.appendChild(assetPickerList);
|
|
1650
|
+
assetPickerModal.appendChild(assetPickerDialog);
|
|
1651
|
+
root.appendChild(assetPickerModal);
|
|
1652
|
+
tooltipSurfaces.add(assetPickerModal);
|
|
1278
1653
|
function applyThemeToSurfaces() {
|
|
1279
1654
|
applyLocalEditorTheme(hostChrome, activeTheme);
|
|
1280
1655
|
applyLocalEditorTheme(workbench.root, activeTheme);
|
|
@@ -1282,6 +1657,8 @@ export function createLocalEditorBrowserUi(options = {}) {
|
|
|
1282
1657
|
applyLocalEditorTheme(measurementOverlay.root, activeTheme);
|
|
1283
1658
|
applyLocalEditorTheme(sceneFrameRateOverlay.root, activeTheme);
|
|
1284
1659
|
applyLocalEditorTheme(shortcutHelpPanel, activeTheme);
|
|
1660
|
+
applyLocalEditorTheme(assetPreviewModal, activeTheme);
|
|
1661
|
+
applyLocalEditorTheme(assetPickerModal, activeTheme);
|
|
1285
1662
|
applyLocalEditorTheme(boxSelectionOverlay, activeTheme);
|
|
1286
1663
|
applyLocalEditorTheme(localTestMenu, activeTheme);
|
|
1287
1664
|
applyLocalEditorTheme(toolbarOverflowMenu, activeTheme);
|
|
@@ -1493,14 +1870,14 @@ export function createLocalEditorBrowserUi(options = {}) {
|
|
|
1493
1870
|
actionGroup,
|
|
1494
1871
|
viewportToolsGroup,
|
|
1495
1872
|
sceneUtilityActions,
|
|
1496
|
-
|
|
1873
|
+
cameraPreviewGroup,
|
|
1497
1874
|
editorStatusButton,
|
|
1498
1875
|
toolbarOverflowButton,
|
|
1499
1876
|
];
|
|
1500
1877
|
const toolbarOverflowItems = [
|
|
1501
1878
|
{ id: 'editor-status', element: editorStatusButton, kind: 'group' },
|
|
1502
1879
|
{ id: 'scene-utilities', element: sceneUtilityActions, kind: 'group' },
|
|
1503
|
-
{ id: '
|
|
1880
|
+
{ id: 'camera-preview', element: cameraPreviewGroup, kind: 'group' },
|
|
1504
1881
|
{ id: 'viewport-tools', element: viewportToolsGroup, kind: 'group' },
|
|
1505
1882
|
{ id: 'transform-actions', element: actionGroup, kind: 'group' },
|
|
1506
1883
|
{ id: 'placement', element: placementGroup, kind: 'group' },
|
|
@@ -1705,9 +2082,9 @@ export function createLocalEditorBrowserUi(options = {}) {
|
|
|
1705
2082
|
const visible = currentState?.grid?.visible ?? true;
|
|
1706
2083
|
callbacks.onGridVisibleChange?.(!visible);
|
|
1707
2084
|
});
|
|
1708
|
-
|
|
1709
|
-
const enabled = currentState?.
|
|
1710
|
-
callbacks.
|
|
2085
|
+
sceneCameraButton.addEventListener('click', () => {
|
|
2086
|
+
const enabled = currentState?.sceneCameraPreview?.enabled ?? false;
|
|
2087
|
+
callbacks.onSceneCameraPreviewToggle?.(!enabled);
|
|
1711
2088
|
});
|
|
1712
2089
|
const toggleCoordinateAxesProjection = (event) => {
|
|
1713
2090
|
event.preventDefault();
|
|
@@ -1766,10 +2143,414 @@ export function createLocalEditorBrowserUi(options = {}) {
|
|
|
1766
2143
|
if (open)
|
|
1767
2144
|
contextMenu.close();
|
|
1768
2145
|
helpOpen = open;
|
|
1769
|
-
|
|
2146
|
+
syncInputModalOpen();
|
|
1770
2147
|
shortcutHelpPanel.style.display = helpOpen ? '' : 'none';
|
|
1771
2148
|
LocalEditorShared.applyButtonActiveState(sceneHelpButton, helpOpen);
|
|
1772
2149
|
};
|
|
2150
|
+
const openAssetPreviewModal = (asset) => {
|
|
2151
|
+
if (!asset.preview)
|
|
2152
|
+
return;
|
|
2153
|
+
contextMenu.close();
|
|
2154
|
+
previewedAsset = asset;
|
|
2155
|
+
assetPreviewMode = 'preview';
|
|
2156
|
+
assetPreviewDialog.setAttribute('aria-label', '资产预览');
|
|
2157
|
+
assetPreviewOpen = true;
|
|
2158
|
+
renderAssetPreviewModal();
|
|
2159
|
+
syncInputModalOpen();
|
|
2160
|
+
assetPreviewCloseButton.focus({ preventScroll: true });
|
|
2161
|
+
};
|
|
2162
|
+
const openAssetMaterialEditorModal = (asset) => {
|
|
2163
|
+
if (!asset.material)
|
|
2164
|
+
return;
|
|
2165
|
+
contextMenu.close();
|
|
2166
|
+
previewedAsset = asset;
|
|
2167
|
+
assetPreviewMode = 'material-editor';
|
|
2168
|
+
assetPreviewDialog.setAttribute('aria-label', '编辑材质球');
|
|
2169
|
+
assetPreviewOpen = true;
|
|
2170
|
+
renderAssetPreviewModal();
|
|
2171
|
+
syncInputModalOpen();
|
|
2172
|
+
assetPreviewCloseButton.focus({ preventScroll: true });
|
|
2173
|
+
};
|
|
2174
|
+
const closeAssetPreviewModal = () => {
|
|
2175
|
+
if (!assetPreviewOpen)
|
|
2176
|
+
return;
|
|
2177
|
+
assetPreviewOpen = false;
|
|
2178
|
+
previewedAsset = null;
|
|
2179
|
+
assetPreviewMode = 'preview';
|
|
2180
|
+
renderAssetPreviewModal();
|
|
2181
|
+
syncInputModalOpen();
|
|
2182
|
+
};
|
|
2183
|
+
const openInspectorAssetPicker = (input, property) => {
|
|
2184
|
+
contextMenu.close();
|
|
2185
|
+
if (helpOpen)
|
|
2186
|
+
setShortcutHelpOpen(false);
|
|
2187
|
+
assetPickerInput = input;
|
|
2188
|
+
assetPickerProperty = property;
|
|
2189
|
+
assetPickerFilter = '';
|
|
2190
|
+
assetPickerOpen = true;
|
|
2191
|
+
closeAssetPreviewModal();
|
|
2192
|
+
renderAssetPickerModal();
|
|
2193
|
+
syncInputModalOpen();
|
|
2194
|
+
assetPickerSearch.focus({ preventScroll: true });
|
|
2195
|
+
assetPickerSearch.select();
|
|
2196
|
+
};
|
|
2197
|
+
const closeInspectorAssetPicker = () => {
|
|
2198
|
+
if (!assetPickerOpen)
|
|
2199
|
+
return;
|
|
2200
|
+
assetPickerOpen = false;
|
|
2201
|
+
assetPickerInput = null;
|
|
2202
|
+
assetPickerProperty = null;
|
|
2203
|
+
assetPickerFilter = '';
|
|
2204
|
+
renderAssetPickerModal();
|
|
2205
|
+
syncInputModalOpen();
|
|
2206
|
+
};
|
|
2207
|
+
function syncInputModalOpen() {
|
|
2208
|
+
inputRouter.setModalOpen(currentState?.mode === 'editor' && (helpOpen || assetPreviewOpen || assetPickerOpen));
|
|
2209
|
+
}
|
|
2210
|
+
function resolvePreviewedAssetFromState() {
|
|
2211
|
+
if (!previewedAsset)
|
|
2212
|
+
return null;
|
|
2213
|
+
const refreshed = currentState?.assets.find(asset => asset.id === previewedAsset?.id)
|
|
2214
|
+
?? currentState?.assets.find(asset => (asset.assetId ?? asset.id) === (previewedAsset?.assetId ?? previewedAsset?.id))
|
|
2215
|
+
?? null;
|
|
2216
|
+
if (refreshed)
|
|
2217
|
+
previewedAsset = refreshed;
|
|
2218
|
+
return previewedAsset;
|
|
2219
|
+
}
|
|
2220
|
+
function renderAssetPreviewModal() {
|
|
2221
|
+
const asset = resolvePreviewedAssetFromState();
|
|
2222
|
+
if (!assetPreviewOpen || !asset || (assetPreviewMode === 'preview' && !asset.preview) || (assetPreviewMode === 'material-editor' && !asset.material)) {
|
|
2223
|
+
assetPreviewModal.style.display = 'none';
|
|
2224
|
+
assetPreviewTitle.textContent = '';
|
|
2225
|
+
assetPreviewMeta.textContent = '';
|
|
2226
|
+
while (assetPreviewBody.firstChild)
|
|
2227
|
+
assetPreviewBody.removeChild(assetPreviewBody.firstChild);
|
|
2228
|
+
return;
|
|
2229
|
+
}
|
|
2230
|
+
if (assetPreviewMode === 'material-editor' && asset.material) {
|
|
2231
|
+
renderAssetMaterialEditorModal(asset);
|
|
2232
|
+
assetPreviewModal.style.display = 'flex';
|
|
2233
|
+
return;
|
|
2234
|
+
}
|
|
2235
|
+
assetPreviewDialog.style.width = 'min(420px,calc(100vw - 64px))';
|
|
2236
|
+
assetPreviewBody.style.cssText = [
|
|
2237
|
+
'display:flex',
|
|
2238
|
+
'align-items:center',
|
|
2239
|
+
'justify-content:center',
|
|
2240
|
+
'min-height:240px',
|
|
2241
|
+
'border:1px solid var(--fps-editor-divider)',
|
|
2242
|
+
'border-radius:4px',
|
|
2243
|
+
'background:var(--fps-editor-panel-soft)',
|
|
2244
|
+
].join(';');
|
|
2245
|
+
assetPreviewTitle.textContent = asset.label;
|
|
2246
|
+
assetPreviewMeta.textContent = [
|
|
2247
|
+
asset.kind ? `Kind: ${asset.kind}` : '',
|
|
2248
|
+
asset.meta ?? asset.assetId ?? asset.id,
|
|
2249
|
+
].filter(Boolean).join(' · ');
|
|
2250
|
+
while (assetPreviewBody.firstChild)
|
|
2251
|
+
assetPreviewBody.removeChild(assetPreviewBody.firstChild);
|
|
2252
|
+
assetPreviewBody.appendChild(LocalEditorPanels.createAssetPreviewElement(doc, asset, 'modal'));
|
|
2253
|
+
assetPreviewModal.style.display = 'flex';
|
|
2254
|
+
}
|
|
2255
|
+
function renderAssetMaterialEditorModal(asset) {
|
|
2256
|
+
const material = asset.material;
|
|
2257
|
+
const profile = material.profile ?? {};
|
|
2258
|
+
const baseColor = profile.baseColor ?? {};
|
|
2259
|
+
const emission = profile.emission ?? {};
|
|
2260
|
+
const readonly = material.readonly === true;
|
|
2261
|
+
const materialKind = material.materialKind === 'standard' ? 'standard' : 'pbr';
|
|
2262
|
+
assetPreviewDialog.style.width = 'min(780px,calc(100vw - 64px))';
|
|
2263
|
+
assetPreviewTitle.textContent = '编辑材质球';
|
|
2264
|
+
assetPreviewMeta.textContent = [
|
|
2265
|
+
material.name || asset.label,
|
|
2266
|
+
materialKind === 'standard' ? 'Standard' : 'PBR',
|
|
2267
|
+
readonly ? '只读系统材质' : '共享资产参数',
|
|
2268
|
+
material.id,
|
|
2269
|
+
].filter(Boolean).join(' · ');
|
|
2270
|
+
while (assetPreviewBody.firstChild)
|
|
2271
|
+
assetPreviewBody.removeChild(assetPreviewBody.firstChild);
|
|
2272
|
+
assetPreviewBody.style.cssText = [
|
|
2273
|
+
'display:grid',
|
|
2274
|
+
'grid-template-columns:minmax(180px,250px) minmax(0,1fr)',
|
|
2275
|
+
'gap:14px',
|
|
2276
|
+
'align-items:start',
|
|
2277
|
+
'min-height:360px',
|
|
2278
|
+
'border:1px solid var(--fps-editor-divider)',
|
|
2279
|
+
'border-radius:4px',
|
|
2280
|
+
'background:var(--fps-editor-panel-soft)',
|
|
2281
|
+
'padding:12px',
|
|
2282
|
+
'box-sizing:border-box',
|
|
2283
|
+
].join(';');
|
|
2284
|
+
const previewColumn = doc.createElement('div');
|
|
2285
|
+
previewColumn.style.cssText = 'display:flex;flex-direction:column;gap:10px;min-width:0';
|
|
2286
|
+
previewColumn.appendChild(LocalEditorPanels.createAssetPreviewElement(doc, asset, 'modal'));
|
|
2287
|
+
const previewMeta = doc.createElement('div');
|
|
2288
|
+
previewMeta.textContent = readonly
|
|
2289
|
+
? '系统默认材质不可编辑。'
|
|
2290
|
+
: '修改会影响所有使用该材质球的物体。';
|
|
2291
|
+
previewMeta.style.cssText = 'color:var(--fps-editor-muted);font-size:11px;font-weight:800;line-height:1.45';
|
|
2292
|
+
previewColumn.appendChild(previewMeta);
|
|
2293
|
+
const form = doc.createElement('div');
|
|
2294
|
+
form.style.cssText = 'display:grid;grid-template-columns:minmax(96px,140px) minmax(0,1fr);gap:7px 10px;align-items:center;min-width:0';
|
|
2295
|
+
appendMaterialEditorReadonlyRow(form, '名称', material.name || asset.label);
|
|
2296
|
+
appendMaterialEditorReadonlyRow(form, '类型', materialKind === 'standard' ? 'Standard 标准材质球' : 'PBR 标准材质球');
|
|
2297
|
+
appendMaterialEditorColorField(form, '基础颜色', 'profile.baseColor.color', baseColor.color ?? { r: 1, g: 1, b: 1 }, readonly);
|
|
2298
|
+
appendMaterialEditorTextureField(form, '基础贴图', 'profile.baseColor.texture.url', baseColor.texture?.url ?? '', readonly);
|
|
2299
|
+
appendMaterialEditorNumberField(form, '亮度', 'profile.baseColor.brightness', baseColor.brightness ?? 1, readonly, 0, 2, 0.05);
|
|
2300
|
+
appendMaterialEditorNumberField(form, '饱和度', 'profile.baseColor.saturation', baseColor.saturation ?? 1, readonly, 0, 2, 0.05);
|
|
2301
|
+
appendMaterialEditorNumberField(form, '对比度', 'profile.baseColor.contrast', baseColor.contrast ?? 1, readonly, 0, 2, 0.05);
|
|
2302
|
+
appendMaterialEditorNumberField(form, '色相', 'profile.baseColor.hue', baseColor.hue ?? 0, readonly, -180, 180, 1);
|
|
2303
|
+
if (materialKind === 'pbr') {
|
|
2304
|
+
appendMaterialEditorNumberField(form, '金属度', 'profile.metallic', profile.metallic ?? 0, readonly, 0, 1, 0.05);
|
|
2305
|
+
appendMaterialEditorNumberField(form, '粗糙度', 'profile.roughness', profile.roughness ?? 1, readonly, 0, 1, 0.05);
|
|
2306
|
+
}
|
|
2307
|
+
appendMaterialEditorColorField(form, '自发光颜色', 'profile.emission.color', emission.color ?? { r: 0, g: 0, b: 0 }, readonly);
|
|
2308
|
+
appendMaterialEditorNumberField(form, '自发光强度', 'profile.emission.intensity', emission.intensity ?? 0, readonly, 0, undefined, 0.05);
|
|
2309
|
+
appendMaterialEditorTextureField(form, '遮罩贴图', 'profile.emission.maskTexture.url', emission.maskTexture?.url ?? '', readonly);
|
|
2310
|
+
assetPreviewBody.appendChild(previewColumn);
|
|
2311
|
+
assetPreviewBody.appendChild(form);
|
|
2312
|
+
}
|
|
2313
|
+
function appendMaterialEditorReadonlyRow(container, label, value) {
|
|
2314
|
+
appendMaterialEditorLabel(container, label);
|
|
2315
|
+
const output = doc.createElement('div');
|
|
2316
|
+
output.textContent = value;
|
|
2317
|
+
output.style.cssText = [
|
|
2318
|
+
'min-height:30px',
|
|
2319
|
+
'display:flex',
|
|
2320
|
+
'align-items:center',
|
|
2321
|
+
'min-width:0',
|
|
2322
|
+
'padding:0 8px',
|
|
2323
|
+
'border:1px solid var(--fps-editor-border)',
|
|
2324
|
+
'border-radius:3px',
|
|
2325
|
+
'background:var(--fps-editor-field)',
|
|
2326
|
+
'color:var(--fps-editor-muted-strong)',
|
|
2327
|
+
'font-size:12px',
|
|
2328
|
+
'font-weight:900',
|
|
2329
|
+
'overflow:hidden',
|
|
2330
|
+
'text-overflow:ellipsis',
|
|
2331
|
+
'white-space:nowrap',
|
|
2332
|
+
].join(';');
|
|
2333
|
+
container.appendChild(output);
|
|
2334
|
+
}
|
|
2335
|
+
function appendMaterialEditorColorField(container, label, fieldPath, color, readonly) {
|
|
2336
|
+
appendMaterialEditorLabel(container, label);
|
|
2337
|
+
const input = doc.createElement('input');
|
|
2338
|
+
input.type = 'color';
|
|
2339
|
+
input.value = materialEditorColorToHex(color);
|
|
2340
|
+
input.disabled = readonly;
|
|
2341
|
+
input.dataset.editorAssetMaterialField = fieldPath;
|
|
2342
|
+
input.dataset.editorAssetMaterialFieldKind = 'color';
|
|
2343
|
+
input.style.cssText = [
|
|
2344
|
+
'width:48px',
|
|
2345
|
+
'height:32px',
|
|
2346
|
+
'padding:2px',
|
|
2347
|
+
'border:1px solid var(--fps-editor-accent)',
|
|
2348
|
+
'border-radius:3px',
|
|
2349
|
+
'background:var(--fps-editor-field)',
|
|
2350
|
+
readonly ? 'opacity:0.62' : 'cursor:pointer',
|
|
2351
|
+
].join(';');
|
|
2352
|
+
container.appendChild(input);
|
|
2353
|
+
}
|
|
2354
|
+
function appendMaterialEditorNumberField(container, label, fieldPath, value, readonly, min, max, step = 0.05) {
|
|
2355
|
+
appendMaterialEditorLabel(container, label);
|
|
2356
|
+
const input = doc.createElement('input');
|
|
2357
|
+
input.type = 'number';
|
|
2358
|
+
input.value = formatMaterialEditorNumber(value);
|
|
2359
|
+
if (min != null)
|
|
2360
|
+
input.min = String(min);
|
|
2361
|
+
if (max != null)
|
|
2362
|
+
input.max = String(max);
|
|
2363
|
+
input.step = String(step);
|
|
2364
|
+
input.disabled = readonly;
|
|
2365
|
+
input.dataset.editorAssetMaterialField = fieldPath;
|
|
2366
|
+
input.dataset.editorAssetMaterialFieldKind = 'number';
|
|
2367
|
+
input.style.cssText = createMaterialEditorFieldCss(readonly);
|
|
2368
|
+
container.appendChild(input);
|
|
2369
|
+
}
|
|
2370
|
+
function appendMaterialEditorTextureField(container, label, fieldPath, value, readonly) {
|
|
2371
|
+
appendMaterialEditorLabel(container, label);
|
|
2372
|
+
const select = doc.createElement('select');
|
|
2373
|
+
select.disabled = readonly;
|
|
2374
|
+
select.dataset.editorAssetMaterialField = fieldPath;
|
|
2375
|
+
select.dataset.editorAssetMaterialFieldKind = 'texture';
|
|
2376
|
+
select.style.cssText = createMaterialEditorFieldCss(readonly);
|
|
2377
|
+
for (const option of createMaterialEditorTextureOptions(value)) {
|
|
2378
|
+
const optionElement = doc.createElement('option');
|
|
2379
|
+
optionElement.value = option.value;
|
|
2380
|
+
optionElement.textContent = option.label;
|
|
2381
|
+
optionElement.title = option.meta ?? option.value;
|
|
2382
|
+
select.appendChild(optionElement);
|
|
2383
|
+
}
|
|
2384
|
+
select.value = value;
|
|
2385
|
+
container.appendChild(select);
|
|
2386
|
+
}
|
|
2387
|
+
function appendMaterialEditorLabel(container, text) {
|
|
2388
|
+
const label = doc.createElement('div');
|
|
2389
|
+
label.textContent = text;
|
|
2390
|
+
label.style.cssText = 'color:var(--fps-editor-muted-strong);font-size:12px;font-weight:900;line-height:1.2';
|
|
2391
|
+
container.appendChild(label);
|
|
2392
|
+
}
|
|
2393
|
+
function createMaterialEditorTextureOptions(currentValue) {
|
|
2394
|
+
const options = [{ label: '继承 / 无', value: '', meta: '清空贴图引用' }];
|
|
2395
|
+
const seen = new Set(['']);
|
|
2396
|
+
for (const asset of currentState?.assets ?? []) {
|
|
2397
|
+
if (resolveBrowserAssetKind(asset) !== 'texture')
|
|
2398
|
+
continue;
|
|
2399
|
+
const value = asset.preview?.kind === 'image'
|
|
2400
|
+
? asset.preview.url
|
|
2401
|
+
: asset.external?.assetUrl ?? asset.external?.assetPath ?? '';
|
|
2402
|
+
if (!value || seen.has(value))
|
|
2403
|
+
continue;
|
|
2404
|
+
seen.add(value);
|
|
2405
|
+
options.push({ label: asset.label, value, meta: asset.meta ?? asset.assetId ?? asset.id });
|
|
2406
|
+
}
|
|
2407
|
+
if (currentValue && !seen.has(currentValue)) {
|
|
2408
|
+
options.push({ label: currentValue, value: currentValue, meta: '当前贴图 URL' });
|
|
2409
|
+
}
|
|
2410
|
+
return options;
|
|
2411
|
+
}
|
|
2412
|
+
function createMaterialEditorFieldCss(readonly) {
|
|
2413
|
+
return [
|
|
2414
|
+
'width:100%',
|
|
2415
|
+
'min-width:0',
|
|
2416
|
+
'height:32px',
|
|
2417
|
+
'padding:0 8px',
|
|
2418
|
+
'border:1px solid var(--fps-editor-accent)',
|
|
2419
|
+
'border-radius:3px',
|
|
2420
|
+
'background:var(--fps-editor-field)',
|
|
2421
|
+
'color:var(--fps-editor-text-strong)',
|
|
2422
|
+
'font-size:12px',
|
|
2423
|
+
'font-weight:800',
|
|
2424
|
+
'box-sizing:border-box',
|
|
2425
|
+
readonly ? 'opacity:0.62' : '',
|
|
2426
|
+
].join(';');
|
|
2427
|
+
}
|
|
2428
|
+
function formatMaterialEditorNumber(value) {
|
|
2429
|
+
if (!Number.isFinite(value))
|
|
2430
|
+
return '0';
|
|
2431
|
+
return Number.isInteger(value) ? String(value) : value.toFixed(4).replace(/0+$/, '').replace(/\.$/, '');
|
|
2432
|
+
}
|
|
2433
|
+
function materialEditorColorToHex(color) {
|
|
2434
|
+
const toHex = (value) => {
|
|
2435
|
+
const normalized = Number.isFinite(value) ? value : 0;
|
|
2436
|
+
const byte = Math.round(Math.min(255, Math.max(0, normalized <= 1 ? normalized * 255 : normalized)));
|
|
2437
|
+
return byte.toString(16).padStart(2, '0');
|
|
2438
|
+
};
|
|
2439
|
+
return `#${toHex(color.r)}${toHex(color.g)}${toHex(color.b)}`;
|
|
2440
|
+
}
|
|
2441
|
+
function materialEditorHexToColor(value) {
|
|
2442
|
+
const match = value.trim().match(/^#?([0-9a-f]{6})$/i);
|
|
2443
|
+
if (!match)
|
|
2444
|
+
return null;
|
|
2445
|
+
const hex = match[1];
|
|
2446
|
+
return {
|
|
2447
|
+
r: Number.parseInt(hex.slice(0, 2), 16) / 255,
|
|
2448
|
+
g: Number.parseInt(hex.slice(2, 4), 16) / 255,
|
|
2449
|
+
b: Number.parseInt(hex.slice(4, 6), 16) / 255,
|
|
2450
|
+
};
|
|
2451
|
+
}
|
|
2452
|
+
function dispatchMaterialEditorFieldChange(input) {
|
|
2453
|
+
const asset = resolvePreviewedAssetFromState();
|
|
2454
|
+
const material = asset?.material;
|
|
2455
|
+
const fieldPath = input.dataset.editorAssetMaterialField;
|
|
2456
|
+
if (!asset || !material || !fieldPath || material.readonly === true)
|
|
2457
|
+
return;
|
|
2458
|
+
const kind = input.dataset.editorAssetMaterialFieldKind;
|
|
2459
|
+
let value;
|
|
2460
|
+
if (kind === 'color' && input instanceof HTMLInputElement) {
|
|
2461
|
+
value = materialEditorHexToColor(input.value);
|
|
2462
|
+
if (!value)
|
|
2463
|
+
return;
|
|
2464
|
+
}
|
|
2465
|
+
else if (kind === 'number' && input instanceof HTMLInputElement) {
|
|
2466
|
+
value = Number(input.value);
|
|
2467
|
+
if (!Number.isFinite(value))
|
|
2468
|
+
return;
|
|
2469
|
+
}
|
|
2470
|
+
else if (kind === 'texture') {
|
|
2471
|
+
value = input.value.trim() || null;
|
|
2472
|
+
}
|
|
2473
|
+
else {
|
|
2474
|
+
value = input.value;
|
|
2475
|
+
}
|
|
2476
|
+
callbacks.onAssetAction?.({
|
|
2477
|
+
actionId: 'asset.edit-material-field',
|
|
2478
|
+
assetId: material.id || asset.assetId || asset.id,
|
|
2479
|
+
browserAssetId: asset.id,
|
|
2480
|
+
assetKind: asset.kind,
|
|
2481
|
+
fieldPath,
|
|
2482
|
+
value,
|
|
2483
|
+
});
|
|
2484
|
+
}
|
|
2485
|
+
function renderAssetPickerModal() {
|
|
2486
|
+
if (!assetPickerOpen || !assetPickerProperty || !assetPickerInput) {
|
|
2487
|
+
assetPickerModal.style.display = 'none';
|
|
2488
|
+
assetPickerTitle.textContent = '';
|
|
2489
|
+
assetPickerSearch.value = '';
|
|
2490
|
+
while (assetPickerList.firstChild)
|
|
2491
|
+
assetPickerList.removeChild(assetPickerList.firstChild);
|
|
2492
|
+
return;
|
|
2493
|
+
}
|
|
2494
|
+
const options = readInspectorAssetPickerOptions(assetPickerProperty);
|
|
2495
|
+
assetPickerTitle.textContent = options.title;
|
|
2496
|
+
assetPickerSearch.value = assetPickerFilter;
|
|
2497
|
+
while (assetPickerList.firstChild)
|
|
2498
|
+
assetPickerList.removeChild(assetPickerList.firstChild);
|
|
2499
|
+
const normalizedFilter = assetPickerFilter.trim().toLowerCase();
|
|
2500
|
+
const candidates = options.candidates.filter(candidate => {
|
|
2501
|
+
if (!normalizedFilter)
|
|
2502
|
+
return true;
|
|
2503
|
+
return [candidate.label, candidate.value, candidate.meta ?? '', candidate.kind ?? '']
|
|
2504
|
+
.some(value => value.toLowerCase().includes(normalizedFilter));
|
|
2505
|
+
});
|
|
2506
|
+
if (candidates.length === 0) {
|
|
2507
|
+
const empty = doc.createElement('div');
|
|
2508
|
+
empty.textContent = '没有匹配的资产。';
|
|
2509
|
+
empty.style.cssText = 'color:var(--fps-editor-muted);font-size:12px;font-weight:800;padding:10px';
|
|
2510
|
+
assetPickerList.appendChild(empty);
|
|
2511
|
+
}
|
|
2512
|
+
for (const candidate of candidates) {
|
|
2513
|
+
assetPickerList.appendChild(createInspectorAssetPickerCandidateButton(candidate, assetPickerInput.value));
|
|
2514
|
+
}
|
|
2515
|
+
assetPickerModal.style.display = 'flex';
|
|
2516
|
+
}
|
|
2517
|
+
function createInspectorAssetPickerCandidateButton(candidate, currentValue) {
|
|
2518
|
+
const button = doc.createElement('button');
|
|
2519
|
+
button.type = 'button';
|
|
2520
|
+
button.dataset.editorAssetPickerCandidate = candidate.value;
|
|
2521
|
+
const selected = candidate.value === currentValue;
|
|
2522
|
+
button.style.cssText = [
|
|
2523
|
+
'min-width:0',
|
|
2524
|
+
'min-height:56px',
|
|
2525
|
+
'display:flex',
|
|
2526
|
+
'align-items:center',
|
|
2527
|
+
'gap:8px',
|
|
2528
|
+
'padding:7px 8px',
|
|
2529
|
+
`border:1px solid ${selected ? 'var(--fps-editor-accent-strong)' : 'var(--fps-editor-border)'}`,
|
|
2530
|
+
'border-radius:3px',
|
|
2531
|
+
`background:${selected ? 'var(--fps-editor-accent-soft)' : 'var(--fps-editor-asset-card-bg)'}`,
|
|
2532
|
+
'color:var(--fps-editor-text)',
|
|
2533
|
+
'text-align:left',
|
|
2534
|
+
'cursor:pointer',
|
|
2535
|
+
].join(';');
|
|
2536
|
+
button.appendChild(LocalEditorPanels.createAssetPreviewElement(doc, {
|
|
2537
|
+
id: candidate.value || '__empty__',
|
|
2538
|
+
label: candidate.label,
|
|
2539
|
+
preview: candidate.preview,
|
|
2540
|
+
}, 'grid'));
|
|
2541
|
+
const body = doc.createElement('div');
|
|
2542
|
+
body.style.cssText = 'min-width:0;flex:1';
|
|
2543
|
+
const label = doc.createElement('div');
|
|
2544
|
+
label.textContent = candidate.label;
|
|
2545
|
+
label.style.cssText = 'font-weight:900;overflow:hidden;text-overflow:ellipsis;white-space:nowrap';
|
|
2546
|
+
const meta = doc.createElement('div');
|
|
2547
|
+
meta.textContent = candidate.meta ?? candidate.value;
|
|
2548
|
+
meta.style.cssText = 'color:var(--fps-editor-muted);font-size:11px;font-weight:800;overflow:hidden;text-overflow:ellipsis;white-space:nowrap';
|
|
2549
|
+
body.appendChild(label);
|
|
2550
|
+
body.appendChild(meta);
|
|
2551
|
+
button.appendChild(body);
|
|
2552
|
+
return button;
|
|
2553
|
+
}
|
|
1773
2554
|
sceneHelpButton.addEventListener('click', () => {
|
|
1774
2555
|
setShortcutHelpOpen(!helpOpen);
|
|
1775
2556
|
});
|
|
@@ -1782,6 +2563,44 @@ export function createLocalEditorBrowserUi(options = {}) {
|
|
|
1782
2563
|
return;
|
|
1783
2564
|
setShortcutHelpOpen(false);
|
|
1784
2565
|
});
|
|
2566
|
+
assetPreviewModal.addEventListener('click', (event) => {
|
|
2567
|
+
const target = event.target instanceof HTMLElement ? event.target : null;
|
|
2568
|
+
if (target?.closest('[data-editor-asset-preview-close]')) {
|
|
2569
|
+
closeAssetPreviewModal();
|
|
2570
|
+
return;
|
|
2571
|
+
}
|
|
2572
|
+
if (target === assetPreviewModal)
|
|
2573
|
+
closeAssetPreviewModal();
|
|
2574
|
+
});
|
|
2575
|
+
assetPreviewBody.addEventListener('change', (event) => {
|
|
2576
|
+
if (assetPreviewMode !== 'material-editor')
|
|
2577
|
+
return;
|
|
2578
|
+
const target = event.target instanceof HTMLElement ? event.target : null;
|
|
2579
|
+
const input = target?.closest('[data-editor-asset-material-field]');
|
|
2580
|
+
if (!input)
|
|
2581
|
+
return;
|
|
2582
|
+
dispatchMaterialEditorFieldChange(input);
|
|
2583
|
+
});
|
|
2584
|
+
assetPickerModal.addEventListener('click', (event) => {
|
|
2585
|
+
const target = event.target instanceof HTMLElement ? event.target : null;
|
|
2586
|
+
if (target?.closest('[data-editor-asset-picker-close]')) {
|
|
2587
|
+
closeInspectorAssetPicker();
|
|
2588
|
+
return;
|
|
2589
|
+
}
|
|
2590
|
+
const candidateButton = target?.closest('[data-editor-asset-picker-candidate]');
|
|
2591
|
+
if (candidateButton && assetPickerInput) {
|
|
2592
|
+
assetPickerInput.value = candidateButton.dataset.editorAssetPickerCandidate ?? '';
|
|
2593
|
+
assetPickerInput.dispatchEvent(new Event('change', { bubbles: true }));
|
|
2594
|
+
closeInspectorAssetPicker();
|
|
2595
|
+
return;
|
|
2596
|
+
}
|
|
2597
|
+
if (target === assetPickerModal)
|
|
2598
|
+
closeInspectorAssetPicker();
|
|
2599
|
+
});
|
|
2600
|
+
assetPickerSearch.addEventListener('input', () => {
|
|
2601
|
+
assetPickerFilter = assetPickerSearch.value;
|
|
2602
|
+
renderAssetPickerModal();
|
|
2603
|
+
});
|
|
1785
2604
|
toolGroup.addEventListener('click', (event) => {
|
|
1786
2605
|
const target = event.target instanceof HTMLElement ? event.target : null;
|
|
1787
2606
|
const tool = target?.closest('[data-transform-tool]')?.dataset.transformTool;
|
|
@@ -1871,11 +2690,70 @@ export function createLocalEditorBrowserUi(options = {}) {
|
|
|
1871
2690
|
render(currentState);
|
|
1872
2691
|
return;
|
|
1873
2692
|
}
|
|
2693
|
+
const assetBrowserTabButton = target?.closest('[data-editor-asset-browser-tab]');
|
|
2694
|
+
const nextAssetBrowserTab = assetBrowserTabButton?.dataset.editorAssetBrowserTab;
|
|
2695
|
+
if (isLocalEditorAssetBrowserTab(nextAssetBrowserTab)) {
|
|
2696
|
+
assetBrowserTab = nextAssetBrowserTab;
|
|
2697
|
+
if (currentState)
|
|
2698
|
+
render(currentState);
|
|
2699
|
+
return;
|
|
2700
|
+
}
|
|
1874
2701
|
const assetButton = target?.closest('[data-editor-asset-id]');
|
|
1875
2702
|
if (assetButton?.dataset.editorAssetId) {
|
|
1876
|
-
callbacks.
|
|
2703
|
+
callbacks.onSelectAsset?.(assetButton.dataset.editorAssetId);
|
|
2704
|
+
return;
|
|
2705
|
+
}
|
|
2706
|
+
});
|
|
2707
|
+
assetPanel.addEventListener('contextmenu', (event) => {
|
|
2708
|
+
const target = event.target instanceof HTMLElement ? event.target : null;
|
|
2709
|
+
const assetButton = target?.closest('[data-editor-asset-id]');
|
|
2710
|
+
const browserAssetId = assetButton?.dataset.editorAssetId;
|
|
2711
|
+
if (!browserAssetId || !currentState)
|
|
2712
|
+
return;
|
|
2713
|
+
const asset = currentState.assets.find(candidate => candidate.id === browserAssetId);
|
|
2714
|
+
if (!asset)
|
|
2715
|
+
return;
|
|
2716
|
+
event.preventDefault();
|
|
2717
|
+
event.stopPropagation();
|
|
2718
|
+
callbacks.onSelectAsset?.(browserAssetId);
|
|
2719
|
+
contextMenu.open({
|
|
2720
|
+
x: event.clientX,
|
|
2721
|
+
y: event.clientY,
|
|
2722
|
+
items: createAssetBrowserMenuItems(currentState, asset),
|
|
2723
|
+
onAction(item) {
|
|
2724
|
+
if (item.id === 'asset.preview') {
|
|
2725
|
+
openAssetPreviewModal(asset);
|
|
2726
|
+
return;
|
|
2727
|
+
}
|
|
2728
|
+
if (item.id === 'asset.edit-material') {
|
|
2729
|
+
openAssetMaterialEditorModal(asset);
|
|
2730
|
+
return;
|
|
2731
|
+
}
|
|
2732
|
+
if (item.id === 'asset.copy-id') {
|
|
2733
|
+
copyTextToClipboard(doc.defaultView, asset.assetId ?? asset.id);
|
|
2734
|
+
return;
|
|
2735
|
+
}
|
|
2736
|
+
callbacks.onAssetAction?.(createAssetBrowserActionInput(item.id, asset));
|
|
2737
|
+
},
|
|
2738
|
+
});
|
|
2739
|
+
});
|
|
2740
|
+
workbench.rightDock.addEventListener('click', (event) => {
|
|
2741
|
+
const target = event.target instanceof HTMLElement ? event.target : null;
|
|
2742
|
+
const openTabButton = target?.closest('[data-editor-open-right-dock-tab]');
|
|
2743
|
+
const openTab = openTabButton?.dataset.editorOpenRightDockTab;
|
|
2744
|
+
if (openTab === 'inspector' || openTab === 'rendering') {
|
|
2745
|
+
panelRegistry.setRightDockTab(openTab);
|
|
2746
|
+
if (currentState)
|
|
2747
|
+
render(currentState);
|
|
1877
2748
|
return;
|
|
1878
2749
|
}
|
|
2750
|
+
const tabButton = target?.closest('[data-editor-right-dock-tab]');
|
|
2751
|
+
const tab = tabButton?.dataset.editorRightDockTab;
|
|
2752
|
+
if (tab === 'inspector' || tab === 'rendering') {
|
|
2753
|
+
panelRegistry.setRightDockTab(tab);
|
|
2754
|
+
if (currentState)
|
|
2755
|
+
render(currentState);
|
|
2756
|
+
}
|
|
1879
2757
|
});
|
|
1880
2758
|
const hierarchyController = createLocalEditorHierarchyController({
|
|
1881
2759
|
doc,
|
|
@@ -1892,6 +2770,8 @@ export function createLocalEditorBrowserUi(options = {}) {
|
|
|
1892
2770
|
onBeforeOpenContextMenu: () => {
|
|
1893
2771
|
if (helpOpen)
|
|
1894
2772
|
setShortcutHelpOpen(false);
|
|
2773
|
+
if (assetPreviewOpen)
|
|
2774
|
+
closeAssetPreviewModal();
|
|
1895
2775
|
},
|
|
1896
2776
|
});
|
|
1897
2777
|
assetPanel.addEventListener('input', (event) => {
|
|
@@ -1914,10 +2794,38 @@ export function createLocalEditorBrowserUi(options = {}) {
|
|
|
1914
2794
|
return;
|
|
1915
2795
|
if (readInspectorCommitMode(input) !== 'live')
|
|
1916
2796
|
return;
|
|
1917
|
-
const propertyInput = createInspectorPropertyInput(input, 'input'
|
|
2797
|
+
const propertyInput = createInspectorPropertyInput(input, 'input');
|
|
1918
2798
|
if (propertyInput)
|
|
1919
2799
|
callbacks.onPropertyInput?.(propertyInput);
|
|
1920
2800
|
});
|
|
2801
|
+
inspectorPanel.addEventListener('click', (event) => {
|
|
2802
|
+
const target = event.target instanceof HTMLElement ? event.target : null;
|
|
2803
|
+
const copyButton = target?.closest('[data-editor-inspector-asset-copy]');
|
|
2804
|
+
if (copyButton) {
|
|
2805
|
+
const control = copyButton.closest('[data-editor-inspector-asset-picker-control]');
|
|
2806
|
+
const input = control?.querySelector('input[data-serialized-path]');
|
|
2807
|
+
const value = copyButton.dataset.editorInspectorAssetCopyValue;
|
|
2808
|
+
if (!input || !value)
|
|
2809
|
+
return;
|
|
2810
|
+
event.preventDefault();
|
|
2811
|
+
event.stopPropagation();
|
|
2812
|
+
input.value = value;
|
|
2813
|
+
input.dispatchEvent(new Event('change', { bubbles: true }));
|
|
2814
|
+
return;
|
|
2815
|
+
}
|
|
2816
|
+
const pickerButton = target?.closest('[data-editor-inspector-asset-picker]');
|
|
2817
|
+
if (!pickerButton)
|
|
2818
|
+
return;
|
|
2819
|
+
const control = pickerButton.closest('[data-editor-inspector-asset-picker-control]');
|
|
2820
|
+
const input = control?.querySelector('input[data-serialized-path]');
|
|
2821
|
+
if (!input || !currentState)
|
|
2822
|
+
return;
|
|
2823
|
+
const property = findInspectorPropertyForInput(currentState, input);
|
|
2824
|
+
if (!property)
|
|
2825
|
+
return;
|
|
2826
|
+
event.preventDefault();
|
|
2827
|
+
openInspectorAssetPicker(input, property);
|
|
2828
|
+
});
|
|
1921
2829
|
inspectorPanel.addEventListener('change', (event) => {
|
|
1922
2830
|
const input = event.target instanceof HTMLInputElement || event.target instanceof HTMLSelectElement
|
|
1923
2831
|
? event.target
|
|
@@ -1928,7 +2836,7 @@ export function createLocalEditorBrowserUi(options = {}) {
|
|
|
1928
2836
|
if (readInspectorCommitMode(input) !== 'change')
|
|
1929
2837
|
return;
|
|
1930
2838
|
}
|
|
1931
|
-
const propertyInput = createInspectorPropertyInput(input, readInspectorImmediateSource(input)
|
|
2839
|
+
const propertyInput = createInspectorPropertyInput(input, readInspectorImmediateSource(input));
|
|
1932
2840
|
if (propertyInput)
|
|
1933
2841
|
callbacks.onPropertyInput?.(propertyInput);
|
|
1934
2842
|
});
|
|
@@ -1936,27 +2844,65 @@ export function createLocalEditorBrowserUi(options = {}) {
|
|
|
1936
2844
|
const input = event.target instanceof HTMLInputElement ? event.target : null;
|
|
1937
2845
|
if (!input?.dataset.serializedPath || !input.dataset.serializedTargetId)
|
|
1938
2846
|
return;
|
|
1939
|
-
if (
|
|
1940
|
-
if (currentState)
|
|
1941
|
-
render(currentState);
|
|
2847
|
+
if (readInspectorCommitMode(input) !== 'blur')
|
|
1942
2848
|
return;
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
2849
|
+
const propertyInput = createInspectorPropertyInput(input, 'input');
|
|
2850
|
+
if (propertyInput)
|
|
2851
|
+
callbacks.onPropertyInput?.(propertyInput);
|
|
2852
|
+
});
|
|
2853
|
+
renderingPanel.addEventListener('input', (event) => {
|
|
2854
|
+
const input = event.target instanceof HTMLInputElement || event.target instanceof HTMLTextAreaElement
|
|
2855
|
+
? event.target
|
|
2856
|
+
: null;
|
|
2857
|
+
if (!input?.dataset.editorRenderingProperty)
|
|
2858
|
+
return;
|
|
2859
|
+
if (input instanceof HTMLInputElement && (input.type === 'checkbox' || input.type === 'color'))
|
|
1951
2860
|
return;
|
|
2861
|
+
if (readRenderingCommitMode(input) !== 'live')
|
|
2862
|
+
return;
|
|
2863
|
+
const propertyInput = createRenderingPropertyInput(input, input instanceof HTMLTextAreaElement ? 'list' : 'input');
|
|
2864
|
+
if (propertyInput)
|
|
2865
|
+
callbacks.onRenderingPropertyChange?.(propertyInput);
|
|
2866
|
+
});
|
|
2867
|
+
renderingPanel.addEventListener('change', (event) => {
|
|
2868
|
+
const input = event.target instanceof HTMLInputElement || event.target instanceof HTMLTextAreaElement
|
|
2869
|
+
? event.target
|
|
2870
|
+
: null;
|
|
2871
|
+
if (!input?.dataset.editorRenderingProperty)
|
|
2872
|
+
return;
|
|
2873
|
+
if ((input instanceof HTMLInputElement && (input.type === 'text' || input.type === 'number')) || input instanceof HTMLTextAreaElement) {
|
|
2874
|
+
if (readRenderingCommitMode(input) !== 'change')
|
|
2875
|
+
return;
|
|
1952
2876
|
}
|
|
1953
|
-
|
|
2877
|
+
const propertyInput = createRenderingPropertyInput(input, readRenderingImmediateSource(input));
|
|
2878
|
+
if (propertyInput)
|
|
2879
|
+
callbacks.onRenderingPropertyChange?.(propertyInput);
|
|
2880
|
+
});
|
|
2881
|
+
renderingPanel.addEventListener('focusout', (event) => {
|
|
2882
|
+
const input = event.target instanceof HTMLInputElement || event.target instanceof HTMLTextAreaElement
|
|
2883
|
+
? event.target
|
|
2884
|
+
: null;
|
|
2885
|
+
if (!input?.dataset.editorRenderingProperty)
|
|
2886
|
+
return;
|
|
2887
|
+
if (readRenderingCommitMode(input) !== 'blur')
|
|
1954
2888
|
return;
|
|
1955
|
-
const propertyInput =
|
|
2889
|
+
const propertyInput = createRenderingPropertyInput(input, input instanceof HTMLTextAreaElement ? 'list' : 'input');
|
|
1956
2890
|
if (propertyInput)
|
|
1957
|
-
callbacks.
|
|
1958
|
-
|
|
1959
|
-
|
|
2891
|
+
callbacks.onRenderingPropertyChange?.(propertyInput);
|
|
2892
|
+
});
|
|
2893
|
+
renderingPanel.addEventListener('click', (event) => {
|
|
2894
|
+
const target = event.target instanceof Element
|
|
2895
|
+
? event.target.closest('[data-editor-rendering-action]')
|
|
2896
|
+
: null;
|
|
2897
|
+
if (!(target instanceof HTMLButtonElement))
|
|
2898
|
+
return;
|
|
2899
|
+
if (target.disabled)
|
|
2900
|
+
return;
|
|
2901
|
+
const actionId = target.dataset.editorRenderingAction;
|
|
2902
|
+
if (!actionId)
|
|
2903
|
+
return;
|
|
2904
|
+
event.preventDefault();
|
|
2905
|
+
callbacks.onRenderingAction?.({ actionId });
|
|
1960
2906
|
});
|
|
1961
2907
|
const onKeyDown = (event) => {
|
|
1962
2908
|
if (event.defaultPrevented)
|
|
@@ -1964,6 +2910,16 @@ export function createLocalEditorBrowserUi(options = {}) {
|
|
|
1964
2910
|
if (currentState?.mode !== 'editor' || currentState.busy)
|
|
1965
2911
|
return;
|
|
1966
2912
|
const key = event.key.toLowerCase();
|
|
2913
|
+
if (key === 'escape' && assetPreviewOpen) {
|
|
2914
|
+
event.preventDefault();
|
|
2915
|
+
closeAssetPreviewModal();
|
|
2916
|
+
return;
|
|
2917
|
+
}
|
|
2918
|
+
if (key === 'escape' && assetPickerOpen) {
|
|
2919
|
+
event.preventDefault();
|
|
2920
|
+
closeInspectorAssetPicker();
|
|
2921
|
+
return;
|
|
2922
|
+
}
|
|
1967
2923
|
if (key === 'escape' && snapSettingsOpen) {
|
|
1968
2924
|
event.preventDefault();
|
|
1969
2925
|
closeSnapSettingsPopover();
|
|
@@ -2053,7 +3009,6 @@ export function createLocalEditorBrowserUi(options = {}) {
|
|
|
2053
3009
|
`[data-serialized-target-id="${cssEscape(active.dataset.serializedTargetId)}"]`,
|
|
2054
3010
|
].join(''),
|
|
2055
3011
|
value: active.value,
|
|
2056
|
-
preserveValue: hasPendingInspectorNumberValue(active),
|
|
2057
3012
|
};
|
|
2058
3013
|
}
|
|
2059
3014
|
if (active.dataset.editorInspectorSearch != null) {
|
|
@@ -2074,8 +3029,6 @@ export function createLocalEditorBrowserUi(options = {}) {
|
|
|
2074
3029
|
if (!input)
|
|
2075
3030
|
return;
|
|
2076
3031
|
input.focus({ preventScroll: true });
|
|
2077
|
-
if (snapshot.value != null && snapshot.preserveValue)
|
|
2078
|
-
input.value = snapshot.value;
|
|
2079
3032
|
if (snapshot.value != null && input.value === snapshot.value) {
|
|
2080
3033
|
const position = input.value.length;
|
|
2081
3034
|
try {
|
|
@@ -2113,7 +3066,7 @@ export function createLocalEditorBrowserUi(options = {}) {
|
|
|
2113
3066
|
contextMenu.close();
|
|
2114
3067
|
hostChrome.style.display = !inEditor && localTestActionsEnabled ? 'flex' : 'none';
|
|
2115
3068
|
enterEditorButton.disabled = disabled;
|
|
2116
|
-
for (const button of [saveButton, saveAndRunButton, discardRunButton, undoButton, redoButton, sceneHelpButton,
|
|
3069
|
+
for (const button of [saveButton, saveAndRunButton, discardRunButton, undoButton, redoButton, sceneHelpButton, sceneCameraButton, gridToggleButton]) {
|
|
2117
3070
|
button.style.display = 'inline-flex';
|
|
2118
3071
|
button.disabled = disabled;
|
|
2119
3072
|
}
|
|
@@ -2123,11 +3076,17 @@ export function createLocalEditorBrowserUi(options = {}) {
|
|
|
2123
3076
|
closeLocalTestMenu();
|
|
2124
3077
|
if (!inEditor)
|
|
2125
3078
|
helpOpen = false;
|
|
2126
|
-
|
|
3079
|
+
if (!inEditor || disabled) {
|
|
3080
|
+
assetPreviewOpen = false;
|
|
3081
|
+
previewedAsset = null;
|
|
3082
|
+
assetPreviewMode = 'preview';
|
|
3083
|
+
assetPickerOpen = false;
|
|
3084
|
+
assetPickerInput = null;
|
|
3085
|
+
assetPickerProperty = null;
|
|
3086
|
+
}
|
|
3087
|
+
syncInputModalOpen();
|
|
2127
3088
|
const transformTool = state.transformTool ?? null;
|
|
2128
3089
|
workbench.root.style.display = inEditor ? '' : 'none';
|
|
2129
|
-
if (inEditor)
|
|
2130
|
-
workbenchLayoutController.refresh();
|
|
2131
3090
|
sceneToolOverlay.style.display = inEditor ? 'flex' : 'none';
|
|
2132
3091
|
if (!inEditor)
|
|
2133
3092
|
closeSnapSettingsPopover();
|
|
@@ -2137,20 +3096,20 @@ export function createLocalEditorBrowserUi(options = {}) {
|
|
|
2137
3096
|
closeTransformActionPopover();
|
|
2138
3097
|
if (!inEditor)
|
|
2139
3098
|
closeOverlaySettingsPopover();
|
|
2140
|
-
const
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
const
|
|
3099
|
+
const sceneCameraPreview = state.sceneCameraPreview ?? { enabled: false, available: false };
|
|
3100
|
+
cameraPreviewGroup.style.display = inEditor ? 'flex' : 'none';
|
|
3101
|
+
sceneCameraButton.disabled = disabled || !sceneCameraPreview.available;
|
|
3102
|
+
const sceneCameraTooltip = sceneCameraPreview.available
|
|
2144
3103
|
? '从 Main Camera 查看当前场景'
|
|
2145
3104
|
: '当前场景没有可预览的 Main Camera';
|
|
2146
|
-
setToolbarButtonTooltip(
|
|
2147
|
-
LocalEditorShared.applyButtonActiveState(
|
|
3105
|
+
setToolbarButtonTooltip(sceneCameraButton, sceneCameraTooltip);
|
|
3106
|
+
LocalEditorShared.applyButtonActiveState(sceneCameraButton, sceneCameraPreview.enabled);
|
|
2148
3107
|
const viewportTools = state.viewportTools ?? null;
|
|
2149
|
-
const viewportControlsDisabled = disabled ||
|
|
2150
|
-
const viewportControlsTooltip =
|
|
3108
|
+
const viewportControlsDisabled = disabled || sceneCameraPreview.enabled;
|
|
3109
|
+
const viewportControlsTooltip = sceneCameraPreview.enabled
|
|
2151
3110
|
? '当前为 Main Camera 预览,请关闭后再使用视口工具'
|
|
2152
3111
|
: '';
|
|
2153
|
-
const viewportProjectionTooltip =
|
|
3112
|
+
const viewportProjectionTooltip = sceneCameraPreview.enabled
|
|
2154
3113
|
? '当前为 Main Camera 预览,投影由 Main Camera 决定;关闭预览后可切换编辑视口投影'
|
|
2155
3114
|
: '';
|
|
2156
3115
|
viewportToolsGroup.style.display = inEditor ? 'flex' : 'none';
|
|
@@ -2247,6 +3206,8 @@ export function createLocalEditorBrowserUi(options = {}) {
|
|
|
2247
3206
|
redoButton.disabled = disabled || !state.session?.canRedo;
|
|
2248
3207
|
LocalEditorShared.applyButtonActiveState(sceneHelpButton, inEditor && helpOpen);
|
|
2249
3208
|
shortcutHelpPanel.style.display = inEditor && helpOpen ? '' : 'none';
|
|
3209
|
+
renderAssetPreviewModal();
|
|
3210
|
+
renderAssetPickerModal();
|
|
2250
3211
|
dirtyBadge.style.display = inEditor && state.session?.dirty ? 'inline-flex' : 'none';
|
|
2251
3212
|
const editorStatusText = [
|
|
2252
3213
|
state.status,
|
|
@@ -2293,12 +3254,22 @@ export function createLocalEditorBrowserUi(options = {}) {
|
|
|
2293
3254
|
if (!inEditor)
|
|
2294
3255
|
return;
|
|
2295
3256
|
const hierarchyDescriptor = panelRegistry.getActivePanel('left');
|
|
2296
|
-
const
|
|
3257
|
+
const rightDescriptor = panelRegistry.getActivePanel('right');
|
|
3258
|
+
const activeRightTab = panelRegistry.getRightDockTab();
|
|
2297
3259
|
hierarchyPanel.dataset.editorPanelId = hierarchyDescriptor?.id ?? 'hierarchy';
|
|
2298
|
-
inspectorPanel.dataset.editorPanelId =
|
|
3260
|
+
inspectorPanel.dataset.editorPanelId = activeRightTab === 'inspector' ? rightDescriptor?.id ?? 'inspector' : 'inspector';
|
|
3261
|
+
renderingPanel.dataset.editorPanelId = activeRightTab === 'rendering' ? rightDescriptor?.id ?? 'rendering' : 'rendering';
|
|
3262
|
+
inspectorPanel.style.display = activeRightTab === 'inspector' ? '' : 'none';
|
|
3263
|
+
renderingPanel.style.display = activeRightTab === 'rendering' ? '' : 'none';
|
|
2299
3264
|
hierarchyController.render(state);
|
|
2300
|
-
LocalEditorPanels.renderWorkbenchBottomDockPanel(doc, assetPanel, state, panelRegistry.getBottomDockTab(), panelRegistry.getPanels('bottom'));
|
|
2301
|
-
LocalEditorPanels.
|
|
3265
|
+
LocalEditorPanels.renderWorkbenchBottomDockPanel(doc, assetPanel, state, panelRegistry.getBottomDockTab(), panelRegistry.getPanels('bottom'), assetBrowserTab);
|
|
3266
|
+
LocalEditorPanels.renderWorkbenchRightDockTabs(doc, rightDockTabs, activeRightTab, panelRegistry.getPanels('right'));
|
|
3267
|
+
if (activeRightTab === 'inspector') {
|
|
3268
|
+
LocalEditorPanels.renderInspectorPanel(doc, inspectorPanel, state, inspectorFilter, options.inspector);
|
|
3269
|
+
}
|
|
3270
|
+
else {
|
|
3271
|
+
LocalEditorPanels.renderRenderingPanel(doc, renderingPanel, state);
|
|
3272
|
+
}
|
|
2302
3273
|
restoreEditableFocus(doc, focusSnapshot);
|
|
2303
3274
|
};
|
|
2304
3275
|
return {
|
|
@@ -2334,8 +3305,11 @@ export function createLocalEditorBrowserUi(options = {}) {
|
|
|
2334
3305
|
overlaySettingsPopover.remove();
|
|
2335
3306
|
spatialOverlay.root.remove();
|
|
2336
3307
|
measurementOverlay.root.remove();
|
|
3308
|
+
sceneFrameRateOverlay.root.remove();
|
|
2337
3309
|
boxSelectionOverlay.remove();
|
|
2338
3310
|
shortcutHelpPanel.remove();
|
|
3311
|
+
assetPreviewModal.remove();
|
|
3312
|
+
assetPickerModal.remove();
|
|
2339
3313
|
hierarchyController.dispose();
|
|
2340
3314
|
workbenchLayoutController.dispose();
|
|
2341
3315
|
tooltipController.dispose();
|