@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
|
@@ -116,7 +116,7 @@ export const CAPTURE_GAP: CapabilityGap = {
|
|
|
116
116
|
* it as a false alarm and stops trusting the instrument, which is the exact
|
|
117
117
|
* failure this module's header forbids.
|
|
118
118
|
*
|
|
119
|
-
* MEASURED 2026-09-19 on a `--template game` scaffold, standalone `
|
|
119
|
+
* MEASURED 2026-09-19 on a `--template game` scaffold, standalone `volter-game-editor edit`
|
|
120
120
|
* (it is NOT frame-specific): `hierarchy.roots()` passed, ten `hierarchy
|
|
121
121
|
* .node()` calls passed, and the walk still failed — three of the default
|
|
122
122
|
* scene's nodes report a `parentId` their listed parent does not claim. What
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Installs the loaded `
|
|
2
|
+
* Installs the loaded `volter.adapter.ts` runtime bindings onto one mounted
|
|
3
3
|
* Game. Game modules stay native: adapter closures close over app-owned stores
|
|
4
4
|
* and functions, while this host-side file projects them onto the existing
|
|
5
5
|
* session command/state/input doors.
|
|
@@ -188,7 +188,7 @@ function installObservations(
|
|
|
188
188
|
function actionMap(binding: AdapterInputBinding, game: Game): Map<string, AdapterInputValueType> {
|
|
189
189
|
const actions = binding.actions(game);
|
|
190
190
|
if (!Array.isArray(actions)) {
|
|
191
|
-
throw new Error('
|
|
191
|
+
throw new Error('volter.adapter.ts input.actions must return an array.');
|
|
192
192
|
}
|
|
193
193
|
const out = new Map<string, AdapterInputValueType>();
|
|
194
194
|
for (const candidate of actions as readonly Partial<AdapterInputAction>[]) {
|
|
@@ -198,13 +198,13 @@ function actionMap(binding: AdapterInputBinding, game: Game): Map<string, Adapte
|
|
|
198
198
|
!ADAPTER_INPUT_VALUE_TYPES.includes(candidate.valueType as AdapterInputValueType)
|
|
199
199
|
) {
|
|
200
200
|
throw new Error(
|
|
201
|
-
'
|
|
201
|
+
'volter.adapter.ts input.actions returned an invalid action; each action needs a non-empty ' +
|
|
202
202
|
'`name` and a supported `valueType`.',
|
|
203
203
|
);
|
|
204
204
|
}
|
|
205
205
|
if (out.has(candidate.name)) {
|
|
206
206
|
throw new Error(
|
|
207
|
-
`
|
|
207
|
+
`volter.adapter.ts input.actions returned duplicate action "${candidate.name}".`,
|
|
208
208
|
);
|
|
209
209
|
}
|
|
210
210
|
out.set(candidate.name, candidate.valueType as AdapterInputValueType);
|
|
@@ -228,7 +228,7 @@ function valueMatches(type: AdapterInputValueType, value: AdapterInputValue): bo
|
|
|
228
228
|
function installInput(game: Game, registry: DebugRegistry, binding: AdapterInputBinding): void {
|
|
229
229
|
if (!game.world(binding.root)) {
|
|
230
230
|
throw new Error(
|
|
231
|
-
`
|
|
231
|
+
`volter.adapter.ts input.root names "${binding.root}", which is not a mounted manifest root.`,
|
|
232
232
|
);
|
|
233
233
|
}
|
|
234
234
|
|
package/src/host/after-paint.ts
CHANGED
|
@@ -4,13 +4,13 @@
|
|
|
4
4
|
* READ THIS BEFORE WRITING `requestAnimationFrame` INTO A BOOT PATH.
|
|
5
5
|
* **rAF does not fire in a hidden tab, ever** — not throttled, not late:
|
|
6
6
|
* zero callbacks until the tab is foregrounded. And "hidden at boot" is the
|
|
7
|
-
* ORDINARY case for this editor, not an edge: every `
|
|
7
|
+
* ORDINARY case for this editor, not an edge: every `volter-game-editor edit` session rooted
|
|
8
8
|
* in an agent worktree opens its one tab behind the human's window
|
|
9
|
-
* (`server/open-browser.ts` → `open -g`), and any `
|
|
9
|
+
* (`server/open-browser.ts` → `open -g`), and any `volter-game-editor edit` that fires while
|
|
10
10
|
* the browser is not frontmost lands the same way. Measured twice in one hour:
|
|
11
11
|
* an editor whose shell mount sat behind a bare double-rAF heartbeat for 20
|
|
12
12
|
* minutes with the relay answering, `document.visibilityState: "hidden"`, and
|
|
13
|
-
* zero console errors, while every `
|
|
13
|
+
* zero console errors, while every `volter-game-editor play` refused with "the editor shell
|
|
14
14
|
* is not bound yet".
|
|
15
15
|
*
|
|
16
16
|
* A paint deferral is only ever an OPTIMISATION — let the committed chrome
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The client half of `/__editor/configurations` (`server/routes/run.ts`): the project's
|
|
3
3
|
* declared run configurations and their runtime status, and the start/stop
|
|
4
|
-
* verbs the transport's picker
|
|
4
|
+
* verbs the transport's picker uses.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import { assertEditorServerResponse, editorServerJson } from '@volter/editor-sdk/kit/editor-server-response';
|
|
@@ -22,7 +22,7 @@ export interface PhaserRealmLike {
|
|
|
22
22
|
export interface BabylonRealmLike {
|
|
23
23
|
BABYLON?: { Engine?: { Instances?: BabylonEngineLike[] } };
|
|
24
24
|
/** Host build seam for module-bundled Babylon, where no BABYLON global exists. */
|
|
25
|
-
|
|
25
|
+
__volterBabylon?: {
|
|
26
26
|
engines?: BabylonEngineLike[];
|
|
27
27
|
/** Optional boot promise published by a host-built classic bundle. */
|
|
28
28
|
ready?: PromiseLike<unknown>;
|
|
@@ -39,7 +39,7 @@ export function findPhaserGame(realm: PhaserRealmLike | null): PhaserGameLike |
|
|
|
39
39
|
|
|
40
40
|
/** Recognize Babylon from its public engine registry or the module-build host seam. */
|
|
41
41
|
export function findBabylonEngine(realm: BabylonRealmLike | null): BabylonEngineLike | null {
|
|
42
|
-
const bridged = realm?.
|
|
42
|
+
const bridged = realm?.__volterBabylon?.engines;
|
|
43
43
|
const engines = Array.isArray(bridged) ? bridged : realm?.BABYLON?.Engine?.Instances;
|
|
44
44
|
if (!Array.isArray(engines)) return null;
|
|
45
45
|
return (
|
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
|
|
9
9
|
import type { AuthoringAdapter, EditorNode, HierarchyProvider } from '@volter/editor-project/adapter';
|
|
10
10
|
import type {
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
VolterGameHierarchyGroup,
|
|
12
|
+
VolterGameHierarchyProvider,
|
|
13
13
|
} from '@volter/editor-project/adapter/ingest/game-contract';
|
|
14
14
|
import { editorConsole } from '@volter/editor-sdk/kit/editor-console';
|
|
15
15
|
|
|
@@ -20,7 +20,7 @@ function groupNodeId(id: string): string {
|
|
|
20
20
|
return `${GROUP_PREFIX}${encodeURIComponent(id)}`;
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
function groupNode(group:
|
|
23
|
+
function groupNode(group: VolterGameHierarchyGroup): EditorNode {
|
|
24
24
|
return {
|
|
25
25
|
id: groupNodeId(group.id),
|
|
26
26
|
label: group.label,
|
|
@@ -32,7 +32,7 @@ function groupNode(group: VgaiGameHierarchyGroup): EditorNode {
|
|
|
32
32
|
};
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
-
function assertGroups(value: unknown):
|
|
35
|
+
function assertGroups(value: unknown): VolterGameHierarchyGroup[] {
|
|
36
36
|
if (!Array.isArray(value)) throw new Error('hierarchy() did not return an array');
|
|
37
37
|
const ids = new Set<string>();
|
|
38
38
|
for (const group of value) {
|
|
@@ -58,12 +58,12 @@ function assertGroups(value: unknown): VgaiGameHierarchyGroup[] {
|
|
|
58
58
|
/** Identity-preserving when the game declares no hierarchy surface. */
|
|
59
59
|
export function withContractHierarchy(
|
|
60
60
|
base: AuthoringAdapter,
|
|
61
|
-
provider:
|
|
61
|
+
provider: VolterGameHierarchyProvider | undefined,
|
|
62
62
|
): AuthoringAdapter {
|
|
63
63
|
if (!provider) return base;
|
|
64
64
|
|
|
65
65
|
let warned = false;
|
|
66
|
-
const groups = ():
|
|
66
|
+
const groups = (): VolterGameHierarchyGroup[] => {
|
|
67
67
|
try {
|
|
68
68
|
return assertGroups(provider());
|
|
69
69
|
} catch (error) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Projects an ingested game's declared SCENES (`window.
|
|
2
|
+
* Projects an ingested game's declared SCENES (`window.volterGame.scenes`, see
|
|
3
3
|
* `@volter/editor-project/adapter/ingest/game-contract`) onto the ordinary
|
|
4
4
|
* {@link StoriesProvider} the editor's story picker already reads — the sibling
|
|
5
5
|
* of `contract-hierarchy-authoring.ts`, which does the same job for the
|
|
@@ -29,11 +29,11 @@ import type {
|
|
|
29
29
|
LiveSceneTable,
|
|
30
30
|
} from '@volter/editor-sdk/host';
|
|
31
31
|
import type { StoriesProvider, StoryRef } from '@volter/editor-project/adapter';
|
|
32
|
-
import type {
|
|
32
|
+
import type { VolterGameContract, VolterGameScene } from '@volter/editor-project/adapter/ingest/game-contract';
|
|
33
33
|
import { readGameScenes } from '@volter/editor-project/adapter/ingest/game-contract';
|
|
34
34
|
import { editorConsole } from '@volter/editor-sdk/kit/editor-console';
|
|
35
35
|
|
|
36
|
-
function assertScenes(value: unknown):
|
|
36
|
+
function assertScenes(value: unknown): VolterGameScene[] {
|
|
37
37
|
if (!Array.isArray(value)) throw new Error('scenes.list() did not return an array');
|
|
38
38
|
const ids = new Set<string>();
|
|
39
39
|
for (const scene of value) {
|
|
@@ -111,7 +111,7 @@ export interface ContractScenesSeams {
|
|
|
111
111
|
* bound: `readGameScenes` owns that shape check.
|
|
112
112
|
*/
|
|
113
113
|
export function createContractScenesStories(
|
|
114
|
-
contract:
|
|
114
|
+
contract: VolterGameContract | null | undefined,
|
|
115
115
|
seams: ContractScenesSeams = {},
|
|
116
116
|
): ContractScenesStories | null {
|
|
117
117
|
const { scenes, malformed } = readGameScenes(contract);
|
|
@@ -121,7 +121,7 @@ export function createContractScenesStories(
|
|
|
121
121
|
if (!scenes) return null;
|
|
122
122
|
|
|
123
123
|
let warned = false;
|
|
124
|
-
const list = ():
|
|
124
|
+
const list = (): VolterGameScene[] => {
|
|
125
125
|
try {
|
|
126
126
|
return assertScenes(scenes.list());
|
|
127
127
|
} catch (error) {
|
|
@@ -53,7 +53,7 @@ import type { ChannelValue } from '@volter/editor-sdk/kit/creation-site-edit';
|
|
|
53
53
|
* that carries none: that object is unanchored, with its reason, and its edits
|
|
54
54
|
* stay live-only (the anti-shim rule).
|
|
55
55
|
*/
|
|
56
|
-
export const DATA_RECORD_KEY = '
|
|
56
|
+
export const DATA_RECORD_KEY = 'volterRecordIndex';
|
|
57
57
|
|
|
58
58
|
/** The record index a live object declares, or `null` when it declares none. */
|
|
59
59
|
export function dataRecordIndexOf(object: unknown): number | null {
|
|
@@ -772,7 +772,7 @@ export class IngestSourcePersistence {
|
|
|
772
772
|
const history = this.history!;
|
|
773
773
|
const descriptor = history.registry.registerProject(
|
|
774
774
|
'overlay',
|
|
775
|
-
'.
|
|
775
|
+
'.volter/ingest-live-objects',
|
|
776
776
|
'Ingested game objects',
|
|
777
777
|
);
|
|
778
778
|
const driver: ResourceDriver = {
|
|
@@ -813,7 +813,7 @@ export class IngestSourcePersistence {
|
|
|
813
813
|
/**
|
|
814
814
|
* Report a refusal where the user can see it, once per gesture.
|
|
815
815
|
*
|
|
816
|
-
* A WARNING, not a log line, because of where each one goes: `
|
|
816
|
+
* A WARNING, not a log line, because of where each one goes: `volter-game-editor status`
|
|
817
817
|
* surfaces `sessionWarnings` and shows nothing at all for `log`. A refused
|
|
818
818
|
* write was invisible to every door the product has — the user's edit did not
|
|
819
819
|
* reach their file and the only explanation lived in a panel nobody could read
|
|
@@ -148,7 +148,7 @@ export async function mountIsolatedPixiScreen(
|
|
|
148
148
|
preserveDrawingBuffer: true,
|
|
149
149
|
});
|
|
150
150
|
const canvas = app.canvas as HTMLCanvasElement;
|
|
151
|
-
canvas.dataset['
|
|
151
|
+
canvas.dataset['volterCanvasSceneSurface'] = 'true';
|
|
152
152
|
canvas.style.position = 'absolute';
|
|
153
153
|
canvas.style.inset = '0';
|
|
154
154
|
canvas.style.width = '100%';
|
|
@@ -82,8 +82,10 @@ import {
|
|
|
82
82
|
readContainerAuthoringLabel,
|
|
83
83
|
} from './pixi-source-identity';
|
|
84
84
|
import { CanvasStructureHistory } from './pixi-structure-history';
|
|
85
|
+
import { PIXI_CREATABLE_KINDS } from './pixi-creatable-kinds';
|
|
85
86
|
import { fromNeutralTransform, toNeutralTransform } from './pixi-transform-channels';
|
|
86
87
|
import { resolvesLiveOnly, runWritePipe } from '@volter/editor-sdk/kit/write-pipe';
|
|
88
|
+
import { editorHost } from '@volter/editor-sdk/host';
|
|
87
89
|
|
|
88
90
|
/**
|
|
89
91
|
* What a write target is handed at construction — the live view it edits
|
|
@@ -245,7 +247,7 @@ export interface PixiAuthoringOptions {
|
|
|
245
247
|
* `pickable` at all — a stage with no mapped surface cannot answer where a
|
|
246
248
|
* client point landed, and guessing one would fabricate a hit.
|
|
247
249
|
*
|
|
248
|
-
* When the element carries `data-
|
|
250
|
+
* When the element carries `data-volter-stage-width`/`-height`, those are the
|
|
249
251
|
* stage's LOGICAL size and the mapping divides the measured rect by them —
|
|
250
252
|
* which is what lets a design surface presented through the shared pan/zoom
|
|
251
253
|
* camera (a CSS scale) pick correctly. Without them the mapping is 1:1,
|
|
@@ -302,12 +304,19 @@ export interface PixiAuthoringOptions {
|
|
|
302
304
|
* `Texture.WHITE` and a size; a graphics gets a drawn rect): a node the author
|
|
303
305
|
* cannot see is indistinguishable from a create that silently failed.
|
|
304
306
|
*/
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
307
|
+
/** The project-local section holding each canvas world's locked and grouped label paths. */
|
|
308
|
+
const EDIT_LOCKS_SECTION = 'canvasEditLocks';
|
|
309
|
+
type EditLocks = Record<string, { readonly locked: readonly string[]; readonly grouped: readonly string[] }>;
|
|
310
|
+
|
|
311
|
+
function readEditLocks(): EditLocks {
|
|
312
|
+
return editorHost().projectLocalState.read<EditLocks>(EDIT_LOCKS_SECTION) ?? {};
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
function writeEditLocks(world: string, locks: EditLocks[string]): void {
|
|
316
|
+
editorHost().projectLocalState.write(EDIT_LOCKS_SECTION, { ...readEditLocks(), [world]: locks });
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
const CANVAS_CREATABLE_KINDS = PIXI_CREATABLE_KINDS;
|
|
311
320
|
|
|
312
321
|
/** What a canvas world can take from the asset browser: an image becomes a
|
|
313
322
|
* Sprite. A model or an audio file has no display object to become here. */
|
|
@@ -404,6 +413,11 @@ function defaultLabelFor(kind: string): string {
|
|
|
404
413
|
* `pixi` is THIS SURFACE's namespace ({@link PixiAuthoringOptions.pixi}): a
|
|
405
414
|
* node built from another graph's classes is a foreign object in the world's
|
|
406
415
|
* own display list, and the world's renderer is the one that has to draw it. */
|
|
416
|
+
/** A ParticleContainer, from whichever copy of Pixi made it: it adds particles, never children. */
|
|
417
|
+
function isParticleContainer(object: Container | null | undefined): boolean {
|
|
418
|
+
return !!object && typeof (object as { addParticle?: unknown }).addParticle === 'function';
|
|
419
|
+
}
|
|
420
|
+
|
|
407
421
|
function createDisplayObject(pixi: CanvasPixiNamespace, kind: string): Container | null {
|
|
408
422
|
switch (kind) {
|
|
409
423
|
case 'container':
|
|
@@ -417,6 +431,31 @@ function createDisplayObject(pixi: CanvasPixiNamespace, kind: string): Container
|
|
|
417
431
|
return new pixi.Text({ text: 'Text', style: { fill: 0xffffff, fontSize: 24 } });
|
|
418
432
|
case 'graphics':
|
|
419
433
|
return new pixi.Graphics().rect(0, 0, 100, 100).fill(0xffffff);
|
|
434
|
+
case 'animatedSprite': {
|
|
435
|
+
const animated = new pixi.AnimatedSprite([pixi.Texture.WHITE]);
|
|
436
|
+
animated.setSize(64, 64);
|
|
437
|
+
return animated;
|
|
438
|
+
}
|
|
439
|
+
case 'tilingSprite':
|
|
440
|
+
return new pixi.TilingSprite({ texture: pixi.Texture.WHITE, width: 100, height: 100 });
|
|
441
|
+
case 'nineSliceSprite':
|
|
442
|
+
return new pixi.NineSliceSprite({ texture: pixi.Texture.WHITE, width: 100, height: 100 });
|
|
443
|
+
case 'bitmapText':
|
|
444
|
+
return new pixi.BitmapText({ text: 'BitmapText', style: { fill: 0xffffff, fontSize: 24 } });
|
|
445
|
+
case 'htmlText':
|
|
446
|
+
return new pixi.HTMLText({ text: 'HTMLText', style: { fill: 0xffffff, fontSize: 24 } });
|
|
447
|
+
case 'meshPlane': {
|
|
448
|
+
const plane = new pixi.MeshPlane({ texture: pixi.Texture.WHITE });
|
|
449
|
+
plane.setSize(100, 100);
|
|
450
|
+
return plane;
|
|
451
|
+
}
|
|
452
|
+
case 'perspectiveMesh': {
|
|
453
|
+
const mesh = new pixi.PerspectiveMesh({ texture: pixi.Texture.WHITE });
|
|
454
|
+
mesh.setSize(100, 100);
|
|
455
|
+
return mesh;
|
|
456
|
+
}
|
|
457
|
+
case 'particleContainer':
|
|
458
|
+
return new pixi.ParticleContainer();
|
|
420
459
|
default:
|
|
421
460
|
return null;
|
|
422
461
|
}
|
|
@@ -490,6 +529,35 @@ interface CanvasBoxEditSession {
|
|
|
490
529
|
readonly id: string;
|
|
491
530
|
readonly transform: Transform;
|
|
492
531
|
readonly rect: { x: number; y: number; width: number; height: number };
|
|
532
|
+
/** The parent's world matrix's linear part (Pixi's a, b, c, d), when the node has a parent. */
|
|
533
|
+
readonly parentLinear?: { a: number; b: number; c: number; d: number };
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
/**
|
|
537
|
+
* The local turn, in degrees, that turns the node's own x axis by `degrees` on screen. Godot measures
|
|
538
|
+
* a rotate drag in the parent's space; a turn added straight to the local rotation goes backwards
|
|
539
|
+
* under a mirrored parent and by the wrong amount under an unevenly scaled one.
|
|
540
|
+
*/
|
|
541
|
+
function localTurn(session: CanvasBoxEditSession, degrees: number): number {
|
|
542
|
+
const m = session.parentLinear;
|
|
543
|
+
if (!m) return degrees;
|
|
544
|
+
const det = m.a * m.d - m.b * m.c;
|
|
545
|
+
if (Math.abs(det) < 1e-9) return degrees;
|
|
546
|
+
const [, , z, w] = session.transform.rotation;
|
|
547
|
+
const r = 2 * Math.atan2(z, w);
|
|
548
|
+
// The node's x axis on screen, turned by the drag, taken back into the parent's space.
|
|
549
|
+
const vx = m.a * Math.cos(r) + m.c * Math.sin(r);
|
|
550
|
+
const vy = m.b * Math.cos(r) + m.d * Math.sin(r);
|
|
551
|
+
const t = (degrees * Math.PI) / 180;
|
|
552
|
+
const wx = vx * Math.cos(t) - vy * Math.sin(t);
|
|
553
|
+
const wy = vx * Math.sin(t) + vy * Math.cos(t);
|
|
554
|
+
const ux = (m.d * wx - m.c * wy) / det;
|
|
555
|
+
const uy = (-m.b * wx + m.a * wy) / det;
|
|
556
|
+
// A long drag passes half a turn: keep the answer beside the turn's own direction in that space.
|
|
557
|
+
const base = det < 0 ? -t : t;
|
|
558
|
+
const raw = Math.atan2(uy, ux) - r - base;
|
|
559
|
+
const wrapped = Math.atan2(Math.sin(raw), Math.cos(raw));
|
|
560
|
+
return ((base + wrapped) * 180) / Math.PI;
|
|
493
561
|
}
|
|
494
562
|
|
|
495
563
|
function boxPatchChannels(patch: Record<string, number>): TransformChannel[] {
|
|
@@ -538,7 +606,18 @@ function transformForBoxPatch(
|
|
|
538
606
|
scale: [...session.transform.scale],
|
|
539
607
|
};
|
|
540
608
|
const rotate = patch['rotate'];
|
|
541
|
-
if (rotate !== undefined)
|
|
609
|
+
if (rotate !== undefined) {
|
|
610
|
+
let turn = localTurn(session, rotate);
|
|
611
|
+
const step = patch['rotationStep'];
|
|
612
|
+
if (step !== undefined && step > 0) {
|
|
613
|
+
// Configure Snap's absolute step lands the node's own rotation on the step from the offset.
|
|
614
|
+
const [, , z, w] = session.transform.rotation;
|
|
615
|
+
const start = (2 * Math.atan2(z, w) * 180) / Math.PI;
|
|
616
|
+
const offset = patch['rotationOffset'] ?? 0;
|
|
617
|
+
turn = Math.round((start + turn - offset) / step) * step + offset - start;
|
|
618
|
+
}
|
|
619
|
+
next.rotation = rotateTransformZ(session.transform, turn);
|
|
620
|
+
}
|
|
542
621
|
const width = patch['width'];
|
|
543
622
|
if (width !== undefined && session.rect.width > 0) {
|
|
544
623
|
next.scale[0] = session.transform.scale[0] * Math.max(0.0001, width / session.rect.width);
|
|
@@ -669,10 +748,17 @@ export class PixiAuthoringAdapter implements AuthoringAdapter {
|
|
|
669
748
|
| undefined;
|
|
670
749
|
private listeners = new Set<() => void>();
|
|
671
750
|
private boxEditSessions = new Map<string, CanvasBoxEditSession>();
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
*
|
|
675
|
-
|
|
751
|
+
/**
|
|
752
|
+
* Lock (a click passes through the node) and Godot's Group (a click inside a grouped node selects
|
|
753
|
+
* the group), held by each node's label path in this world so they survive the remount a source
|
|
754
|
+
* write causes and the next session. Kept per checkout, as Unity keeps scene pickability per
|
|
755
|
+
* user; Godot writes them into the scene file instead.
|
|
756
|
+
*/
|
|
757
|
+
private lockedPaths = new Set<string>();
|
|
758
|
+
private groupedPaths = new Set<string>();
|
|
759
|
+
private readonly editLockWorld: string;
|
|
760
|
+
/** Each node's label path, cleared whenever the tree is re-projected. */
|
|
761
|
+
private pathKeys = new Map<string, string>();
|
|
676
762
|
|
|
677
763
|
constructor(
|
|
678
764
|
private readonly root: Container,
|
|
@@ -728,8 +814,10 @@ export class PixiAuthoringAdapter implements AuthoringAdapter {
|
|
|
728
814
|
const hit = this.projector.pick(clientX, clientY, (id) => this.isPickLocked(id));
|
|
729
815
|
return hit ? this.groupOf(hit) : hit;
|
|
730
816
|
},
|
|
731
|
-
candidates: (clientX, clientY) =>
|
|
732
|
-
this.projector.candidates(clientX, clientY, (id) =>
|
|
817
|
+
candidates: (clientX, clientY, options) =>
|
|
818
|
+
this.projector.candidates(clientX, clientY, (id) =>
|
|
819
|
+
options?.includeLocked ? false : this.isPickLocked(id),
|
|
820
|
+
),
|
|
733
821
|
};
|
|
734
822
|
this.rects = {
|
|
735
823
|
rect: (id) => this.projector.rect(id),
|
|
@@ -750,7 +838,46 @@ export class PixiAuthoringAdapter implements AuthoringAdapter {
|
|
|
750
838
|
this.target.provenance.source === 'source-code' && this.target.persistence !== undefined,
|
|
751
839
|
};
|
|
752
840
|
this.target.bind({ a2d: this.a2d, store, journal: opts.journal, notify: () => this.notify() });
|
|
753
|
-
this.
|
|
841
|
+
this.editLockWorld = opts.journal.id;
|
|
842
|
+
const saved = readEditLocks()[this.editLockWorld];
|
|
843
|
+
this.lockedPaths = new Set(saved?.locked ?? []);
|
|
844
|
+
this.groupedPaths = new Set(saved?.grouped ?? []);
|
|
845
|
+
const structure = this.target.structure ?? this.liveStructure;
|
|
846
|
+
// A placed creation reaches the structure in the new node's PARENT's own space: the target
|
|
847
|
+
// writes `x`/`y` as authored, and only this adapter knows the frame `rects` answer in.
|
|
848
|
+
// A ParticleContainer holds particles its code adds, never display objects: Pixi's `addChild`
|
|
849
|
+
// on one throws, so nothing is created or moved into it.
|
|
850
|
+
const holdsNoChildren = (parentId: string | null | undefined): boolean =>
|
|
851
|
+
!!parentId && isParticleContainer(this.projector.object(parentId));
|
|
852
|
+
const noChildren = 'a ParticleContainer holds particles added with addParticle, not display objects.';
|
|
853
|
+
this.structure = {
|
|
854
|
+
...structure,
|
|
855
|
+
create: (kind, parentId, at) =>
|
|
856
|
+
holdsNoChildren(parentId)
|
|
857
|
+
? { id: '', ack: this.refuseStructure(noChildren) }
|
|
858
|
+
: structure.create(kind, parentId, at ? this.pointInParent(parentId ?? null, at) : undefined),
|
|
859
|
+
...(structure.reparent
|
|
860
|
+
? {
|
|
861
|
+
reparent: (id: string, newParentId: string | null) =>
|
|
862
|
+
holdsNoChildren(newParentId) ? this.refuseStructure(noChildren) : structure.reparent!(id, newParentId),
|
|
863
|
+
}
|
|
864
|
+
: {}),
|
|
865
|
+
...(structure.paste
|
|
866
|
+
? {
|
|
867
|
+
paste: (parentId: string | null) =>
|
|
868
|
+
holdsNoChildren(parentId) ? this.refuseStructure(noChildren) : structure.paste!(parentId),
|
|
869
|
+
}
|
|
870
|
+
: {}),
|
|
871
|
+
...(structure.canPaste
|
|
872
|
+
? { canPaste: (parentId: string | null) => !holdsNoChildren(parentId) && structure.canPaste!(parentId) }
|
|
873
|
+
: {}),
|
|
874
|
+
...(structure.creatableKinds
|
|
875
|
+
? {
|
|
876
|
+
creatableKinds: (parentId: string | null) =>
|
|
877
|
+
holdsNoChildren(parentId) ? [] : structure.creatableKinds!(parentId),
|
|
878
|
+
}
|
|
879
|
+
: {}),
|
|
880
|
+
};
|
|
754
881
|
this.assetDrop =
|
|
755
882
|
this.target.assetDrop ??
|
|
756
883
|
({
|
|
@@ -851,6 +978,7 @@ export class PixiAuthoringAdapter implements AuthoringAdapter {
|
|
|
851
978
|
queueMicrotask(() => {
|
|
852
979
|
this.structureNotifyQueued = false;
|
|
853
980
|
this.projector.reproject();
|
|
981
|
+
this.pathKeys.clear();
|
|
854
982
|
this.watchStructure(this.root);
|
|
855
983
|
this.target.onReindex();
|
|
856
984
|
this.notify();
|
|
@@ -951,7 +1079,7 @@ export class PixiAuthoringAdapter implements AuthoringAdapter {
|
|
|
951
1079
|
// degrade — a half-answer would be a menu item that silently does nothing.
|
|
952
1080
|
|
|
953
1081
|
private readonly liveStructure: StructureProvider = {
|
|
954
|
-
create: (kind, parentId) => this.createStructuralNode(kind, parentId ?? null),
|
|
1082
|
+
create: (kind, parentId, at) => this.createStructuralNode(kind, parentId ?? null, at),
|
|
955
1083
|
remove: (id) => this.removeStructuralNodes([id]),
|
|
956
1084
|
removeMany: (ids) => this.removeStructuralNodes(ids),
|
|
957
1085
|
duplicate: (id) => this.duplicateStructuralNode(id),
|
|
@@ -967,7 +1095,7 @@ export class PixiAuthoringAdapter implements AuthoringAdapter {
|
|
|
967
1095
|
private containerFor(parentId: string | null): Container | null {
|
|
968
1096
|
if (!parentId) return this.root;
|
|
969
1097
|
const object = this.projector.object(parentId);
|
|
970
|
-
if (!object || typeof object.addChild !== 'function') return null;
|
|
1098
|
+
if (!object || typeof object.addChild !== 'function' || isParticleContainer(object)) return null;
|
|
971
1099
|
return object;
|
|
972
1100
|
}
|
|
973
1101
|
|
|
@@ -1015,6 +1143,7 @@ export class PixiAuthoringAdapter implements AuthoringAdapter {
|
|
|
1015
1143
|
*/
|
|
1016
1144
|
private afterStructuralChange(): void {
|
|
1017
1145
|
this.projector.reproject();
|
|
1146
|
+
this.pathKeys.clear();
|
|
1018
1147
|
this.watchStructure(this.root);
|
|
1019
1148
|
this.target.onReindex();
|
|
1020
1149
|
this.notify();
|
|
@@ -1031,7 +1160,22 @@ export class PixiAuthoringAdapter implements AuthoringAdapter {
|
|
|
1031
1160
|
* same turn. The live-only report is the other half of the answer and rides
|
|
1032
1161
|
* back in `ack` (see `StructuralIdWrite`) instead of being fired `void`.
|
|
1033
1162
|
*/
|
|
1034
|
-
|
|
1163
|
+
/** A point in the `rects` frame, in the space of the container a new child of `parentId` joins. */
|
|
1164
|
+
private pointInParent(
|
|
1165
|
+
parentId: string | null,
|
|
1166
|
+
at: { readonly x: number; readonly y: number },
|
|
1167
|
+
): { x: number; y: number } | undefined {
|
|
1168
|
+
const parent = this.containerFor(parentId);
|
|
1169
|
+
if (!parent || typeof parent.updateLocalTransform !== 'function') return undefined;
|
|
1170
|
+
const local = this.localToRootMatrix(parent).clone().invert().apply({ x: at.x, y: at.y });
|
|
1171
|
+
return { x: local.x, y: local.y };
|
|
1172
|
+
}
|
|
1173
|
+
|
|
1174
|
+
private createStructuralNode(
|
|
1175
|
+
kind: string,
|
|
1176
|
+
parentId: string | null,
|
|
1177
|
+
at?: { readonly x: number; readonly y: number },
|
|
1178
|
+
): StructuralIdWrite {
|
|
1035
1179
|
const parent = this.containerFor(parentId);
|
|
1036
1180
|
if (!parent) {
|
|
1037
1181
|
return {
|
|
@@ -1047,6 +1191,7 @@ export class PixiAuthoringAdapter implements AuthoringAdapter {
|
|
|
1047
1191
|
};
|
|
1048
1192
|
}
|
|
1049
1193
|
node.label = defaultLabelFor(kind);
|
|
1194
|
+
if (at) node.position.set(at.x, at.y);
|
|
1050
1195
|
const label = `Create ${node.label}`;
|
|
1051
1196
|
this.structureHistory.track(parent);
|
|
1052
1197
|
this.structureHistory.track(node);
|
|
@@ -1280,7 +1425,13 @@ export class PixiAuthoringAdapter implements AuthoringAdapter {
|
|
|
1280
1425
|
this.boxEditSessions.delete(id);
|
|
1281
1426
|
return;
|
|
1282
1427
|
}
|
|
1283
|
-
|
|
1428
|
+
const parent = this.projector.object(id)?.parent?.worldTransform;
|
|
1429
|
+
this.boxEditSessions.set(id, {
|
|
1430
|
+
id,
|
|
1431
|
+
rect,
|
|
1432
|
+
transform: this.transforms.get(id),
|
|
1433
|
+
...(parent ? { parentLinear: { a: parent.a, b: parent.b, c: parent.c, d: parent.d } } : {}),
|
|
1434
|
+
});
|
|
1284
1435
|
this.transforms.beginEdit?.(id);
|
|
1285
1436
|
},
|
|
1286
1437
|
apply: (id, patch) => this.applyBoxEdit(id, patch),
|
|
@@ -1514,15 +1665,19 @@ export class PixiAuthoringAdapter implements AuthoringAdapter {
|
|
|
1514
1665
|
],
|
|
1515
1666
|
get: (id, path) =>
|
|
1516
1667
|
path === 'locked'
|
|
1517
|
-
? this.
|
|
1668
|
+
? this.lockedPaths.has(this.pathKeyOf(id))
|
|
1518
1669
|
: path === 'grouped'
|
|
1519
|
-
? this.
|
|
1670
|
+
? this.groupedPaths.has(this.pathKeyOf(id))
|
|
1520
1671
|
: this.target.get(id, path),
|
|
1521
1672
|
set: (id, path, value) => {
|
|
1522
1673
|
if (path === 'locked' || path === 'grouped') {
|
|
1523
|
-
const set = path === 'locked' ? this.
|
|
1524
|
-
if (value === true) set.add(id);
|
|
1525
|
-
else set.delete(id);
|
|
1674
|
+
const set = path === 'locked' ? this.lockedPaths : this.groupedPaths;
|
|
1675
|
+
if (value === true) set.add(this.pathKeyOf(id));
|
|
1676
|
+
else set.delete(this.pathKeyOf(id));
|
|
1677
|
+
writeEditLocks(this.editLockWorld, {
|
|
1678
|
+
locked: [...this.lockedPaths],
|
|
1679
|
+
grouped: [...this.groupedPaths],
|
|
1680
|
+
});
|
|
1526
1681
|
this.notify();
|
|
1527
1682
|
return;
|
|
1528
1683
|
}
|
|
@@ -1532,12 +1687,36 @@ export class PixiAuthoringAdapter implements AuthoringAdapter {
|
|
|
1532
1687
|
remove: (id, path) => this.target.remove?.(id, path),
|
|
1533
1688
|
};
|
|
1534
1689
|
|
|
1690
|
+
/** `id`'s label path from its world's root: each step the authored label and its place among
|
|
1691
|
+
* same-labelled siblings, so it holds across line edits and reloads (a rename drops it). */
|
|
1692
|
+
private pathKeyOf(id: string): string {
|
|
1693
|
+
const known = this.pathKeys.get(id);
|
|
1694
|
+
if (known !== undefined) return known;
|
|
1695
|
+
const labelOf = (nodeId: string): string => this.projector.object(nodeId)?.label ?? '';
|
|
1696
|
+
const segments: string[] = [];
|
|
1697
|
+
let current: string | null = id;
|
|
1698
|
+
while (current) {
|
|
1699
|
+
const node = this.projector.node(current);
|
|
1700
|
+
if (!node) break;
|
|
1701
|
+
const siblings = node.parentId
|
|
1702
|
+
? (this.projector.node(node.parentId)?.childIds ?? [])
|
|
1703
|
+
: this.projector.roots().map((root) => root.id);
|
|
1704
|
+
const label = labelOf(current);
|
|
1705
|
+
const place = siblings.filter((sibling) => labelOf(sibling) === label).indexOf(current);
|
|
1706
|
+
segments.unshift(`${label}#${Math.max(0, place)}`);
|
|
1707
|
+
current = node.parentId ?? null;
|
|
1708
|
+
}
|
|
1709
|
+
const key = segments.join('/');
|
|
1710
|
+
this.pathKeys.set(id, key);
|
|
1711
|
+
return key;
|
|
1712
|
+
}
|
|
1713
|
+
|
|
1535
1714
|
/** The outermost grouped node that contains `id`, or `id` itself when none does. */
|
|
1536
1715
|
private groupOf(id: string): string {
|
|
1537
1716
|
let group = id;
|
|
1538
1717
|
let current: string | null = id;
|
|
1539
1718
|
while (current) {
|
|
1540
|
-
if (this.
|
|
1719
|
+
if (this.groupedPaths.has(this.pathKeyOf(current))) group = current;
|
|
1541
1720
|
current = this.projector.node(current)?.parentId ?? null;
|
|
1542
1721
|
}
|
|
1543
1722
|
return group;
|
|
@@ -1546,7 +1725,7 @@ export class PixiAuthoringAdapter implements AuthoringAdapter {
|
|
|
1546
1725
|
private isPickLocked(id: string): boolean {
|
|
1547
1726
|
let current: string | null = id;
|
|
1548
1727
|
while (current) {
|
|
1549
|
-
if (this.
|
|
1728
|
+
if (this.lockedPaths.has(this.pathKeyOf(current))) return true;
|
|
1550
1729
|
current = this.projector.node(current)?.parentId ?? null;
|
|
1551
1730
|
}
|
|
1552
1731
|
return false;
|
|
@@ -1565,8 +1744,7 @@ export class PixiAuthoringAdapter implements AuthoringAdapter {
|
|
|
1565
1744
|
|
|
1566
1745
|
dispose(): void {
|
|
1567
1746
|
this.boxEditSessions.clear();
|
|
1568
|
-
this.
|
|
1569
|
-
this.groupedIds.clear();
|
|
1747
|
+
this.pathKeys.clear();
|
|
1570
1748
|
for (const object of this.watchedForStructure) {
|
|
1571
1749
|
object.off('childAdded', this.onStructureChanged);
|
|
1572
1750
|
object.off('childRemoved', this.onStructureChanged);
|