@volter/editor-threejs 0.5.212 → 0.5.213
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/package.json +3 -3
- package/src/kit/components/Object3DDocumentViewport.tsx +2 -4
- package/src/kit/components/StageHost.tsx +12 -36
- package/src/kit/components/asset-viewers/EntityModelDocument.tsx +2 -4
- package/src/kit/components/asset-viewers/EnvironmentAssetDocument.tsx +4 -6
- package/src/kit/components/asset-viewers/LiveModuleDocument.tsx +7 -31
- package/src/kit/components/asset-viewers/LutAssetDocument.tsx +4 -6
- package/src/kit/components/asset-viewers/ModelAssetDocument.tsx +4 -8
- package/src/kit/components/asset-viewers/Object3DPreview.tsx +3 -19
- package/src/kit/components/asset-viewers/QuarksAssetDocument.tsx +4 -8
- package/src/kit/components/asset-viewers/ShaderAssetDocument.tsx +37 -55
- package/src/kit/components/object3d-contribution-surfaces.tsx +4 -2
- package/src/kit/editor-viewport.ts +10 -0
- package/src/kit/viewport-surface-status.tsx +0 -44
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@volter/editor-threejs",
|
|
3
3
|
"publisher": "volter",
|
|
4
4
|
"author": "Volter AI, Inc.",
|
|
5
|
-
"version": "0.5.
|
|
5
|
+
"version": "0.5.213",
|
|
6
6
|
"engines": {
|
|
7
7
|
"vscode": "*"
|
|
8
8
|
},
|
|
@@ -416,11 +416,11 @@
|
|
|
416
416
|
}
|
|
417
417
|
},
|
|
418
418
|
"dependencies": {
|
|
419
|
-
"@volter/project": "0.5.
|
|
419
|
+
"@volter/project": "0.5.213",
|
|
420
420
|
"@sparkjsdev/spark": "^2.1.0",
|
|
421
421
|
"three.quarks": "^0.16.0",
|
|
422
422
|
"typescript": "5.9.3",
|
|
423
|
-
"@volter/sdk": "0.5.
|
|
423
|
+
"@volter/sdk": "0.5.213",
|
|
424
424
|
"postprocessing": "6.39.5",
|
|
425
425
|
"quarks.core": "0.16.0",
|
|
426
426
|
"@fortawesome/free-solid-svg-icons": "^7.2.0"
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
import { lazy, Suspense, useEffect } from 'react';
|
|
10
10
|
import { announceDocumentStage } from '@volter/sdk/document-viewports';
|
|
11
11
|
import type { Object3DDocumentViewportProps } from './StageHost';
|
|
12
|
-
import {
|
|
12
|
+
import { Progress } from '@volter/sdk/progress';
|
|
13
13
|
|
|
14
14
|
const LazyObject3DDocumentViewport = lazy(async () => {
|
|
15
15
|
const module = await import('./StageHost');
|
|
@@ -26,9 +26,7 @@ export function Object3DDocumentViewport(props: Object3DDocumentViewportProps) {
|
|
|
26
26
|
return (
|
|
27
27
|
<Suspense
|
|
28
28
|
fallback={
|
|
29
|
-
<
|
|
30
|
-
{OBJECT3D_SURFACE_BUILDING}
|
|
31
|
-
</ViewportSurfaceStatus>
|
|
29
|
+
props.chromeless ? null : <Progress documentId={props.documentId} title="Preparing the viewport" />
|
|
32
30
|
}
|
|
33
31
|
>
|
|
34
32
|
<LazyObject3DDocumentViewport {...props} />
|
|
@@ -3,7 +3,7 @@ import { ShellStore } from '@volter/sdk/shell-store';
|
|
|
3
3
|
import type { ToolObject3DAuthoringProps, ToolObject3DDocumentAuthoring, ToolObject3DPreviewSource } from '../../object3d-contributions';
|
|
4
4
|
import { invalidateStages, stageGeneration } from '@volter/sdk/stage-invalidation';
|
|
5
5
|
import type { StageTransportSnapshot } from '@volter/sdk/host';
|
|
6
|
-
import { EditorIcon, editorIcons, IconButton, themeVars } from '@volter/sdk/widgets';
|
|
6
|
+
import { EditorBanner, EditorIcon, editorIcons, IconButton, themeVars } from '@volter/sdk/widgets';
|
|
7
7
|
import type { AuthoringAdapter } from '@volter/project/adapter';
|
|
8
8
|
import { contentWorldBounds } from '@volter/editor-threejs/viewport/content-bounds';
|
|
9
9
|
import { isInEditorOwnedSubtree } from '@volter/editor-threejs/viewport/editor-layers';
|
|
@@ -105,7 +105,7 @@ import {
|
|
|
105
105
|
watchPaletteBackdrop,
|
|
106
106
|
} from './standard-viewport-dressing';
|
|
107
107
|
import { ViewportFurniture } from './ViewportFurniture';
|
|
108
|
-
import {
|
|
108
|
+
import { Progress } from '@volter/sdk/progress';
|
|
109
109
|
import { workspaceHistoryService } from '@volter/sdk/components/workspace-history';
|
|
110
110
|
import {
|
|
111
111
|
bindViewPresentation,
|
|
@@ -2346,34 +2346,10 @@ export function Object3DDocumentViewport({
|
|
|
2346
2346
|
) : null}
|
|
2347
2347
|
{/* Show a dismissible error notice over any stage that remains available. */}
|
|
2348
2348
|
{error && (
|
|
2349
|
-
<
|
|
2350
|
-
|
|
2351
|
-
style={{
|
|
2352
|
-
|
|
2353
|
-
insetInline: 0,
|
|
2354
|
-
top: 0,
|
|
2355
|
-
display: 'flex',
|
|
2356
|
-
justifyContent: 'center',
|
|
2357
|
-
padding: 'var(--volter-space-3)',
|
|
2358
|
-
pointerEvents: 'none',
|
|
2359
|
-
}}
|
|
2360
|
-
>
|
|
2361
|
-
<div
|
|
2362
|
-
className="volter-chrome-island"
|
|
2363
|
-
style={{
|
|
2364
|
-
display: 'flex',
|
|
2365
|
-
alignItems: 'center',
|
|
2366
|
-
gap: 'var(--volter-space-3)',
|
|
2367
|
-
maxWidth: '80%',
|
|
2368
|
-
padding: 'var(--volter-space-2) var(--volter-space-3)',
|
|
2369
|
-
borderRadius: 'var(--volter-radius-md)',
|
|
2370
|
-
border: `var(--volter-stroke-resting) solid ${themeVars.semantic.danger}`,
|
|
2371
|
-
background: themeVars.surface.raised,
|
|
2372
|
-
color: themeVars.semantic.danger,
|
|
2373
|
-
pointerEvents: 'auto',
|
|
2374
|
-
}}
|
|
2375
|
-
>
|
|
2376
|
-
<span>{error}</span>
|
|
2349
|
+
<EditorBanner
|
|
2350
|
+
tone="error"
|
|
2351
|
+
style={{ position: 'absolute', insetInline: 0, top: 0 }}
|
|
2352
|
+
actions={
|
|
2377
2353
|
<IconButton
|
|
2378
2354
|
size="compact"
|
|
2379
2355
|
aria-label="Dismiss"
|
|
@@ -2382,13 +2358,13 @@ export function Object3DDocumentViewport({
|
|
|
2382
2358
|
>
|
|
2383
2359
|
<EditorIcon icon={editorIcons.action.close} />
|
|
2384
2360
|
</IconButton>
|
|
2385
|
-
|
|
2386
|
-
|
|
2361
|
+
}
|
|
2362
|
+
>
|
|
2363
|
+
{error}
|
|
2364
|
+
</EditorBanner>
|
|
2387
2365
|
)}
|
|
2388
|
-
{!error && surfaceStatus === 'building' && (
|
|
2389
|
-
<
|
|
2390
|
-
{OBJECT3D_SURFACE_BUILDING}
|
|
2391
|
-
</ViewportSurfaceStatus>
|
|
2366
|
+
{!error && !chromeless && surfaceStatus === 'building' && (
|
|
2367
|
+
<Progress documentId={documentId} title="Preparing the viewport" />
|
|
2392
2368
|
)}
|
|
2393
2369
|
{/* Inset furniture by the canvas bleed so chrome stays inside the visible panel. */}
|
|
2394
2370
|
<div
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { threeStateOf } from '../../three-state';
|
|
2
|
-
import {
|
|
2
|
+
import { StateSurface } from '@volter/sdk/widgets';
|
|
3
3
|
import { liveMixerFor } from '@volter/editor-threejs/animation/live-mixers';
|
|
4
4
|
import { useCallback, useSyncExternalStore } from 'react';
|
|
5
5
|
import type * as THREE from 'three';
|
|
@@ -94,9 +94,7 @@ export function EntityModelDocument({
|
|
|
94
94
|
|
|
95
95
|
if (!source)
|
|
96
96
|
return (
|
|
97
|
-
<
|
|
98
|
-
Scene entity {entityId} is no longer available.
|
|
99
|
-
</div>
|
|
97
|
+
<StateSurface tone="error" title={`Scene entity ${entityId} is no longer available`} />
|
|
100
98
|
);
|
|
101
99
|
return (
|
|
102
100
|
<Object3DSnapshotDocument
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { editorHost } from '@volter/sdk/host';
|
|
2
2
|
import { faCloudSun, faSliders } from '@fortawesome/free-solid-svg-icons';
|
|
3
|
-
import { NumberInput, themeVars } from '@volter/sdk/widgets';
|
|
3
|
+
import { NumberInput, StateSurface, themeVars } from '@volter/sdk/widgets';
|
|
4
|
+
import { Progress } from '@volter/sdk/progress';
|
|
4
5
|
import { useEffect, useMemo, useRef, useState } from 'react';
|
|
5
6
|
import * as THREE from 'three';
|
|
6
7
|
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
|
|
@@ -386,9 +387,7 @@ export function EnvironmentAssetDocument({
|
|
|
386
387
|
title={title}
|
|
387
388
|
status="HDR image unavailable"
|
|
388
389
|
>
|
|
389
|
-
<
|
|
390
|
-
{loadError}
|
|
391
|
-
</div>
|
|
390
|
+
<StateSurface tone="error" title={`Could not load ${title}`} detail={loadError} />
|
|
392
391
|
</AssetEditorShell>
|
|
393
392
|
);
|
|
394
393
|
}
|
|
@@ -400,9 +399,8 @@ export function EnvironmentAssetDocument({
|
|
|
400
399
|
active={active}
|
|
401
400
|
type="environment image"
|
|
402
401
|
title={title}
|
|
403
|
-
status="Loading HDR image"
|
|
404
402
|
>
|
|
405
|
-
<
|
|
403
|
+
<Progress documentId={documentId} title={`Loading ${title}`} />
|
|
406
404
|
</AssetEditorShell>
|
|
407
405
|
);
|
|
408
406
|
}
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
import { beginLiveModuleRevision } from '@volter/sdk/project-module-url';
|
|
14
|
-
import { themeVars } from '@volter/sdk/widgets';
|
|
14
|
+
import { EditorBanner, themeVars } from '@volter/sdk/widgets';
|
|
15
15
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
16
16
|
import * as THREE from 'three';
|
|
17
17
|
import { disposeProjectAssetModel } from '../../asset-preview';
|
|
@@ -281,37 +281,13 @@ export function LiveModuleDocument({
|
|
|
281
281
|
* and never a toast — the document itself has to say why it is stale. */
|
|
282
282
|
function BuildFailureBanner({ failure }: { readonly failure: BuildFailure }) {
|
|
283
283
|
return (
|
|
284
|
-
<
|
|
285
|
-
|
|
284
|
+
<EditorBanner
|
|
285
|
+
tone="error"
|
|
286
286
|
data-testid="live-module-error"
|
|
287
|
-
style={{
|
|
288
|
-
|
|
289
|
-
top: 8,
|
|
290
|
-
left: 8,
|
|
291
|
-
right: 8,
|
|
292
|
-
zIndex: 2,
|
|
293
|
-
maxHeight: '60%',
|
|
294
|
-
overflow: 'auto',
|
|
295
|
-
padding: '8px 10px',
|
|
296
|
-
borderRadius: themeVars.shape.small,
|
|
297
|
-
background: themeVars.surface.raised,
|
|
298
|
-
border: `1px solid ${themeVars.semantic.danger}`,
|
|
299
|
-
color: themeVars.semantic.danger,
|
|
300
|
-
fontFamily: themeVars.typography.mono,
|
|
301
|
-
fontSize: 11,
|
|
302
|
-
lineHeight: 1.5,
|
|
303
|
-
whiteSpace: 'pre-wrap',
|
|
304
|
-
wordBreak: 'break-word',
|
|
305
|
-
pointerEvents: 'auto',
|
|
306
|
-
}}
|
|
287
|
+
style={{ position: 'absolute', top: 0, left: 0, right: 0, zIndex: 2 }}
|
|
288
|
+
detail={failure.stack ? `${failure.message}\n${failure.stack}` : failure.message}
|
|
307
289
|
>
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
</div>
|
|
311
|
-
<div style={{ marginTop: 4 }}>{failure.message}</div>
|
|
312
|
-
{failure.stack ? (
|
|
313
|
-
<div style={{ marginTop: 6, color: themeVars.content.muted }}>{failure.stack}</div>
|
|
314
|
-
) : null}
|
|
315
|
-
</div>
|
|
290
|
+
This module did not build — showing the last good model.
|
|
291
|
+
</EditorBanner>
|
|
316
292
|
);
|
|
317
293
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { editorHost } from '@volter/sdk/host';
|
|
2
2
|
import { faCircleHalfStroke, faSliders } from '@fortawesome/free-solid-svg-icons';
|
|
3
|
-
import { Checkbox, NumberInput, themeVars } from '@volter/sdk/widgets';
|
|
3
|
+
import { Checkbox, NumberInput, StateSurface, themeVars } from '@volter/sdk/widgets';
|
|
4
|
+
import { Progress } from '@volter/sdk/progress';
|
|
4
5
|
import { EffectComposer, EffectPass, LUT3DEffect, RenderPass } from 'postprocessing';
|
|
5
6
|
import { useEffect, useMemo, useRef, useState } from 'react';
|
|
6
7
|
import * as THREE from 'three';
|
|
@@ -367,9 +368,7 @@ export function LutAssetDocument({
|
|
|
367
368
|
title={basename(assetPath)}
|
|
368
369
|
status="CUBE LUT unavailable"
|
|
369
370
|
>
|
|
370
|
-
<
|
|
371
|
-
{loadError}
|
|
372
|
-
</div>
|
|
371
|
+
<StateSurface tone="error" title={`Could not load ${basename(assetPath)}`} detail={loadError} />
|
|
373
372
|
</AssetEditorShell>
|
|
374
373
|
);
|
|
375
374
|
}
|
|
@@ -381,9 +380,8 @@ export function LutAssetDocument({
|
|
|
381
380
|
active={active}
|
|
382
381
|
type="color grade"
|
|
383
382
|
title={basename(assetPath)}
|
|
384
|
-
status="Loading CUBE LUT"
|
|
385
383
|
>
|
|
386
|
-
<
|
|
384
|
+
<Progress documentId={documentId} title={`Loading ${basename(assetPath)}`} />
|
|
387
385
|
</AssetEditorShell>
|
|
388
386
|
);
|
|
389
387
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { StateSurface } from '@volter/sdk/widgets';
|
|
2
|
+
import { Progress } from '@volter/sdk/progress';
|
|
2
3
|
import { useCallback, useEffect, useState } from 'react';
|
|
3
4
|
import type * as THREE from 'three';
|
|
4
5
|
import {
|
|
@@ -83,14 +84,9 @@ export function ModelAssetDocument({
|
|
|
83
84
|
}, [assetPath, model]);
|
|
84
85
|
if (error)
|
|
85
86
|
return (
|
|
86
|
-
<
|
|
87
|
-
{error}
|
|
88
|
-
</div>
|
|
89
|
-
);
|
|
90
|
-
if (!model)
|
|
91
|
-
return (
|
|
92
|
-
<div style={{ padding: 20, color: themeVars.content.muted }}>Loading {displayName}…</div>
|
|
87
|
+
<StateSurface tone="error" title={`Could not load ${displayName}`} detail={error} />
|
|
93
88
|
);
|
|
89
|
+
if (!model) return <Progress documentId={documentId} title={`Loading ${displayName}`} />;
|
|
94
90
|
return (
|
|
95
91
|
<Object3DDocumentViewport
|
|
96
92
|
documentId={documentId}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ToolObject3DPreviewContext, ToolObject3DPreviewExtension } from '../../../object3d-contributions';
|
|
2
|
-
import { themeVars } from '@volter/sdk/widgets';
|
|
2
|
+
import { StateSurface, themeVars } from '@volter/sdk/widgets';
|
|
3
3
|
import { useEffect, useRef, useState } from 'react';
|
|
4
4
|
import * as THREE from 'three';
|
|
5
5
|
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
|
|
@@ -294,24 +294,8 @@ export function Object3DPreview({
|
|
|
294
294
|
background: themeVars.surface.inset,
|
|
295
295
|
}}
|
|
296
296
|
>
|
|
297
|
-
{
|
|
298
|
-
|
|
299
|
-
Loading…
|
|
300
|
-
</span>
|
|
301
|
-
) : null}
|
|
302
|
-
{!active ? (
|
|
303
|
-
<span style={{ position: 'absolute', padding: 10, color: themeVars.content.muted }}>
|
|
304
|
-
Preview suspended while inactive.
|
|
305
|
-
</span>
|
|
306
|
-
) : null}
|
|
307
|
-
{error ? (
|
|
308
|
-
<div
|
|
309
|
-
role="alert"
|
|
310
|
-
style={{ position: 'absolute', padding: 10, color: themeVars.semantic.danger }}
|
|
311
|
-
>
|
|
312
|
-
{error}
|
|
313
|
-
</div>
|
|
314
|
-
) : null}
|
|
297
|
+
{!active ? <StateSurface fill compact title="Preview suspended while inactive." /> : null}
|
|
298
|
+
{error ? <StateSurface fill compact tone="error" title={`${displayName} could not be previewed`} detail={error} /> : null}
|
|
315
299
|
</div>
|
|
316
300
|
);
|
|
317
301
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { ToolObject3DDocumentAuthoringFactory } from '../../../object3d-contributions';
|
|
2
|
-
import {
|
|
2
|
+
import { StateSurface } from '@volter/sdk/widgets';
|
|
3
|
+
import { Progress } from '@volter/sdk/progress';
|
|
3
4
|
import type {
|
|
4
5
|
AuthoringAdapter,
|
|
5
6
|
InspectorProvider,
|
|
@@ -503,14 +504,9 @@ export function QuarksAssetDocument({
|
|
|
503
504
|
|
|
504
505
|
if (error)
|
|
505
506
|
return (
|
|
506
|
-
<
|
|
507
|
-
{error}
|
|
508
|
-
</div>
|
|
509
|
-
);
|
|
510
|
-
if (!artifact)
|
|
511
|
-
return (
|
|
512
|
-
<div style={{ padding: 20, color: themeVars.content.muted }}>Loading {displayName}…</div>
|
|
507
|
+
<StateSurface tone="error" title={`Could not load ${displayName}`} detail={error} />
|
|
513
508
|
);
|
|
509
|
+
if (!artifact) return <Progress documentId={documentId} title={`Loading ${displayName}`} />;
|
|
514
510
|
return (
|
|
515
511
|
<Object3DDocumentViewport
|
|
516
512
|
documentId={documentId}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { editorHost } from '@volter/sdk/host';
|
|
2
2
|
import { faCode, faSliders, faTriangleExclamation } from '@fortawesome/free-solid-svg-icons';
|
|
3
|
-
import { Checkbox, ColorInput, NumberInput, Select, themeVars } from '@volter/sdk/widgets';
|
|
3
|
+
import { Checkbox, ColorInput, EditorBanner, NumberInput, Select, spaceVar, StateSurface, Text, themeVars } from '@volter/sdk/widgets';
|
|
4
|
+
import { Progress } from '@volter/sdk/progress';
|
|
4
5
|
import { useEffect, useMemo, useRef, useState } from 'react';
|
|
5
6
|
import * as THREE from 'three';
|
|
6
7
|
import { readProjectTextFile } from '@volter/sdk/api/project-source';
|
|
@@ -449,6 +450,14 @@ function UniformControlRow({
|
|
|
449
450
|
);
|
|
450
451
|
}
|
|
451
452
|
|
|
453
|
+
/** One diagnostic as a line: where (the file, its line and column) and what. */
|
|
454
|
+
function diagnosticLine(diagnostic: ShaderDiagnostic, files: ShaderFiles): string {
|
|
455
|
+
const file = diagnostic.stage === 'program' ? null : files[diagnostic.stage];
|
|
456
|
+
const where = file?.available ? basename(file.path) : diagnostic.stage;
|
|
457
|
+
const at = diagnostic.line ? `:${diagnostic.line}${diagnostic.column ? `:${diagnostic.column}` : ''}` : '';
|
|
458
|
+
return `${where}${at} · ${diagnostic.message}`;
|
|
459
|
+
}
|
|
460
|
+
|
|
452
461
|
function DiagnosticList({
|
|
453
462
|
diagnostics,
|
|
454
463
|
files,
|
|
@@ -457,37 +466,21 @@ function DiagnosticList({
|
|
|
457
466
|
files: ShaderFiles;
|
|
458
467
|
}) {
|
|
459
468
|
if (diagnostics.length === 0) {
|
|
460
|
-
return
|
|
461
|
-
<div style={{ color: themeVars.semantic.success, fontSize: 11, padding: 4 }}>
|
|
462
|
-
Shader program compiled successfully.
|
|
463
|
-
</div>
|
|
464
|
-
);
|
|
469
|
+
return <StateSurface inline tone="success" title="Shader program compiled successfully." />;
|
|
465
470
|
}
|
|
466
471
|
return (
|
|
467
|
-
<div style={{ display: 'flex', flexDirection: 'column', gap:
|
|
468
|
-
{diagnostics.map((diagnostic, index) =>
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
lineHeight: 1.45,
|
|
480
|
-
}}
|
|
481
|
-
>
|
|
482
|
-
{file?.available ? basename(file.path) : diagnostic.stage}
|
|
483
|
-
{diagnostic.line
|
|
484
|
-
? `:${diagnostic.line}${diagnostic.column ? `:${diagnostic.column}` : ''}`
|
|
485
|
-
: ''}
|
|
486
|
-
{' · '}
|
|
487
|
-
{diagnostic.message}
|
|
488
|
-
</div>
|
|
489
|
-
);
|
|
490
|
-
})}
|
|
472
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: spaceVar[2] }}>
|
|
473
|
+
{diagnostics.map((diagnostic, index) => (
|
|
474
|
+
<Text
|
|
475
|
+
key={`${diagnostic.stage}:${diagnostic.line ?? 0}:${diagnostic.message}:${index}`}
|
|
476
|
+
as="div"
|
|
477
|
+
variant="code"
|
|
478
|
+
selectable
|
|
479
|
+
tone={diagnostic.severity === 'warning' ? 'warning' : 'danger'}
|
|
480
|
+
>
|
|
481
|
+
{diagnosticLine(diagnostic, files)}
|
|
482
|
+
</Text>
|
|
483
|
+
))}
|
|
491
484
|
</div>
|
|
492
485
|
);
|
|
493
486
|
}
|
|
@@ -589,17 +582,18 @@ export function ShaderAssetDocument({
|
|
|
589
582
|
order: PROPERTIES_SECTION_ORDER,
|
|
590
583
|
body: {
|
|
591
584
|
kind: 'custom',
|
|
592
|
-
|
|
585
|
+
// (shown once the files are read: the document passes its sections only then)
|
|
586
|
+
render: () => files && (
|
|
593
587
|
<div
|
|
594
588
|
style={{ display: 'flex', flexDirection: 'column', gap: 5, padding: 4, fontSize: 10 }}
|
|
595
589
|
>
|
|
596
590
|
<span>
|
|
597
|
-
Vertex · {
|
|
598
|
-
{
|
|
591
|
+
Vertex · {basename(files.vertex.path)}
|
|
592
|
+
{!files.vertex.available ? ' (preview fallback)' : ''}
|
|
599
593
|
</span>
|
|
600
594
|
<span>
|
|
601
|
-
Fragment · {
|
|
602
|
-
{
|
|
595
|
+
Fragment · {basename(files.fragment.path)}
|
|
596
|
+
{!files.fragment.available ? ' (preview fallback)' : ''}
|
|
603
597
|
</span>
|
|
604
598
|
<span style={{ color: themeVars.content.muted }}>
|
|
605
599
|
Ordinary sibling GLSL files; runtime composition stays in TS/TSX.
|
|
@@ -658,9 +652,7 @@ export function ShaderAssetDocument({
|
|
|
658
652
|
title={basename(assetPath)}
|
|
659
653
|
status="Shader unavailable"
|
|
660
654
|
>
|
|
661
|
-
<
|
|
662
|
-
{loadError}
|
|
663
|
-
</div>
|
|
655
|
+
<StateSurface tone="error" title={`Could not load ${basename(assetPath)}`} detail={loadError} />
|
|
664
656
|
</AssetEditorShell>
|
|
665
657
|
);
|
|
666
658
|
}
|
|
@@ -671,9 +663,8 @@ export function ShaderAssetDocument({
|
|
|
671
663
|
active={active}
|
|
672
664
|
type="shader"
|
|
673
665
|
title={basename(assetPath)}
|
|
674
|
-
status="Loading GLSL"
|
|
675
666
|
>
|
|
676
|
-
<
|
|
667
|
+
<Progress documentId={documentId} title={`Loading ${basename(assetPath)}`} />
|
|
677
668
|
</AssetEditorShell>
|
|
678
669
|
);
|
|
679
670
|
}
|
|
@@ -721,22 +712,13 @@ export function ShaderAssetDocument({
|
|
|
721
712
|
</Select>
|
|
722
713
|
</div>
|
|
723
714
|
{diagnostics.length > 0 && (
|
|
724
|
-
<
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
right: 12,
|
|
729
|
-
bottom: 12,
|
|
730
|
-
maxHeight: 120,
|
|
731
|
-
overflow: 'auto',
|
|
732
|
-
padding: 8,
|
|
733
|
-
border: `1px solid ${themeVars.semantic.danger}`,
|
|
734
|
-
borderRadius: 6,
|
|
735
|
-
background: themeVars.surface.overlay,
|
|
736
|
-
}}
|
|
715
|
+
<EditorBanner
|
|
716
|
+
tone={diagnostics.some((diagnostic) => diagnostic.severity !== 'warning') ? 'error' : 'warning'}
|
|
717
|
+
style={{ position: 'absolute', left: 0, right: 0, bottom: 0 }}
|
|
718
|
+
detail={diagnostics.map((diagnostic) => diagnosticLine(diagnostic, files)).join('\n')}
|
|
737
719
|
>
|
|
738
|
-
|
|
739
|
-
</
|
|
720
|
+
{`${diagnostics.length} issue${diagnostics.length === 1 ? '' : 's'}`}
|
|
721
|
+
</EditorBanner>
|
|
740
722
|
)}
|
|
741
723
|
</div>
|
|
742
724
|
</AssetEditorShell>
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { announceDocumentStage } from '@volter/sdk/document-viewports';
|
|
2
|
+
import { Progress } from '@volter/sdk/progress';
|
|
2
3
|
import { lazy, Suspense, useEffect } from 'react';
|
|
3
4
|
import type { ToolObject3DAuthoringProps, ToolObject3DPreviewProps } from '../../object3d-contributions';
|
|
4
5
|
|
|
@@ -12,7 +13,8 @@ const Authoring = lazy(async () => ({ default: (await import('./StageHost')).Too
|
|
|
12
13
|
|
|
13
14
|
export function Object3DPreviewSurface(props: ToolObject3DPreviewProps) {
|
|
14
15
|
return (
|
|
15
|
-
|
|
16
|
+
// (a preview card reports no wait: it is a card in a panel or a catalog, neither a document nor a view)
|
|
17
|
+
<Suspense fallback={null}>
|
|
16
18
|
<Preview {...props} />
|
|
17
19
|
</Suspense>
|
|
18
20
|
);
|
|
@@ -26,7 +28,7 @@ export function Object3DAuthoringSurface(props: ToolObject3DAuthoringProps) {
|
|
|
26
28
|
// `workspace.document` contributions do not, and which is which belongs to the PROJECT.
|
|
27
29
|
useEffect(() => announceDocumentStage(props.documentId), [props.documentId]);
|
|
28
30
|
return (
|
|
29
|
-
<Suspense fallback={<
|
|
31
|
+
<Suspense fallback={<Progress documentId={props.documentId} title="Loading 3D authoring surface" />}>
|
|
30
32
|
<Authoring {...props} />
|
|
31
33
|
</Suspense>
|
|
32
34
|
);
|
|
@@ -477,6 +477,14 @@ const _gridQuat = new THREE.Quaternion();
|
|
|
477
477
|
const _gridEye = new THREE.Vector3();
|
|
478
478
|
const _gridUp = new THREE.Vector3(0, 1, 0);
|
|
479
479
|
|
|
480
|
+
/**
|
|
481
|
+
* What the floor's overlays (its grid and its axes) are drawn with: a surface in their own plane, a level's ground at
|
|
482
|
+
* 0, shows them on it, as Blender's grid passes its depth test on a tie (`DRW_STATE_DEPTH_LESS_EQUAL`,
|
|
483
|
+
* overlay_grid.hh). three's depth test passes a tie too; the offset keeps it where the two surfaces' depths round
|
|
484
|
+
* apart, which would split it pixel by pixel.
|
|
485
|
+
*/
|
|
486
|
+
const ON_THE_FLOOR = { polygonOffset: true, polygonOffsetFactor: -1, polygonOffsetUnits: -1 } as const;
|
|
487
|
+
|
|
480
488
|
/**
|
|
481
489
|
* The floor grid as a shader over one plane (`extent` metres square, y = 0).
|
|
482
490
|
* Lines are `fwidth`-antialiased so they keep one on-screen width at every
|
|
@@ -604,6 +612,7 @@ function createFloorGrid(extent: number): THREE.Mesh<THREE.PlaneGeometry, THREE.
|
|
|
604
612
|
transparent: true,
|
|
605
613
|
depthWrite: false,
|
|
606
614
|
side: THREE.DoubleSide,
|
|
615
|
+
...ON_THE_FLOOR,
|
|
607
616
|
});
|
|
608
617
|
const mesh = new THREE.Mesh(geometry, material);
|
|
609
618
|
mesh.name = 'editor-floor-grid';
|
|
@@ -1000,6 +1009,7 @@ export class EditorViewport {
|
|
|
1000
1009
|
opacity: 1,
|
|
1001
1010
|
depthWrite: false,
|
|
1002
1011
|
toneMapped: false,
|
|
1012
|
+
...ON_THE_FLOOR,
|
|
1003
1013
|
}),
|
|
1004
1014
|
);
|
|
1005
1015
|
// Share the floor's grazing fade. LineMaterial interpolates the segment endpoints with
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The 3D board's empty-viewport vocabulary, reused by every other design
|
|
3
|
-
* surface that would otherwise sit blank while it builds. Scene's root-aware
|
|
4
|
-
* loading/failure/empty states live in `SurfaceStateOverlay`; this compact
|
|
5
|
-
* label remains for object/board construction whose subject is already known.
|
|
6
|
-
*
|
|
7
|
-
* Idle / building / ready is viewport STATE, not a new overlay channel —
|
|
8
|
-
* the board already owns this shape (`ThreeBoardDocument`: idle "Open this
|
|
9
|
-
* tab…", building "Settling N stories…"). Scene's first design
|
|
10
|
-
* mount and `Object3DDocumentViewport`'s first construct are the same wait
|
|
11
|
-
* with different subjects. Copy names the subject being prepared; it does not
|
|
12
|
-
* claim a narrower phase (such as physics settling) while indexing, mounting,
|
|
13
|
-
* bounds/layout, or renderer setup is actually in flight.
|
|
14
|
-
*/
|
|
15
|
-
|
|
16
|
-
import { themeVars } from '@volter/sdk/widgets';
|
|
17
|
-
import type { ReactNode } from 'react';
|
|
18
|
-
|
|
19
|
-
/** Fixed construct tail after the board scene exists — viewport-bind, not settle. */
|
|
20
|
-
export const OBJECT3D_SURFACE_BUILDING = 'Preparing the viewport…';
|
|
21
|
-
|
|
22
|
-
export function ViewportSurfaceStatus({
|
|
23
|
-
testId,
|
|
24
|
-
children,
|
|
25
|
-
}: {
|
|
26
|
-
readonly testId: string;
|
|
27
|
-
readonly children: ReactNode;
|
|
28
|
-
}) {
|
|
29
|
-
return (
|
|
30
|
-
<div
|
|
31
|
-
data-testid={testId}
|
|
32
|
-
style={{
|
|
33
|
-
position: 'absolute',
|
|
34
|
-
inset: 0,
|
|
35
|
-
padding: 16,
|
|
36
|
-
fontSize: 12,
|
|
37
|
-
color: themeVars.content.muted,
|
|
38
|
-
pointerEvents: 'none',
|
|
39
|
-
}}
|
|
40
|
-
>
|
|
41
|
-
{children}
|
|
42
|
-
</div>
|
|
43
|
-
);
|
|
44
|
-
}
|