@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,141 +1,71 @@
|
|
|
1
1
|
import type { ReactNode } from 'react';
|
|
2
|
-
import { useEffect
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
structureWidth: number;
|
|
10
|
-
detailsWidth: number;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
const defaults: AssetEditorLayout = {
|
|
14
|
-
structureCollapsed: false,
|
|
15
|
-
detailsCollapsed: false,
|
|
16
|
-
structureWidth: 240,
|
|
17
|
-
detailsWidth: 300,
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
export function assetEditorLayoutKey(
|
|
21
|
-
type: string,
|
|
22
|
-
projectId = getCurrentProject()?.rootPath ?? '__unscoped__',
|
|
23
|
-
): string {
|
|
24
|
-
return `vgai:asset-editor-layout:${projectId}:${type}`;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export function readAssetEditorLayout(type: string): AssetEditorLayout {
|
|
28
|
-
if (typeof localStorage === 'undefined') return defaults;
|
|
29
|
-
try {
|
|
30
|
-
return { ...defaults, ...JSON.parse(localStorage.getItem(assetEditorLayoutKey(type)) ?? '{}') };
|
|
31
|
-
} catch {
|
|
32
|
-
return defaults;
|
|
33
|
-
}
|
|
34
|
-
}
|
|
2
|
+
import { useEffect } from 'react';
|
|
3
|
+
import {
|
|
4
|
+
ASSET_EDITOR_CONTROLS_UTILITY_ID,
|
|
5
|
+
activateAssetEditorContext,
|
|
6
|
+
} from '../asset-editor-context';
|
|
7
|
+
import { Button } from '../design-system';
|
|
8
|
+
import { showWorkspaceUtility } from '../workspace-dock-controller';
|
|
35
9
|
|
|
36
10
|
export function AssetEditorShell({
|
|
11
|
+
documentId,
|
|
12
|
+
active = true,
|
|
37
13
|
type,
|
|
38
14
|
title,
|
|
39
15
|
toolbar,
|
|
40
16
|
structure,
|
|
41
17
|
details,
|
|
42
18
|
status,
|
|
19
|
+
controls,
|
|
43
20
|
children,
|
|
44
21
|
}: {
|
|
22
|
+
documentId: string;
|
|
23
|
+
active?: boolean;
|
|
45
24
|
type: string;
|
|
46
25
|
title: string;
|
|
47
26
|
toolbar?: ReactNode;
|
|
48
27
|
structure?: ReactNode;
|
|
49
28
|
details?: ReactNode;
|
|
50
|
-
status?:
|
|
29
|
+
status?: string;
|
|
30
|
+
controls?: ReactNode;
|
|
51
31
|
children: ReactNode;
|
|
52
32
|
}) {
|
|
53
|
-
const [layout, setLayout] = useState(() => readAssetEditorLayout(type));
|
|
54
33
|
useEffect(() => {
|
|
55
|
-
|
|
56
|
-
|
|
34
|
+
if (!active) return;
|
|
35
|
+
return activateAssetEditorContext({
|
|
36
|
+
documentId,
|
|
37
|
+
title,
|
|
38
|
+
type,
|
|
39
|
+
...(structure ? { structure } : {}),
|
|
40
|
+
...(details ? { details } : {}),
|
|
41
|
+
...(status ? { status } : {}),
|
|
42
|
+
...(controls ? { controls } : {}),
|
|
43
|
+
});
|
|
44
|
+
}, [active, controls, details, documentId, status, structure, title, type]);
|
|
45
|
+
|
|
46
|
+
const hasToolbar = toolbar != null || controls != null;
|
|
57
47
|
return (
|
|
58
48
|
<section
|
|
59
49
|
className="vgai-asset-editor"
|
|
60
50
|
data-testid={`asset-editor:${type}`}
|
|
61
|
-
|
|
62
|
-
data-details-collapsed={layout.detailsCollapsed}
|
|
63
|
-
style={
|
|
64
|
-
{
|
|
65
|
-
'--asset-editor-structure-width': `${layout.structureWidth}px`,
|
|
66
|
-
'--asset-editor-details-width': `${layout.detailsWidth}px`,
|
|
67
|
-
} as React.CSSProperties
|
|
68
|
-
}
|
|
51
|
+
aria-label={`${title} Asset Editor`}
|
|
69
52
|
>
|
|
70
|
-
|
|
71
|
-
<
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
{details && (
|
|
85
|
-
<Button
|
|
86
|
-
type="button"
|
|
87
|
-
aria-pressed={!layout.detailsCollapsed}
|
|
88
|
-
onClick={() =>
|
|
89
|
-
setLayout((value) => ({ ...value, detailsCollapsed: !value.detailsCollapsed }))
|
|
90
|
-
}
|
|
91
|
-
>
|
|
92
|
-
Details
|
|
93
|
-
</Button>
|
|
94
|
-
)}
|
|
95
|
-
</header>
|
|
96
|
-
{structure && (
|
|
97
|
-
<aside className="vgai-asset-editor__structure" aria-label="Asset structure">
|
|
98
|
-
{structure}
|
|
99
|
-
<label className="vgai-asset-editor__resize">
|
|
100
|
-
Structure width
|
|
101
|
-
<TextInput
|
|
102
|
-
aria-label="Asset structure pane width"
|
|
103
|
-
type="range"
|
|
104
|
-
min={180}
|
|
105
|
-
max={420}
|
|
106
|
-
value={layout.structureWidth}
|
|
107
|
-
onChange={(event) =>
|
|
108
|
-
setLayout((value) => ({ ...value, structureWidth: Number(event.target.value) }))
|
|
109
|
-
}
|
|
110
|
-
/>
|
|
111
|
-
</label>
|
|
112
|
-
</aside>
|
|
53
|
+
{hasToolbar && (
|
|
54
|
+
<header className="vgai-asset-editor__toolbar">
|
|
55
|
+
<div className="vgai-asset-editor__commands">{toolbar}</div>
|
|
56
|
+
{controls != null && (
|
|
57
|
+
<Button
|
|
58
|
+
type="button"
|
|
59
|
+
variant="ghost"
|
|
60
|
+
size="compact"
|
|
61
|
+
onClick={() => showWorkspaceUtility(ASSET_EDITOR_CONTROLS_UTILITY_ID)}
|
|
62
|
+
>
|
|
63
|
+
Controls
|
|
64
|
+
</Button>
|
|
65
|
+
)}
|
|
66
|
+
</header>
|
|
113
67
|
)}
|
|
114
68
|
<main className="vgai-asset-editor__viewport">{children}</main>
|
|
115
|
-
{details && (
|
|
116
|
-
<aside className="vgai-asset-editor__details" aria-label="Asset details">
|
|
117
|
-
{details}
|
|
118
|
-
<label className="vgai-asset-editor__resize">
|
|
119
|
-
Details width
|
|
120
|
-
<TextInput
|
|
121
|
-
aria-label="Asset details pane width"
|
|
122
|
-
type="range"
|
|
123
|
-
min={220}
|
|
124
|
-
max={480}
|
|
125
|
-
value={layout.detailsWidth}
|
|
126
|
-
onChange={(event) =>
|
|
127
|
-
setLayout((value) => ({ ...value, detailsWidth: Number(event.target.value) }))
|
|
128
|
-
}
|
|
129
|
-
/>
|
|
130
|
-
</label>
|
|
131
|
-
</aside>
|
|
132
|
-
)}
|
|
133
|
-
<footer
|
|
134
|
-
className="vgai-asset-editor__status"
|
|
135
|
-
style={{ borderColor: border[1], color: text[2] }}
|
|
136
|
-
>
|
|
137
|
-
{status}
|
|
138
|
-
</footer>
|
|
139
69
|
</section>
|
|
140
70
|
);
|
|
141
71
|
}
|
|
@@ -15,20 +15,37 @@ export function AssetImportDetails({ assetPath }: { assetPath: string }) {
|
|
|
15
15
|
useEffect(() => {
|
|
16
16
|
let cancelled = false;
|
|
17
17
|
const path = importRecipePath(assetPath);
|
|
18
|
-
getStorageBackend()
|
|
19
|
-
|
|
18
|
+
const backend = getStorageBackend();
|
|
19
|
+
backend
|
|
20
|
+
.exists(path)
|
|
21
|
+
.then((exists) => (exists ? backend.read(path) : null))
|
|
20
22
|
.then(
|
|
21
23
|
(content) => {
|
|
22
24
|
if (cancelled) return;
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
25
|
+
if (content === null) {
|
|
26
|
+
setSaved(null);
|
|
27
|
+
setDraft(null);
|
|
28
|
+
setError(null);
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
try {
|
|
32
|
+
const plan = assetImportPlanSchema.parse(JSON.parse(content));
|
|
33
|
+
setSaved(plan);
|
|
34
|
+
setDraft(plan);
|
|
35
|
+
setError(null);
|
|
36
|
+
} catch (cause) {
|
|
37
|
+
setSaved(null);
|
|
38
|
+
setDraft(null);
|
|
39
|
+
setError(
|
|
40
|
+
`Invalid import recipe: ${cause instanceof Error ? cause.message : String(cause)}`,
|
|
41
|
+
);
|
|
42
|
+
}
|
|
27
43
|
},
|
|
28
44
|
() => {
|
|
29
45
|
if (!cancelled) {
|
|
30
46
|
setSaved(null);
|
|
31
47
|
setDraft(null);
|
|
48
|
+
setError(null);
|
|
32
49
|
}
|
|
33
50
|
},
|
|
34
51
|
);
|
|
@@ -50,6 +67,7 @@ export function AssetImportDetails({ assetPath }: { assetPath: string }) {
|
|
|
50
67
|
return (
|
|
51
68
|
<SectionHeader label="Import">
|
|
52
69
|
<div>No saved import recipe. Native project asset.</div>
|
|
70
|
+
{error && <div style={{ color: danger }}>{error}</div>}
|
|
53
71
|
</SectionHeader>
|
|
54
72
|
);
|
|
55
73
|
const setSetting = <K extends keyof AssetImportPlan['settings']>(
|
|
@@ -83,6 +83,11 @@ import {
|
|
|
83
83
|
faTriangleExclamation,
|
|
84
84
|
} from '@fortawesome/free-solid-svg-icons';
|
|
85
85
|
import { useCallback, useEffect, useReducer, useRef, useState, useSyncExternalStore } from 'react';
|
|
86
|
+
import {
|
|
87
|
+
assetEditorContextVersion,
|
|
88
|
+
getActiveAssetEditorContext,
|
|
89
|
+
subscribeAssetEditorContext,
|
|
90
|
+
} from '../asset-editor-context';
|
|
86
91
|
import { getActiveAuthoring, hasAuthoringOverride } from '../authoring/active-adapter';
|
|
87
92
|
import { CompositeAuthoringAdapter } from '../authoring/composite-authoring-adapter';
|
|
88
93
|
import { requestWorldFocus, worldFocusScenePath } from '../authoring/edit-mode-authoring';
|
|
@@ -1342,7 +1347,7 @@ function Row({
|
|
|
1342
1347
|
|
|
1343
1348
|
// --- Main panel ---
|
|
1344
1349
|
|
|
1345
|
-
|
|
1350
|
+
function SceneHierarchyContent() {
|
|
1346
1351
|
const store = useEditorStore();
|
|
1347
1352
|
useSyncExternalStore(store.subscribe, store.getSnapshot);
|
|
1348
1353
|
const adapter = getActiveAuthoring(store);
|
|
@@ -1930,3 +1935,23 @@ export function GameHierarchy() {
|
|
|
1930
1935
|
</Panel>
|
|
1931
1936
|
);
|
|
1932
1937
|
}
|
|
1938
|
+
|
|
1939
|
+
/** The navigator slot follows the active center document. Scene/world
|
|
1940
|
+
* documents use the normal authoring hierarchy; a structured Asset Editor
|
|
1941
|
+
* contributes its model/bone/material outline into this same outer panel. */
|
|
1942
|
+
export function GameHierarchy() {
|
|
1943
|
+
useSyncExternalStore(subscribeAssetEditorContext, assetEditorContextVersion);
|
|
1944
|
+
const context = getActiveAssetEditorContext();
|
|
1945
|
+
if (!context?.structure) return <SceneHierarchyContent />;
|
|
1946
|
+
return (
|
|
1947
|
+
<Panel
|
|
1948
|
+
name={`${context.title} Structure`}
|
|
1949
|
+
hideHeader
|
|
1950
|
+
data-testid="asset-editor-structure"
|
|
1951
|
+
onPointerDown={() => setActiveScope('hierarchy')}
|
|
1952
|
+
style={{ flex: 1, minWidth: 0, minHeight: 0, overflow: 'auto' }}
|
|
1953
|
+
>
|
|
1954
|
+
{context.structure}
|
|
1955
|
+
</Panel>
|
|
1956
|
+
);
|
|
1957
|
+
}
|
|
@@ -27,7 +27,16 @@ import type { AuthoringAdapter, EditorNode, PropertyDescriptor, Transform } from
|
|
|
27
27
|
import { faXmark } from '@fortawesome/free-solid-svg-icons';
|
|
28
28
|
import { useSyncExternalStore } from 'react';
|
|
29
29
|
import * as THREE from 'three';
|
|
30
|
-
import {
|
|
30
|
+
import {
|
|
31
|
+
assetEditorContextVersion,
|
|
32
|
+
getActiveAssetEditorContext,
|
|
33
|
+
subscribeAssetEditorContext,
|
|
34
|
+
} from '../asset-editor-context';
|
|
35
|
+
import {
|
|
36
|
+
assetSelectionVersion,
|
|
37
|
+
getSelectedAsset,
|
|
38
|
+
subscribeAssetSelection,
|
|
39
|
+
} from '../asset-selection';
|
|
31
40
|
import { getActiveAuthoring } from '../authoring/active-adapter';
|
|
32
41
|
import { CompositeAuthoringAdapter } from '../authoring/composite-authoring-adapter';
|
|
33
42
|
import { ReactWorldAuthoringAdapter } from '../authoring/react-world-authoring-adapter';
|
|
@@ -57,6 +66,7 @@ import {
|
|
|
57
66
|
workspaceDocumentRegistryVersion,
|
|
58
67
|
} from '../workspace-document-registry';
|
|
59
68
|
import { openEntityAssetDocument } from './asset-documents';
|
|
69
|
+
import { AssetSelectionSection } from './asset-selection-section';
|
|
60
70
|
import { groupProperties } from './inspector-property-grouping';
|
|
61
71
|
import { groupLabelStyle, LABEL_COL_WIDTH } from './inspector-widgets/shared';
|
|
62
72
|
|
|
@@ -179,7 +189,7 @@ function AxisRow({
|
|
|
179
189
|
);
|
|
180
190
|
}
|
|
181
191
|
|
|
182
|
-
|
|
192
|
+
function SceneInspector() {
|
|
183
193
|
const store = useEditorStore();
|
|
184
194
|
useSyncExternalStore(store.subscribe, store.getSnapshot);
|
|
185
195
|
// Re-render when section registrations change (W3): project inspector
|
|
@@ -723,3 +733,29 @@ export function Inspector() {
|
|
|
723
733
|
</Panel>
|
|
724
734
|
);
|
|
725
735
|
}
|
|
736
|
+
|
|
737
|
+
/** Asset documents reuse the ONE outer Inspector rather than rendering a
|
|
738
|
+
* second details rail inside the center document. */
|
|
739
|
+
export function Inspector() {
|
|
740
|
+
useSyncExternalStore(subscribeAssetEditorContext, assetEditorContextVersion);
|
|
741
|
+
useSyncExternalStore(subscribeAssetSelection, assetSelectionVersion);
|
|
742
|
+
const context = getActiveAssetEditorContext();
|
|
743
|
+
const selectedAsset = getSelectedAsset();
|
|
744
|
+
if (!context?.details && !selectedAsset) return <SceneInspector />;
|
|
745
|
+
return (
|
|
746
|
+
<Panel
|
|
747
|
+
name="Inspector"
|
|
748
|
+
hideHeader
|
|
749
|
+
data-testid="inspector-panel"
|
|
750
|
+
onPointerDown={() => setActiveScope('inspector')}
|
|
751
|
+
style={{ overflowY: 'auto' }}
|
|
752
|
+
>
|
|
753
|
+
<div
|
|
754
|
+
data-testid={context ? 'asset-editor-details' : 'asset-selection-details'}
|
|
755
|
+
style={{ pointerEvents: 'auto', padding: context ? 8 : 0 }}
|
|
756
|
+
>
|
|
757
|
+
{context?.details ?? <AssetSelectionSection />}
|
|
758
|
+
</div>
|
|
759
|
+
</Panel>
|
|
760
|
+
);
|
|
761
|
+
}
|