worldorbit 2.5.16 → 2.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +81 -15
- package/dist/browser/core/dist/index.js +1228 -110
- package/dist/browser/editor/dist/index.js +1896 -180
- package/dist/browser/markdown/dist/index.js +1071 -99
- package/dist/browser/viewer/dist/index.js +1127 -113
- package/dist/unpkg/core/dist/index.js +1228 -110
- package/dist/unpkg/editor/dist/index.js +1896 -180
- package/dist/unpkg/markdown/dist/index.js +1071 -99
- package/dist/unpkg/viewer/dist/index.js +1127 -113
- package/dist/unpkg/worldorbit-core.min.js +12 -12
- package/dist/unpkg/worldorbit-editor.min.js +295 -203
- package/dist/unpkg/worldorbit-markdown.min.js +66 -58
- package/dist/unpkg/worldorbit-viewer.min.js +84 -76
- package/dist/unpkg/worldorbit.js +1304 -124
- package/dist/unpkg/worldorbit.min.js +88 -80
- package/package.json +1 -1
- package/packages/core/dist/atlas-edit.js +75 -1
- package/packages/core/dist/atlas-validate.js +211 -8
- package/packages/core/dist/draft-parse.js +401 -22
- package/packages/core/dist/draft.d.ts +5 -2
- package/packages/core/dist/draft.js +103 -8
- package/packages/core/dist/format.js +99 -6
- package/packages/core/dist/load.js +9 -2
- package/packages/core/dist/normalize.js +1 -0
- package/packages/core/dist/scene.js +400 -64
- package/packages/core/dist/types.d.ts +60 -4
- package/packages/editor/dist/editor.js +702 -65
- package/packages/editor/dist/types.d.ts +3 -1
- package/packages/viewer/dist/atlas-state.js +11 -2
- package/packages/viewer/dist/atlas-viewer.js +19 -7
- package/packages/viewer/dist/render.js +31 -2
- package/packages/viewer/dist/theme.js +1 -0
- package/packages/viewer/dist/tooltip.js +9 -0
- package/packages/viewer/dist/types.d.ts +12 -2
- package/packages/viewer/dist/viewer.js +28 -1
|
@@ -64,7 +64,11 @@ const OBJECT_NUMBER_FIELDS = ["albedo"];
|
|
|
64
64
|
const FIELD_HELP = {
|
|
65
65
|
"defaults-view": {
|
|
66
66
|
description: "Sets the default camera projection for the atlas.",
|
|
67
|
-
references: [
|
|
67
|
+
references: [
|
|
68
|
+
"Topdown = map-like",
|
|
69
|
+
"Isometric = angled overview",
|
|
70
|
+
"Orthographic/Perspective = 3D-ready semantic views",
|
|
71
|
+
],
|
|
68
72
|
},
|
|
69
73
|
"defaults-scale": {
|
|
70
74
|
description: "Chooses the overall spacing/style preset used by the renderer.",
|
|
@@ -76,15 +80,71 @@ const FIELD_HELP = {
|
|
|
76
80
|
},
|
|
77
81
|
"viewpoint-projection": {
|
|
78
82
|
description: "Overrides the projection for this saved viewpoint.",
|
|
79
|
-
references: [
|
|
83
|
+
references: [
|
|
84
|
+
"Topdown = flat orbital map",
|
|
85
|
+
"Isometric = angled scene",
|
|
86
|
+
"Orthographic/Perspective = stored with current 2D fallback",
|
|
87
|
+
],
|
|
80
88
|
},
|
|
81
89
|
"viewpoint-zoom": {
|
|
82
90
|
description: "Controls how closely this viewpoint frames the system.",
|
|
83
91
|
references: ["1 = scene fit", "2+ = close-up"],
|
|
84
92
|
},
|
|
85
93
|
"viewpoint-rotation": {
|
|
86
|
-
description: "
|
|
87
|
-
references: ["90deg = quarter turn", "
|
|
94
|
+
description: "Legacy 2D screen rotation. This is separate from the Schema 2.5 camera block.",
|
|
95
|
+
references: ["90deg = quarter turn", "Use camera.azimuth for semantic view direction"],
|
|
96
|
+
},
|
|
97
|
+
"viewpoint-camera-azimuth": {
|
|
98
|
+
description: "Horizontal camera direction in degrees for Schema 2.5 viewpoints.",
|
|
99
|
+
references: ["0 = forward/default", "90 = quarter orbit around the scene"],
|
|
100
|
+
},
|
|
101
|
+
"viewpoint-camera-elevation": {
|
|
102
|
+
description: "Vertical camera tilt in degrees for 3D-ready viewpoints.",
|
|
103
|
+
references: ["0 = level", "30 = gentle look down"],
|
|
104
|
+
},
|
|
105
|
+
"viewpoint-camera-roll": {
|
|
106
|
+
description: "Rolls the camera around its forward axis.",
|
|
107
|
+
references: ["0 = upright", "15 = slight bank"],
|
|
108
|
+
},
|
|
109
|
+
"viewpoint-camera-distance": {
|
|
110
|
+
description: "Semantic camera distance for perspective viewpoints.",
|
|
111
|
+
references: ["4 = close", "12 = wide framing"],
|
|
112
|
+
},
|
|
113
|
+
"viewpoint-events": {
|
|
114
|
+
description: "Lists event IDs that this viewpoint should feature in its detail panel.",
|
|
115
|
+
references: ["solar-eclipse-naar", "transit-window conjunction"],
|
|
116
|
+
},
|
|
117
|
+
"event-kind": {
|
|
118
|
+
description: "Short semantic event type for tooling and viewer overlays.",
|
|
119
|
+
references: ["solar-eclipse", "lunar-eclipse", "transit"],
|
|
120
|
+
},
|
|
121
|
+
"event-target": {
|
|
122
|
+
description: "Primary object this event is centered on.",
|
|
123
|
+
references: ["Naar", "Seyra"],
|
|
124
|
+
},
|
|
125
|
+
"event-participants": {
|
|
126
|
+
description: "Objects that participate in the event snapshot or description.",
|
|
127
|
+
references: ["Iyath Naar Seyra", "Naar Seyra Orun"],
|
|
128
|
+
},
|
|
129
|
+
"event-timing": {
|
|
130
|
+
description: "Free-text timing note for the event.",
|
|
131
|
+
references: ['"Every late bloom season"', '"At local midyear"'],
|
|
132
|
+
},
|
|
133
|
+
"event-visibility": {
|
|
134
|
+
description: "Notes where or how the event is visible.",
|
|
135
|
+
references: ['"Visible from Naar"', '"Southern hemisphere only"'],
|
|
136
|
+
},
|
|
137
|
+
"event-epoch": {
|
|
138
|
+
description: "Optional event-wide epoch that event poses inherit unless they override it.",
|
|
139
|
+
references: ['"JY-0001.0"', '"Naar bloom cycle year 18"'],
|
|
140
|
+
},
|
|
141
|
+
"event-referencePlane": {
|
|
142
|
+
description: "Optional event-wide reference plane for all poses in this snapshot.",
|
|
143
|
+
references: ["ecliptic", "naar-equatorial"],
|
|
144
|
+
},
|
|
145
|
+
"event-viewpoints": {
|
|
146
|
+
description: "Viewpoint IDs that should list this event prominently.",
|
|
147
|
+
references: ["naar-system", "overview inner-system"],
|
|
88
148
|
},
|
|
89
149
|
"placement-target": {
|
|
90
150
|
description: "Names the body or reference this object is attached to.",
|
|
@@ -122,6 +182,14 @@ const FIELD_HELP = {
|
|
|
122
182
|
description: "Starting position of the object along its orbit.",
|
|
123
183
|
references: ["0deg = start position", "180deg = opposite side"],
|
|
124
184
|
},
|
|
185
|
+
"pose-epoch": {
|
|
186
|
+
description: "Overrides the effective epoch for this pose only.",
|
|
187
|
+
references: ['"JY-0001.0"', "Falls back to event, object, then system"],
|
|
188
|
+
},
|
|
189
|
+
"pose-referencePlane": {
|
|
190
|
+
description: "Overrides the effective reference plane for this pose only.",
|
|
191
|
+
references: ["naar-equatorial", "Falls back to event, object, then system"],
|
|
192
|
+
},
|
|
125
193
|
"prop-radius": {
|
|
126
194
|
description: "Visual body size or real-world-inspired radius value.",
|
|
127
195
|
references: ["1re = Earth radius", "1sol = Sun radius"],
|
|
@@ -206,12 +274,25 @@ export function createWorldOrbitEditor(container, options = {}) {
|
|
|
206
274
|
minimap: true,
|
|
207
275
|
tooltipMode: "hover",
|
|
208
276
|
onSelectionChange(selectedObject) {
|
|
277
|
+
const activeEventId = selection ? selectionEventId(selection) : null;
|
|
209
278
|
if (ignoreViewerSelection || !selectedObject) {
|
|
210
|
-
if (!ignoreViewerSelection
|
|
211
|
-
|
|
279
|
+
if (!ignoreViewerSelection) {
|
|
280
|
+
if (selection?.kind === "event-pose" && selection.id) {
|
|
281
|
+
setSelection({ kind: "event", id: selection.id }, false, true);
|
|
282
|
+
}
|
|
283
|
+
else if (selection?.kind === "object") {
|
|
284
|
+
setSelection(activeEventId ? { kind: "event", id: activeEventId } : null, false, true);
|
|
285
|
+
}
|
|
286
|
+
else if (selection?.kind === "event" && selection.id) {
|
|
287
|
+
setSelection({ kind: "event", id: selection.id }, false, true);
|
|
288
|
+
}
|
|
212
289
|
}
|
|
213
290
|
return;
|
|
214
291
|
}
|
|
292
|
+
if (activeEventId && findEventPose(atlasDocument, activeEventId, selectedObject.objectId)) {
|
|
293
|
+
setSelection({ kind: "event-pose", id: activeEventId, key: selectedObject.objectId }, false, true);
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
215
296
|
setSelection({ kind: "object", id: selectedObject.objectId }, false, true);
|
|
216
297
|
},
|
|
217
298
|
onViewChange() {
|
|
@@ -221,6 +302,7 @@ export function createWorldOrbitEditor(container, options = {}) {
|
|
|
221
302
|
toolbar.addEventListener("click", handleToolbarClick);
|
|
222
303
|
outline.addEventListener("click", handleOutlineClick);
|
|
223
304
|
overlay.addEventListener("pointerdown", handleOverlayPointerDown);
|
|
305
|
+
inspector?.addEventListener("click", handleInspectorClick);
|
|
224
306
|
inspector?.addEventListener("input", handleInspectorInput);
|
|
225
307
|
inspector?.addEventListener("change", handleInspectorChange);
|
|
226
308
|
sourcePane?.addEventListener("input", handleSourceInput);
|
|
@@ -295,6 +377,30 @@ export function createWorldOrbitEditor(container, options = {}) {
|
|
|
295
377
|
replaceAtlasDocument(nextDocument, true, { kind: "object", id });
|
|
296
378
|
return id;
|
|
297
379
|
},
|
|
380
|
+
addEvent() {
|
|
381
|
+
const id = createUniqueId("event", atlasDocument.events.map((event) => event.id));
|
|
382
|
+
const created = {
|
|
383
|
+
id,
|
|
384
|
+
kind: "",
|
|
385
|
+
label: humanizeIdentifier(id),
|
|
386
|
+
summary: null,
|
|
387
|
+
targetObjectId: null,
|
|
388
|
+
participantObjectIds: [],
|
|
389
|
+
timing: null,
|
|
390
|
+
visibility: null,
|
|
391
|
+
epoch: null,
|
|
392
|
+
referencePlane: null,
|
|
393
|
+
tags: [],
|
|
394
|
+
color: null,
|
|
395
|
+
hidden: false,
|
|
396
|
+
positions: [],
|
|
397
|
+
};
|
|
398
|
+
const nextDocument = cloneAtlasDocument(atlasDocument);
|
|
399
|
+
nextDocument.events.push(created);
|
|
400
|
+
nextDocument.events.sort(compareEvents);
|
|
401
|
+
replaceAtlasDocument(nextDocument, true, { kind: "event", id });
|
|
402
|
+
return id;
|
|
403
|
+
},
|
|
298
404
|
addViewpoint() {
|
|
299
405
|
const id = createUniqueId("viewpoint", atlasDocument.system?.viewpoints.map((viewpoint) => viewpoint.id) ?? []);
|
|
300
406
|
const created = {
|
|
@@ -303,10 +409,12 @@ export function createWorldOrbitEditor(container, options = {}) {
|
|
|
303
409
|
summary: "",
|
|
304
410
|
focusObjectId: null,
|
|
305
411
|
selectedObjectId: null,
|
|
412
|
+
events: [],
|
|
306
413
|
projection: atlasDocument.system?.defaults.view ?? "topdown",
|
|
307
414
|
preset: atlasDocument.system?.defaults.preset ?? null,
|
|
308
415
|
zoom: null,
|
|
309
416
|
rotationDeg: 0,
|
|
417
|
+
camera: null,
|
|
310
418
|
layers: {},
|
|
311
419
|
filter: null,
|
|
312
420
|
};
|
|
@@ -363,6 +471,7 @@ export function createWorldOrbitEditor(container, options = {}) {
|
|
|
363
471
|
toolbar.removeEventListener("click", handleToolbarClick);
|
|
364
472
|
outline.removeEventListener("click", handleOutlineClick);
|
|
365
473
|
overlay.removeEventListener("pointerdown", handleOverlayPointerDown);
|
|
474
|
+
inspector?.removeEventListener("click", handleInspectorClick);
|
|
366
475
|
inspector?.removeEventListener("input", handleInspectorInput);
|
|
367
476
|
inspector?.removeEventListener("change", handleInspectorChange);
|
|
368
477
|
sourcePane?.removeEventListener("input", handleSourceInput);
|
|
@@ -408,7 +517,7 @@ export function createWorldOrbitEditor(container, options = {}) {
|
|
|
408
517
|
}
|
|
409
518
|
function getCurrentSourceForExport() {
|
|
410
519
|
if (dragState?.changed) {
|
|
411
|
-
return
|
|
520
|
+
return formatAtlasSource(atlasDocument);
|
|
412
521
|
}
|
|
413
522
|
return canonicalSource;
|
|
414
523
|
}
|
|
@@ -447,7 +556,7 @@ export function createWorldOrbitEditor(container, options = {}) {
|
|
|
447
556
|
}
|
|
448
557
|
clearSourceInputTimer();
|
|
449
558
|
atlasDocument = cloneAtlasDocument(nextDocument);
|
|
450
|
-
canonicalSource =
|
|
559
|
+
canonicalSource = formatAtlasSource(atlasDocument);
|
|
451
560
|
if (!preserveSourceText) {
|
|
452
561
|
sourceText = canonicalSource;
|
|
453
562
|
}
|
|
@@ -488,10 +597,10 @@ export function createWorldOrbitEditor(container, options = {}) {
|
|
|
488
597
|
if (commitHistory) {
|
|
489
598
|
history.push(createHistoryEntry());
|
|
490
599
|
future.length = 0;
|
|
491
|
-
sourceText =
|
|
600
|
+
sourceText = formatAtlasSource(nextDocument);
|
|
492
601
|
}
|
|
493
602
|
atlasDocument = cloneAtlasDocument(nextDocument);
|
|
494
|
-
canonicalSource =
|
|
603
|
+
canonicalSource = formatAtlasSource(atlasDocument);
|
|
495
604
|
diagnostics = mergeDiagnostics(loadedDiagnostics, collectDocumentDiagnostics(atlasDocument));
|
|
496
605
|
selection = normalizeSelection(selection);
|
|
497
606
|
syncViewer({
|
|
@@ -510,11 +619,15 @@ export function createWorldOrbitEditor(container, options = {}) {
|
|
|
510
619
|
const previousState = viewer.getState();
|
|
511
620
|
const currentRenderOptions = viewer.getRenderOptions();
|
|
512
621
|
const nextPreset = atlasDocument.system?.defaults.preset ?? "atlas-card";
|
|
622
|
+
const nextActiveEventId = selection ? selectionEventId(selection) : null;
|
|
513
623
|
ignoreViewerSelection = true;
|
|
514
|
-
if (currentRenderOptions.preset !== nextPreset ||
|
|
624
|
+
if (currentRenderOptions.preset !== nextPreset ||
|
|
625
|
+
currentRenderOptions.projection !== "document" ||
|
|
626
|
+
(currentRenderOptions.activeEventId ?? null) !== nextActiveEventId) {
|
|
515
627
|
viewer.setRenderOptions({
|
|
516
628
|
preset: nextPreset,
|
|
517
629
|
projection: "document",
|
|
630
|
+
activeEventId: nextActiveEventId,
|
|
518
631
|
});
|
|
519
632
|
}
|
|
520
633
|
viewer.setDocument(materializeAtlasDocument(atlasDocument));
|
|
@@ -524,12 +637,19 @@ export function createWorldOrbitEditor(container, options = {}) {
|
|
|
524
637
|
else if (options.preserveCamera !== false) {
|
|
525
638
|
viewer.setState({
|
|
526
639
|
...previousState,
|
|
527
|
-
selectedObjectId: selection?.kind === "object"
|
|
640
|
+
selectedObjectId: selection?.kind === "object"
|
|
641
|
+
? selection.id ?? null
|
|
642
|
+
: selection?.kind === "event-pose"
|
|
643
|
+
? selection.key ?? null
|
|
644
|
+
: null,
|
|
528
645
|
});
|
|
529
646
|
}
|
|
530
647
|
else if (selection?.kind === "object" && selection.id) {
|
|
531
648
|
viewer.focusObject(selection.id);
|
|
532
649
|
}
|
|
650
|
+
else if (selection?.kind === "event-pose" && selection.key) {
|
|
651
|
+
viewer.focusObject(selection.key);
|
|
652
|
+
}
|
|
533
653
|
ignoreViewerSelection = false;
|
|
534
654
|
}
|
|
535
655
|
function emitSnapshot() {
|
|
@@ -547,9 +667,13 @@ export function createWorldOrbitEditor(container, options = {}) {
|
|
|
547
667
|
selection = normalizeSelection(nextSelection);
|
|
548
668
|
if (syncViewerSelection) {
|
|
549
669
|
ignoreViewerSelection = true;
|
|
670
|
+
viewer.setRenderOptions({ activeEventId: selection ? selectionEventId(selection) : null });
|
|
550
671
|
if (selection?.kind === "object" && selection.id) {
|
|
551
672
|
viewer.focusObject(selection.id);
|
|
552
673
|
}
|
|
674
|
+
else if (selection?.kind === "event-pose" && selection.key) {
|
|
675
|
+
viewer.focusObject(selection.key);
|
|
676
|
+
}
|
|
553
677
|
else if (selection?.kind === "viewpoint" && selection.id) {
|
|
554
678
|
viewer.goToViewpoint(selection.id);
|
|
555
679
|
}
|
|
@@ -604,6 +728,7 @@ export function createWorldOrbitEditor(container, options = {}) {
|
|
|
604
728
|
${OBJECT_TYPES.map((type) => `<option value="${escapeHtml(type)}"${type === objectType ? " selected" : ""}>${escapeHtml(humanizeIdentifier(type))}</option>`).join("")}
|
|
605
729
|
</select>
|
|
606
730
|
<button type="button" data-editor-action="add-object">Add object</button>
|
|
731
|
+
<button type="button" data-editor-action="add-event">Add event</button>
|
|
607
732
|
<button type="button" data-editor-action="add-viewpoint">Add viewpoint</button>
|
|
608
733
|
<button type="button" data-editor-action="add-annotation">Add annotation</button>
|
|
609
734
|
<button type="button" data-editor-action="add-metadata">Add metadata</button>
|
|
@@ -650,6 +775,14 @@ export function createWorldOrbitEditor(container, options = {}) {
|
|
|
650
775
|
.join("")
|
|
651
776
|
: `<p class="wo-editor-empty">No annotations yet.</p>`}
|
|
652
777
|
</div>
|
|
778
|
+
<div class="wo-editor-outline-section">
|
|
779
|
+
<h3>Events</h3>
|
|
780
|
+
${atlasDocument.events.length > 0
|
|
781
|
+
? atlasDocument.events
|
|
782
|
+
.map((eventEntry) => renderEventOutlineItems(eventEntry, activeKey, diagnosticBuckets))
|
|
783
|
+
.join("")
|
|
784
|
+
: `<p class="wo-editor-empty">No events yet.</p>`}
|
|
785
|
+
</div>
|
|
653
786
|
<div class="wo-editor-outline-section">
|
|
654
787
|
<h3>Objects</h3>
|
|
655
788
|
${atlasDocument.objects.length > 0
|
|
@@ -701,6 +834,7 @@ export function createWorldOrbitEditor(container, options = {}) {
|
|
|
701
834
|
selection: selection ? { path: { ...selection } } : null,
|
|
702
835
|
system: atlasDocument.system,
|
|
703
836
|
viewpoints: atlasDocument.system?.viewpoints ?? [],
|
|
837
|
+
events: atlasDocument.events,
|
|
704
838
|
objects: atlasDocument.objects,
|
|
705
839
|
};
|
|
706
840
|
if (!selection) {
|
|
@@ -729,6 +863,17 @@ export function createWorldOrbitEditor(container, options = {}) {
|
|
|
729
863
|
applyInspectorSectionState(inspector, inspectorSectionState);
|
|
730
864
|
decorateInspectorDiagnostics(selection, diagnostics);
|
|
731
865
|
return;
|
|
866
|
+
case "event":
|
|
867
|
+
inspector.innerHTML = diagnosticSummary + renderEventInspector(formState, selection.id ?? "");
|
|
868
|
+
applyInspectorSectionState(inspector, inspectorSectionState);
|
|
869
|
+
decorateInspectorDiagnostics(selection, diagnostics);
|
|
870
|
+
return;
|
|
871
|
+
case "event-pose":
|
|
872
|
+
inspector.innerHTML =
|
|
873
|
+
diagnosticSummary + renderEventPoseInspector(formState, selection.id ?? "", selection.key ?? "");
|
|
874
|
+
applyInspectorSectionState(inspector, inspectorSectionState);
|
|
875
|
+
decorateInspectorDiagnostics(selection, diagnostics);
|
|
876
|
+
return;
|
|
732
877
|
case "annotation":
|
|
733
878
|
inspector.innerHTML = diagnosticSummary + renderAnnotationInspector(formState, selection.id ?? "");
|
|
734
879
|
applyInspectorSectionState(inspector, inspectorSectionState);
|
|
@@ -761,10 +906,15 @@ export function createWorldOrbitEditor(container, options = {}) {
|
|
|
761
906
|
return;
|
|
762
907
|
}
|
|
763
908
|
overlay.innerHTML = "";
|
|
764
|
-
|
|
909
|
+
const selectedObjectId = selection?.kind === "object"
|
|
910
|
+
? selection.id ?? null
|
|
911
|
+
: selection?.kind === "event-pose"
|
|
912
|
+
? selection.key ?? null
|
|
913
|
+
: null;
|
|
914
|
+
if (!selectedObjectId) {
|
|
765
915
|
return;
|
|
766
916
|
}
|
|
767
|
-
const details = viewer.getObjectDetails(
|
|
917
|
+
const details = viewer.getObjectDetails(selectedObjectId);
|
|
768
918
|
if (!details) {
|
|
769
919
|
return;
|
|
770
920
|
}
|
|
@@ -883,6 +1033,9 @@ export function createWorldOrbitEditor(container, options = {}) {
|
|
|
883
1033
|
case "add-viewpoint":
|
|
884
1034
|
api.addViewpoint();
|
|
885
1035
|
return;
|
|
1036
|
+
case "add-event":
|
|
1037
|
+
api.addEvent();
|
|
1038
|
+
return;
|
|
886
1039
|
case "add-annotation":
|
|
887
1040
|
api.addAnnotation();
|
|
888
1041
|
return;
|
|
@@ -917,6 +1070,35 @@ export function createWorldOrbitEditor(container, options = {}) {
|
|
|
917
1070
|
key: button.dataset.pathKey || undefined,
|
|
918
1071
|
}, true, true);
|
|
919
1072
|
}
|
|
1073
|
+
function handleInspectorClick(event) {
|
|
1074
|
+
const pathButton = event.target?.closest("[data-path-kind]");
|
|
1075
|
+
if (pathButton) {
|
|
1076
|
+
setSelection({
|
|
1077
|
+
kind: pathButton.dataset.pathKind,
|
|
1078
|
+
id: pathButton.dataset.pathId || undefined,
|
|
1079
|
+
key: pathButton.dataset.pathKey || undefined,
|
|
1080
|
+
}, true, true);
|
|
1081
|
+
return;
|
|
1082
|
+
}
|
|
1083
|
+
const actionButton = event.target?.closest("[data-editor-action]");
|
|
1084
|
+
if (!actionButton) {
|
|
1085
|
+
return;
|
|
1086
|
+
}
|
|
1087
|
+
if (actionButton.dataset.editorAction === "add-event-pose") {
|
|
1088
|
+
const eventId = actionButton.dataset.editorEventId ||
|
|
1089
|
+
(selection?.kind === "event" || selection?.kind === "event-pose" ? selection.id ?? "" : "");
|
|
1090
|
+
if (!eventId) {
|
|
1091
|
+
return;
|
|
1092
|
+
}
|
|
1093
|
+
const nextDocument = addEventPose(atlasDocument, eventId);
|
|
1094
|
+
const createdEvent = nextDocument.events.find((entry) => entry.id === eventId);
|
|
1095
|
+
const createdPose = createdEvent?.positions.at(-1) ?? createdEvent?.positions[0];
|
|
1096
|
+
replaceAtlasDocument(nextDocument, true, createdPose
|
|
1097
|
+
? { kind: "event-pose", id: eventId, key: createdPose.objectId }
|
|
1098
|
+
: { kind: "event", id: eventId });
|
|
1099
|
+
return;
|
|
1100
|
+
}
|
|
1101
|
+
}
|
|
920
1102
|
function handleInspectorInput() {
|
|
921
1103
|
applyInspectorState(false);
|
|
922
1104
|
}
|
|
@@ -940,6 +1122,12 @@ export function createWorldOrbitEditor(container, options = {}) {
|
|
|
940
1122
|
case "viewpoint":
|
|
941
1123
|
replaceAtlasDocument(buildViewpointDocumentFromInspector(selection.id ?? ""), commitHistory, selection, false);
|
|
942
1124
|
return;
|
|
1125
|
+
case "event":
|
|
1126
|
+
replaceAtlasDocument(buildEventDocumentFromInspector(selection.id ?? ""), commitHistory, selection, false);
|
|
1127
|
+
return;
|
|
1128
|
+
case "event-pose":
|
|
1129
|
+
replaceAtlasDocument(buildEventPoseDocumentFromInspector(selection.id ?? "", selection.key ?? ""), commitHistory, selection, false);
|
|
1130
|
+
return;
|
|
943
1131
|
case "annotation":
|
|
944
1132
|
replaceAtlasDocument(buildAnnotationDocumentFromInspector(selection.id ?? ""), commitHistory, selection, false);
|
|
945
1133
|
return;
|
|
@@ -1010,6 +1198,7 @@ export function createWorldOrbitEditor(container, options = {}) {
|
|
|
1010
1198
|
kind,
|
|
1011
1199
|
objectId,
|
|
1012
1200
|
pointerId: event.pointerId,
|
|
1201
|
+
path: selection ? { ...selection } : { kind: "object", id: objectId },
|
|
1013
1202
|
startedFrom: createHistoryEntry(),
|
|
1014
1203
|
changed: false,
|
|
1015
1204
|
orbitRadiusContext: kind === "orbit-radius" && details
|
|
@@ -1022,8 +1211,8 @@ export function createWorldOrbitEditor(container, options = {}) {
|
|
|
1022
1211
|
function handleWindowPointerMove(event) {
|
|
1023
1212
|
if (!dragState ||
|
|
1024
1213
|
dragState.pointerId !== event.pointerId ||
|
|
1025
|
-
selection
|
|
1026
|
-
selection
|
|
1214
|
+
!selection ||
|
|
1215
|
+
selectionKey(selection) !== selectionKey(dragState.path)) {
|
|
1027
1216
|
return;
|
|
1028
1217
|
}
|
|
1029
1218
|
const details = viewer.getObjectDetails(dragState.objectId);
|
|
@@ -1035,27 +1224,27 @@ export function createWorldOrbitEditor(container, options = {}) {
|
|
|
1035
1224
|
switch (dragState.kind) {
|
|
1036
1225
|
case "orbit-phase":
|
|
1037
1226
|
if (details.object.placement?.mode === "orbit" && details.orbit) {
|
|
1038
|
-
nextDocument = updateOrbitPhase(atlasDocument, dragState.objectId, details, pointer);
|
|
1227
|
+
nextDocument = updateOrbitPhase(atlasDocument, dragState.path, dragState.objectId, details, pointer);
|
|
1039
1228
|
}
|
|
1040
1229
|
break;
|
|
1041
1230
|
case "orbit-radius":
|
|
1042
1231
|
if (details.object.placement?.mode === "orbit" && details.orbit) {
|
|
1043
|
-
nextDocument = updateOrbitRadius(atlasDocument, dragState.objectId, details, pointer, dragState.orbitRadiusContext ?? null);
|
|
1232
|
+
nextDocument = updateOrbitRadius(atlasDocument, dragState.path, dragState.objectId, details, pointer, dragState.orbitRadiusContext ?? null);
|
|
1044
1233
|
}
|
|
1045
1234
|
break;
|
|
1046
1235
|
case "at-reference":
|
|
1047
1236
|
if (details.object.placement?.mode === "at") {
|
|
1048
|
-
nextDocument = updateAtReference(atlasDocument, dragState.objectId, viewer.getScene(), pointer);
|
|
1237
|
+
nextDocument = updateAtReference(atlasDocument, dragState.path, dragState.objectId, viewer.getScene(), pointer);
|
|
1049
1238
|
}
|
|
1050
1239
|
break;
|
|
1051
1240
|
case "surface-target":
|
|
1052
1241
|
if (details.object.placement?.mode === "surface") {
|
|
1053
|
-
nextDocument = updateSurfaceTarget(atlasDocument, dragState.objectId, viewer.getScene(), pointer);
|
|
1242
|
+
nextDocument = updateSurfaceTarget(atlasDocument, dragState.path, dragState.objectId, viewer.getScene(), pointer);
|
|
1054
1243
|
}
|
|
1055
1244
|
break;
|
|
1056
1245
|
case "free-distance":
|
|
1057
1246
|
if (details.object.placement?.mode === "free") {
|
|
1058
|
-
nextDocument = updateFreeDistance(atlasDocument, dragState.objectId, viewer.getScene(), details, pointer);
|
|
1247
|
+
nextDocument = updateFreeDistance(atlasDocument, dragState.path, dragState.objectId, viewer.getScene(), details, pointer);
|
|
1059
1248
|
}
|
|
1060
1249
|
break;
|
|
1061
1250
|
}
|
|
@@ -1087,7 +1276,7 @@ export function createWorldOrbitEditor(container, options = {}) {
|
|
|
1087
1276
|
}
|
|
1088
1277
|
history.push(dragState.startedFrom);
|
|
1089
1278
|
future.length = 0;
|
|
1090
|
-
canonicalSource =
|
|
1279
|
+
canonicalSource = formatAtlasSource(atlasDocument);
|
|
1091
1280
|
sourceText = canonicalSource;
|
|
1092
1281
|
dragState = null;
|
|
1093
1282
|
renderAll();
|
|
@@ -1189,15 +1378,18 @@ export function createWorldOrbitEditor(container, options = {}) {
|
|
|
1189
1378
|
null,
|
|
1190
1379
|
zoom: parseNullableNumber(readOptionalTextInput(form, "viewpoint-zoom")),
|
|
1191
1380
|
rotationDeg: parseNullableNumber(readOptionalTextInput(form, "viewpoint-rotation")) ?? 0,
|
|
1381
|
+
camera: buildViewCameraFromForm(form),
|
|
1192
1382
|
layers: {
|
|
1193
1383
|
background: readCheckbox(form, "layer-background"),
|
|
1194
1384
|
guides: readCheckbox(form, "layer-guides"),
|
|
1195
1385
|
"orbits-back": readCheckbox(form, "layer-orbits-back"),
|
|
1196
1386
|
"orbits-front": readCheckbox(form, "layer-orbits-front"),
|
|
1387
|
+
events: readCheckbox(form, "layer-events"),
|
|
1197
1388
|
objects: readCheckbox(form, "layer-objects"),
|
|
1198
1389
|
labels: readCheckbox(form, "layer-labels"),
|
|
1199
1390
|
metadata: readCheckbox(form, "layer-metadata"),
|
|
1200
1391
|
},
|
|
1392
|
+
events: splitTokens(readOptionalTextInput(form, "viewpoint-events")),
|
|
1201
1393
|
filter: {
|
|
1202
1394
|
query: readOptionalTextInput(form, "filter-query"),
|
|
1203
1395
|
objectTypes: parseObjectTypes(readOptionalTextInput(form, "filter-object-types")),
|
|
@@ -1214,6 +1406,77 @@ export function createWorldOrbitEditor(container, options = {}) {
|
|
|
1214
1406
|
}
|
|
1215
1407
|
return nextDocument;
|
|
1216
1408
|
}
|
|
1409
|
+
function buildEventDocumentFromInspector(currentId) {
|
|
1410
|
+
const nextDocument = cloneAtlasDocument(atlasDocument);
|
|
1411
|
+
const form = inspector?.querySelector("form[data-editor-form='event']");
|
|
1412
|
+
const current = nextDocument.events.find((entry) => entry.id === currentId);
|
|
1413
|
+
if (!form || !current) {
|
|
1414
|
+
return nextDocument;
|
|
1415
|
+
}
|
|
1416
|
+
const nextId = readTextInput(form, "event-id") || current.id;
|
|
1417
|
+
const replacement = {
|
|
1418
|
+
...current,
|
|
1419
|
+
id: nextId,
|
|
1420
|
+
kind: readTextInput(form, "event-kind"),
|
|
1421
|
+
label: readTextInput(form, "event-label") || current.label,
|
|
1422
|
+
summary: readOptionalTextInput(form, "event-summary"),
|
|
1423
|
+
targetObjectId: readOptionalTextInput(form, "event-target"),
|
|
1424
|
+
participantObjectIds: splitTokens(readOptionalTextInput(form, "event-participants")),
|
|
1425
|
+
timing: readOptionalTextInput(form, "event-timing"),
|
|
1426
|
+
visibility: readOptionalTextInput(form, "event-visibility"),
|
|
1427
|
+
epoch: readOptionalTextInput(form, "event-epoch"),
|
|
1428
|
+
referencePlane: readOptionalTextInput(form, "event-referencePlane"),
|
|
1429
|
+
tags: splitTokens(readOptionalTextInput(form, "event-tags")),
|
|
1430
|
+
color: readOptionalTextInput(form, "event-color"),
|
|
1431
|
+
hidden: readCheckbox(form, "event-hidden"),
|
|
1432
|
+
};
|
|
1433
|
+
nextDocument.events = nextDocument.events
|
|
1434
|
+
.filter((entry) => entry.id !== current.id)
|
|
1435
|
+
.concat(replacement)
|
|
1436
|
+
.sort(compareEvents);
|
|
1437
|
+
syncEventViewpointReferences(nextDocument, current.id, replacement.id, splitTokens(readOptionalTextInput(form, "event-viewpoints")));
|
|
1438
|
+
if (current.id !== replacement.id) {
|
|
1439
|
+
selection = { kind: "event", id: replacement.id };
|
|
1440
|
+
}
|
|
1441
|
+
return nextDocument;
|
|
1442
|
+
}
|
|
1443
|
+
function buildEventPoseDocumentFromInspector(eventId, objectId) {
|
|
1444
|
+
const nextDocument = cloneAtlasDocument(atlasDocument);
|
|
1445
|
+
const form = inspector?.querySelector("form[data-editor-form='event-pose']");
|
|
1446
|
+
const eventEntry = nextDocument.events.find((entry) => entry.id === eventId);
|
|
1447
|
+
const currentPose = eventEntry?.positions.find((entry) => entry.objectId === objectId);
|
|
1448
|
+
if (!form || !eventEntry || !currentPose) {
|
|
1449
|
+
return nextDocument;
|
|
1450
|
+
}
|
|
1451
|
+
const nextObjectId = readTextInput(form, "pose-object-id") || currentPose.objectId;
|
|
1452
|
+
const replacement = {
|
|
1453
|
+
objectId: nextObjectId,
|
|
1454
|
+
placement: buildPlacementFromPoseForm(form, currentPose),
|
|
1455
|
+
epoch: readOptionalTextInput(form, "pose-epoch"),
|
|
1456
|
+
referencePlane: readOptionalTextInput(form, "pose-referencePlane"),
|
|
1457
|
+
};
|
|
1458
|
+
const inner = parseOptionalUnit(readOptionalTextInput(form, "prop-inner"));
|
|
1459
|
+
const outer = parseOptionalUnit(readOptionalTextInput(form, "prop-outer"));
|
|
1460
|
+
if (inner) {
|
|
1461
|
+
replacement.inner = inner;
|
|
1462
|
+
}
|
|
1463
|
+
if (outer) {
|
|
1464
|
+
replacement.outer = outer;
|
|
1465
|
+
}
|
|
1466
|
+
eventEntry.positions = eventEntry.positions
|
|
1467
|
+
.filter((entry) => entry.objectId !== currentPose.objectId)
|
|
1468
|
+
.concat(replacement)
|
|
1469
|
+
.sort(compareEventPoses);
|
|
1470
|
+
if (eventEntry.targetObjectId !== replacement.objectId &&
|
|
1471
|
+
!eventEntry.participantObjectIds.includes(replacement.objectId)) {
|
|
1472
|
+
eventEntry.participantObjectIds.push(replacement.objectId);
|
|
1473
|
+
eventEntry.participantObjectIds.sort((left, right) => left.localeCompare(right));
|
|
1474
|
+
}
|
|
1475
|
+
if (currentPose.objectId !== replacement.objectId) {
|
|
1476
|
+
selection = { kind: "event-pose", id: eventId, key: replacement.objectId };
|
|
1477
|
+
}
|
|
1478
|
+
return nextDocument;
|
|
1479
|
+
}
|
|
1217
1480
|
function buildAnnotationDocumentFromInspector(currentId) {
|
|
1218
1481
|
const nextDocument = cloneAtlasDocument(atlasDocument);
|
|
1219
1482
|
const form = inspector?.querySelector("form[data-editor-form='annotation']");
|
|
@@ -1421,7 +1684,7 @@ function resolveInitialEditorState(options) {
|
|
|
1421
1684
|
const atlasDocument = cloneAtlasDocument(options.atlasDocument);
|
|
1422
1685
|
return {
|
|
1423
1686
|
atlasDocument,
|
|
1424
|
-
source:
|
|
1687
|
+
source: formatAtlasSource(atlasDocument),
|
|
1425
1688
|
diagnostics: collectDocumentDiagnostics(atlasDocument),
|
|
1426
1689
|
};
|
|
1427
1690
|
}
|
|
@@ -1431,7 +1694,7 @@ function resolveInitialEditorState(options) {
|
|
|
1431
1694
|
const atlasDocument = loaded.value.atlasDocument ?? upgradeDocumentToV2(loaded.value.document);
|
|
1432
1695
|
return {
|
|
1433
1696
|
atlasDocument,
|
|
1434
|
-
source:
|
|
1697
|
+
source: formatAtlasSource(atlasDocument),
|
|
1435
1698
|
diagnostics: mergeDiagnostics(resolveAtlasDiagnostics(atlasDocument, loaded.diagnostics), collectDocumentDiagnostics(atlasDocument)),
|
|
1436
1699
|
};
|
|
1437
1700
|
}
|
|
@@ -1439,10 +1702,13 @@ function resolveInitialEditorState(options) {
|
|
|
1439
1702
|
const atlasDocument = createEmptyAtlasDocument("WorldOrbit");
|
|
1440
1703
|
return {
|
|
1441
1704
|
atlasDocument,
|
|
1442
|
-
source:
|
|
1705
|
+
source: formatAtlasSource(atlasDocument),
|
|
1443
1706
|
diagnostics: collectDocumentDiagnostics(atlasDocument),
|
|
1444
1707
|
};
|
|
1445
1708
|
}
|
|
1709
|
+
function formatAtlasSource(document) {
|
|
1710
|
+
return formatDocument(document, { schema: document.version });
|
|
1711
|
+
}
|
|
1446
1712
|
function buildEditorMarkup() {
|
|
1447
1713
|
const previewOpen = shouldPreviewSectionBeOpenByDefault();
|
|
1448
1714
|
return `<section class="wo-editor-shell">
|
|
@@ -1563,6 +1829,17 @@ function renderOutlineButton(path, label, activeKey, diagnosticBuckets) {
|
|
|
1563
1829
|
: "";
|
|
1564
1830
|
return `<button type="button" class="wo-editor-outline-item${key === activeKey ? " is-active" : ""}" data-path-kind="${escapeHtml(path.kind)}"${path.id ? ` data-path-id="${escapeHtml(path.id)}"` : ""}${path.key ? ` data-path-key="${escapeHtml(path.key)}"` : ""}><span>${escapeHtml(label)}</span>${badge}</button>`;
|
|
1565
1831
|
}
|
|
1832
|
+
function renderEventOutlineItems(eventEntry, activeKey, diagnosticBuckets) {
|
|
1833
|
+
return `<div class="wo-editor-outline-group">
|
|
1834
|
+
${renderOutlineButton({ kind: "event", id: eventEntry.id }, eventEntry.label || eventEntry.id, activeKey, diagnosticBuckets)}
|
|
1835
|
+
${eventEntry.positions.length > 0
|
|
1836
|
+
? `<div class="wo-editor-outline-children">${[...eventEntry.positions]
|
|
1837
|
+
.sort(compareEventPoses)
|
|
1838
|
+
.map((pose) => renderOutlineButton({ kind: "event-pose", id: eventEntry.id, key: pose.objectId }, pose.objectId, activeKey, diagnosticBuckets))
|
|
1839
|
+
.join("")}</div>`
|
|
1840
|
+
: ""}
|
|
1841
|
+
</div>`;
|
|
1842
|
+
}
|
|
1566
1843
|
function renderSystemInspector(formState) {
|
|
1567
1844
|
return `<form class="wo-editor-form" data-editor-form="system">
|
|
1568
1845
|
<h2>System</h2>
|
|
@@ -1577,6 +1854,8 @@ function renderDefaultsInspector(formState) {
|
|
|
1577
1854
|
${renderInspectorSection("defaults", "basics", "Basics", `${renderSelectField("Projection", "defaults-view", [
|
|
1578
1855
|
["topdown", "Topdown"],
|
|
1579
1856
|
["isometric", "Isometric"],
|
|
1857
|
+
["orthographic", "Orthographic"],
|
|
1858
|
+
["perspective", "Perspective"],
|
|
1580
1859
|
], defaults?.view ?? "topdown")}
|
|
1581
1860
|
${renderTextField("Scale preset", "defaults-scale", defaults?.scale ?? "")}
|
|
1582
1861
|
${renderTextField("Units", "defaults-units", defaults?.units ?? "")}
|
|
@@ -1613,6 +1892,8 @@ function renderViewpointInspector(formState, id) {
|
|
|
1613
1892
|
${renderSelectField("Projection", "viewpoint-projection", [
|
|
1614
1893
|
["topdown", "Topdown"],
|
|
1615
1894
|
["isometric", "Isometric"],
|
|
1895
|
+
["orthographic", "Orthographic"],
|
|
1896
|
+
["perspective", "Perspective"],
|
|
1616
1897
|
], viewpoint.projection)}
|
|
1617
1898
|
${renderSelectField("Preset", "viewpoint-preset", [
|
|
1618
1899
|
["", "Document default"],
|
|
@@ -1623,12 +1904,18 @@ function renderViewpointInspector(formState, id) {
|
|
|
1623
1904
|
], viewpoint.preset ?? "")}
|
|
1624
1905
|
${renderTextField("Zoom", "viewpoint-zoom", viewpoint.zoom === null ? "" : String(viewpoint.zoom))}
|
|
1625
1906
|
${renderTextField("Rotation", "viewpoint-rotation", String(viewpoint.rotationDeg))}`, true)}
|
|
1907
|
+
${renderInspectorSection("viewpoint", "camera", "Camera", `${renderTextField("Azimuth", "viewpoint-camera-azimuth", viewpoint.camera?.azimuth === null || viewpoint.camera?.azimuth === undefined ? "" : String(viewpoint.camera.azimuth))}
|
|
1908
|
+
${renderTextField("Elevation", "viewpoint-camera-elevation", viewpoint.camera?.elevation === null || viewpoint.camera?.elevation === undefined ? "" : String(viewpoint.camera.elevation))}
|
|
1909
|
+
${renderTextField("Roll", "viewpoint-camera-roll", viewpoint.camera?.roll === null || viewpoint.camera?.roll === undefined ? "" : String(viewpoint.camera.roll))}
|
|
1910
|
+
${renderTextField("Distance", "viewpoint-camera-distance", viewpoint.camera?.distance === null || viewpoint.camera?.distance === undefined ? "" : String(viewpoint.camera.distance))}
|
|
1911
|
+
<p class="wo-editor-inline-note">Rotation stays a 2D screen-rotation hint. The camera block stores Schema 2.5 view direction and framing.</p>`)}
|
|
1626
1912
|
${renderInspectorSection("viewpoint", "layers", "Layers", `<fieldset class="wo-editor-fieldset">
|
|
1627
1913
|
<legend>Layers</legend>
|
|
1628
1914
|
${renderCheckboxField("Background", "layer-background", viewpoint.layers.background !== false)}
|
|
1629
1915
|
${renderCheckboxField("Guides", "layer-guides", viewpoint.layers.guides !== false)}
|
|
1630
1916
|
${renderCheckboxField("Orbits back", "layer-orbits-back", viewpoint.layers["orbits-back"] !== false)}
|
|
1631
1917
|
${renderCheckboxField("Orbits front", "layer-orbits-front", viewpoint.layers["orbits-front"] !== false)}
|
|
1918
|
+
${renderCheckboxField("Events", "layer-events", viewpoint.layers.events !== false)}
|
|
1632
1919
|
${renderCheckboxField("Objects", "layer-objects", viewpoint.layers.objects !== false)}
|
|
1633
1920
|
${renderCheckboxField("Labels", "layer-labels", viewpoint.layers.labels !== false)}
|
|
1634
1921
|
${renderCheckboxField("Metadata", "layer-metadata", viewpoint.layers.metadata !== false)}
|
|
@@ -1636,7 +1923,88 @@ function renderViewpointInspector(formState, id) {
|
|
|
1636
1923
|
${renderInspectorSection("viewpoint", "filter", "Filter", `${renderTextField("Filter query", "filter-query", viewpoint.filter?.query ?? "")}
|
|
1637
1924
|
${renderTextField("Filter object types", "filter-object-types", viewpoint.filter?.objectTypes.join(" ") ?? "")}
|
|
1638
1925
|
${renderTextField("Filter tags", "filter-tags", viewpoint.filter?.tags.join(" ") ?? "")}
|
|
1639
|
-
${renderTextField("Filter groups", "filter-groups", viewpoint.filter?.groupIds.join(" ") ?? "")}
|
|
1926
|
+
${renderTextField("Filter groups", "filter-groups", viewpoint.filter?.groupIds.join(" ") ?? "")}
|
|
1927
|
+
${renderTextField("Events", "viewpoint-events", viewpoint.events.join(" "))}`)}
|
|
1928
|
+
</form>`;
|
|
1929
|
+
}
|
|
1930
|
+
function renderEventInspector(formState, id) {
|
|
1931
|
+
const eventEntry = formState.events.find((entry) => entry.id === id);
|
|
1932
|
+
if (!eventEntry) {
|
|
1933
|
+
return `<p class="wo-editor-empty">Event not found.</p>`;
|
|
1934
|
+
}
|
|
1935
|
+
const linkedViewpoints = formState.viewpoints
|
|
1936
|
+
.filter((viewpoint) => viewpoint.events.includes(eventEntry.id))
|
|
1937
|
+
.map((viewpoint) => viewpoint.id)
|
|
1938
|
+
.join(" ");
|
|
1939
|
+
return `<form class="wo-editor-form" data-editor-form="event">
|
|
1940
|
+
<h2>Event</h2>
|
|
1941
|
+
${renderInspectorSection("event", "basics", "Basics", `${renderTextField("ID", "event-id", eventEntry.id)}
|
|
1942
|
+
${renderTextField("Kind", "event-kind", eventEntry.kind)}
|
|
1943
|
+
${renderTextField("Label", "event-label", eventEntry.label)}
|
|
1944
|
+
${renderTextAreaField("Summary", "event-summary", eventEntry.summary ?? "")}
|
|
1945
|
+
${renderTextField("Target object", "event-target", eventEntry.targetObjectId ?? "")}
|
|
1946
|
+
${renderTextField("Participants", "event-participants", eventEntry.participantObjectIds.join(" "))}
|
|
1947
|
+
${renderTextField("Timing", "event-timing", eventEntry.timing ?? "")}
|
|
1948
|
+
${renderTextField("Visibility", "event-visibility", eventEntry.visibility ?? "")}
|
|
1949
|
+
${renderTextField("Epoch", "event-epoch", eventEntry.epoch ?? "")}
|
|
1950
|
+
${renderTextField("Reference plane", "event-referencePlane", eventEntry.referencePlane ?? "")}
|
|
1951
|
+
${renderTextField("Tags", "event-tags", eventEntry.tags.join(" "))}
|
|
1952
|
+
${renderTextField("Color", "event-color", eventEntry.color ?? "")}
|
|
1953
|
+
${renderCheckboxField("Hidden", "event-hidden", eventEntry.hidden === true)}`, true)}
|
|
1954
|
+
${renderInspectorSection("event", "viewpoints", "Viewpoints", `${renderTextField("Viewpoints", "event-viewpoints", linkedViewpoints)}`)}
|
|
1955
|
+
${renderInspectorSection("event", "positions", "Positions", `${eventEntry.positions.length > 0
|
|
1956
|
+
? `<div class="wo-editor-inline-list">${eventEntry.positions
|
|
1957
|
+
.map((pose) => renderOutlineButton({ kind: "event-pose", id: eventEntry.id, key: pose.objectId }, pose.objectId, null, new Map()))
|
|
1958
|
+
.join("")}</div>`
|
|
1959
|
+
: `<p class="wo-editor-empty">No event poses yet.</p>`}
|
|
1960
|
+
<div class="wo-editor-inline-actions">
|
|
1961
|
+
<button type="button" data-editor-action="add-event-pose" data-editor-event-id="${escapeHtml(eventEntry.id)}">Add pose</button>
|
|
1962
|
+
</div>`)}
|
|
1963
|
+
</form>`;
|
|
1964
|
+
}
|
|
1965
|
+
function renderEventPoseInspector(formState, eventId, objectId) {
|
|
1966
|
+
const eventEntry = formState.events.find((entry) => entry.id === eventId);
|
|
1967
|
+
const pose = eventEntry?.positions.find((entry) => entry.objectId === objectId);
|
|
1968
|
+
if (!eventEntry || !pose) {
|
|
1969
|
+
return `<p class="wo-editor-empty">Event pose not found.</p>`;
|
|
1970
|
+
}
|
|
1971
|
+
const placementMode = pose.placement?.mode ?? "";
|
|
1972
|
+
const placementTarget = pose.placement?.mode === "orbit" || pose.placement?.mode === "surface" || pose.placement?.mode === "at"
|
|
1973
|
+
? pose.placement.target
|
|
1974
|
+
: "";
|
|
1975
|
+
const freeValue = pose.placement?.mode === "free"
|
|
1976
|
+
? pose.placement.distance
|
|
1977
|
+
? formatUnitValue(pose.placement.distance)
|
|
1978
|
+
: pose.placement.descriptor ?? ""
|
|
1979
|
+
: "";
|
|
1980
|
+
return `<form class="wo-editor-form" data-editor-form="event-pose">
|
|
1981
|
+
<h2>Event Pose</h2>
|
|
1982
|
+
<p class="wo-editor-inline-note">Event <strong>${escapeHtml(eventEntry.label || eventEntry.id)}</strong></p>
|
|
1983
|
+
${renderInspectorSection("event-pose", "identity", "Identity", `${renderTextField("Object", "pose-object-id", pose.objectId)}
|
|
1984
|
+
<div class="wo-editor-inline-actions">
|
|
1985
|
+
<button type="button" data-path-kind="event" data-path-id="${escapeHtml(eventEntry.id)}">Select event</button>
|
|
1986
|
+
</div>`, true)}
|
|
1987
|
+
${renderInspectorSection("event-pose", "placement", "Placement", `${renderSelectField("Placement mode", "placement-mode", [
|
|
1988
|
+
["", "None"],
|
|
1989
|
+
["orbit", "Orbit"],
|
|
1990
|
+
["at", "At"],
|
|
1991
|
+
["surface", "Surface"],
|
|
1992
|
+
["free", "Free"],
|
|
1993
|
+
], placementMode)}
|
|
1994
|
+
${renderTextField("Placement target", "placement-target", placementTarget)}
|
|
1995
|
+
${renderTextField("Free value", "placement-free", freeValue)}
|
|
1996
|
+
${renderTextField("Distance", "placement-distance", pose.placement?.mode === "orbit" && pose.placement.distance ? formatUnitValue(pose.placement.distance) : "")}
|
|
1997
|
+
${renderTextField("Semi-major", "placement-semiMajor", pose.placement?.mode === "orbit" && pose.placement.semiMajor ? formatUnitValue(pose.placement.semiMajor) : "")}
|
|
1998
|
+
${renderTextField("Eccentricity", "placement-eccentricity", pose.placement?.mode === "orbit" && pose.placement.eccentricity !== undefined ? String(pose.placement.eccentricity) : "")}
|
|
1999
|
+
${renderTextField("Period", "placement-period", pose.placement?.mode === "orbit" && pose.placement.period ? formatUnitValue(pose.placement.period) : "")}
|
|
2000
|
+
${renderTextField("Angle", "placement-angle", pose.placement?.mode === "orbit" && pose.placement.angle ? formatUnitValue(pose.placement.angle) : "")}
|
|
2001
|
+
${renderTextField("Inclination", "placement-inclination", pose.placement?.mode === "orbit" && pose.placement.inclination ? formatUnitValue(pose.placement.inclination) : "")}
|
|
2002
|
+
${renderTextField("Phase", "placement-phase", pose.placement?.mode === "orbit" && pose.placement.phase ? formatUnitValue(pose.placement.phase) : "")}
|
|
2003
|
+
${renderTextField("Inner", "prop-inner", pose.inner ? formatUnitValue(pose.inner) : "")}
|
|
2004
|
+
${renderTextField("Outer", "prop-outer", pose.outer ? formatUnitValue(pose.outer) : "")}`, true)}
|
|
2005
|
+
${renderInspectorSection("event-pose", "context", "Context", `${renderTextField("Epoch", "pose-epoch", pose.epoch ?? "")}
|
|
2006
|
+
${renderTextField("Reference plane", "pose-referencePlane", pose.referencePlane ?? "")}
|
|
2007
|
+
<p class="wo-editor-inline-note">Falls back to event, then object, then system context when left empty.</p>`)}
|
|
1640
2008
|
</form>`;
|
|
1641
2009
|
}
|
|
1642
2010
|
function renderAnnotationInspector(formState, id) {
|
|
@@ -1749,6 +2117,12 @@ function readCheckbox(form, name) {
|
|
|
1749
2117
|
return form.elements.namedItem(name)?.checked ?? false;
|
|
1750
2118
|
}
|
|
1751
2119
|
function buildPlacementFromForm(form, current) {
|
|
2120
|
+
return buildPlacementFromValues(form, current.placement, current.id);
|
|
2121
|
+
}
|
|
2122
|
+
function buildPlacementFromPoseForm(form, current) {
|
|
2123
|
+
return buildPlacementFromValues(form, current.placement, current.objectId);
|
|
2124
|
+
}
|
|
2125
|
+
function buildPlacementFromValues(form, currentPlacement, fallbackTarget) {
|
|
1752
2126
|
const mode = readTextInput(form, "placement-mode");
|
|
1753
2127
|
const target = readOptionalTextInput(form, "placement-target");
|
|
1754
2128
|
switch (mode) {
|
|
@@ -1756,9 +2130,9 @@ function buildPlacementFromForm(form, current) {
|
|
|
1756
2130
|
return {
|
|
1757
2131
|
mode,
|
|
1758
2132
|
target: target ??
|
|
1759
|
-
(
|
|
1760
|
-
?
|
|
1761
|
-
:
|
|
2133
|
+
(currentPlacement?.mode === "orbit"
|
|
2134
|
+
? currentPlacement.target
|
|
2135
|
+
: fallbackTarget),
|
|
1762
2136
|
distance: parseOptionalUnit(readOptionalTextInput(form, "placement-distance")),
|
|
1763
2137
|
semiMajor: parseOptionalUnit(readOptionalTextInput(form, "placement-semiMajor")),
|
|
1764
2138
|
eccentricity: parseNullableNumber(readOptionalTextInput(form, "placement-eccentricity")) ?? undefined,
|
|
@@ -1770,13 +2144,13 @@ function buildPlacementFromForm(form, current) {
|
|
|
1770
2144
|
case "at":
|
|
1771
2145
|
return {
|
|
1772
2146
|
mode,
|
|
1773
|
-
target: target ??
|
|
1774
|
-
reference: parseAtReferenceString(target ??
|
|
2147
|
+
target: target ?? fallbackTarget,
|
|
2148
|
+
reference: parseAtReferenceString(target ?? fallbackTarget),
|
|
1775
2149
|
};
|
|
1776
2150
|
case "surface":
|
|
1777
2151
|
return {
|
|
1778
2152
|
mode,
|
|
1779
|
-
target: target ??
|
|
2153
|
+
target: target ?? fallbackTarget,
|
|
1780
2154
|
};
|
|
1781
2155
|
case "free": {
|
|
1782
2156
|
const freeValue = readOptionalTextInput(form, "placement-free");
|
|
@@ -1826,6 +2200,20 @@ function parseNullableNumber(value) {
|
|
|
1826
2200
|
const parsed = Number(value);
|
|
1827
2201
|
return Number.isFinite(parsed) ? parsed : null;
|
|
1828
2202
|
}
|
|
2203
|
+
function buildViewCameraFromForm(form) {
|
|
2204
|
+
const camera = {
|
|
2205
|
+
azimuth: parseNullableNumber(readOptionalTextInput(form, "viewpoint-camera-azimuth")),
|
|
2206
|
+
elevation: parseNullableNumber(readOptionalTextInput(form, "viewpoint-camera-elevation")),
|
|
2207
|
+
roll: parseNullableNumber(readOptionalTextInput(form, "viewpoint-camera-roll")),
|
|
2208
|
+
distance: parseNullableNumber(readOptionalTextInput(form, "viewpoint-camera-distance")),
|
|
2209
|
+
};
|
|
2210
|
+
return camera.azimuth !== null ||
|
|
2211
|
+
camera.elevation !== null ||
|
|
2212
|
+
camera.roll !== null ||
|
|
2213
|
+
camera.distance !== null
|
|
2214
|
+
? camera
|
|
2215
|
+
: null;
|
|
2216
|
+
}
|
|
1829
2217
|
function parseObjectTypes(value) {
|
|
1830
2218
|
const tokens = splitTokens(value);
|
|
1831
2219
|
return tokens.filter((token) => OBJECT_TYPES.includes(token));
|
|
@@ -1920,9 +2308,48 @@ function renameObjectReferences(document, fromId, toId) {
|
|
|
1920
2308
|
annotation.sourceObjectId = toId;
|
|
1921
2309
|
}
|
|
1922
2310
|
}
|
|
2311
|
+
for (const eventEntry of document.events) {
|
|
2312
|
+
if (eventEntry.targetObjectId === fromId) {
|
|
2313
|
+
eventEntry.targetObjectId = toId;
|
|
2314
|
+
}
|
|
2315
|
+
eventEntry.participantObjectIds = eventEntry.participantObjectIds.map((entry) => entry === fromId ? toId : entry);
|
|
2316
|
+
for (const pose of eventEntry.positions) {
|
|
2317
|
+
if (pose.objectId === fromId) {
|
|
2318
|
+
pose.objectId = toId;
|
|
2319
|
+
}
|
|
2320
|
+
if (pose.placement?.mode === "orbit" && pose.placement.target === fromId) {
|
|
2321
|
+
pose.placement.target = toId;
|
|
2322
|
+
}
|
|
2323
|
+
if (pose.placement?.mode === "surface" && pose.placement.target === fromId) {
|
|
2324
|
+
pose.placement.target = toId;
|
|
2325
|
+
}
|
|
2326
|
+
if (pose.placement?.mode === "at") {
|
|
2327
|
+
const reference = pose.placement.reference;
|
|
2328
|
+
if (reference.kind === "anchor" && reference.objectId === fromId) {
|
|
2329
|
+
reference.objectId = toId;
|
|
2330
|
+
}
|
|
2331
|
+
if (reference.kind === "lagrange") {
|
|
2332
|
+
if (reference.primary === fromId) {
|
|
2333
|
+
reference.primary = toId;
|
|
2334
|
+
}
|
|
2335
|
+
if (reference.secondary === fromId) {
|
|
2336
|
+
reference.secondary = toId;
|
|
2337
|
+
}
|
|
2338
|
+
}
|
|
2339
|
+
pose.placement.target = formatAtReference(reference);
|
|
2340
|
+
}
|
|
2341
|
+
}
|
|
2342
|
+
eventEntry.positions.sort(compareEventPoses);
|
|
2343
|
+
}
|
|
1923
2344
|
}
|
|
1924
2345
|
function removeSelectedNode(document, selection) {
|
|
1925
2346
|
const next = removeAtlasDocumentNode(document, selection);
|
|
2347
|
+
if (selection.kind === "event" && selection.id) {
|
|
2348
|
+
for (const viewpoint of next.system?.viewpoints ?? []) {
|
|
2349
|
+
viewpoint.events = viewpoint.events.filter((eventId) => eventId !== selection.id);
|
|
2350
|
+
}
|
|
2351
|
+
return next;
|
|
2352
|
+
}
|
|
1926
2353
|
if (selection.kind !== "object" || !selection.id) {
|
|
1927
2354
|
return next;
|
|
1928
2355
|
}
|
|
@@ -1959,9 +2386,47 @@ function removeSelectedNode(document, selection) {
|
|
|
1959
2386
|
annotation.sourceObjectId = null;
|
|
1960
2387
|
}
|
|
1961
2388
|
}
|
|
2389
|
+
for (const eventEntry of next.events) {
|
|
2390
|
+
if (eventEntry.targetObjectId === selection.id) {
|
|
2391
|
+
eventEntry.targetObjectId = null;
|
|
2392
|
+
}
|
|
2393
|
+
eventEntry.participantObjectIds = eventEntry.participantObjectIds.filter((entry) => entry !== selection.id);
|
|
2394
|
+
eventEntry.positions = eventEntry.positions.filter((entry) => entry.objectId !== selection.id);
|
|
2395
|
+
for (const pose of eventEntry.positions) {
|
|
2396
|
+
if (pose.placement?.mode === "orbit" && pose.placement.target === selection.id) {
|
|
2397
|
+
pose.placement = null;
|
|
2398
|
+
}
|
|
2399
|
+
if (pose.placement?.mode === "surface" && pose.placement.target === selection.id) {
|
|
2400
|
+
pose.placement = null;
|
|
2401
|
+
}
|
|
2402
|
+
if (pose.placement?.mode === "at") {
|
|
2403
|
+
const reference = pose.placement.reference;
|
|
2404
|
+
const touchesSelection = (reference.kind === "anchor" && reference.objectId === selection.id) ||
|
|
2405
|
+
(reference.kind === "lagrange" &&
|
|
2406
|
+
(reference.primary === selection.id || reference.secondary === selection.id));
|
|
2407
|
+
if (touchesSelection) {
|
|
2408
|
+
pose.placement = null;
|
|
2409
|
+
}
|
|
2410
|
+
}
|
|
2411
|
+
}
|
|
2412
|
+
}
|
|
1962
2413
|
return next;
|
|
1963
2414
|
}
|
|
1964
|
-
function
|
|
2415
|
+
function findEditablePlacementOwner(document, path, objectId) {
|
|
2416
|
+
if (path.kind === "event-pose" && path.id && path.key) {
|
|
2417
|
+
const pose = findEventPose(document, path.id, path.key);
|
|
2418
|
+
if (pose?.placement) {
|
|
2419
|
+
return { placement: pose.placement };
|
|
2420
|
+
}
|
|
2421
|
+
return null;
|
|
2422
|
+
}
|
|
2423
|
+
const object = findObject(document, objectId);
|
|
2424
|
+
if (object?.placement) {
|
|
2425
|
+
return { placement: object.placement };
|
|
2426
|
+
}
|
|
2427
|
+
return null;
|
|
2428
|
+
}
|
|
2429
|
+
function updateOrbitPhase(document, path, objectId, details, pointer) {
|
|
1965
2430
|
const orbit = details.orbit;
|
|
1966
2431
|
if (!orbit || details.object.placement?.mode !== "orbit") {
|
|
1967
2432
|
return document;
|
|
@@ -1972,17 +2437,17 @@ function updateOrbitPhase(document, objectId, details, pointer) {
|
|
|
1972
2437
|
const radians = Math.atan2((unrotated.y - orbit.cy) / Math.max(ry, 1), (unrotated.x - orbit.cx) / Math.max(rx, 1));
|
|
1973
2438
|
const phaseDeg = normalizeDegrees((radians * 180) / Math.PI);
|
|
1974
2439
|
const next = cloneAtlasDocument(document);
|
|
1975
|
-
const
|
|
1976
|
-
if (!
|
|
2440
|
+
const placementOwner = findEditablePlacementOwner(next, path, objectId);
|
|
2441
|
+
if (!placementOwner || placementOwner.placement.mode !== "orbit") {
|
|
1977
2442
|
return document;
|
|
1978
2443
|
}
|
|
1979
|
-
|
|
2444
|
+
placementOwner.placement.phase = {
|
|
1980
2445
|
value: roundNumber(phaseDeg, 2),
|
|
1981
2446
|
unit: "deg",
|
|
1982
2447
|
};
|
|
1983
2448
|
return next;
|
|
1984
2449
|
}
|
|
1985
|
-
function updateOrbitRadius(document, objectId, details, pointer, dragContext) {
|
|
2450
|
+
function updateOrbitRadius(document, path, objectId, details, pointer, dragContext) {
|
|
1986
2451
|
const orbit = details.orbit;
|
|
1987
2452
|
if (!orbit || details.object.placement?.mode !== "orbit" || !dragContext) {
|
|
1988
2453
|
return document;
|
|
@@ -1992,49 +2457,49 @@ function updateOrbitRadius(document, objectId, details, pointer, dragContext) {
|
|
|
1992
2457
|
const nextBaseRadius = Math.max(nextDisplayedRadius - dragContext.radiusOffsetPx, dragContext.innerPx);
|
|
1993
2458
|
const nextMetric = orbitRadiusPxToMetric(nextBaseRadius, dragContext.innerPx, dragContext.stepPx);
|
|
1994
2459
|
const next = cloneAtlasDocument(document);
|
|
1995
|
-
const
|
|
1996
|
-
if (!
|
|
2460
|
+
const placementOwner = findEditablePlacementOwner(next, path, objectId);
|
|
2461
|
+
if (!placementOwner || placementOwner.placement.mode !== "orbit") {
|
|
1997
2462
|
return document;
|
|
1998
2463
|
}
|
|
1999
|
-
const currentValue =
|
|
2000
|
-
|
|
2464
|
+
const currentValue = placementOwner.placement.semiMajor ??
|
|
2465
|
+
placementOwner.placement.distance ?? {
|
|
2001
2466
|
value: 1,
|
|
2002
2467
|
unit: "au",
|
|
2003
2468
|
};
|
|
2004
2469
|
const scaled = distanceMetricToUnitValue(Math.max(nextMetric, 0), dragContext.preferredUnit ?? currentValue.unit);
|
|
2005
|
-
if (
|
|
2006
|
-
|
|
2470
|
+
if (placementOwner.placement.semiMajor) {
|
|
2471
|
+
placementOwner.placement.semiMajor = scaled;
|
|
2007
2472
|
}
|
|
2008
2473
|
else {
|
|
2009
|
-
|
|
2474
|
+
placementOwner.placement.distance = scaled;
|
|
2010
2475
|
}
|
|
2011
2476
|
return next;
|
|
2012
2477
|
}
|
|
2013
|
-
function updateAtReference(document, objectId, scene, pointer) {
|
|
2478
|
+
function updateAtReference(document, path, objectId, scene, pointer) {
|
|
2014
2479
|
const candidate = findNearestAtCandidate(scene, objectId, pointer);
|
|
2015
2480
|
if (!candidate) {
|
|
2016
2481
|
return document;
|
|
2017
2482
|
}
|
|
2018
2483
|
const next = cloneAtlasDocument(document);
|
|
2019
|
-
const
|
|
2020
|
-
if (!
|
|
2484
|
+
const placementOwner = findEditablePlacementOwner(next, path, objectId);
|
|
2485
|
+
if (!placementOwner || placementOwner.placement.mode !== "at") {
|
|
2021
2486
|
return document;
|
|
2022
2487
|
}
|
|
2023
|
-
|
|
2024
|
-
|
|
2488
|
+
placementOwner.placement.reference = candidate.reference;
|
|
2489
|
+
placementOwner.placement.target = formatAtReference(candidate.reference);
|
|
2025
2490
|
return next;
|
|
2026
2491
|
}
|
|
2027
|
-
function updateSurfaceTarget(document, objectId, scene, pointer) {
|
|
2492
|
+
function updateSurfaceTarget(document, path, objectId, scene, pointer) {
|
|
2028
2493
|
const target = findNearestSceneObject(scene, objectId, pointer, (entry) => SURFACE_TARGET_TYPES.has(entry.object.type));
|
|
2029
2494
|
if (!target) {
|
|
2030
2495
|
return document;
|
|
2031
2496
|
}
|
|
2032
2497
|
const next = cloneAtlasDocument(document);
|
|
2033
|
-
const
|
|
2034
|
-
if (!
|
|
2498
|
+
const placementOwner = findEditablePlacementOwner(next, path, objectId);
|
|
2499
|
+
if (!placementOwner || placementOwner.placement.mode !== "surface") {
|
|
2035
2500
|
return document;
|
|
2036
2501
|
}
|
|
2037
|
-
|
|
2502
|
+
placementOwner.placement.target = target.objectId;
|
|
2038
2503
|
return next;
|
|
2039
2504
|
}
|
|
2040
2505
|
function createOrbitRadiusDragContext(document, scene, details) {
|
|
@@ -2042,8 +2507,9 @@ function createOrbitRadiusDragContext(document, scene, details) {
|
|
|
2042
2507
|
return null;
|
|
2043
2508
|
}
|
|
2044
2509
|
const targetId = details.object.placement.target;
|
|
2045
|
-
const siblingCount =
|
|
2046
|
-
entry.placement.target === targetId
|
|
2510
|
+
const siblingCount = scene.objects.filter((entry) => entry.object.placement?.mode === "orbit" &&
|
|
2511
|
+
entry.object.placement.target === targetId &&
|
|
2512
|
+
!entry.hidden).length;
|
|
2047
2513
|
const spacingFactor = layoutPresetSpacingForScene(scene.layoutPreset);
|
|
2048
2514
|
const stepPx = (siblingCount > 2 ? 54 : 64) * spacingFactor * scene.scaleModel.orbitDistanceMultiplier;
|
|
2049
2515
|
const innerPx = details.parent.radius +
|
|
@@ -2059,28 +2525,28 @@ function createOrbitRadiusDragContext(document, scene, details) {
|
|
|
2059
2525
|
preferredUnit: currentValue?.unit ?? null,
|
|
2060
2526
|
};
|
|
2061
2527
|
}
|
|
2062
|
-
function updateFreeDistance(document, objectId, scene, details, pointer) {
|
|
2528
|
+
function updateFreeDistance(document, path, objectId, scene, details, pointer) {
|
|
2063
2529
|
if (details.object.placement?.mode !== "free") {
|
|
2064
2530
|
return document;
|
|
2065
2531
|
}
|
|
2066
2532
|
const railX = scene.width - scene.padding - 140;
|
|
2067
2533
|
const offsetPx = Math.max(0, railX - pointer.x);
|
|
2068
2534
|
const next = cloneAtlasDocument(document);
|
|
2069
|
-
const
|
|
2070
|
-
if (!
|
|
2535
|
+
const placementOwner = findEditablePlacementOwner(next, path, objectId);
|
|
2536
|
+
if (!placementOwner || placementOwner.placement.mode !== "free") {
|
|
2071
2537
|
return document;
|
|
2072
2538
|
}
|
|
2073
|
-
const preferredUnit = normalizeFreeDistanceUnit(
|
|
2539
|
+
const preferredUnit = normalizeFreeDistanceUnit(placementOwner.placement.distance?.unit ?? null);
|
|
2074
2540
|
const metric = offsetPx / Math.max(FREE_DISTANCE_PIXEL_FACTOR * scene.scaleModel.freePlacementMultiplier, 1);
|
|
2075
2541
|
if (metric < 0.01) {
|
|
2076
|
-
|
|
2077
|
-
if (!
|
|
2078
|
-
delete
|
|
2542
|
+
placementOwner.placement.distance = undefined;
|
|
2543
|
+
if (!placementOwner.placement.descriptor) {
|
|
2544
|
+
delete placementOwner.placement.descriptor;
|
|
2079
2545
|
}
|
|
2080
2546
|
return next;
|
|
2081
2547
|
}
|
|
2082
|
-
|
|
2083
|
-
delete
|
|
2548
|
+
placementOwner.placement.distance = distanceMetricToUnitValue(metric, preferredUnit);
|
|
2549
|
+
delete placementOwner.placement.descriptor;
|
|
2084
2550
|
return next;
|
|
2085
2551
|
}
|
|
2086
2552
|
function findNearestSceneObject(scene, selectedObjectId, pointer, predicate = () => true) {
|
|
@@ -2356,9 +2822,85 @@ function mapDiagnosticFieldToInputNames(selection, field) {
|
|
|
2356
2822
|
return ["viewpoint-zoom"];
|
|
2357
2823
|
case "rotationDeg":
|
|
2358
2824
|
return ["viewpoint-rotation"];
|
|
2825
|
+
case "camera":
|
|
2826
|
+
return [
|
|
2827
|
+
"viewpoint-camera-azimuth",
|
|
2828
|
+
"viewpoint-camera-elevation",
|
|
2829
|
+
"viewpoint-camera-roll",
|
|
2830
|
+
"viewpoint-camera-distance",
|
|
2831
|
+
];
|
|
2832
|
+
case "camera.azimuth":
|
|
2833
|
+
return ["viewpoint-camera-azimuth"];
|
|
2834
|
+
case "camera.elevation":
|
|
2835
|
+
return ["viewpoint-camera-elevation"];
|
|
2836
|
+
case "camera.roll":
|
|
2837
|
+
return ["viewpoint-camera-roll"];
|
|
2838
|
+
case "camera.distance":
|
|
2839
|
+
return ["viewpoint-camera-distance"];
|
|
2840
|
+
case "events":
|
|
2841
|
+
return ["viewpoint-events"];
|
|
2842
|
+
default:
|
|
2843
|
+
return [];
|
|
2844
|
+
}
|
|
2845
|
+
case "event":
|
|
2846
|
+
switch (field) {
|
|
2847
|
+
case "id":
|
|
2848
|
+
return ["event-id"];
|
|
2849
|
+
case "kind":
|
|
2850
|
+
return ["event-kind"];
|
|
2851
|
+
case "label":
|
|
2852
|
+
return ["event-label"];
|
|
2853
|
+
case "summary":
|
|
2854
|
+
return ["event-summary"];
|
|
2855
|
+
case "targetObjectId":
|
|
2856
|
+
case "target":
|
|
2857
|
+
return ["event-target"];
|
|
2858
|
+
case "participantObjectIds":
|
|
2859
|
+
case "participants":
|
|
2860
|
+
return ["event-participants"];
|
|
2861
|
+
case "timing":
|
|
2862
|
+
return ["event-timing"];
|
|
2863
|
+
case "visibility":
|
|
2864
|
+
return ["event-visibility"];
|
|
2865
|
+
case "epoch":
|
|
2866
|
+
return ["event-epoch"];
|
|
2867
|
+
case "referencePlane":
|
|
2868
|
+
return ["event-referencePlane"];
|
|
2869
|
+
case "tags":
|
|
2870
|
+
return ["event-tags"];
|
|
2871
|
+
case "color":
|
|
2872
|
+
return ["event-color"];
|
|
2873
|
+
case "hidden":
|
|
2874
|
+
return ["event-hidden"];
|
|
2359
2875
|
default:
|
|
2360
2876
|
return [];
|
|
2361
2877
|
}
|
|
2878
|
+
case "event-pose":
|
|
2879
|
+
if (field === "objectId") {
|
|
2880
|
+
return ["pose-object-id"];
|
|
2881
|
+
}
|
|
2882
|
+
if (field === "placement") {
|
|
2883
|
+
return ["placement-mode"];
|
|
2884
|
+
}
|
|
2885
|
+
if (field === "reference" || field === "target") {
|
|
2886
|
+
return ["placement-target"];
|
|
2887
|
+
}
|
|
2888
|
+
if (field === "descriptor") {
|
|
2889
|
+
return ["placement-free"];
|
|
2890
|
+
}
|
|
2891
|
+
if (PLACEMENT_DIAGNOSTIC_FIELDS.has(field)) {
|
|
2892
|
+
return [`placement-${field}`];
|
|
2893
|
+
}
|
|
2894
|
+
if (field === "inner" || field === "outer") {
|
|
2895
|
+
return [`prop-${field}`];
|
|
2896
|
+
}
|
|
2897
|
+
if (field === "epoch") {
|
|
2898
|
+
return ["pose-epoch"];
|
|
2899
|
+
}
|
|
2900
|
+
if (field === "referencePlane") {
|
|
2901
|
+
return ["pose-referencePlane"];
|
|
2902
|
+
}
|
|
2903
|
+
return [];
|
|
2362
2904
|
case "annotation":
|
|
2363
2905
|
switch (field) {
|
|
2364
2906
|
case "id":
|
|
@@ -2444,6 +2986,10 @@ function describePath(path) {
|
|
|
2444
2986
|
return `Metadata: ${path.key ?? ""}`;
|
|
2445
2987
|
case "group":
|
|
2446
2988
|
return `Group: ${path.id ?? ""}`;
|
|
2989
|
+
case "event":
|
|
2990
|
+
return `Event: ${path.id ?? ""}`;
|
|
2991
|
+
case "event-pose":
|
|
2992
|
+
return `Event Pose: ${path.id ?? ""} / ${path.key ?? ""}`;
|
|
2447
2993
|
case "object":
|
|
2448
2994
|
return `Object: ${path.id ?? ""}`;
|
|
2449
2995
|
case "viewpoint":
|
|
@@ -2455,11 +3001,72 @@ function describePath(path) {
|
|
|
2455
3001
|
}
|
|
2456
3002
|
}
|
|
2457
3003
|
function selectionKey(path) {
|
|
2458
|
-
return path ? `${path.kind}:${path.id ?? path.key ?? ""}` : null;
|
|
3004
|
+
return path ? `${path.kind}:${path.id ?? ""}:${path.key ?? ""}` : null;
|
|
3005
|
+
}
|
|
3006
|
+
function selectionEventId(path) {
|
|
3007
|
+
if (!path) {
|
|
3008
|
+
return null;
|
|
3009
|
+
}
|
|
3010
|
+
return path.kind === "event" || path.kind === "event-pose" ? path.id ?? null : null;
|
|
2459
3011
|
}
|
|
2460
3012
|
function compareObjects(left, right) {
|
|
2461
3013
|
return left.id.localeCompare(right.id);
|
|
2462
3014
|
}
|
|
3015
|
+
function compareEvents(left, right) {
|
|
3016
|
+
return left.id.localeCompare(right.id);
|
|
3017
|
+
}
|
|
3018
|
+
function compareEventPoses(left, right) {
|
|
3019
|
+
return left.objectId.localeCompare(right.objectId);
|
|
3020
|
+
}
|
|
3021
|
+
function findEvent(document, eventId) {
|
|
3022
|
+
return document.events.find((entry) => entry.id === eventId) ?? null;
|
|
3023
|
+
}
|
|
3024
|
+
function findEventPose(document, eventId, objectId) {
|
|
3025
|
+
return findEvent(document, eventId)?.positions.find((entry) => entry.objectId === objectId) ?? null;
|
|
3026
|
+
}
|
|
3027
|
+
function findObject(document, objectId) {
|
|
3028
|
+
return document.objects.find((entry) => entry.id === objectId) ?? null;
|
|
3029
|
+
}
|
|
3030
|
+
function addEventPose(document, eventId) {
|
|
3031
|
+
const next = cloneAtlasDocument(document);
|
|
3032
|
+
const eventEntry = next.events.find((entry) => entry.id === eventId);
|
|
3033
|
+
if (!eventEntry) {
|
|
3034
|
+
return document;
|
|
3035
|
+
}
|
|
3036
|
+
const baseObject = next.objects.find((object) => !eventEntry.positions.some((pose) => pose.objectId === object.id)) ??
|
|
3037
|
+
next.objects[0];
|
|
3038
|
+
if (!baseObject) {
|
|
3039
|
+
return document;
|
|
3040
|
+
}
|
|
3041
|
+
if (eventEntry.targetObjectId !== baseObject.id &&
|
|
3042
|
+
!eventEntry.participantObjectIds.includes(baseObject.id)) {
|
|
3043
|
+
eventEntry.participantObjectIds.push(baseObject.id);
|
|
3044
|
+
eventEntry.participantObjectIds.sort((left, right) => left.localeCompare(right));
|
|
3045
|
+
}
|
|
3046
|
+
eventEntry.positions.push(createEventPoseFromObject(baseObject));
|
|
3047
|
+
eventEntry.positions.sort(compareEventPoses);
|
|
3048
|
+
return next;
|
|
3049
|
+
}
|
|
3050
|
+
function createEventPoseFromObject(object) {
|
|
3051
|
+
return {
|
|
3052
|
+
objectId: object.id,
|
|
3053
|
+
placement: object.placement ? structuredClone(object.placement) : null,
|
|
3054
|
+
inner: readUnitValue(object.properties.inner),
|
|
3055
|
+
outer: readUnitValue(object.properties.outer),
|
|
3056
|
+
};
|
|
3057
|
+
}
|
|
3058
|
+
function syncEventViewpointReferences(document, previousEventId, nextEventId, viewpointIds) {
|
|
3059
|
+
const desired = new Set(viewpointIds);
|
|
3060
|
+
for (const viewpoint of document.system?.viewpoints ?? []) {
|
|
3061
|
+
const currentIds = new Set(viewpoint.events);
|
|
3062
|
+
currentIds.delete(previousEventId);
|
|
3063
|
+
currentIds.delete(nextEventId);
|
|
3064
|
+
if (desired.has(viewpoint.id)) {
|
|
3065
|
+
currentIds.add(nextEventId);
|
|
3066
|
+
}
|
|
3067
|
+
viewpoint.events = [...currentIds].sort((left, right) => left.localeCompare(right));
|
|
3068
|
+
}
|
|
3069
|
+
}
|
|
2463
3070
|
function createUniqueId(prefix, existing) {
|
|
2464
3071
|
const safePrefix = prefix.trim() || "item";
|
|
2465
3072
|
let counter = 1;
|
|
@@ -2494,6 +3101,9 @@ function readUnitProperty(value) {
|
|
|
2494
3101
|
? formatUnitValue(value)
|
|
2495
3102
|
: "";
|
|
2496
3103
|
}
|
|
3104
|
+
function readUnitValue(value) {
|
|
3105
|
+
return value && typeof value === "object" && "value" in value ? value : undefined;
|
|
3106
|
+
}
|
|
2497
3107
|
function readNumberProperty(value) {
|
|
2498
3108
|
return typeof value === "number" ? String(value) : "";
|
|
2499
3109
|
}
|
|
@@ -2803,6 +3413,12 @@ function installEditorStyles() {
|
|
|
2803
3413
|
.wo-editor-overlay-diagnostic-warning { border: 1px solid rgba(240, 180, 100, 0.24); }
|
|
2804
3414
|
.wo-editor-outline { display: grid; gap: 14px; }
|
|
2805
3415
|
.wo-editor-outline-section { display: grid; gap: 8px; }
|
|
3416
|
+
.wo-editor-outline-group { display: grid; gap: 6px; }
|
|
3417
|
+
.wo-editor-outline-children {
|
|
3418
|
+
display: grid;
|
|
3419
|
+
gap: 6px;
|
|
3420
|
+
padding-left: 16px;
|
|
3421
|
+
}
|
|
2806
3422
|
.wo-editor-outline-section h3 {
|
|
2807
3423
|
margin: 0;
|
|
2808
3424
|
color: rgba(237,246,255,0.68);
|
|
@@ -2842,6 +3458,27 @@ function installEditorStyles() {
|
|
|
2842
3458
|
background: rgba(255, 120, 120, 0.18);
|
|
2843
3459
|
color: #ffb2b2;
|
|
2844
3460
|
}
|
|
3461
|
+
.wo-editor-inline-list { display: grid; gap: 8px; }
|
|
3462
|
+
.wo-editor-inline-actions {
|
|
3463
|
+
display: flex;
|
|
3464
|
+
flex-wrap: wrap;
|
|
3465
|
+
gap: 10px;
|
|
3466
|
+
margin-top: 12px;
|
|
3467
|
+
}
|
|
3468
|
+
.wo-editor-inline-actions button {
|
|
3469
|
+
border: 1px solid rgba(255,255,255,0.14);
|
|
3470
|
+
border-radius: 999px;
|
|
3471
|
+
background: rgba(255,255,255,0.06);
|
|
3472
|
+
color: #edf6ff;
|
|
3473
|
+
cursor: pointer;
|
|
3474
|
+
font: 600 12px/1.2 "Segoe UI Variable", "Segoe UI", sans-serif;
|
|
3475
|
+
padding: 8px 12px;
|
|
3476
|
+
}
|
|
3477
|
+
.wo-editor-inline-note {
|
|
3478
|
+
margin: 0 0 12px;
|
|
3479
|
+
color: rgba(237,246,255,0.72);
|
|
3480
|
+
font: 500 12px/1.5 "Segoe UI Variable", "Segoe UI", sans-serif;
|
|
3481
|
+
}
|
|
2845
3482
|
.wo-editor-diagnostics { display: grid; gap: 10px; }
|
|
2846
3483
|
.wo-editor-diagnostic {
|
|
2847
3484
|
display: grid;
|