@volter/editor-game 0.5.67 → 0.5.69
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/NOTICE +7 -7
- package/contributions/autoplay.service.ts +1 -1
- package/contributions/build-player.document.tsx +95 -0
- package/contributions/build-profiles.document.tsx +1 -1
- package/contributions/build-progress.status.tsx +2 -2
- package/contributions/build.header.tsx +1 -1
- package/contributions/connection.status.tsx +1 -1
- package/contributions/coverage.service.ts +2 -2
- package/contributions/game-eval.command.ts +6 -6
- package/contributions/gameplay.command.ts +2 -2
- package/contributions/generations.status.tsx +1 -1
- package/contributions/ingest.service.ts +2 -2
- package/contributions/play.command.ts +6 -6
- package/contributions/react/react-inspector.service.ts +1 -1
- package/package.json +9 -8
- package/serving/index.ts +1 -1
- package/src/asset-budget/AssetBudgetPanel.tsx +1 -1
- package/src/asset-budget/asset-budget-model.ts +14 -14
- package/src/asset-budget/optimize-apply.ts +8 -8
- package/src/bridge/call.ts +3 -3
- package/src/bridge/dispatch.ts +12 -12
- package/src/bridge/screenshot.ts +6 -6
- package/src/build/BuildProfilesPanel.tsx +19 -8
- package/src/build/build-session.ts +35 -0
- package/src/canvas/canvas-board/CanvasBoardDocument.tsx +78 -5
- package/src/canvas/canvas-board/canvas-component-board.ts +1 -1
- package/src/canvas/canvas-design-mount.ts +1 -1
- package/src/coverage/live-project-verbs.ts +3 -3
- package/src/coverage/native-system-coverage.ts +3 -3
- package/src/coverage/root-coverage.ts +2 -2
- package/src/coverage/session-coverage.ts +2 -2
- package/src/design-system-stories/InspectorNarrowBodies.stories.tsx +2 -2
- package/src/game-document/DevicePresetPicker.tsx +6 -6
- package/src/game-document/GameCaptureFrameButton.tsx +1 -1
- package/src/game-document/GameDocument.tsx +2 -2
- package/src/game-document/GamePanel.tsx +9 -9
- package/src/game-document/InstanceInspectorPicker.tsx +1 -1
- package/src/game-document/device-preview.ts +4 -4
- package/src/game-document/physics-debug.ts +2 -2
- package/src/generation/GenerationActivity.tsx +22 -22
- package/src/generation/GenerationGallery.css +79 -79
- package/src/generation/generation-documents.tsx +25 -25
- package/src/generation/generation-presentation.ts +1 -1
- package/src/host/adapter-reach.ts +1 -1
- package/src/host/adapter-runtime-bindings.ts +5 -5
- package/src/host/after-paint.ts +3 -3
- package/src/host/api/configurations.ts +1 -1
- package/src/host/authoring/canvas-runtime-recognition.ts +2 -2
- package/src/host/authoring/contract-hierarchy-authoring.ts +6 -6
- package/src/host/authoring/contract-scenes-stories.ts +5 -5
- package/src/host/authoring/ingest-data-writer.ts +1 -1
- package/src/host/authoring/ingest-source-persistence.ts +2 -2
- package/src/host/authoring/mount-isolated-pixi-screen.ts +1 -1
- package/src/host/authoring/pixi-authoring-adapter.ts +206 -28
- package/src/host/authoring/pixi-creatable-kinds.ts +80 -0
- package/src/host/authoring/pixi-live-write-target.ts +6 -5
- package/src/host/authoring/pixi-source-write-target.ts +118 -20
- package/src/host/authoring/pixi-structure-history.ts +3 -3
- package/src/host/binding-resolver.ts +6 -6
- package/src/host/browser-transpile.ts +10 -10
- package/src/host/canvas-preview-frames.ts +1 -1
- package/src/host/components/CameraAuthoringOverlay.tsx +14 -14
- package/src/host/components/HeaderTelemetry.tsx +11 -11
- package/src/host/components/PixiIsolationSceneContent.tsx +3 -3
- package/src/host/components/ResolutionPicker.tsx +4 -4
- package/src/host/components/scene-document.tsx +3 -3
- package/src/host/components/world-root-stage-binding.tsx +1 -1
- package/src/host/components/world-root-stage.ts +1 -1
- package/src/host/coverage/authoring-read-probe.ts +1 -1
- package/src/host/coverage/capability-coverage.ts +22 -22
- package/src/host/coverage/game-contract-seam-evidence.ts +8 -8
- package/src/host/coverage/project-verb-coverage.ts +1 -1
- package/src/host/coverage/system-adapter-coverage.ts +19 -6
- package/src/host/game-location-guard.ts +3 -3
- package/src/host/game-module-access.ts +6 -6
- package/src/host/game-realm-page.ts +3 -3
- package/src/host/game-realm-reclaim.ts +1 -1
- package/src/host/gameplay-recording.ts +5 -5
- package/src/host/gated-globals.ts +13 -13
- package/src/host/ingest/registry.ts +14 -14
- package/src/host/play-control-hook.ts +1 -1
- package/src/host/playwright-shim.ts +2 -2
- package/src/host/projection/pixi.ts +2 -2
- package/src/host/react-mount-runtime.ts +1 -1
- package/src/host/roots/canvas-root.tsx +13 -1
- package/src/host/roots/r3f-root.tsx +1 -1
- package/src/host/scene-view-drawability.ts +1 -1
- package/src/host/sdk/tools.ts +1 -1
- package/src/host/served-bundle-runtime-modules.ts +2 -0
- package/src/host/staged-projects.ts +1 -1
- package/src/host/stories/story-pixi-preview.ts +1 -1
- package/src/host/stories/story-three-preview.ts +1 -1
- package/src/host/three-ingest-runtime.ts +1 -1
- package/src/ingest/active-ingest.ts +1 -1
- package/src/ingest/authoring/ingest-dom-surface-authoring.ts +4 -4
- package/src/ingest/authoring/ingest-root-adapter.ts +11 -11
- package/src/ingest/capture-wait-report.ts +4 -4
- package/src/ingest/discovery-public-ingest.ts +4 -4
- package/src/ingest/dom-stub-mark.ts +1 -1
- package/src/ingest/entry-load.ts +1 -1
- package/src/ingest/game-contract-realm.ts +3 -3
- package/src/ingest/game-pointer-lock.ts +1 -1
- package/src/ingest/ingest-canvas-scene-document.tsx +3 -3
- package/src/ingest/ingest-evidence-hook.ts +6 -6
- package/src/ingest/ingest-frame-snapshot.ts +2 -2
- package/src/ingest/ingest-play-commands.ts +1 -1
- package/src/ingest/ingest-play-control.ts +11 -11
- package/src/ingest/ingest-render-debug.ts +5 -5
- package/src/ingest/ingest-siblings.ts +3 -3
- package/src/ingest/module-mode.ts +2 -2
- package/src/ingest/mount-canvas-ingest-root.ts +10 -10
- package/src/ingest/mount-coverage.ts +3 -3
- package/src/ingest/mount-dom-ingest-root.ts +7 -7
- package/src/ingest/mount-ingest-root.ts +10 -10
- package/src/ingest/mount-three-ingest-root.ts +5 -5
- package/src/ingest/resolve-three.ts +2 -2
- package/src/ingest/served-bundle.ts +6 -6
- package/src/ingest/served-html-boot.ts +8 -8
- package/src/ingest/surface-dom.ts +9 -9
- package/src/ingest/surface-three.ts +1 -1
- package/src/ingest/types.ts +1 -1
- package/src/ingest/unmount-ingest-root.ts +3 -3
- package/src/network/NetworkInspectorPanel.tsx +521 -54
- package/src/network/network-inspector-model.ts +6 -0
- package/src/play/play-boot-stall.ts +1 -1
- package/src/play/play-mode.ts +31 -30
- package/src/play/play-recording.ts +12 -12
- package/src/play-bar/PlayBar.tsx +8 -8
- package/src/play-bar/PlayerCountPicker.tsx +3 -3
- package/src/profiler/FrameDebuggerPanel.tsx +2 -2
- package/src/profiler/PerformancePanel.tsx +4 -4
- package/src/react/design-time-react-mount.ts +5 -5
- package/src/react/dom-authoring-adapter.ts +3 -3
- package/src/react/react-inspector-section.tsx +2 -2
- package/src/react/react-world-authoring-adapter.ts +20 -20
- package/src/react/story-documents/story-documents.tsx +4 -4
- package/src/react/ui-component-board.ts +1 -1
- package/src/runtime/adapter/audio-meter.ts +1 -1
- package/src/runtime/adapter/ingest/contract-debug-adapter.ts +4 -4
- package/src/runtime/adapter/ingest/contract-system-adapters.ts +9 -9
- package/src/runtime/adapter/ingest/merge-debug-adapters.ts +2 -2
- package/src/runtime/adapter/ingest/observation-debug-adapter.ts +1 -1
- package/src/runtime/adapter/native-debug-module.ts +5 -5
- package/src/runtime/audio/pose-guard.ts +1 -1
- package/src/runtime/core/game-scoped-slot.ts +1 -1
- package/src/runtime/create-runtime.ts +15 -15
- package/src/runtime/debug-bridge.ts +25 -25
- package/src/runtime/debug-registry.ts +17 -17
- package/src/runtime/dev/instruments.ts +1 -1
- package/src/runtime/dev/performance-profiler.ts +2 -2
- package/src/runtime/dev/register-render-vitals.ts +2 -2
- package/src/runtime/dev/static-batch-advisor.ts +3 -3
- package/src/runtime/dev-build.ts +3 -3
- package/src/runtime/game.ts +5 -5
- package/src/runtime/input-router.ts +24 -11
- package/src/runtime/mount-manifest.ts +9 -9
- package/src/runtime/pixi/authoring.ts +1 -1
- package/src/runtime/pixi/ingest.ts +1 -1
- package/src/runtime/presentation.ts +1 -1
- package/src/runtime/render-control.ts +21 -21
- package/src/runtime/render-seed.ts +3 -3
- package/src/runtime/run-ticks-settled.ts +1 -1
- package/src/services/game-audio.ts +3 -3
- package/src/services/game-network.ts +125 -15
- package/src/three/authoring/model-asset-inspector-section.css +15 -15
- package/src/three/authoring/model-asset-inspector-section.tsx +7 -7
- package/src/three/authoring/r3f-design-session.ts +10 -10
- package/src/three/authoring/r3f-source-authoring-adapter.ts +5 -5
- package/src/three/authoring/three-authoring-adapter.ts +5 -4
- package/src/three/authoring/typed-three-inspector.ts +1 -1
- package/src/three/story-documents/three-story-documents.tsx +3 -3
- package/src/three/three-board/board-scene.ts +9 -9
- package/src/three/three-board/three-component-board.ts +1 -1
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The display objects a canvas world creates, as its create dialog lists them: Pixi's own class
|
|
3
|
+
* tree (every one of them is a Container) and a line on what each is, from Pixi's API reference.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { CreatableKind } from '@volter/editor-project/adapter';
|
|
7
|
+
|
|
8
|
+
export const PIXI_CREATABLE_KINDS: readonly CreatableKind[] = [
|
|
9
|
+
{
|
|
10
|
+
kind: 'container',
|
|
11
|
+
label: 'Container',
|
|
12
|
+
description: 'A general-purpose display object that holds children, and transforms and draws them together.',
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
kind: 'sprite',
|
|
16
|
+
label: 'Sprite',
|
|
17
|
+
extends: 'container',
|
|
18
|
+
description: 'Draws one texture: an image, or a frame of a spritesheet.',
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
kind: 'animatedSprite',
|
|
22
|
+
label: 'AnimatedSprite',
|
|
23
|
+
extends: 'sprite',
|
|
24
|
+
description: 'Plays a list of textures as frames at an animation speed.',
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
kind: 'tilingSprite',
|
|
28
|
+
label: 'TilingSprite',
|
|
29
|
+
extends: 'container',
|
|
30
|
+
description: 'Repeats one texture across its width and height, scrollable by its tile position.',
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
kind: 'nineSliceSprite',
|
|
34
|
+
label: 'NineSliceSprite',
|
|
35
|
+
extends: 'container',
|
|
36
|
+
description: 'Stretches a texture without stretching its corners: the nine-slice panel of a UI.',
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
kind: 'text',
|
|
40
|
+
label: 'Text',
|
|
41
|
+
extends: 'container',
|
|
42
|
+
description: 'Draws a string with a text style (font, size, fill, wrapping).',
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
kind: 'bitmapText',
|
|
46
|
+
label: 'BitmapText',
|
|
47
|
+
extends: 'container',
|
|
48
|
+
description: 'Draws a string from a bitmap font: fast to change every frame.',
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
kind: 'htmlText',
|
|
52
|
+
label: 'HTMLText',
|
|
53
|
+
extends: 'container',
|
|
54
|
+
description: 'Draws a string of HTML and CSS markup.',
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
kind: 'graphics',
|
|
58
|
+
label: 'Graphics',
|
|
59
|
+
extends: 'container',
|
|
60
|
+
description: 'Draws vector shapes (rectangles, circles, paths) from its draw callback.',
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
kind: 'meshPlane',
|
|
64
|
+
label: 'MeshPlane',
|
|
65
|
+
extends: 'container',
|
|
66
|
+
description: 'A textured grid of vertices that can be bent and deformed.',
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
kind: 'perspectiveMesh',
|
|
70
|
+
label: 'PerspectiveMesh',
|
|
71
|
+
extends: 'meshPlane',
|
|
72
|
+
description: 'A texture drawn in perspective between four corner points.',
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
kind: 'particleContainer',
|
|
76
|
+
label: 'ParticleContainer',
|
|
77
|
+
extends: 'container',
|
|
78
|
+
description: 'Draws many lightweight particles in one batch, added from code with addParticle.',
|
|
79
|
+
},
|
|
80
|
+
];
|
|
@@ -30,6 +30,7 @@
|
|
|
30
30
|
* moved, and the persistence parameter is what this unit added.
|
|
31
31
|
*/
|
|
32
32
|
|
|
33
|
+
import { physicsOwnerOf } from '@volter/editor-project/adapter';
|
|
33
34
|
import type {
|
|
34
35
|
AuthoringAdapter2D,
|
|
35
36
|
Overlay2D,
|
|
@@ -554,7 +555,7 @@ export function createLiveCanvasWriteTarget(
|
|
|
554
555
|
* `literalsFor` is what a rendering one needs. A revert or an apply is a
|
|
555
556
|
* deliberate act with somewhere to await, so it must not silently do nothing
|
|
556
557
|
* merely because nothing happened to have described this node first (an agent
|
|
557
|
-
* driving the provider through `
|
|
558
|
+
* driving the provider through `volter-game-editor eval` never does).
|
|
558
559
|
*/
|
|
559
560
|
const literalsNow = async (
|
|
560
561
|
id: string,
|
|
@@ -807,7 +808,7 @@ export function createLiveCanvasWriteTarget(
|
|
|
807
808
|
beginTransformEdit(id: string): void {
|
|
808
809
|
historyResource?.assertCanMutate();
|
|
809
810
|
const display = a2d.displayObject(id);
|
|
810
|
-
if (display && physics
|
|
811
|
+
if (display && physicsOwnerOf(physics, display) === 'physics') physics.freeze(display);
|
|
811
812
|
editStartState = captureHistoryState();
|
|
812
813
|
editBaseline = persist ? (a2d.getTransform(id) ?? undefined) : undefined;
|
|
813
814
|
editMovedOrigin = false;
|
|
@@ -816,7 +817,7 @@ export function createLiveCanvasWriteTarget(
|
|
|
816
817
|
writeTransform(id: string, next: Transform2DValue): void {
|
|
817
818
|
historyResource?.assertCanMutate();
|
|
818
819
|
const display = a2d.displayObject(id);
|
|
819
|
-
if (display && physics
|
|
820
|
+
if (display && physicsOwnerOf(physics, display) === 'physics') {
|
|
820
821
|
physics.commit(display, next.position, next.rotation);
|
|
821
822
|
}
|
|
822
823
|
a2d.setTransform(id, next);
|
|
@@ -829,7 +830,7 @@ export function createLiveCanvasWriteTarget(
|
|
|
829
830
|
// source write is attempted.
|
|
830
831
|
endTransformEdit(id: string): void | Promise<WriteAck> {
|
|
831
832
|
const display = a2d.displayObject(id);
|
|
832
|
-
if (display && physics
|
|
833
|
+
if (display && physicsOwnerOf(physics, display) === 'physics') physics.unfreeze(display);
|
|
833
834
|
const label = `Transform ${labelOf(id)}`;
|
|
834
835
|
const before = editStartState;
|
|
835
836
|
const baseline = editBaseline;
|
|
@@ -907,7 +908,7 @@ export function createLiveCanvasWriteTarget(
|
|
|
907
908
|
* channel a refused transform does: they asked for source writes, so each
|
|
908
909
|
* one that did not land matters individually. Unarmed, the first structural
|
|
909
910
|
* edit of the session raises the warning that reaches the product's own
|
|
910
|
-
* doors (`
|
|
911
|
+
* doors (`volter-game-editor status`, the session journal, one ack to clear) — a whole
|
|
911
912
|
* class of this author's edits will not be saved, which is exactly what an
|
|
912
913
|
* unresolved warning is for — and every later op logs, because repeating an
|
|
913
914
|
* ack-requiring entry per created node would flood the set with the lane
|
|
@@ -42,6 +42,7 @@ import {
|
|
|
42
42
|
transform2DChanged,
|
|
43
43
|
} from './pixi-transform-channels';
|
|
44
44
|
import { createStructWritePipe, type StructOpOptions } from './struct-write-pipe';
|
|
45
|
+
import { PIXI_CREATABLE_KINDS } from './pixi-creatable-kinds';
|
|
45
46
|
import {
|
|
46
47
|
clipboardOutcome,
|
|
47
48
|
LIVE_ONLY_ACK,
|
|
@@ -102,6 +103,7 @@ const PANEL_OWNED_PROPS = new Set([
|
|
|
102
103
|
'scale-y',
|
|
103
104
|
'label',
|
|
104
105
|
'visible',
|
|
106
|
+
'skew',
|
|
105
107
|
]);
|
|
106
108
|
/** Props the drop snippet writes itself. */
|
|
107
109
|
const DROP_SNIPPET_PROPS = new Set(['x', 'y', 'name', 'children']);
|
|
@@ -112,16 +114,20 @@ const CREATE_SNIPPETS: Readonly<Record<string, string>> = {
|
|
|
112
114
|
sprite: '<pixiSprite label="Sprite" />',
|
|
113
115
|
text: '<pixiText label="Text" text="Text" />',
|
|
114
116
|
graphics: '<pixiGraphics label="Graphics" />',
|
|
117
|
+
animatedSprite: '<pixiAnimatedSprite label="AnimatedSprite" textures={[Texture.WHITE]} width={64} height={64} />',
|
|
118
|
+
tilingSprite: '<pixiTilingSprite label="TilingSprite" texture={Texture.WHITE} width={100} height={100} />',
|
|
119
|
+
nineSliceSprite: '<pixiNineSliceSprite label="NineSliceSprite" texture={Texture.WHITE} width={100} height={100} />',
|
|
120
|
+
bitmapText: '<pixiBitmapText label="BitmapText" text="BitmapText" />',
|
|
121
|
+
htmlText: '<pixiHTMLText label="HTMLText" text="HTMLText" />',
|
|
122
|
+
meshPlane: '<pixiMeshPlane label="MeshPlane" texture={Texture.WHITE} width={100} height={100} />',
|
|
123
|
+
perspectiveMesh: '<pixiPerspectiveMesh label="PerspectiveMesh" texture={Texture.WHITE} width={100} height={100} />',
|
|
124
|
+
particleContainer: '<pixiParticleContainer label="ParticleContainer" />',
|
|
115
125
|
};
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
sprite: 'Sprite',
|
|
119
|
-
text: 'Text',
|
|
120
|
-
graphics: 'Graphics',
|
|
121
|
-
};
|
|
126
|
+
/** A kind whose snippet names Pixi's `Texture`, which the file must then import. */
|
|
127
|
+
const TEXTURE_SNIPPET_KINDS = new Set(['animatedSprite', 'tilingSprite', 'nineSliceSprite', 'meshPlane', 'perspectiveMesh']);
|
|
122
128
|
|
|
123
129
|
function isHiddenProp(name: string): boolean {
|
|
124
|
-
return HIDDEN_PROPS.has(name) || /^on[A-Z]/.test(name) || name.startsWith('
|
|
130
|
+
return HIDDEN_PROPS.has(name) || /^on[A-Z]/.test(name) || name.startsWith('__volter');
|
|
125
131
|
}
|
|
126
132
|
|
|
127
133
|
/** Present a source identifier the way native engine inspectors present
|
|
@@ -217,6 +223,10 @@ export function createSourceCanvasWriteTarget(
|
|
|
217
223
|
const dynamicPaths = new Set<string>();
|
|
218
224
|
/** Pre-gesture 2D poses, one per id currently under a drag. */
|
|
219
225
|
const editStarts = new Map<string, Transform2DValue>();
|
|
226
|
+
const pendingOrigins = new Map<
|
|
227
|
+
string,
|
|
228
|
+
{ kind: 'pivot' | 'anchor'; value: readonly [number, number]; before: readonly [number, number] }
|
|
229
|
+
>();
|
|
220
230
|
let commitQueue = Promise.resolve();
|
|
221
231
|
|
|
222
232
|
const oidOf = (id: string): string | null => {
|
|
@@ -464,10 +474,16 @@ export function createSourceCanvasWriteTarget(
|
|
|
464
474
|
const channels = (['position', 'rotation', 'scale'] as const).filter((channel) =>
|
|
465
475
|
transform2DChanged(channel, before, after),
|
|
466
476
|
);
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
477
|
+
// A moved origin (pivot or anchor) is written with the position that compensates it.
|
|
478
|
+
const origin = pendingOrigins.get(id);
|
|
479
|
+
pendingOrigins.delete(id);
|
|
480
|
+
if (channels.length === 0 && !origin) return false;
|
|
481
|
+
|
|
482
|
+
const revert = (channel: TransformChannel | 'origin'): void => {
|
|
483
|
+
if (channel === 'origin') {
|
|
484
|
+
const display = a2d.displayObject(id) as (Container & Record<string, unknown>) | null;
|
|
485
|
+
if (display && origin) display[origin.kind] = { x: origin.before[0], y: origin.before[1] };
|
|
486
|
+
} else if (channel === 'position') a2d.setTransform(id, { position: before.position });
|
|
471
487
|
else if (channel === 'rotation') a2d.setTransform(id, { rotation: before.rotation });
|
|
472
488
|
else a2d.setTransform(id, { scale: before.scale });
|
|
473
489
|
notify();
|
|
@@ -495,7 +511,7 @@ export function createSourceCanvasWriteTarget(
|
|
|
495
511
|
const instance = componentIdentityOf(id);
|
|
496
512
|
const channelBlocked = (channel: TransformChannel): boolean =>
|
|
497
513
|
componentOwnedChannel(id, channel, attrs, after);
|
|
498
|
-
const writes: Array<{ channel: TransformChannel; prop: string; value: string }> = [];
|
|
514
|
+
const writes: Array<{ channel: TransformChannel | 'origin'; prop: string; value: string }> = [];
|
|
499
515
|
for (const channel of channels) {
|
|
500
516
|
if (channelBlocked(channel)) {
|
|
501
517
|
dynamicPaths.add(`${id}|${channel}`);
|
|
@@ -532,6 +548,16 @@ export function createSourceCanvasWriteTarget(
|
|
|
532
548
|
}
|
|
533
549
|
for (const write of plan.writes) writes.push({ channel, ...write });
|
|
534
550
|
}
|
|
551
|
+
if (origin) {
|
|
552
|
+
if (instance && channelBlocked('position')) revert('origin');
|
|
553
|
+
else {
|
|
554
|
+
writes.push({
|
|
555
|
+
channel: 'origin',
|
|
556
|
+
prop: origin.kind,
|
|
557
|
+
value: `{ x: ${formatSourceNumber(origin.value[0])}, y: ${formatSourceNumber(origin.value[1])} }`,
|
|
558
|
+
});
|
|
559
|
+
}
|
|
560
|
+
}
|
|
535
561
|
if (writes.length === 0) return false;
|
|
536
562
|
|
|
537
563
|
let persisted = false;
|
|
@@ -543,7 +569,8 @@ export function createSourceCanvasWriteTarget(
|
|
|
543
569
|
// must not need a trip to the text editor.
|
|
544
570
|
addIfMissing: true,
|
|
545
571
|
// `scale={1.5}` becomes `scale={{ x, y }}` when a gesture makes it non-uniform.
|
|
546
|
-
|
|
572
|
+
// The same for an origin authored as one number (`pivot={60}`).
|
|
573
|
+
...(write.prop === 'scale' || write.channel === 'origin' ? { allowShapeUpgrade: true } : {}),
|
|
547
574
|
});
|
|
548
575
|
if (res.changed) {
|
|
549
576
|
persisted = true;
|
|
@@ -595,6 +622,38 @@ export function createSourceCanvasWriteTarget(
|
|
|
595
622
|
|
|
596
623
|
/** One prop edit. Answers whether a byte reached the entry TSX; every
|
|
597
624
|
* refusal has already named itself on the console before it returns. */
|
|
625
|
+
/** Godot's Node2D Skew, one axis in degrees: Pixi's `skew` point in radians, written whole. */
|
|
626
|
+
const writeSkew = async (id: string, axis: 'x' | 'y', degrees: number): Promise<boolean> => {
|
|
627
|
+
const display = a2d.displayObject(id);
|
|
628
|
+
const oid = oidOf(id);
|
|
629
|
+
if (!display || !oid || !backend?.writeProp || !Number.isFinite(degrees)) return false;
|
|
630
|
+
const before = { x: display.skew.x, y: display.skew.y };
|
|
631
|
+
const radians = (degrees * Math.PI) / 180;
|
|
632
|
+
const next = { x: axis === 'x' ? radians : before.x, y: axis === 'y' ? radians : before.y };
|
|
633
|
+
display.skew.set(next.x, next.y);
|
|
634
|
+
notify();
|
|
635
|
+
const res = await backend.writeProp(
|
|
636
|
+
oid,
|
|
637
|
+
'skew',
|
|
638
|
+
// Radians to six places, so a typed angle reads back as typed (four places lose 0.003°).
|
|
639
|
+
`{ x: ${String(Number(next.x.toFixed(6)))}, y: ${String(Number(next.y.toFixed(6)))} }`,
|
|
640
|
+
{ addIfMissing: true, allowShapeUpgrade: true },
|
|
641
|
+
);
|
|
642
|
+
if (!res.changed && (res.dynamic || res.error)) {
|
|
643
|
+
display.skew.set(before.x, before.y);
|
|
644
|
+
notify();
|
|
645
|
+
// biome-ignore lint/suspicious/noConsole: a refused write must say why
|
|
646
|
+
console.warn(
|
|
647
|
+
`[canvas-source ${entryPath}] skew write refused for oid "${oid}": ` +
|
|
648
|
+
`${res.dynamic ? 'dynamic expression (guarded)' : res.error} — reverting.`,
|
|
649
|
+
);
|
|
650
|
+
return false;
|
|
651
|
+
}
|
|
652
|
+
await refreshSourceState();
|
|
653
|
+
notify();
|
|
654
|
+
return res.changed;
|
|
655
|
+
};
|
|
656
|
+
|
|
598
657
|
const writeJsxProp = async (
|
|
599
658
|
id: string,
|
|
600
659
|
path: string,
|
|
@@ -1130,12 +1189,20 @@ export function createSourceCanvasWriteTarget(
|
|
|
1130
1189
|
// The new element's oid is minted server-side on the remount, so the id
|
|
1131
1190
|
// half is '' and the ack half is this creation's own piped write — handed
|
|
1132
1191
|
// back rather than fired `void` (see `StructuralIdWrite`).
|
|
1133
|
-
create: (kind, parentId) => {
|
|
1134
|
-
|
|
1192
|
+
create: (kind, parentId, at) => {
|
|
1193
|
+
// `at` arrives in the new node's parent's own space (the adapter converts it).
|
|
1194
|
+
const base = CREATE_SNIPPETS[kind];
|
|
1195
|
+
const snippet =
|
|
1196
|
+
base && at
|
|
1197
|
+
? base.replace(/\s*\/>$/, ` x={${formatSourceNumber(at.x)}} y={${formatSourceNumber(at.y)}} />`)
|
|
1198
|
+
: base;
|
|
1135
1199
|
// Nothing was attempted (the palette is empty in both cases), so there is
|
|
1136
1200
|
// no write to ack.
|
|
1137
1201
|
if (!snippet || !backend?.writeStruct) return { id: '', ack: undefined };
|
|
1138
|
-
|
|
1202
|
+
const options = TEXTURE_SNIPPET_KINDS.has(kind)
|
|
1203
|
+
? { snippet, ensureImport: { name: 'Texture', module: 'pixi.js' } }
|
|
1204
|
+
: { snippet };
|
|
1205
|
+
if (parentId) return { id: '', ack: structOp(parentId, 'create', options) };
|
|
1139
1206
|
const lastRoot = a2d.roots().at(-1);
|
|
1140
1207
|
if (!lastRoot) {
|
|
1141
1208
|
return {
|
|
@@ -1145,15 +1212,14 @@ export function createSourceCanvasWriteTarget(
|
|
|
1145
1212
|
),
|
|
1146
1213
|
};
|
|
1147
1214
|
}
|
|
1148
|
-
return { id: '', ack: structOp(lastRoot.id, 'create-sibling',
|
|
1215
|
+
return { id: '', ack: structOp(lastRoot.id, 'create-sibling', options) };
|
|
1149
1216
|
},
|
|
1150
1217
|
creatableKinds: (parentId) => {
|
|
1151
1218
|
if (!backend?.writeStruct) return [];
|
|
1152
1219
|
if (parentId && !oidOf(parentId)) return [];
|
|
1153
1220
|
if (!parentId && a2d.roots().length === 0) return [];
|
|
1154
|
-
return
|
|
1155
|
-
|
|
1156
|
-
label: CREATE_LABELS[kind] ?? kind,
|
|
1221
|
+
return PIXI_CREATABLE_KINDS.filter((entry) => CREATE_SNIPPETS[entry.kind] !== undefined).map((entry) => ({
|
|
1222
|
+
...entry,
|
|
1157
1223
|
}));
|
|
1158
1224
|
},
|
|
1159
1225
|
// Returns the promise (never `void structOp(…)`): `deleteSelection`
|
|
@@ -1469,6 +1535,21 @@ export function createSourceCanvasWriteTarget(
|
|
|
1469
1535
|
if (!editStarts.has(id)) editStarts.set(id, read2D(id));
|
|
1470
1536
|
},
|
|
1471
1537
|
|
|
1538
|
+
/**
|
|
1539
|
+
* Mid-gesture origin write — see {@link CanvasWriteTarget.writeOrigin}. The live object moves
|
|
1540
|
+
* now; the `pivot`/`anchor` prop is written at `endTransformEdit`, in the same gesture as the
|
|
1541
|
+
* position that compensates it.
|
|
1542
|
+
*/
|
|
1543
|
+
writeOrigin(id: string, kind: 'pivot' | 'anchor', value: readonly [number, number]): void {
|
|
1544
|
+
const display = a2d.displayObject(id) as (Container & Record<string, unknown>) | null;
|
|
1545
|
+
const current = display?.[kind] as { x?: unknown; y?: unknown } | undefined;
|
|
1546
|
+
if (!display || typeof current?.x !== 'number' || typeof current.y !== 'number') return;
|
|
1547
|
+
const before = pendingOrigins.get(id)?.before ?? ([current.x, current.y] as const);
|
|
1548
|
+
display[kind] = { x: value[0], y: value[1] };
|
|
1549
|
+
pendingOrigins.set(id, { kind, value, before });
|
|
1550
|
+
notify();
|
|
1551
|
+
},
|
|
1552
|
+
|
|
1472
1553
|
writeTransform(id: string, next: Transform2DValue): void {
|
|
1473
1554
|
// The live tree moves first so the paused frame follows the gesture; the
|
|
1474
1555
|
// source write happens once, at the end.
|
|
@@ -1535,6 +1616,13 @@ export function createSourceCanvasWriteTarget(
|
|
|
1535
1616
|
{ path: 'visible', label: 'Visible', type: 'boolean', group: 'Visibility' },
|
|
1536
1617
|
];
|
|
1537
1618
|
const attrs = attrsOf(id) ?? [];
|
|
1619
|
+
// Godot's Node2D Skew, in its Transform group; a skew the source computes is shown, not edited.
|
|
1620
|
+
const skewAttr = attrs.find((attr) => attr.name === 'skew');
|
|
1621
|
+
const skewFixed = skewAttr ? !skewAttr.isLiteral : false;
|
|
1622
|
+
props.push(
|
|
1623
|
+
{ path: 'skew.x', label: 'Skew X (deg)', type: 'number', group: 'Transform', readonly: skewFixed },
|
|
1624
|
+
{ path: 'skew.y', label: 'Skew Y (deg)', type: 'number', group: 'Transform', readonly: skewFixed },
|
|
1625
|
+
);
|
|
1538
1626
|
const declared = declaredPropsOf(id);
|
|
1539
1627
|
const attrByName = new Map(attrs.map((attr) => [attr.name, attr]));
|
|
1540
1628
|
const names = [
|
|
@@ -1573,6 +1661,9 @@ export function createSourceCanvasWriteTarget(
|
|
|
1573
1661
|
if (echoed !== undefined) return echoed;
|
|
1574
1662
|
if (path === 'name') return display.label ?? '';
|
|
1575
1663
|
if (path === 'visible') return display.visible;
|
|
1664
|
+
if (path === 'skew.x' || path === 'skew.y') {
|
|
1665
|
+
return Number((((path === 'skew.x' ? display.skew.x : display.skew.y) * 180) / Math.PI).toFixed(4));
|
|
1666
|
+
}
|
|
1576
1667
|
if (!path.startsWith('jsx.')) return undefined;
|
|
1577
1668
|
return propValue(attrsOf(id), declaredPropsOf(id), path.slice(4));
|
|
1578
1669
|
},
|
|
@@ -1581,6 +1672,13 @@ export function createSourceCanvasWriteTarget(
|
|
|
1581
1672
|
// edit, awaited. A path this target owns no prop for performed no write and
|
|
1582
1673
|
// returns nothing.
|
|
1583
1674
|
set(id: string, path: string, value: unknown): void | Promise<WriteAck> {
|
|
1675
|
+
if (path === 'skew.x' || path === 'skew.y') {
|
|
1676
|
+
return piped(
|
|
1677
|
+
() => writeSkew(id, path === 'skew.x' ? 'x' : 'y', Number(value)),
|
|
1678
|
+
undefined,
|
|
1679
|
+
destinationOf(id),
|
|
1680
|
+
);
|
|
1681
|
+
}
|
|
1584
1682
|
const prop =
|
|
1585
1683
|
path === 'name'
|
|
1586
1684
|
? 'label'
|
|
@@ -42,7 +42,7 @@ const ROOT_UID = '#root';
|
|
|
42
42
|
* `AuthoringAdapter2D` uses for `__authId`, for the same reason (there is no
|
|
43
43
|
* side table that survives the object outliving one walk). */
|
|
44
44
|
interface StructureStamped {
|
|
45
|
-
|
|
45
|
+
__volterStructUid?: string;
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
/** A node's own local pose, as plain numbers. Recorded because `reparent`
|
|
@@ -185,10 +185,10 @@ export class CanvasStructureHistory {
|
|
|
185
185
|
private uidOf(object: Container): string {
|
|
186
186
|
if (object === this.root) return ROOT_UID;
|
|
187
187
|
const stamped = object as Container & StructureStamped;
|
|
188
|
-
const existing = stamped.
|
|
188
|
+
const existing = stamped.__volterStructUid;
|
|
189
189
|
if (existing && this.byUid.get(existing) === object) return existing;
|
|
190
190
|
const uid = existing ?? `s${crypto.randomUUID()}`;
|
|
191
|
-
stamped.
|
|
191
|
+
stamped.__volterStructUid = uid;
|
|
192
192
|
this.byUid.set(uid, object);
|
|
193
193
|
return uid;
|
|
194
194
|
}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*
|
|
5
5
|
* A game is a native program plus a declaration of itself; the editor is a
|
|
6
6
|
* universal client of that declaration. This module is where the declaration
|
|
7
|
-
* is READ: the manifest root, the project's parsed `
|
|
7
|
+
* is READ: the manifest root, the project's parsed `volter.adapter.ts`, and the
|
|
8
8
|
* entry module's own static surface, resolved against ONE realm
|
|
9
9
|
* (`realm-services.ts` — dev or packaged).
|
|
10
10
|
*
|
|
@@ -85,7 +85,7 @@ export interface ResolveRootBindingOptions {
|
|
|
85
85
|
* Resolve one manifest root against one realm.
|
|
86
86
|
*
|
|
87
87
|
* `adapterDef` is an INPUT, never something this function goes and fetches:
|
|
88
|
-
* `
|
|
88
|
+
* `volter.adapter.ts` loading stays in `project-adapter.ts`, and the binding is
|
|
89
89
|
* the hand-off's shape rather than a second loader.
|
|
90
90
|
*/
|
|
91
91
|
export async function resolveRootBinding(
|
|
@@ -252,7 +252,7 @@ function rejectCapturedIngestAsRootAdapter(root: ResolvedAdapterRoot): never {
|
|
|
252
252
|
export interface ResolveAllRootEntriesOptions {
|
|
253
253
|
/**
|
|
254
254
|
* Remount the named region's entrypoint at this swap-slot key. Only that
|
|
255
|
-
* world's entry URL carries the override; siblings share `?
|
|
255
|
+
* world's entry URL carries the override; siblings share `?volter-mount=`
|
|
256
256
|
* alone so the module graph stays one instance.
|
|
257
257
|
*/
|
|
258
258
|
readonly selectionOverride?:
|
|
@@ -276,7 +276,7 @@ interface ResolvedComposition {
|
|
|
276
276
|
* resolvers would give each root its own generation, which is the defect.
|
|
277
277
|
*
|
|
278
278
|
* The COMPOSITION level is likewise where the project's two ambient inputs are
|
|
279
|
-
* fetched — the realm's services and the parsed `
|
|
279
|
+
* fetched — the realm's services and the parsed `volter.adapter.ts` — because
|
|
280
280
|
* both are properties of the open project rather than of any one root, and
|
|
281
281
|
* asking once per composition is what keeps every root of a game bound to the
|
|
282
282
|
* same declaration. `resolveRootBinding` itself still fetches neither: it is
|
|
@@ -302,7 +302,7 @@ async function resolveComposition(
|
|
|
302
302
|
out.push({ world, resolved });
|
|
303
303
|
}
|
|
304
304
|
// Stringified because that is the form the id travels in everywhere else:
|
|
305
|
-
// the entry url's `?
|
|
305
|
+
// the entry url's `?volter-mount=` value, and therefore the key
|
|
306
306
|
// `gated-globals.ts` reads back off a module url.
|
|
307
307
|
return { roots: out, mountId: String(epoch) };
|
|
308
308
|
}
|
|
@@ -351,7 +351,7 @@ export async function resolveAllRoots(
|
|
|
351
351
|
*
|
|
352
352
|
* It also returns this composition's `mountId` — the identity the whole
|
|
353
353
|
* isolation layer is keyed by. It is minted here (one epoch per composition),
|
|
354
|
-
* rides every project module url as `?
|
|
354
|
+
* rides every project module url as `?volter-mount=`, and is what
|
|
355
355
|
* `gated-globals.ts` resolves a realm and an input gate under.
|
|
356
356
|
*
|
|
357
357
|
* It does NOT return the entry modules' harvested `debug`/`systems` bindings.
|
|
@@ -174,7 +174,7 @@ export function registerRuntimeModules(modules: Record<string, ModuleNamespace>)
|
|
|
174
174
|
}
|
|
175
175
|
// Exposed on globalThis so shim modules (which run in their own module scope)
|
|
176
176
|
// can reach the live namespaces by specifier.
|
|
177
|
-
(globalThis as Record<string, unknown>)['
|
|
177
|
+
(globalThis as Record<string, unknown>)['__volterModules'] = Object.fromEntries(runtimeModules);
|
|
178
178
|
}
|
|
179
179
|
|
|
180
180
|
/**
|
|
@@ -259,7 +259,7 @@ function loadRuntimeModule(spec: string): Promise<void> {
|
|
|
259
259
|
|
|
260
260
|
/**
|
|
261
261
|
* The call-time half of a literal dynamic import in project code. The rewrite
|
|
262
|
-
* turns `import('tone')` into `globalThis.
|
|
262
|
+
* turns `import('tone')` into `globalThis.__volterImportRuntimeModule('tone')`,
|
|
263
263
|
* so the load — and the wall, if the browser editor does not ship it — happens
|
|
264
264
|
* exactly when the project's own code executes the import, never earlier.
|
|
265
265
|
*/
|
|
@@ -267,7 +267,7 @@ export async function importRuntimeModule(spec: string): Promise<ModuleNamespace
|
|
|
267
267
|
if (!runtimeModules.has(spec)) await loadRuntimeModule(spec);
|
|
268
268
|
return runtimeModules.get(spec) as ModuleNamespace;
|
|
269
269
|
}
|
|
270
|
-
(globalThis as Record<string, unknown>)['
|
|
270
|
+
(globalThis as Record<string, unknown>)['__volterImportRuntimeModule'] = importRuntimeModule;
|
|
271
271
|
|
|
272
272
|
/**
|
|
273
273
|
* Bring every static bare import live, then rewrite by the lexer's own byte
|
|
@@ -304,7 +304,7 @@ export async function resolveAndRewriteBareImports(code: string): Promise<string
|
|
|
304
304
|
out = `${out.slice(0, record.s)}${shimUrlFor(record.n)}${out.slice(record.e)}`;
|
|
305
305
|
} else if (record.d >= 0) {
|
|
306
306
|
// Literal dynamic import: swap the whole `import(...)` expression.
|
|
307
|
-
out = `${out.slice(0, record.ss)}globalThis.
|
|
307
|
+
out = `${out.slice(0, record.ss)}globalThis.__volterImportRuntimeModule(${JSON.stringify(
|
|
308
308
|
record.n,
|
|
309
309
|
)})${out.slice(record.se)}`;
|
|
310
310
|
}
|
|
@@ -379,7 +379,7 @@ function shimUrlFor(spec: string): string {
|
|
|
379
379
|
// `registerRuntimeModules` user) — the same wall, said the same way.
|
|
380
380
|
if (!ns) throw unregisteredRuntimeModules([spec]);
|
|
381
381
|
|
|
382
|
-
const lines = [`const __m = globalThis.
|
|
382
|
+
const lines = [`const __m = globalThis.__volterModules[${JSON.stringify(spec)}];`];
|
|
383
383
|
for (const key of Object.keys(ns)) {
|
|
384
384
|
if (key === 'default' || !IDENT_RE.test(key) || RESERVED_WORDS.has(key)) continue;
|
|
385
385
|
lines.push(`export const ${key} = __m[${JSON.stringify(key)}];`);
|
|
@@ -432,7 +432,7 @@ export async function transpileToModuleUrl(
|
|
|
432
432
|
|
|
433
433
|
/**
|
|
434
434
|
* Prepend the game-globals lexical shadow to project source — the browser-side
|
|
435
|
-
* analogue of the dev server's `
|
|
435
|
+
* analogue of the dev server's `volter-game-globals` Vite plugin
|
|
436
436
|
* (`server/dev.ts` + `shouldShadowGameGlobals`). The server shadows per-module
|
|
437
437
|
* because Vite transforms EVERY module indiscriminately and must be told which
|
|
438
438
|
* ones are game code; the browser has no such firehose — {@link importFromSource}
|
|
@@ -541,7 +541,7 @@ export function loaderFor(path: string): esbuild.Loader {
|
|
|
541
541
|
function resolveServedModulePath(importer: string, specifier: string): string {
|
|
542
542
|
if (specifier.startsWith('/')) return specifier;
|
|
543
543
|
if (/^(?:https?:|blob:|data:)/.test(specifier)) return specifier;
|
|
544
|
-
const origin = globalThis.location?.origin ?? 'https://
|
|
544
|
+
const origin = globalThis.location?.origin ?? 'https://volter.invalid';
|
|
545
545
|
const resolved = new URL(specifier, new URL(importer, origin));
|
|
546
546
|
return resolved.origin === origin
|
|
547
547
|
? `${resolved.pathname}${resolved.search}${resolved.hash}`
|
|
@@ -572,7 +572,7 @@ export async function bundleServedModule(
|
|
|
572
572
|
define: PROJECT_MODULE_DEFINES,
|
|
573
573
|
plugins: [
|
|
574
574
|
{
|
|
575
|
-
name: '
|
|
575
|
+
name: 'volter-served-module',
|
|
576
576
|
setup(build) {
|
|
577
577
|
build.onResolve({ filter: /.*/ }, (args) => {
|
|
578
578
|
if (
|
|
@@ -586,9 +586,9 @@ export async function bundleServedModule(
|
|
|
586
586
|
args.kind === 'entry-point'
|
|
587
587
|
? args.path
|
|
588
588
|
: resolveServedModulePath(args.importer, args.path);
|
|
589
|
-
return { path, namespace: '
|
|
589
|
+
return { path, namespace: 'volter-served' };
|
|
590
590
|
});
|
|
591
|
-
build.onLoad({ filter: /.*/, namespace: '
|
|
591
|
+
build.onLoad({ filter: /.*/, namespace: 'volter-served' }, async (args) => {
|
|
592
592
|
const response = await fetchImpl(args.path);
|
|
593
593
|
if (!response.ok) {
|
|
594
594
|
throw new Error(
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
* five callers never had a story in hand (`authoring/canvas-design-mount.ts`,
|
|
18
18
|
* `authoring/pixi-still-presentation.ts`, `components/world-root-stage.ts`,
|
|
19
19
|
* `live-canvas-frame.ts`, and the ingest lane's canvas mount); moving it out is
|
|
20
|
-
* the part of the CSF estate's exit to `@
|
|
20
|
+
* the part of the CSF estate's exit to `@volter/game` (WORK.md §The open-source
|
|
21
21
|
* launch, phase 1 unit 3) that does not wait on anything else.
|
|
22
22
|
*/
|
|
23
23
|
|
|
@@ -56,13 +56,13 @@ function CameraPreview({ previewRef }: CameraAuthoringOverlayProps) {
|
|
|
56
56
|
: 16 / 9;
|
|
57
57
|
|
|
58
58
|
return (
|
|
59
|
-
<div className="
|
|
60
|
-
<Inline className="
|
|
59
|
+
<div className="volter-camera-preview" data-testid="camera-preview">
|
|
60
|
+
<Inline className="volter-camera-preview-header" gap={4} align="center">
|
|
61
61
|
<EditorIcon icon={faCamera} size="sm" />
|
|
62
62
|
<Text
|
|
63
63
|
variant="label"
|
|
64
64
|
truncate
|
|
65
|
-
className="
|
|
65
|
+
className="volter-camera-preview-title"
|
|
66
66
|
title={`${preview.name} · ${preview.lens}`}
|
|
67
67
|
>
|
|
68
68
|
{preview.name}
|
|
@@ -77,7 +77,7 @@ function CameraPreview({ previewRef }: CameraAuthoringOverlayProps) {
|
|
|
77
77
|
<EditorIcon icon={faThumbtack} size="sm" />
|
|
78
78
|
</IconButton>
|
|
79
79
|
</Tooltip>
|
|
80
|
-
<div ref={menuRef} className="
|
|
80
|
+
<div ref={menuRef} className="volter-camera-preview-menu-anchor">
|
|
81
81
|
<Tooltip text="Camera actions">
|
|
82
82
|
<IconButton
|
|
83
83
|
aria-label="Camera actions"
|
|
@@ -89,7 +89,7 @@ function CameraPreview({ previewRef }: CameraAuthoringOverlayProps) {
|
|
|
89
89
|
</IconButton>
|
|
90
90
|
</Tooltip>
|
|
91
91
|
{menuOpen ? (
|
|
92
|
-
<EditorPopover className="
|
|
92
|
+
<EditorPopover className="volter-camera-preview-menu">
|
|
93
93
|
<Button
|
|
94
94
|
variant="ghost"
|
|
95
95
|
size="compact"
|
|
@@ -130,7 +130,7 @@ function CameraPreview({ previewRef }: CameraAuthoringOverlayProps) {
|
|
|
130
130
|
</Inline>
|
|
131
131
|
<div
|
|
132
132
|
ref={previewRef}
|
|
133
|
-
className="
|
|
133
|
+
className="volter-camera-preview-image"
|
|
134
134
|
style={{ aspectRatio: String(Math.max(0.5, Math.min(3, aspect))) }}
|
|
135
135
|
role="img"
|
|
136
136
|
aria-label={`Live view through ${preview.name}`}
|
|
@@ -142,13 +142,13 @@ function CameraPreview({ previewRef }: CameraAuthoringOverlayProps) {
|
|
|
142
142
|
|
|
143
143
|
function CompositionGuides() {
|
|
144
144
|
return (
|
|
145
|
-
<div className="
|
|
146
|
-
<div className="
|
|
147
|
-
<div className="
|
|
148
|
-
<div className="
|
|
149
|
-
<div className="
|
|
150
|
-
<div className="
|
|
151
|
-
<div className="
|
|
145
|
+
<div className="volter-camera-guides" aria-hidden="true">
|
|
146
|
+
<div className="volter-camera-guide volter-camera-guide-v1" />
|
|
147
|
+
<div className="volter-camera-guide volter-camera-guide-v2" />
|
|
148
|
+
<div className="volter-camera-guide volter-camera-guide-h1" />
|
|
149
|
+
<div className="volter-camera-guide volter-camera-guide-h2" />
|
|
150
|
+
<div className="volter-camera-safe-frame" />
|
|
151
|
+
<div className="volter-camera-center-mark" />
|
|
152
152
|
</div>
|
|
153
153
|
);
|
|
154
154
|
}
|
|
@@ -163,7 +163,7 @@ function CameraViewBanner() {
|
|
|
163
163
|
<EditorSurface
|
|
164
164
|
variant="overlay"
|
|
165
165
|
border
|
|
166
|
-
className="
|
|
166
|
+
className="volter-camera-view-banner volter-chrome-island volter-glass-island"
|
|
167
167
|
data-testid="camera-view-banner"
|
|
168
168
|
>
|
|
169
169
|
<Inline gap={6} align="center">
|