@vgai/editor 0.4.0-canary.20260715.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/assets/{axe-C7YBtOuM.js → axe-BJjvBeOZ.js} +1 -1
- package/dist/assets/{browserAll-DDtGGE48.js → browserAll-Dn0pgD87.js} +1 -1
- package/dist/assets/{create-runtime-DbTlBlKG.js → create-runtime-BF9OD-U8.js} +1 -1
- package/dist/assets/{game-Dch9sAPc.js → game-B346oj_f.js} +1 -1
- package/dist/assets/{game-BE0r7DMI.js → game-BCNN63uV.js} +1 -1
- package/dist/assets/{game-SAFn90pA.js → game-BR0GBNgF.js} +1 -1
- package/dist/assets/{game-Cqzj_lpD.js → game-CLNm2g0y.js} +1 -1
- package/dist/assets/{game-CcHfYxjP.js → game-DQxhCYsZ.js} +1 -1
- package/dist/assets/{game-BdTHR2_X.js → game-DeTGdgYt.js} +1 -1
- package/dist/assets/{game-loop-DMIgSMV-.js → game-loop-t5W_oqxz.js} +1 -1
- package/dist/assets/{game-source-Dk83w26t.js → game-source-DzE9taML.js} +1 -1
- package/dist/assets/{game-DSZdeCHu.js → game-u6c2Patu.js} +1 -1
- package/dist/assets/{game-C3iQGSqo.js → game-umrHIqFB.js} +1 -1
- package/dist/assets/{index-C-LhOTBN.js → index-CgcH1RpF.js} +1 -1
- package/dist/assets/{index-CDkxceqE.js → index-D3tcNB2T.js} +1 -1
- package/dist/assets/{index-CQEfi217.js → index-DbQzGLxv.js} +771 -771
- package/dist/assets/{index-4idIsHHJ.js → index-Fc8Ezr20.js} +1 -1
- package/dist/assets/index-bAGhEnnO.css +1 -0
- package/dist/assets/{ingest-siblings-s0P5I1Ub.js → ingest-siblings-CW_xbvrF.js} +1 -1
- package/dist/assets/{mount-manifest-DXMOlF79.js → mount-manifest-DgUscyaD.js} +1 -1
- package/dist/assets/{react-18-C9eHn8x1.js → react-18-C4tVCii_.js} +1 -1
- package/dist/assets/webworkerAll-BA4aq7xq.js +1 -0
- package/dist/assets/{world-documents-GM0a9Kv7.js → world-documents-BSaA5YIF.js} +1 -1
- package/dist/index.html +2 -2
- package/dist-server/packaged.mjs +21 -1
- package/package.json +3 -3
- package/server/packaged.ts +20 -0
- package/src/asset-editor-context.tsx +130 -0
- package/src/asset-preview.ts +4 -0
- package/src/asset-selection.ts +9 -4
- package/src/asset-workflow/thumbnail-system.ts +16 -7
- package/src/components/AssetBrowser.tsx +330 -539
- package/src/components/AssetEditorShell.tsx +42 -112
- package/src/components/AssetImportDetails.tsx +24 -6
- package/src/components/GameHierarchy.tsx +26 -1
- package/src/components/Inspector.tsx +38 -2
- package/src/components/OnlineAssetBrowser.tsx +295 -343
- package/src/components/WorkspaceDock.tsx +6 -12
- package/src/components/asset-documents.tsx +41 -12
- package/src/components/asset-selection-section.tsx +34 -1
- package/src/components/asset-viewers/ModelViewer.tsx +7 -4
- package/src/components/asset-viewers/OnlineAssetDetail.tsx +233 -205
- package/src/components/asset-workflow.css +197 -264
- package/src/editor-api.ts +1 -1
- package/template/vgai.game.json +1 -1
- package/dist/assets/index-Dvi-V0NE.css +0 -1
- package/dist/assets/webworkerAll-Fn0e-ouF.js +0 -1
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
type DockviewApi,
|
|
3
3
|
DockviewDefaultTab,
|
|
4
|
-
type DockviewPanelApi,
|
|
5
4
|
DockviewReact,
|
|
6
5
|
type DockviewTheme,
|
|
7
6
|
type IDockviewPanelHeaderProps,
|
|
@@ -116,16 +115,6 @@ export const vgaiDockviewTheme: DockviewTheme = {
|
|
|
116
115
|
tabAnimation: 'smooth',
|
|
117
116
|
};
|
|
118
117
|
|
|
119
|
-
function usePanelActive(api: DockviewPanelApi): boolean {
|
|
120
|
-
const [active, setActive] = useState(api.isActive);
|
|
121
|
-
useEffect(() => {
|
|
122
|
-
setActive(api.isActive);
|
|
123
|
-
const disposable = api.onDidActiveChange((event) => setActive(event.isActive));
|
|
124
|
-
return () => disposable.dispose();
|
|
125
|
-
}, [api]);
|
|
126
|
-
return active;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
118
|
function StaticPanelContent({ staticKind }: { staticKind: StaticPanelKind }) {
|
|
130
119
|
const onOpenScene = useContext(OpenSceneContext);
|
|
131
120
|
switch (staticKind) {
|
|
@@ -145,7 +134,6 @@ function StaticPanelContent({ staticKind }: { staticKind: StaticPanelKind }) {
|
|
|
145
134
|
}
|
|
146
135
|
|
|
147
136
|
function WorkspacePanel({ api, params }: IDockviewPanelProps<WorkspacePanelParams>) {
|
|
148
|
-
const active = usePanelActive(api);
|
|
149
137
|
useSyncExternalStore(
|
|
150
138
|
subscribeWorkspaceDocuments,
|
|
151
139
|
workspaceDocumentRegistryVersion,
|
|
@@ -192,6 +180,12 @@ function WorkspacePanel({ api, params }: IDockviewPanelProps<WorkspacePanelParam
|
|
|
192
180
|
if (!document) return <div className="vgai-dock-empty">Restoring document…</div>;
|
|
193
181
|
const Content = document.descriptor.Content;
|
|
194
182
|
const Toolbar = document.descriptor.Toolbar;
|
|
183
|
+
// A bottom utility can own keyboard focus while the center document stays
|
|
184
|
+
// visibly selected in its own group. Dockview's `api.isActive` is global
|
|
185
|
+
// focus, not document activation; using it here suspended previews and
|
|
186
|
+
// cleared their Inspector/Hierarchy context whenever Console or Controls
|
|
187
|
+
// opened. The document registry is the semantic center-tab authority.
|
|
188
|
+
const active = activeWorkspaceDocumentId() === params.documentId;
|
|
195
189
|
return (
|
|
196
190
|
<div
|
|
197
191
|
className="vgai-dock-document"
|
|
@@ -12,14 +12,14 @@
|
|
|
12
12
|
* open-asset`) keeps its meaning unchanged:
|
|
13
13
|
*
|
|
14
14
|
* - project asset → the serving path itself (`/textures/crate.png`)
|
|
15
|
-
* -
|
|
15
|
+
* - legacy entity editor key → `asset-lab:entity:<entityId>`
|
|
16
16
|
* - online asset → `online:<source>:<id>`
|
|
17
17
|
*
|
|
18
18
|
* Titles are source-owned file names as authored (§8 — `crate.png`, never a
|
|
19
19
|
* technology bucket); the online document keeps the catalog's own asset name.
|
|
20
20
|
*
|
|
21
21
|
* Per-document RUNTIME state (an online asset resolving to a local path after
|
|
22
|
-
* download) lives in
|
|
22
|
+
* download) lives in an HMR-stable module spec map in the house
|
|
23
23
|
* `useSyncExternalStore` shape — the registry's descriptors stay immutable,
|
|
24
24
|
* and `AssetDocumentContent` re-renders from the spec map. Specs are dropped
|
|
25
25
|
* on document dispose (close / project teardown via
|
|
@@ -60,7 +60,7 @@ import './asset-workflow.css';
|
|
|
60
60
|
export interface AssetDocumentSpec {
|
|
61
61
|
readonly assetPath: string;
|
|
62
62
|
readonly kind: AssetKind;
|
|
63
|
-
/** Entity source for a session-only Asset
|
|
63
|
+
/** Entity source for a session-only live-object Asset Editor. Never serialized. */
|
|
64
64
|
readonly entityId?: string;
|
|
65
65
|
/** Present while this document shows an online (not-yet-downloaded) asset. */
|
|
66
66
|
readonly online?: OnlineAssetInfo;
|
|
@@ -74,8 +74,20 @@ export interface AssetDocumentMutation {
|
|
|
74
74
|
paths?: readonly string[];
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
-
const
|
|
78
|
-
const
|
|
77
|
+
const hotData = import.meta.hot?.data;
|
|
78
|
+
const _specs =
|
|
79
|
+
(hotData?.['vgai:asset-document-specs'] as Map<string, AssetDocumentSpec> | undefined) ??
|
|
80
|
+
new Map<string, AssetDocumentSpec>();
|
|
81
|
+
const _pathAliases =
|
|
82
|
+
(hotData?.['vgai:asset-document-path-aliases'] as Map<string, string> | undefined) ??
|
|
83
|
+
new Map<string, string>();
|
|
84
|
+
if (hotData) {
|
|
85
|
+
// Workspace descriptors intentionally survive compatible Fast Refreshes.
|
|
86
|
+
// Their specs must survive with them or the next persistence snapshot drops
|
|
87
|
+
// the open document and leaves a titled-but-empty Dockview tab on reload.
|
|
88
|
+
hotData['vgai:asset-document-specs'] = _specs;
|
|
89
|
+
hotData['vgai:asset-document-path-aliases'] = _pathAliases;
|
|
90
|
+
}
|
|
79
91
|
let _version = 0;
|
|
80
92
|
const _listeners = new Set<() => void>();
|
|
81
93
|
|
|
@@ -113,7 +125,7 @@ export interface AssetDocumentStore {
|
|
|
113
125
|
setActiveViewportTab(tab: 'scene' | 'game'): void;
|
|
114
126
|
}
|
|
115
127
|
|
|
116
|
-
/**
|
|
128
|
+
/** A live-entity Asset Editor additionally needs the object map for its §8 title. */
|
|
117
129
|
export interface EntityAssetDocumentStore extends AssetDocumentStore {
|
|
118
130
|
readonly objectMap: Map<string, THREE.Object3D>;
|
|
119
131
|
}
|
|
@@ -159,7 +171,7 @@ export function openAssetDocument(
|
|
|
159
171
|
return openSpecDocument(store, id, title, { assetPath, kind }, { sourcePath: assetPath });
|
|
160
172
|
}
|
|
161
173
|
|
|
162
|
-
/** Open the Asset
|
|
174
|
+
/** Open the typed Asset Editor for a live entity (legacy id retained for API compatibility). */
|
|
163
175
|
export function openEntityAssetDocument(store: EntityAssetDocumentStore, entityId: string): void {
|
|
164
176
|
const object = store.objectMap.get(entityId);
|
|
165
177
|
if (!object) return;
|
|
@@ -277,6 +289,8 @@ function AssetViewerBody({
|
|
|
277
289
|
if (spec.unavailableReason) {
|
|
278
290
|
return (
|
|
279
291
|
<AssetEditorShell
|
|
292
|
+
documentId={id}
|
|
293
|
+
active={active}
|
|
280
294
|
type={spec.kind}
|
|
281
295
|
title={spec.assetPath.split('/').pop() || 'Deleted asset'}
|
|
282
296
|
status="Source unavailable"
|
|
@@ -288,16 +302,29 @@ function AssetViewerBody({
|
|
|
288
302
|
const route = assetDocumentViewerRoute(spec);
|
|
289
303
|
if (route === 'online' && spec.online) {
|
|
290
304
|
return (
|
|
291
|
-
<
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
305
|
+
<AssetEditorShell
|
|
306
|
+
documentId={id}
|
|
307
|
+
active={active}
|
|
308
|
+
type="online"
|
|
309
|
+
title={spec.online.name}
|
|
310
|
+
details={
|
|
311
|
+
<OnlineAssetDetail
|
|
312
|
+
online={spec.online}
|
|
313
|
+
mode="inspector"
|
|
314
|
+
onResolved={(localPath) => resolveOnlineAssetDocument(id, localPath)}
|
|
315
|
+
/>
|
|
316
|
+
}
|
|
317
|
+
status={`${spec.online.type} · ${spec.online.source}`}
|
|
318
|
+
>
|
|
319
|
+
<OnlineAssetDetail online={spec.online} mode="preview" />
|
|
320
|
+
</AssetEditorShell>
|
|
295
321
|
);
|
|
296
322
|
}
|
|
297
323
|
const title = spec.assetPath.split('/').pop() || spec.entityId || 'Asset';
|
|
298
324
|
if (route === 'model')
|
|
299
325
|
return (
|
|
300
326
|
<ModelViewer
|
|
327
|
+
documentId={id}
|
|
301
328
|
assetPath={spec.assetPath || undefined}
|
|
302
329
|
entityId={spec.entityId}
|
|
303
330
|
active={active}
|
|
@@ -330,6 +357,8 @@ function AssetViewerBody({
|
|
|
330
357
|
}
|
|
331
358
|
return (
|
|
332
359
|
<AssetEditorShell
|
|
360
|
+
documentId={id}
|
|
361
|
+
active={active}
|
|
333
362
|
type={route}
|
|
334
363
|
title={title}
|
|
335
364
|
details={
|
|
@@ -369,7 +398,7 @@ export function AssetDocumentContent({ documentId, active }: WorkspaceDocumentCo
|
|
|
369
398
|
style={{
|
|
370
399
|
flex: 1,
|
|
371
400
|
minHeight: 0,
|
|
372
|
-
|
|
401
|
+
overflow: 'hidden',
|
|
373
402
|
pointerEvents: 'auto',
|
|
374
403
|
background: bg[1],
|
|
375
404
|
}}
|
|
@@ -22,13 +22,14 @@
|
|
|
22
22
|
import { resolveUrl } from '@engine/loader';
|
|
23
23
|
import { MaterialFileSchema } from '@engine/scene/schema/material';
|
|
24
24
|
import { useEffect, useState } from 'react';
|
|
25
|
-
import { getSelectedAsset, type SelectedAsset } from '../asset-selection';
|
|
25
|
+
import { getSelectedAsset, type SelectedAsset, setSelectedAsset } from '../asset-selection';
|
|
26
26
|
import { assetDocumentKind } from '../asset-workflow/asset-capabilities';
|
|
27
27
|
import { PROJECT_ASSET_COMMANDS } from '../asset-workflow/project-asset-commands';
|
|
28
28
|
import { Button, bg, border, radius, text } from '../design-system';
|
|
29
29
|
import { registerInspectorSections } from '../inspector-section-registry';
|
|
30
30
|
import { getModelThumbnailRenderer } from '../model-thumbnail';
|
|
31
31
|
import { validateInputMapForViewer } from './asset-viewers/InputMapViewer';
|
|
32
|
+
import { OnlineAssetDetail } from './asset-viewers/OnlineAssetDetail';
|
|
32
33
|
|
|
33
34
|
const labelStyle: React.CSSProperties = { fontSize: 11, color: text[3], marginBottom: 2 };
|
|
34
35
|
const valueStyle: React.CSSProperties = { fontSize: 11, color: text[1], wordBreak: 'break-all' };
|
|
@@ -227,6 +228,38 @@ export function AssetSelectionSection() {
|
|
|
227
228
|
const asset = getSelectedAsset();
|
|
228
229
|
if (!asset) return null;
|
|
229
230
|
const ext = asset.name.includes('.') ? (asset.name.split('.').pop() ?? '') : '';
|
|
231
|
+
if (asset.online) {
|
|
232
|
+
const online = asset.online;
|
|
233
|
+
return (
|
|
234
|
+
<div
|
|
235
|
+
data-testid="inspector-asset-selection"
|
|
236
|
+
style={{
|
|
237
|
+
pointerEvents: 'auto',
|
|
238
|
+
borderBottom: `1px solid ${border[1]}`,
|
|
239
|
+
display: 'flex',
|
|
240
|
+
flexDirection: 'column',
|
|
241
|
+
gap: 6,
|
|
242
|
+
}}
|
|
243
|
+
>
|
|
244
|
+
<OnlineAssetDetail
|
|
245
|
+
online={online}
|
|
246
|
+
mode="inspector"
|
|
247
|
+
onResolved={(localPath) => setSelectedAsset({ ...asset, path: localPath })}
|
|
248
|
+
/>
|
|
249
|
+
<div style={{ padding: '0 8px 8px' }}>
|
|
250
|
+
<Button
|
|
251
|
+
type="button"
|
|
252
|
+
style={{ width: '100%' }}
|
|
253
|
+
onClick={() =>
|
|
254
|
+
window.dispatchEvent(new CustomEvent('editor:open-online-asset', { detail: online }))
|
|
255
|
+
}
|
|
256
|
+
>
|
|
257
|
+
Open in Asset Editor
|
|
258
|
+
</Button>
|
|
259
|
+
</div>
|
|
260
|
+
</div>
|
|
261
|
+
);
|
|
262
|
+
}
|
|
230
263
|
return (
|
|
231
264
|
<div
|
|
232
265
|
data-testid="inspector-asset-selection"
|
|
@@ -50,6 +50,7 @@ import { AssetImportDetails } from '../AssetImportDetails';
|
|
|
50
50
|
import { writeProjectAsset } from '../asset-editor-persistence';
|
|
51
51
|
|
|
52
52
|
interface ModelViewerProps {
|
|
53
|
+
documentId?: string | undefined;
|
|
53
54
|
assetPath: string | undefined;
|
|
54
55
|
entityId: string | undefined;
|
|
55
56
|
materialPath?: string | undefined;
|
|
@@ -71,6 +72,7 @@ interface ModelViewerRuntime {
|
|
|
71
72
|
|
|
72
73
|
// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: one viewer coordinates embedded/full chrome and both live-entity and file-backed Asset Editor sources.
|
|
73
74
|
export function ModelViewer({
|
|
75
|
+
documentId,
|
|
74
76
|
assetPath,
|
|
75
77
|
entityId,
|
|
76
78
|
materialPath,
|
|
@@ -493,6 +495,8 @@ export function ModelViewer({
|
|
|
493
495
|
|
|
494
496
|
return (
|
|
495
497
|
<AssetEditorShell
|
|
498
|
+
documentId={documentId ?? `asset-editor:${assetPath ?? entityId ?? displayName}`}
|
|
499
|
+
active={active}
|
|
496
500
|
type="model"
|
|
497
501
|
title={displayName}
|
|
498
502
|
toolbar={
|
|
@@ -663,7 +667,8 @@ export function ModelViewer({
|
|
|
663
667
|
)}
|
|
664
668
|
</div>
|
|
665
669
|
}
|
|
666
|
-
status={
|
|
670
|
+
status={`${loading ? `${loadStage}…` : error ? `${issue?.stage ?? 'rendering'} failed · ${issue?.code}` : 'Ready'} · ${inspection?.triangles.toLocaleString() ?? 0} triangles`}
|
|
671
|
+
controls={
|
|
667
672
|
inspection?.clips ? (
|
|
668
673
|
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
|
|
669
674
|
<Select
|
|
@@ -712,9 +717,7 @@ export function ModelViewer({
|
|
|
712
717
|
</Select>
|
|
713
718
|
</label>
|
|
714
719
|
</div>
|
|
715
|
-
) :
|
|
716
|
-
`${loading ? `${loadStage}…` : error ? `${issue?.stage ?? 'rendering'} failed · ${issue?.code}` : 'Ready'} · ${inspection?.triangles.toLocaleString() ?? 0} triangles`
|
|
717
|
-
)
|
|
720
|
+
) : undefined
|
|
718
721
|
}
|
|
719
722
|
>
|
|
720
723
|
{viewport}
|