worldorbit 3.0.5 → 3.0.7
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 +2 -2
- package/dist/browser/viewer/dist/embed.js +86 -13
- package/dist/browser/viewer/dist/runtime-3d.js +396 -117
- package/dist/browser/viewer/dist/theme.js +27 -0
- package/dist/browser/viewer/dist/types.d.ts +17 -0
- package/dist/browser/viewer/dist/viewer.js +268 -12
- package/dist/unpkg/viewer/dist/embed.js +86 -13
- package/dist/unpkg/viewer/dist/runtime-3d.js +396 -117
- package/dist/unpkg/viewer/dist/theme.js +27 -0
- package/dist/unpkg/viewer/dist/types.d.ts +17 -0
- package/dist/unpkg/viewer/dist/viewer.js +268 -12
- package/dist/unpkg/worldorbit-editor.min.js +368 -314
- package/dist/unpkg/worldorbit-markdown.min.js +13 -13
- package/dist/unpkg/worldorbit-viewer.min.js +272 -218
- package/dist/unpkg/worldorbit.js +735 -127
- package/dist/unpkg/worldorbit.min.js +265 -211
- package/package.json +1 -1
- package/packages/viewer/dist/embed.js +86 -13
- package/packages/viewer/dist/runtime-3d.js +396 -117
- package/packages/viewer/dist/theme.js +27 -0
- package/packages/viewer/dist/types.d.ts +17 -0
- package/packages/viewer/dist/viewer.js +268 -12
|
@@ -30,6 +30,15 @@ const THEME_PRESETS = {
|
|
|
30
30
|
starCore: "#ffcc67",
|
|
31
31
|
starStroke: "rgba(255, 245, 203, 0.85)",
|
|
32
32
|
starGlow: "#ffe8a3",
|
|
33
|
+
spaceFog: "#07131d",
|
|
34
|
+
starfield: "rgba(226, 239, 255, 0.9)",
|
|
35
|
+
starfieldDim: "rgba(164, 194, 228, 0.45)",
|
|
36
|
+
objectSpecular: "#f5f8ff",
|
|
37
|
+
orbitOpacity: 0.34,
|
|
38
|
+
orbitBandOpacity: 0.24,
|
|
39
|
+
selectionHalo: "rgba(255, 214, 139, 0.9)",
|
|
40
|
+
atmosphere: "rgba(143, 202, 255, 0.4)",
|
|
41
|
+
cometTail: "rgba(193, 243, 255, 0.7)",
|
|
33
42
|
fontFamily: "\"Segoe UI Variable\", \"Bahnschrift\", sans-serif",
|
|
34
43
|
displayFont: "\"Bahnschrift\", \"Segoe UI Variable\", sans-serif"
|
|
35
44
|
},
|
|
@@ -53,6 +62,15 @@ const THEME_PRESETS = {
|
|
|
53
62
|
starCore: "#e5f98c",
|
|
54
63
|
starStroke: "rgba(246, 255, 217, 0.9)",
|
|
55
64
|
starGlow: "#fffab4",
|
|
65
|
+
spaceFog: "#071723",
|
|
66
|
+
starfield: "rgba(220, 255, 245, 0.9)",
|
|
67
|
+
starfieldDim: "rgba(124, 212, 195, 0.42)",
|
|
68
|
+
objectSpecular: "#ecfffb",
|
|
69
|
+
orbitOpacity: 0.3,
|
|
70
|
+
orbitBandOpacity: 0.22,
|
|
71
|
+
selectionHalo: "rgba(120, 255, 215, 0.85)",
|
|
72
|
+
atmosphere: "rgba(120, 255, 215, 0.32)",
|
|
73
|
+
cometTail: "rgba(181, 255, 236, 0.68)",
|
|
56
74
|
fontFamily: "\"Segoe UI Variable\", \"Bahnschrift\", sans-serif",
|
|
57
75
|
displayFont: "\"Bahnschrift\", \"Segoe UI Variable\", sans-serif"
|
|
58
76
|
},
|
|
@@ -76,6 +94,15 @@ const THEME_PRESETS = {
|
|
|
76
94
|
starCore: "#ffb766",
|
|
77
95
|
starStroke: "rgba(255, 236, 205, 0.88)",
|
|
78
96
|
starGlow: "#ffe2ad",
|
|
97
|
+
spaceFog: "#1c0d12",
|
|
98
|
+
starfield: "rgba(255, 232, 214, 0.88)",
|
|
99
|
+
starfieldDim: "rgba(255, 176, 138, 0.38)",
|
|
100
|
+
objectSpecular: "#fff0e6",
|
|
101
|
+
orbitOpacity: 0.3,
|
|
102
|
+
orbitBandOpacity: 0.24,
|
|
103
|
+
selectionHalo: "rgba(255, 178, 125, 0.85)",
|
|
104
|
+
atmosphere: "rgba(255, 190, 140, 0.26)",
|
|
105
|
+
cometTail: "rgba(255, 214, 173, 0.62)",
|
|
79
106
|
fontFamily: "\"Segoe UI Variable\", \"Bahnschrift\", sans-serif",
|
|
80
107
|
displayFont: "\"Bahnschrift\", \"Segoe UI Variable\", sans-serif"
|
|
81
108
|
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import type { CoordinatePoint, RenderOrbitVisual, RenderProjectionFallback, RenderPresetName, RenderSceneEvent, RenderSceneGroup, RenderSceneLabel, RenderScaleModel, RenderScene, RenderSceneObject, RenderSceneViewpoint, SceneRenderOptions, SpatialScene, ViewProjection, WorldOrbitObject, WorldOrbitDocument, WorldOrbitViewCamera } from "@worldorbit/core";
|
|
2
2
|
export type WorldOrbitThemeName = "atlas" | "nightglass" | "ember";
|
|
3
3
|
export type WorldOrbitViewMode = "2d" | "3d";
|
|
4
|
+
export type WorldOrbit3DQuality = "low" | "balanced" | "high";
|
|
5
|
+
export type WorldOrbit3DStyle = "symbolic" | "cinematic";
|
|
4
6
|
export type WorldOrbitEmbedMode = "static" | "interactive" | "interactive-2d" | "interactive-3d";
|
|
5
7
|
export type TooltipMode = "hover" | "pinned" | "disabled";
|
|
6
8
|
export interface WorldOrbitTheme {
|
|
@@ -23,6 +25,15 @@ export interface WorldOrbitTheme {
|
|
|
23
25
|
starCore: string;
|
|
24
26
|
starStroke: string;
|
|
25
27
|
starGlow: string;
|
|
28
|
+
spaceFog: string;
|
|
29
|
+
starfield: string;
|
|
30
|
+
starfieldDim: string;
|
|
31
|
+
objectSpecular: string;
|
|
32
|
+
orbitOpacity: number;
|
|
33
|
+
orbitBandOpacity: number;
|
|
34
|
+
selectionHalo: string;
|
|
35
|
+
atmosphere: string;
|
|
36
|
+
cometTail: string;
|
|
26
37
|
fontFamily: string;
|
|
27
38
|
displayFont: string;
|
|
28
39
|
}
|
|
@@ -64,6 +75,8 @@ export interface ViewerRenderOptions extends Omit<SvgRenderOptions, "selectedObj
|
|
|
64
75
|
projection?: "document" | ViewProjection;
|
|
65
76
|
scaleModel?: Partial<RenderScaleModel>;
|
|
66
77
|
viewMode?: WorldOrbitViewMode;
|
|
78
|
+
quality?: WorldOrbit3DQuality;
|
|
79
|
+
style3d?: WorldOrbit3DStyle;
|
|
67
80
|
}
|
|
68
81
|
export interface ViewerState {
|
|
69
82
|
scale: number;
|
|
@@ -118,6 +131,8 @@ export interface ViewerAtlasState {
|
|
|
118
131
|
scaleModel?: Partial<RenderScaleModel>;
|
|
119
132
|
activeEventId?: string | null;
|
|
120
133
|
viewMode?: WorldOrbitViewMode;
|
|
134
|
+
quality?: WorldOrbit3DQuality;
|
|
135
|
+
style3d?: WorldOrbit3DStyle;
|
|
121
136
|
};
|
|
122
137
|
filter: ViewerFilter | null;
|
|
123
138
|
}
|
|
@@ -274,6 +289,8 @@ export interface WorldOrbitEmbedPayload {
|
|
|
274
289
|
subtitle?: string;
|
|
275
290
|
preset?: SceneRenderOptions["preset"];
|
|
276
291
|
viewMode?: WorldOrbitViewMode;
|
|
292
|
+
quality?: WorldOrbit3DQuality;
|
|
293
|
+
style3d?: WorldOrbit3DStyle;
|
|
277
294
|
initialViewpointId?: string;
|
|
278
295
|
initialSelectionObjectId?: string;
|
|
279
296
|
initialFilter?: ViewerFilter | null;
|
|
@@ -45,6 +45,8 @@ export function createInteractiveViewer(container, options) {
|
|
|
45
45
|
preset: options.preset,
|
|
46
46
|
projection: options.projection,
|
|
47
47
|
viewMode: options.viewMode ?? "2d",
|
|
48
|
+
quality: options.quality ?? "balanced",
|
|
49
|
+
style3d: options.style3d ?? "symbolic",
|
|
48
50
|
camera: options.camera ? { ...options.camera } : null,
|
|
49
51
|
scaleModel: options.scaleModel ? { ...options.scaleModel } : undefined,
|
|
50
52
|
theme: options.theme,
|
|
@@ -66,6 +68,7 @@ export function createInteractiveViewer(container, options) {
|
|
|
66
68
|
let cameraRoot = null;
|
|
67
69
|
let runtime3d = null;
|
|
68
70
|
let minimapRoot = null;
|
|
71
|
+
let labelRoot = null;
|
|
69
72
|
let tooltipRoot = null;
|
|
70
73
|
let suppressClick = false;
|
|
71
74
|
let activePointerId = null;
|
|
@@ -90,7 +93,7 @@ export function createInteractiveViewer(container, options) {
|
|
|
90
93
|
if (previousTabIndex === null) {
|
|
91
94
|
container.tabIndex = 0;
|
|
92
95
|
}
|
|
93
|
-
|
|
96
|
+
installViewerOverlayStyles();
|
|
94
97
|
container.classList.add("wo-viewer-container");
|
|
95
98
|
container.style.touchAction = behavior.touch ? "none" : previousTouchAction;
|
|
96
99
|
if (!container.style.position) {
|
|
@@ -671,6 +674,8 @@ export function createInteractiveViewer(container, options) {
|
|
|
671
674
|
stopAnimationLoop();
|
|
672
675
|
runtime3d?.destroy();
|
|
673
676
|
runtime3d = null;
|
|
677
|
+
labelRoot?.remove();
|
|
678
|
+
labelRoot = null;
|
|
674
679
|
tooltipRoot?.remove();
|
|
675
680
|
tooltipRoot = null;
|
|
676
681
|
minimapRoot?.remove();
|
|
@@ -704,6 +709,7 @@ export function createInteractiveViewer(container, options) {
|
|
|
704
709
|
svgElement = null;
|
|
705
710
|
cameraRoot = null;
|
|
706
711
|
minimapRoot = null;
|
|
712
|
+
labelRoot = null;
|
|
707
713
|
tooltipRoot = null;
|
|
708
714
|
if (is3DView()) {
|
|
709
715
|
spatialScene = spatialScene ?? renderSpatialSceneFromInput(currentInput, renderOptions, providedSpatialScene);
|
|
@@ -723,6 +729,10 @@ export function createInteractiveViewer(container, options) {
|
|
|
723
729
|
minimapRoot.dataset.worldorbitMinimapRoot = "true";
|
|
724
730
|
container.append(minimapRoot);
|
|
725
731
|
}
|
|
732
|
+
labelRoot = document.createElement("div");
|
|
733
|
+
labelRoot.className = "wo-viewer-label-root";
|
|
734
|
+
labelRoot.dataset.worldorbitLabelRoot = "true";
|
|
735
|
+
container.append(labelRoot);
|
|
726
736
|
if (behavior.tooltipMode !== "disabled") {
|
|
727
737
|
tooltipRoot = document.createElement("div");
|
|
728
738
|
tooltipRoot.className = "wo-viewer-tooltip-root";
|
|
@@ -734,6 +744,7 @@ export function createInteractiveViewer(container, options) {
|
|
|
734
744
|
if (!is3DView() && (!svgElement || !cameraRoot)) {
|
|
735
745
|
throw new Error("Interactive viewer could not locate the rendered SVG camera root.");
|
|
736
746
|
}
|
|
747
|
+
suppressStaticLabelLayers();
|
|
737
748
|
state = resetView
|
|
738
749
|
? is3DView()
|
|
739
750
|
? { ...DEFAULT_VIEWER_STATE }
|
|
@@ -783,14 +794,15 @@ export function createInteractiveViewer(container, options) {
|
|
|
783
794
|
return;
|
|
784
795
|
}
|
|
785
796
|
cameraRoot.setAttribute("transform", composeViewerTransform(scene, state));
|
|
797
|
+
updateScreenLabels();
|
|
786
798
|
updateMinimap();
|
|
787
799
|
updateTooltip();
|
|
788
800
|
}
|
|
789
801
|
function applySelection(objectId, emitCallback = true) {
|
|
790
802
|
if (!is3DView() && state.selectedObjectId) {
|
|
791
|
-
container
|
|
792
|
-
.
|
|
793
|
-
|
|
803
|
+
for (const element of container.querySelectorAll(`[data-object-id="${cssEscape(state.selectedObjectId)}"]`)) {
|
|
804
|
+
element.classList.remove("wo-object-selected");
|
|
805
|
+
}
|
|
794
806
|
}
|
|
795
807
|
state = {
|
|
796
808
|
...state,
|
|
@@ -799,9 +811,9 @@ export function createInteractiveViewer(container, options) {
|
|
|
799
811
|
: null,
|
|
800
812
|
};
|
|
801
813
|
if (!is3DView() && state.selectedObjectId) {
|
|
802
|
-
container
|
|
803
|
-
.
|
|
804
|
-
|
|
814
|
+
for (const element of container.querySelectorAll(`[data-object-id="${cssEscape(state.selectedObjectId)}"]`)) {
|
|
815
|
+
element.classList.add("wo-object-selected");
|
|
816
|
+
}
|
|
805
817
|
}
|
|
806
818
|
syncAtlasHighlights();
|
|
807
819
|
updateTooltip();
|
|
@@ -1160,15 +1172,18 @@ export function createInteractiveViewer(container, options) {
|
|
|
1160
1172
|
};
|
|
1161
1173
|
}
|
|
1162
1174
|
function project2DTooltipPoint(renderObject) {
|
|
1163
|
-
if (!svgElement) {
|
|
1164
|
-
return null;
|
|
1165
|
-
}
|
|
1166
1175
|
const anchor = {
|
|
1167
1176
|
x: renderObject.anchorX ?? renderObject.x,
|
|
1168
1177
|
y: renderObject.anchorY ??
|
|
1169
1178
|
renderObject.y - Math.max(renderObject.visualRadius, renderObject.radius),
|
|
1170
1179
|
};
|
|
1171
|
-
|
|
1180
|
+
return project2DScenePointToContainer(anchor);
|
|
1181
|
+
}
|
|
1182
|
+
function project2DScenePointToContainer(point) {
|
|
1183
|
+
if (!svgElement) {
|
|
1184
|
+
return null;
|
|
1185
|
+
}
|
|
1186
|
+
const viewportPoint = projectWorldPoint(point);
|
|
1172
1187
|
const svgRect = svgElement.getBoundingClientRect();
|
|
1173
1188
|
const containerRect = container.getBoundingClientRect();
|
|
1174
1189
|
return {
|
|
@@ -1274,9 +1289,192 @@ export function createInteractiveViewer(container, options) {
|
|
|
1274
1289
|
state,
|
|
1275
1290
|
timeSeconds: animationState.timeSeconds,
|
|
1276
1291
|
});
|
|
1292
|
+
updateScreenLabels();
|
|
1277
1293
|
updateMinimap();
|
|
1278
1294
|
updateTooltip();
|
|
1279
1295
|
}
|
|
1296
|
+
function suppressStaticLabelLayers() {
|
|
1297
|
+
if (is3DView()) {
|
|
1298
|
+
return;
|
|
1299
|
+
}
|
|
1300
|
+
container
|
|
1301
|
+
.querySelector('[data-layer-id="labels"]')
|
|
1302
|
+
?.setAttribute("display", "none");
|
|
1303
|
+
for (const element of container.querySelectorAll(".wo-event-label")) {
|
|
1304
|
+
element.setAttribute("display", "none");
|
|
1305
|
+
}
|
|
1306
|
+
}
|
|
1307
|
+
function updateScreenLabels() {
|
|
1308
|
+
if (!labelRoot) {
|
|
1309
|
+
return;
|
|
1310
|
+
}
|
|
1311
|
+
const descriptors = buildScreenLabelDescriptors();
|
|
1312
|
+
labelRoot.replaceChildren(...descriptors.map((descriptor) => createScreenLabelElement(descriptor)));
|
|
1313
|
+
labelRoot.hidden = descriptors.length === 0;
|
|
1314
|
+
}
|
|
1315
|
+
function buildScreenLabelDescriptors() {
|
|
1316
|
+
const descriptors = [];
|
|
1317
|
+
const visibleObjectIds = getVisibleObjectIds();
|
|
1318
|
+
if (layerEnabled("labels")) {
|
|
1319
|
+
for (const label of scene.labels) {
|
|
1320
|
+
if (label.hidden || !visibleObjectIds.has(label.objectId)) {
|
|
1321
|
+
continue;
|
|
1322
|
+
}
|
|
1323
|
+
if (is3DView() && !shouldRender3DLabel(label.objectId, visibleObjectIds)) {
|
|
1324
|
+
continue;
|
|
1325
|
+
}
|
|
1326
|
+
const point = is3DView()
|
|
1327
|
+
? runtime3d?.projectObjectToContainer(label.objectId) ?? null
|
|
1328
|
+
: project2DScenePointToContainer({ x: label.x, y: label.y });
|
|
1329
|
+
if (!point) {
|
|
1330
|
+
continue;
|
|
1331
|
+
}
|
|
1332
|
+
descriptors.push({
|
|
1333
|
+
key: `object:${label.renderId}`,
|
|
1334
|
+
kind: "object",
|
|
1335
|
+
point: is3DView()
|
|
1336
|
+
? { x: point.x, y: point.y - 18 }
|
|
1337
|
+
: point,
|
|
1338
|
+
textAnchor: label.textAnchor,
|
|
1339
|
+
objectId: label.objectId,
|
|
1340
|
+
primaryText: label.label,
|
|
1341
|
+
secondaryText: label.secondaryLabel,
|
|
1342
|
+
secondaryOffset: Math.max(label.secondaryY - label.y, 12),
|
|
1343
|
+
});
|
|
1344
|
+
}
|
|
1345
|
+
}
|
|
1346
|
+
if (!is3DView() && layerEnabled("events")) {
|
|
1347
|
+
for (const event of scene.events) {
|
|
1348
|
+
if (event.hidden || !isEventVisible(event, visibleObjectIds)) {
|
|
1349
|
+
continue;
|
|
1350
|
+
}
|
|
1351
|
+
const point = project2DScenePointToContainer({ x: event.x, y: event.y - 10 });
|
|
1352
|
+
if (!point) {
|
|
1353
|
+
continue;
|
|
1354
|
+
}
|
|
1355
|
+
descriptors.push({
|
|
1356
|
+
key: `event:${event.renderId}`,
|
|
1357
|
+
kind: "event",
|
|
1358
|
+
point,
|
|
1359
|
+
textAnchor: "middle",
|
|
1360
|
+
primaryText: event.event.label || event.event.id,
|
|
1361
|
+
});
|
|
1362
|
+
}
|
|
1363
|
+
}
|
|
1364
|
+
return descriptors;
|
|
1365
|
+
}
|
|
1366
|
+
function isEventVisible(event, visibleObjectIds) {
|
|
1367
|
+
return event.objectIds.some((objectId) => visibleObjectIds.has(objectId));
|
|
1368
|
+
}
|
|
1369
|
+
function shouldRender3DLabel(objectId, visibleObjectIds) {
|
|
1370
|
+
if (!is3DView()) {
|
|
1371
|
+
return true;
|
|
1372
|
+
}
|
|
1373
|
+
if (objectId === state.selectedObjectId || objectId === hoveredObjectId) {
|
|
1374
|
+
return true;
|
|
1375
|
+
}
|
|
1376
|
+
const object = getObjectById(objectId);
|
|
1377
|
+
if (!object || object.hidden || !visibleObjectIds.has(objectId)) {
|
|
1378
|
+
return false;
|
|
1379
|
+
}
|
|
1380
|
+
if (object.object.type === "star") {
|
|
1381
|
+
return true;
|
|
1382
|
+
}
|
|
1383
|
+
const selected = state.selectedObjectId ? buildObjectDetails(state.selectedObjectId) : null;
|
|
1384
|
+
const hovered = hoveredObjectId ? buildObjectDetails(hoveredObjectId) : null;
|
|
1385
|
+
const selectedFocus = selected
|
|
1386
|
+
? new Set([
|
|
1387
|
+
selected.objectId,
|
|
1388
|
+
...selected.renderObject.ancestorIds,
|
|
1389
|
+
...selected.renderObject.childIds,
|
|
1390
|
+
])
|
|
1391
|
+
: null;
|
|
1392
|
+
const hoveredFocus = hovered
|
|
1393
|
+
? new Set([
|
|
1394
|
+
hovered.objectId,
|
|
1395
|
+
...hovered.renderObject.ancestorIds,
|
|
1396
|
+
...hovered.renderObject.childIds,
|
|
1397
|
+
])
|
|
1398
|
+
: null;
|
|
1399
|
+
if (selectedFocus?.has(objectId) || hoveredFocus?.has(objectId)) {
|
|
1400
|
+
return true;
|
|
1401
|
+
}
|
|
1402
|
+
if (object.semanticGroupIds.length > 0 && object.visualRadius >= 12) {
|
|
1403
|
+
return true;
|
|
1404
|
+
}
|
|
1405
|
+
return object.childIds.length > 0 && object.visualRadius >= 10;
|
|
1406
|
+
}
|
|
1407
|
+
function createScreenLabelElement(descriptor) {
|
|
1408
|
+
const element = document.createElement("div");
|
|
1409
|
+
element.className = `wo-viewer-label wo-viewer-label-${descriptor.kind}`;
|
|
1410
|
+
element.dataset.worldorbitScreenLabel = "true";
|
|
1411
|
+
element.dataset.labelKey = descriptor.key;
|
|
1412
|
+
element.dataset.anchor = descriptor.textAnchor;
|
|
1413
|
+
element.style.left = `${descriptor.point.x}px`;
|
|
1414
|
+
element.style.top = `${descriptor.point.y}px`;
|
|
1415
|
+
if (descriptor.objectId) {
|
|
1416
|
+
element.dataset.objectId = descriptor.objectId;
|
|
1417
|
+
for (const className of resolveScreenLabelClasses(descriptor.objectId)) {
|
|
1418
|
+
element.classList.add(className);
|
|
1419
|
+
}
|
|
1420
|
+
}
|
|
1421
|
+
const primary = document.createElement("span");
|
|
1422
|
+
primary.className = "wo-viewer-label-primary";
|
|
1423
|
+
if (descriptor.kind === "object") {
|
|
1424
|
+
primary.style.fontSize = `${14 * scene.scaleModel.labelMultiplier}px`;
|
|
1425
|
+
}
|
|
1426
|
+
primary.textContent = descriptor.primaryText;
|
|
1427
|
+
element.append(primary);
|
|
1428
|
+
if (descriptor.secondaryText) {
|
|
1429
|
+
const secondary = document.createElement("span");
|
|
1430
|
+
secondary.className = "wo-viewer-label-secondary";
|
|
1431
|
+
secondary.style.fontSize = `${11 * scene.scaleModel.labelMultiplier}px`;
|
|
1432
|
+
secondary.style.marginTop = `${Math.max(descriptor.secondaryOffset ?? 12, 10) - 10}px`;
|
|
1433
|
+
secondary.textContent = descriptor.secondaryText;
|
|
1434
|
+
element.append(secondary);
|
|
1435
|
+
}
|
|
1436
|
+
return element;
|
|
1437
|
+
}
|
|
1438
|
+
function layerEnabled(id) {
|
|
1439
|
+
return renderOptions.layers?.[id] !== false;
|
|
1440
|
+
}
|
|
1441
|
+
function resolveScreenLabelClasses(objectId) {
|
|
1442
|
+
const classes = [];
|
|
1443
|
+
const selectedDetails = buildObjectDetails(state.selectedObjectId);
|
|
1444
|
+
const hoveredDetails = buildObjectDetails(hoveredObjectId);
|
|
1445
|
+
if (state.selectedObjectId === objectId) {
|
|
1446
|
+
classes.push("wo-object-selected");
|
|
1447
|
+
}
|
|
1448
|
+
if (selectedDetails) {
|
|
1449
|
+
const selectedChain = new Set([
|
|
1450
|
+
selectedDetails.objectId,
|
|
1451
|
+
...selectedDetails.renderObject.childIds,
|
|
1452
|
+
...selectedDetails.renderObject.ancestorIds,
|
|
1453
|
+
]);
|
|
1454
|
+
const selectedAncestors = new Set(selectedDetails.ancestors.map((ancestor) => ancestor.objectId));
|
|
1455
|
+
if (selectedChain.has(objectId)) {
|
|
1456
|
+
classes.push("wo-chain-selected");
|
|
1457
|
+
}
|
|
1458
|
+
if (selectedAncestors.has(objectId)) {
|
|
1459
|
+
classes.push("wo-ancestor-selected");
|
|
1460
|
+
}
|
|
1461
|
+
}
|
|
1462
|
+
if (hoveredDetails) {
|
|
1463
|
+
const hoveredChain = new Set([
|
|
1464
|
+
hoveredDetails.objectId,
|
|
1465
|
+
...hoveredDetails.renderObject.childIds,
|
|
1466
|
+
...hoveredDetails.renderObject.ancestorIds,
|
|
1467
|
+
]);
|
|
1468
|
+
const hoveredAncestors = new Set(hoveredDetails.ancestors.map((ancestor) => ancestor.objectId));
|
|
1469
|
+
if (hoveredChain.has(objectId)) {
|
|
1470
|
+
classes.push("wo-chain-hover");
|
|
1471
|
+
}
|
|
1472
|
+
if (hoveredAncestors.has(objectId)) {
|
|
1473
|
+
classes.push("wo-ancestor-hover");
|
|
1474
|
+
}
|
|
1475
|
+
}
|
|
1476
|
+
return classes;
|
|
1477
|
+
}
|
|
1280
1478
|
function create3DFocusState(objectId) {
|
|
1281
1479
|
const target = spatialScene?.focusTargets.find((entry) => entry.objectId === objectId);
|
|
1282
1480
|
if (!target) {
|
|
@@ -1345,6 +1543,8 @@ function cloneRenderOptions(renderOptions) {
|
|
|
1345
1543
|
: renderOptions.theme,
|
|
1346
1544
|
activeEventId: renderOptions.activeEventId ?? null,
|
|
1347
1545
|
viewMode: renderOptions.viewMode ?? "2d",
|
|
1546
|
+
quality: renderOptions.quality ?? "balanced",
|
|
1547
|
+
style3d: renderOptions.style3d ?? "symbolic",
|
|
1348
1548
|
};
|
|
1349
1549
|
}
|
|
1350
1550
|
function mergeRenderOptions(current, next) {
|
|
@@ -1383,6 +1583,8 @@ function mergeRenderOptions(current, next) {
|
|
|
1383
1583
|
? { ...next.theme }
|
|
1384
1584
|
: next.theme ?? current.theme,
|
|
1385
1585
|
viewMode: next.viewMode ?? current.viewMode ?? "2d",
|
|
1586
|
+
quality: next.quality ?? current.quality ?? "balanced",
|
|
1587
|
+
style3d: next.style3d ?? current.style3d ?? "symbolic",
|
|
1386
1588
|
};
|
|
1387
1589
|
}
|
|
1388
1590
|
function hasSceneAffectingRenderOptions(options) {
|
|
@@ -1557,7 +1759,7 @@ function cssEscape(value) {
|
|
|
1557
1759
|
}
|
|
1558
1760
|
return value.replace(/["\\]/g, "\\$&");
|
|
1559
1761
|
}
|
|
1560
|
-
function
|
|
1762
|
+
function installViewerOverlayStyles() {
|
|
1561
1763
|
if (typeof document === "undefined" || document.getElementById(TOOLTIP_STYLE_ID)) {
|
|
1562
1764
|
return;
|
|
1563
1765
|
}
|
|
@@ -1592,6 +1794,60 @@ function installViewerTooltipStyles() {
|
|
|
1592
1794
|
height: 100%;
|
|
1593
1795
|
min-height: 320px;
|
|
1594
1796
|
}
|
|
1797
|
+
.wo-viewer-label-root {
|
|
1798
|
+
position: absolute;
|
|
1799
|
+
inset: 0;
|
|
1800
|
+
z-index: 8;
|
|
1801
|
+
pointer-events: none;
|
|
1802
|
+
overflow: hidden;
|
|
1803
|
+
}
|
|
1804
|
+
.wo-viewer-label {
|
|
1805
|
+
position: absolute;
|
|
1806
|
+
display: grid;
|
|
1807
|
+
gap: 2px;
|
|
1808
|
+
padding: 4px 8px;
|
|
1809
|
+
border-radius: 999px;
|
|
1810
|
+
background: linear-gradient(180deg, rgba(5, 16, 26, 0.72), rgba(5, 16, 26, 0.38));
|
|
1811
|
+
border: 1px solid rgba(164, 194, 228, 0.16);
|
|
1812
|
+
color: #edf6ff;
|
|
1813
|
+
font-family: "Segoe UI Variable", "Segoe UI", sans-serif;
|
|
1814
|
+
line-height: 1.15;
|
|
1815
|
+
text-shadow: 0 1px 2px rgba(7, 16, 25, 0.65), 0 0 18px rgba(7, 16, 25, 0.18);
|
|
1816
|
+
white-space: nowrap;
|
|
1817
|
+
}
|
|
1818
|
+
.wo-viewer-label[data-anchor="middle"] { transform: translate(-50%, 0); }
|
|
1819
|
+
.wo-viewer-label[data-anchor="end"] { transform: translate(-100%, 0); }
|
|
1820
|
+
.wo-viewer-label-primary {
|
|
1821
|
+
font-size: 14px;
|
|
1822
|
+
font-weight: 600;
|
|
1823
|
+
letter-spacing: 0.02em;
|
|
1824
|
+
}
|
|
1825
|
+
.wo-viewer-label-secondary {
|
|
1826
|
+
font-size: 11px;
|
|
1827
|
+
font-weight: 500;
|
|
1828
|
+
color: rgba(237, 246, 255, 0.72);
|
|
1829
|
+
}
|
|
1830
|
+
.wo-viewer-label-event {
|
|
1831
|
+
color: #ffce8a;
|
|
1832
|
+
text-transform: uppercase;
|
|
1833
|
+
letter-spacing: 0.04em;
|
|
1834
|
+
}
|
|
1835
|
+
.wo-viewer-label-event .wo-viewer-label-primary {
|
|
1836
|
+
font-size: 10px;
|
|
1837
|
+
font-weight: 700;
|
|
1838
|
+
}
|
|
1839
|
+
.wo-viewer-label.wo-object-selected .wo-viewer-label-primary,
|
|
1840
|
+
.wo-viewer-label.wo-chain-selected .wo-viewer-label-primary,
|
|
1841
|
+
.wo-viewer-label.wo-chain-hover .wo-viewer-label-primary {
|
|
1842
|
+
color: #ffce8a;
|
|
1843
|
+
}
|
|
1844
|
+
.wo-viewer-label.wo-object-selected .wo-viewer-label-secondary {
|
|
1845
|
+
color: #8fcaff;
|
|
1846
|
+
}
|
|
1847
|
+
.wo-viewer-label.wo-ancestor-selected .wo-viewer-label-primary,
|
|
1848
|
+
.wo-viewer-label.wo-ancestor-hover .wo-viewer-label-primary {
|
|
1849
|
+
opacity: 0.82;
|
|
1850
|
+
}
|
|
1595
1851
|
.wo-viewer-tooltip-root {
|
|
1596
1852
|
position: absolute;
|
|
1597
1853
|
z-index: 12;
|
|
@@ -67,6 +67,7 @@ export function createWorldOrbitEmbedMarkup(payload, options = {}) {
|
|
|
67
67
|
}
|
|
68
68
|
export function mountWorldOrbitEmbeds(root = document, options = {}) {
|
|
69
69
|
const viewers = new Map();
|
|
70
|
+
const cleanupCallbacks = [];
|
|
70
71
|
const elements = [...root.querySelectorAll(EMBED_SELECTOR)];
|
|
71
72
|
for (const element of elements) {
|
|
72
73
|
const payload = deserializePayloadFromElement(element);
|
|
@@ -82,14 +83,16 @@ export function mountWorldOrbitEmbeds(root = document, options = {}) {
|
|
|
82
83
|
const viewMode = options.viewer?.viewMode ??
|
|
83
84
|
payload.options?.viewMode ??
|
|
84
85
|
embedModeToViewMode(mode);
|
|
86
|
+
const measureViewport = () => resolveEmbedViewport(element, payload.scene, options);
|
|
85
87
|
if (mode === "interactive-2d" || mode === "interactive-3d") {
|
|
86
88
|
try {
|
|
89
|
+
const viewport = measureViewport();
|
|
87
90
|
const viewer = createInteractiveViewer(element, {
|
|
88
91
|
...options.viewer,
|
|
89
92
|
scene: payload.scene,
|
|
90
93
|
spatialScene: payload.spatialScene,
|
|
91
|
-
width:
|
|
92
|
-
height:
|
|
94
|
+
width: viewport.width,
|
|
95
|
+
height: viewport.height,
|
|
93
96
|
padding: options.padding ?? payload.scene.padding,
|
|
94
97
|
preset,
|
|
95
98
|
theme,
|
|
@@ -105,6 +108,13 @@ export function mountWorldOrbitEmbeds(root = document, options = {}) {
|
|
|
105
108
|
viewer.setAtlasState(payload.options.atlasState);
|
|
106
109
|
}
|
|
107
110
|
viewers.set(element, viewer);
|
|
111
|
+
cleanupCallbacks.push(bindEmbedResize(element, () => {
|
|
112
|
+
const nextViewport = measureViewport();
|
|
113
|
+
viewer.setRenderOptions({
|
|
114
|
+
width: nextViewport.width,
|
|
115
|
+
height: nextViewport.height,
|
|
116
|
+
});
|
|
117
|
+
}));
|
|
108
118
|
options.onMount?.(viewer, element);
|
|
109
119
|
}
|
|
110
120
|
catch (error) {
|
|
@@ -118,17 +128,22 @@ export function mountWorldOrbitEmbeds(root = document, options = {}) {
|
|
|
118
128
|
}
|
|
119
129
|
}
|
|
120
130
|
else {
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
131
|
+
const renderStaticEmbed = () => {
|
|
132
|
+
const viewport = measureViewport();
|
|
133
|
+
element.innerHTML = renderSceneToSvg(payload.scene, {
|
|
134
|
+
width: viewport.width,
|
|
135
|
+
height: viewport.height,
|
|
136
|
+
padding: options.padding ?? payload.scene.padding,
|
|
137
|
+
preset,
|
|
138
|
+
theme,
|
|
139
|
+
layers,
|
|
140
|
+
filter: initialFilter,
|
|
141
|
+
selectedObjectId: initialSelectionObjectId ?? null,
|
|
142
|
+
subtitle,
|
|
143
|
+
});
|
|
144
|
+
};
|
|
145
|
+
renderStaticEmbed();
|
|
146
|
+
cleanupCallbacks.push(bindEmbedResize(element, renderStaticEmbed));
|
|
132
147
|
options.onMount?.(null, element);
|
|
133
148
|
}
|
|
134
149
|
element.dataset.worldorbitMounted = "true";
|
|
@@ -136,14 +151,72 @@ export function mountWorldOrbitEmbeds(root = document, options = {}) {
|
|
|
136
151
|
return {
|
|
137
152
|
viewers: [...viewers.values()],
|
|
138
153
|
destroy() {
|
|
154
|
+
for (const cleanup of cleanupCallbacks) {
|
|
155
|
+
cleanup();
|
|
156
|
+
}
|
|
139
157
|
for (const [element, viewer] of viewers.entries()) {
|
|
140
158
|
viewer.destroy();
|
|
141
159
|
element.removeAttribute("data-worldorbit-mounted");
|
|
142
160
|
}
|
|
161
|
+
for (const element of elements) {
|
|
162
|
+
element.removeAttribute("data-worldorbit-mounted");
|
|
163
|
+
}
|
|
143
164
|
viewers.clear();
|
|
144
165
|
},
|
|
145
166
|
};
|
|
146
167
|
}
|
|
168
|
+
function resolveEmbedViewport(element, scene, options) {
|
|
169
|
+
const rect = element.getBoundingClientRect();
|
|
170
|
+
const width = sanitizeViewportDimension(options.width) ??
|
|
171
|
+
sanitizeViewportDimension(element.clientWidth) ??
|
|
172
|
+
sanitizeViewportDimension(rect.width) ??
|
|
173
|
+
scene.width;
|
|
174
|
+
const explicitHeight = sanitizeViewportDimension(options.height) ??
|
|
175
|
+
sanitizeViewportDimension(element.clientHeight) ??
|
|
176
|
+
sanitizeViewportDimension(rect.height);
|
|
177
|
+
const fallbackHeight = Math.max(Math.round(width * (scene.height / Math.max(scene.width, 1))), Math.min(scene.height, 240));
|
|
178
|
+
return {
|
|
179
|
+
width,
|
|
180
|
+
height: explicitHeight ?? fallbackHeight,
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
function sanitizeViewportDimension(value) {
|
|
184
|
+
return typeof value === "number" && Number.isFinite(value) && value > 0
|
|
185
|
+
? Math.round(value)
|
|
186
|
+
: null;
|
|
187
|
+
}
|
|
188
|
+
function bindEmbedResize(element, callback) {
|
|
189
|
+
let lastWidth = -1;
|
|
190
|
+
let lastHeight = -1;
|
|
191
|
+
const run = () => {
|
|
192
|
+
const rect = element.getBoundingClientRect();
|
|
193
|
+
const nextWidth = Math.round(Math.max(element.clientWidth || rect.width, 0));
|
|
194
|
+
const nextHeight = Math.round(Math.max(element.clientHeight || rect.height, 0));
|
|
195
|
+
if (nextWidth === lastWidth && nextHeight === lastHeight) {
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
lastWidth = nextWidth;
|
|
199
|
+
lastHeight = nextHeight;
|
|
200
|
+
callback();
|
|
201
|
+
};
|
|
202
|
+
run();
|
|
203
|
+
if (typeof ResizeObserver !== "undefined") {
|
|
204
|
+
const observer = new ResizeObserver(() => {
|
|
205
|
+
run();
|
|
206
|
+
});
|
|
207
|
+
observer.observe(element);
|
|
208
|
+
return () => {
|
|
209
|
+
observer.disconnect();
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
const handleWindowResize = () => {
|
|
213
|
+
run();
|
|
214
|
+
};
|
|
215
|
+
window.addEventListener("resize", handleWindowResize);
|
|
216
|
+
return () => {
|
|
217
|
+
window.removeEventListener("resize", handleWindowResize);
|
|
218
|
+
};
|
|
219
|
+
}
|
|
147
220
|
function deserializePayloadFromElement(element) {
|
|
148
221
|
const serialized = element.dataset.worldorbitPayload;
|
|
149
222
|
if (!serialized) {
|