@volter/editor-game 0.5.67 → 0.5.68
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/contributions/build-player.document.tsx +95 -0
- package/package.json +8 -7
- package/src/build/BuildProfilesPanel.tsx +11 -0
- package/src/build/build-session.ts +35 -0
- package/src/canvas/canvas-board/CanvasBoardDocument.tsx +76 -3
- package/src/host/authoring/pixi-authoring-adapter.ts +148 -26
- package/src/host/authoring/pixi-creatable-kinds.ts +62 -0
- package/src/host/authoring/pixi-source-write-target.ts +114 -19
- package/src/host/components/PixiIsolationSceneContent.tsx +1 -1
- package/src/host/roots/canvas-root.tsx +12 -0
- package/src/host/served-bundle-runtime-modules.ts +2 -0
- package/src/ingest/ingest-canvas-scene-document.tsx +1 -1
- package/src/network/NetworkInspectorPanel.tsx +519 -52
- package/src/network/network-inspector-model.ts +6 -0
- package/src/runtime/create-runtime.ts +1 -1
- package/src/runtime/input-router.ts +24 -11
- package/src/services/game-network.ts +121 -11
|
@@ -0,0 +1,62 @@
|
|
|
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
|
+
];
|
|
@@ -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,13 +114,14 @@ 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" />',
|
|
115
122
|
};
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
sprite: 'Sprite',
|
|
119
|
-
text: 'Text',
|
|
120
|
-
graphics: 'Graphics',
|
|
121
|
-
};
|
|
123
|
+
/** A kind whose snippet names Pixi's `Texture`, which the file must then import. */
|
|
124
|
+
const TEXTURE_SNIPPET_KINDS = new Set(['animatedSprite', 'tilingSprite', 'nineSliceSprite']);
|
|
122
125
|
|
|
123
126
|
function isHiddenProp(name: string): boolean {
|
|
124
127
|
return HIDDEN_PROPS.has(name) || /^on[A-Z]/.test(name) || name.startsWith('__vgai');
|
|
@@ -217,6 +220,10 @@ export function createSourceCanvasWriteTarget(
|
|
|
217
220
|
const dynamicPaths = new Set<string>();
|
|
218
221
|
/** Pre-gesture 2D poses, one per id currently under a drag. */
|
|
219
222
|
const editStarts = new Map<string, Transform2DValue>();
|
|
223
|
+
const pendingOrigins = new Map<
|
|
224
|
+
string,
|
|
225
|
+
{ kind: 'pivot' | 'anchor'; value: readonly [number, number]; before: readonly [number, number] }
|
|
226
|
+
>();
|
|
220
227
|
let commitQueue = Promise.resolve();
|
|
221
228
|
|
|
222
229
|
const oidOf = (id: string): string | null => {
|
|
@@ -464,10 +471,16 @@ export function createSourceCanvasWriteTarget(
|
|
|
464
471
|
const channels = (['position', 'rotation', 'scale'] as const).filter((channel) =>
|
|
465
472
|
transform2DChanged(channel, before, after),
|
|
466
473
|
);
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
474
|
+
// A moved origin (pivot or anchor) is written with the position that compensates it.
|
|
475
|
+
const origin = pendingOrigins.get(id);
|
|
476
|
+
pendingOrigins.delete(id);
|
|
477
|
+
if (channels.length === 0 && !origin) return false;
|
|
478
|
+
|
|
479
|
+
const revert = (channel: TransformChannel | 'origin'): void => {
|
|
480
|
+
if (channel === 'origin') {
|
|
481
|
+
const display = a2d.displayObject(id) as (Container & Record<string, unknown>) | null;
|
|
482
|
+
if (display && origin) display[origin.kind] = { x: origin.before[0], y: origin.before[1] };
|
|
483
|
+
} else if (channel === 'position') a2d.setTransform(id, { position: before.position });
|
|
471
484
|
else if (channel === 'rotation') a2d.setTransform(id, { rotation: before.rotation });
|
|
472
485
|
else a2d.setTransform(id, { scale: before.scale });
|
|
473
486
|
notify();
|
|
@@ -495,7 +508,7 @@ export function createSourceCanvasWriteTarget(
|
|
|
495
508
|
const instance = componentIdentityOf(id);
|
|
496
509
|
const channelBlocked = (channel: TransformChannel): boolean =>
|
|
497
510
|
componentOwnedChannel(id, channel, attrs, after);
|
|
498
|
-
const writes: Array<{ channel: TransformChannel; prop: string; value: string }> = [];
|
|
511
|
+
const writes: Array<{ channel: TransformChannel | 'origin'; prop: string; value: string }> = [];
|
|
499
512
|
for (const channel of channels) {
|
|
500
513
|
if (channelBlocked(channel)) {
|
|
501
514
|
dynamicPaths.add(`${id}|${channel}`);
|
|
@@ -532,6 +545,16 @@ export function createSourceCanvasWriteTarget(
|
|
|
532
545
|
}
|
|
533
546
|
for (const write of plan.writes) writes.push({ channel, ...write });
|
|
534
547
|
}
|
|
548
|
+
if (origin) {
|
|
549
|
+
if (instance && channelBlocked('position')) revert('origin');
|
|
550
|
+
else {
|
|
551
|
+
writes.push({
|
|
552
|
+
channel: 'origin',
|
|
553
|
+
prop: origin.kind,
|
|
554
|
+
value: `{ x: ${formatSourceNumber(origin.value[0])}, y: ${formatSourceNumber(origin.value[1])} }`,
|
|
555
|
+
});
|
|
556
|
+
}
|
|
557
|
+
}
|
|
535
558
|
if (writes.length === 0) return false;
|
|
536
559
|
|
|
537
560
|
let persisted = false;
|
|
@@ -543,7 +566,8 @@ export function createSourceCanvasWriteTarget(
|
|
|
543
566
|
// must not need a trip to the text editor.
|
|
544
567
|
addIfMissing: true,
|
|
545
568
|
// `scale={1.5}` becomes `scale={{ x, y }}` when a gesture makes it non-uniform.
|
|
546
|
-
|
|
569
|
+
// The same for an origin authored as one number (`pivot={60}`).
|
|
570
|
+
...(write.prop === 'scale' || write.channel === 'origin' ? { allowShapeUpgrade: true } : {}),
|
|
547
571
|
});
|
|
548
572
|
if (res.changed) {
|
|
549
573
|
persisted = true;
|
|
@@ -595,6 +619,38 @@ export function createSourceCanvasWriteTarget(
|
|
|
595
619
|
|
|
596
620
|
/** One prop edit. Answers whether a byte reached the entry TSX; every
|
|
597
621
|
* refusal has already named itself on the console before it returns. */
|
|
622
|
+
/** Godot's Node2D Skew, one axis in degrees: Pixi's `skew` point in radians, written whole. */
|
|
623
|
+
const writeSkew = async (id: string, axis: 'x' | 'y', degrees: number): Promise<boolean> => {
|
|
624
|
+
const display = a2d.displayObject(id);
|
|
625
|
+
const oid = oidOf(id);
|
|
626
|
+
if (!display || !oid || !backend?.writeProp || !Number.isFinite(degrees)) return false;
|
|
627
|
+
const before = { x: display.skew.x, y: display.skew.y };
|
|
628
|
+
const radians = (degrees * Math.PI) / 180;
|
|
629
|
+
const next = { x: axis === 'x' ? radians : before.x, y: axis === 'y' ? radians : before.y };
|
|
630
|
+
display.skew.set(next.x, next.y);
|
|
631
|
+
notify();
|
|
632
|
+
const res = await backend.writeProp(
|
|
633
|
+
oid,
|
|
634
|
+
'skew',
|
|
635
|
+
// Radians to six places, so a typed angle reads back as typed (four places lose 0.003°).
|
|
636
|
+
`{ x: ${String(Number(next.x.toFixed(6)))}, y: ${String(Number(next.y.toFixed(6)))} }`,
|
|
637
|
+
{ addIfMissing: true, allowShapeUpgrade: true },
|
|
638
|
+
);
|
|
639
|
+
if (!res.changed && (res.dynamic || res.error)) {
|
|
640
|
+
display.skew.set(before.x, before.y);
|
|
641
|
+
notify();
|
|
642
|
+
// biome-ignore lint/suspicious/noConsole: a refused write must say why
|
|
643
|
+
console.warn(
|
|
644
|
+
`[canvas-source ${entryPath}] skew write refused for oid "${oid}": ` +
|
|
645
|
+
`${res.dynamic ? 'dynamic expression (guarded)' : res.error} — reverting.`,
|
|
646
|
+
);
|
|
647
|
+
return false;
|
|
648
|
+
}
|
|
649
|
+
await refreshSourceState();
|
|
650
|
+
notify();
|
|
651
|
+
return res.changed;
|
|
652
|
+
};
|
|
653
|
+
|
|
598
654
|
const writeJsxProp = async (
|
|
599
655
|
id: string,
|
|
600
656
|
path: string,
|
|
@@ -1130,12 +1186,20 @@ export function createSourceCanvasWriteTarget(
|
|
|
1130
1186
|
// The new element's oid is minted server-side on the remount, so the id
|
|
1131
1187
|
// half is '' and the ack half is this creation's own piped write — handed
|
|
1132
1188
|
// back rather than fired `void` (see `StructuralIdWrite`).
|
|
1133
|
-
create: (kind, parentId) => {
|
|
1134
|
-
|
|
1189
|
+
create: (kind, parentId, at) => {
|
|
1190
|
+
// `at` arrives in the new node's parent's own space (the adapter converts it).
|
|
1191
|
+
const base = CREATE_SNIPPETS[kind];
|
|
1192
|
+
const snippet =
|
|
1193
|
+
base && at
|
|
1194
|
+
? base.replace(/\s*\/>$/, ` x={${formatSourceNumber(at.x)}} y={${formatSourceNumber(at.y)}} />`)
|
|
1195
|
+
: base;
|
|
1135
1196
|
// Nothing was attempted (the palette is empty in both cases), so there is
|
|
1136
1197
|
// no write to ack.
|
|
1137
1198
|
if (!snippet || !backend?.writeStruct) return { id: '', ack: undefined };
|
|
1138
|
-
|
|
1199
|
+
const options = TEXTURE_SNIPPET_KINDS.has(kind)
|
|
1200
|
+
? { snippet, ensureImport: { name: 'Texture', module: 'pixi.js' } }
|
|
1201
|
+
: { snippet };
|
|
1202
|
+
if (parentId) return { id: '', ack: structOp(parentId, 'create', options) };
|
|
1139
1203
|
const lastRoot = a2d.roots().at(-1);
|
|
1140
1204
|
if (!lastRoot) {
|
|
1141
1205
|
return {
|
|
@@ -1145,15 +1209,14 @@ export function createSourceCanvasWriteTarget(
|
|
|
1145
1209
|
),
|
|
1146
1210
|
};
|
|
1147
1211
|
}
|
|
1148
|
-
return { id: '', ack: structOp(lastRoot.id, 'create-sibling',
|
|
1212
|
+
return { id: '', ack: structOp(lastRoot.id, 'create-sibling', options) };
|
|
1149
1213
|
},
|
|
1150
1214
|
creatableKinds: (parentId) => {
|
|
1151
1215
|
if (!backend?.writeStruct) return [];
|
|
1152
1216
|
if (parentId && !oidOf(parentId)) return [];
|
|
1153
1217
|
if (!parentId && a2d.roots().length === 0) return [];
|
|
1154
|
-
return
|
|
1155
|
-
|
|
1156
|
-
label: CREATE_LABELS[kind] ?? kind,
|
|
1218
|
+
return PIXI_CREATABLE_KINDS.filter((entry) => CREATE_SNIPPETS[entry.kind] !== undefined).map((entry) => ({
|
|
1219
|
+
...entry,
|
|
1157
1220
|
}));
|
|
1158
1221
|
},
|
|
1159
1222
|
// Returns the promise (never `void structOp(…)`): `deleteSelection`
|
|
@@ -1469,6 +1532,21 @@ export function createSourceCanvasWriteTarget(
|
|
|
1469
1532
|
if (!editStarts.has(id)) editStarts.set(id, read2D(id));
|
|
1470
1533
|
},
|
|
1471
1534
|
|
|
1535
|
+
/**
|
|
1536
|
+
* Mid-gesture origin write — see {@link CanvasWriteTarget.writeOrigin}. The live object moves
|
|
1537
|
+
* now; the `pivot`/`anchor` prop is written at `endTransformEdit`, in the same gesture as the
|
|
1538
|
+
* position that compensates it.
|
|
1539
|
+
*/
|
|
1540
|
+
writeOrigin(id: string, kind: 'pivot' | 'anchor', value: readonly [number, number]): void {
|
|
1541
|
+
const display = a2d.displayObject(id) as (Container & Record<string, unknown>) | null;
|
|
1542
|
+
const current = display?.[kind] as { x?: unknown; y?: unknown } | undefined;
|
|
1543
|
+
if (!display || typeof current?.x !== 'number' || typeof current.y !== 'number') return;
|
|
1544
|
+
const before = pendingOrigins.get(id)?.before ?? ([current.x, current.y] as const);
|
|
1545
|
+
display[kind] = { x: value[0], y: value[1] };
|
|
1546
|
+
pendingOrigins.set(id, { kind, value, before });
|
|
1547
|
+
notify();
|
|
1548
|
+
},
|
|
1549
|
+
|
|
1472
1550
|
writeTransform(id: string, next: Transform2DValue): void {
|
|
1473
1551
|
// The live tree moves first so the paused frame follows the gesture; the
|
|
1474
1552
|
// source write happens once, at the end.
|
|
@@ -1535,6 +1613,13 @@ export function createSourceCanvasWriteTarget(
|
|
|
1535
1613
|
{ path: 'visible', label: 'Visible', type: 'boolean', group: 'Visibility' },
|
|
1536
1614
|
];
|
|
1537
1615
|
const attrs = attrsOf(id) ?? [];
|
|
1616
|
+
// Godot's Node2D Skew, in its Transform group; a skew the source computes is shown, not edited.
|
|
1617
|
+
const skewAttr = attrs.find((attr) => attr.name === 'skew');
|
|
1618
|
+
const skewFixed = skewAttr ? !skewAttr.isLiteral : false;
|
|
1619
|
+
props.push(
|
|
1620
|
+
{ path: 'skew.x', label: 'Skew X (deg)', type: 'number', group: 'Transform', readonly: skewFixed },
|
|
1621
|
+
{ path: 'skew.y', label: 'Skew Y (deg)', type: 'number', group: 'Transform', readonly: skewFixed },
|
|
1622
|
+
);
|
|
1538
1623
|
const declared = declaredPropsOf(id);
|
|
1539
1624
|
const attrByName = new Map(attrs.map((attr) => [attr.name, attr]));
|
|
1540
1625
|
const names = [
|
|
@@ -1573,6 +1658,9 @@ export function createSourceCanvasWriteTarget(
|
|
|
1573
1658
|
if (echoed !== undefined) return echoed;
|
|
1574
1659
|
if (path === 'name') return display.label ?? '';
|
|
1575
1660
|
if (path === 'visible') return display.visible;
|
|
1661
|
+
if (path === 'skew.x' || path === 'skew.y') {
|
|
1662
|
+
return Number((((path === 'skew.x' ? display.skew.x : display.skew.y) * 180) / Math.PI).toFixed(4));
|
|
1663
|
+
}
|
|
1576
1664
|
if (!path.startsWith('jsx.')) return undefined;
|
|
1577
1665
|
return propValue(attrsOf(id), declaredPropsOf(id), path.slice(4));
|
|
1578
1666
|
},
|
|
@@ -1581,6 +1669,13 @@ export function createSourceCanvasWriteTarget(
|
|
|
1581
1669
|
// edit, awaited. A path this target owns no prop for performed no write and
|
|
1582
1670
|
// returns nothing.
|
|
1583
1671
|
set(id: string, path: string, value: unknown): void | Promise<WriteAck> {
|
|
1672
|
+
if (path === 'skew.x' || path === 'skew.y') {
|
|
1673
|
+
return piped(
|
|
1674
|
+
() => writeSkew(id, path === 'skew.x' ? 'x' : 'y', Number(value)),
|
|
1675
|
+
undefined,
|
|
1676
|
+
destinationOf(id),
|
|
1677
|
+
);
|
|
1678
|
+
}
|
|
1584
1679
|
const prop =
|
|
1585
1680
|
path === 'name'
|
|
1586
1681
|
? 'label'
|
|
@@ -197,7 +197,7 @@ export function PixiIsolationSceneContent({
|
|
|
197
197
|
}}
|
|
198
198
|
>
|
|
199
199
|
<CanvasSceneBackdrop view={view} documentId={documentId} />
|
|
200
|
-
{adapter ? <RootSelectionOverlay adapter={adapter} view={view} transformModeAware /> : null}
|
|
200
|
+
{adapter ? <RootSelectionOverlay adapter={adapter} view={view} transformModeAware presentationId={documentId} /> : null}
|
|
201
201
|
<CanvasSceneControls
|
|
202
202
|
active={active}
|
|
203
203
|
{...(adapter ? { adapter } : {})}
|
|
@@ -307,6 +307,18 @@ function canvasWorldAdapter(
|
|
|
307
307
|
// the host only gates/advances it.
|
|
308
308
|
mountedApp.ticker.update(elapsedMs);
|
|
309
309
|
},
|
|
310
|
+
// Pixi's own hit test finds only interactive objects (`eventMode` static or dynamic), so a
|
|
311
|
+
// HUD claims its buttons and lets every other point fall through to the world beneath.
|
|
312
|
+
hitTest(x: number, y: number): boolean {
|
|
313
|
+
if (disposed) return false;
|
|
314
|
+
// Pixi points its boundary at the last rendered scene inside each of its own pointer
|
|
315
|
+
// handlers; a hit test asked from outside them does the same, or finds no root.
|
|
316
|
+
const boundary = mountedApp.renderer.events.rootBoundary;
|
|
317
|
+
const rendered = mountedApp.renderer.lastObjectRendered;
|
|
318
|
+
if (!rendered) return false;
|
|
319
|
+
boundary.rootTarget = rendered;
|
|
320
|
+
return boundary.hitTest(x, y) !== null;
|
|
321
|
+
},
|
|
310
322
|
resize(width: number, height: number): void {
|
|
311
323
|
if (disposed) return;
|
|
312
324
|
// `autoDensity: true` makes Pixi re-stamp `canvas.style.width`/
|
|
@@ -172,6 +172,8 @@ export const BUNDLE_RUNTIME_MODULE_LOADERS: Record<string, () => Promise<unknown
|
|
|
172
172
|
// against the same sweep.
|
|
173
173
|
'@colyseus/sdk': () => import('@colyseus/sdk'), // template main.ts
|
|
174
174
|
'react-dom': () => import('react-dom'), // template main.ts
|
|
175
|
+
'pixi.js': () => import('pixi.js'), // template main.ts (a `canvas` root)
|
|
176
|
+
'@pixi/react': () => import('@pixi/react'), // template main.ts (a `canvas` root)
|
|
175
177
|
// ---- Editor-citizen modules PROJECT TOOL CONTRIBUTIONS import. A
|
|
176
178
|
// contribution is project source rendered inside the editor's own panels,
|
|
177
179
|
// so these must resolve to the editor's LIVE modules — a second copy of the
|
|
@@ -148,7 +148,7 @@ export function IngestCanvasSceneContent({
|
|
|
148
148
|
}}
|
|
149
149
|
>
|
|
150
150
|
<CanvasSceneBackdrop view={view} documentId={documentId} />
|
|
151
|
-
<RootSelectionOverlay adapter={adapter} view={view} transformModeAware />
|
|
151
|
+
<RootSelectionOverlay adapter={adapter} view={view} transformModeAware presentationId={documentId} />
|
|
152
152
|
<SurfaceStateOverlay
|
|
153
153
|
explanation={surfaceExplanation}
|
|
154
154
|
testId="ingest-canvas-surface-status"
|