worldorbit 3.2.1 → 4.0.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 +546 -545
- package/dist/browser/core/dist/atlas-edit.js +146 -1
- package/dist/browser/core/dist/atlas-validate.js +105 -10
- package/dist/browser/core/dist/draft-parse.js +341 -16
- package/dist/browser/core/dist/draft.d.ts +2 -1
- package/dist/browser/core/dist/draft.js +25 -3
- package/dist/browser/core/dist/format.js +86 -4
- package/dist/browser/core/dist/index.d.ts +1 -0
- package/dist/browser/core/dist/index.js +1 -0
- package/dist/browser/core/dist/load.js +7 -2
- package/dist/browser/core/dist/normalize.js +1 -0
- package/dist/browser/core/dist/scene.js +11 -2
- package/dist/browser/core/dist/schema.js +11 -1
- package/dist/browser/core/dist/solver.d.ts +26 -0
- package/dist/browser/core/dist/solver.js +27 -0
- package/dist/browser/core/dist/types.d.ts +57 -3
- package/dist/browser/editor/dist/editor.js +844 -719
- package/dist/browser/editor/dist/types.d.ts +2 -1
- package/dist/browser/obsidian-plugin/dist/diagnostics.d.ts +3 -0
- package/dist/browser/obsidian-plugin/dist/diagnostics.js +23 -0
- package/dist/browser/obsidian-plugin/dist/examples.d.ts +3 -0
- package/dist/browser/obsidian-plugin/dist/examples.js +77 -0
- package/dist/browser/obsidian-plugin/dist/index.d.ts +9 -0
- package/dist/browser/obsidian-plugin/dist/index.js +8 -0
- package/dist/browser/obsidian-plugin/dist/main.d.ts +2 -0
- package/dist/browser/obsidian-plugin/dist/main.js +2 -0
- package/dist/browser/obsidian-plugin/dist/navigation.d.ts +6 -0
- package/dist/browser/obsidian-plugin/dist/navigation.js +44 -0
- package/dist/browser/obsidian-plugin/dist/plugin.d.ts +8 -0
- package/dist/browser/obsidian-plugin/dist/plugin.js +508 -0
- package/dist/browser/obsidian-plugin/dist/positions.d.ts +7 -0
- package/dist/browser/obsidian-plugin/dist/positions.js +14 -0
- package/dist/browser/obsidian-plugin/dist/settings.d.ts +2 -0
- package/dist/browser/obsidian-plugin/dist/settings.js +5 -0
- package/dist/browser/obsidian-plugin/dist/theme.d.ts +2 -0
- package/dist/browser/obsidian-plugin/dist/theme.js +31 -0
- package/dist/browser/obsidian-plugin/dist/types.d.ts +42 -0
- package/dist/browser/obsidian-plugin/dist/types.js +1 -0
- package/dist/browser/obsidian-plugin/dist/viewer-host.d.ts +14 -0
- package/dist/browser/obsidian-plugin/dist/viewer-host.js +110 -0
- package/dist/browser/viewer/dist/index.d.ts +1 -0
- package/dist/browser/viewer/dist/index.js +1 -0
- package/dist/browser/viewer/dist/interactive-2d.d.ts +21 -0
- package/dist/browser/viewer/dist/interactive-2d.js +201 -0
- package/dist/browser/viewer/dist/minimap.js +9 -7
- package/dist/browser/viewer/dist/render.d.ts +1 -1
- package/dist/browser/viewer/dist/render.js +25 -20
- package/dist/browser/viewer/dist/runtime-3d.js +2 -0
- package/dist/browser/viewer/dist/viewer-state.d.ts +1 -1
- package/dist/browser/viewer/dist/viewer-state.js +1 -1
- package/dist/browser/viewer/dist/viewer.js +7 -3
- package/dist/obsidian-plugin/LICENSE +21 -0
- package/dist/obsidian-plugin/README.md +141 -0
- package/dist/obsidian-plugin/main.js +108 -0
- package/dist/obsidian-plugin/manifest.json +9 -0
- package/dist/obsidian-plugin/obsidian_scsh_1.png +0 -0
- package/dist/obsidian-plugin/obsidian_scsh_2.png +0 -0
- package/dist/obsidian-plugin/styles.css +164 -0
- package/dist/unpkg/core/dist/atlas-edit.js +146 -1
- package/dist/unpkg/core/dist/atlas-validate.js +105 -10
- package/dist/unpkg/core/dist/draft-parse.js +341 -16
- package/dist/unpkg/core/dist/draft.d.ts +2 -1
- package/dist/unpkg/core/dist/draft.js +25 -3
- package/dist/unpkg/core/dist/format.js +86 -4
- package/dist/unpkg/core/dist/index.d.ts +1 -0
- package/dist/unpkg/core/dist/index.js +1 -0
- package/dist/unpkg/core/dist/load.js +7 -2
- package/dist/unpkg/core/dist/normalize.js +1 -0
- package/dist/unpkg/core/dist/scene.js +11 -2
- package/dist/unpkg/core/dist/schema.js +11 -1
- package/dist/unpkg/core/dist/solver.d.ts +26 -0
- package/dist/unpkg/core/dist/solver.js +27 -0
- package/dist/unpkg/core/dist/types.d.ts +57 -3
- package/dist/unpkg/editor/dist/editor.js +844 -719
- package/dist/unpkg/editor/dist/types.d.ts +2 -1
- package/dist/unpkg/obsidian-plugin/dist/diagnostics.d.ts +3 -0
- package/dist/unpkg/obsidian-plugin/dist/diagnostics.js +23 -0
- package/dist/unpkg/obsidian-plugin/dist/examples.d.ts +3 -0
- package/dist/unpkg/obsidian-plugin/dist/examples.js +77 -0
- package/dist/unpkg/obsidian-plugin/dist/index.d.ts +9 -0
- package/dist/unpkg/obsidian-plugin/dist/index.js +8 -0
- package/dist/unpkg/obsidian-plugin/dist/main.d.ts +2 -0
- package/dist/unpkg/obsidian-plugin/dist/main.js +2 -0
- package/dist/unpkg/obsidian-plugin/dist/navigation.d.ts +6 -0
- package/dist/unpkg/obsidian-plugin/dist/navigation.js +44 -0
- package/dist/unpkg/obsidian-plugin/dist/plugin.d.ts +8 -0
- package/dist/unpkg/obsidian-plugin/dist/plugin.js +508 -0
- package/dist/unpkg/obsidian-plugin/dist/positions.d.ts +7 -0
- package/dist/unpkg/obsidian-plugin/dist/positions.js +14 -0
- package/dist/unpkg/obsidian-plugin/dist/settings.d.ts +2 -0
- package/dist/unpkg/obsidian-plugin/dist/settings.js +5 -0
- package/dist/unpkg/obsidian-plugin/dist/theme.d.ts +2 -0
- package/dist/unpkg/obsidian-plugin/dist/theme.js +31 -0
- package/dist/unpkg/obsidian-plugin/dist/types.d.ts +42 -0
- package/dist/unpkg/obsidian-plugin/dist/types.js +1 -0
- package/dist/unpkg/obsidian-plugin/dist/viewer-host.d.ts +14 -0
- package/dist/unpkg/obsidian-plugin/dist/viewer-host.js +110 -0
- package/dist/unpkg/viewer/dist/index.d.ts +1 -0
- package/dist/unpkg/viewer/dist/index.js +1 -0
- package/dist/unpkg/viewer/dist/interactive-2d.d.ts +21 -0
- package/dist/unpkg/viewer/dist/interactive-2d.js +201 -0
- package/dist/unpkg/viewer/dist/minimap.js +9 -7
- package/dist/unpkg/viewer/dist/render.d.ts +1 -1
- package/dist/unpkg/viewer/dist/render.js +25 -20
- package/dist/unpkg/viewer/dist/runtime-3d.js +2 -0
- package/dist/unpkg/viewer/dist/viewer-state.d.ts +1 -1
- package/dist/unpkg/viewer/dist/viewer-state.js +1 -1
- package/dist/unpkg/viewer/dist/viewer.js +7 -3
- package/dist/unpkg/worldorbit-core.min.js +10 -10
- package/dist/unpkg/worldorbit-editor.min.js +359 -332
- package/dist/unpkg/worldorbit-markdown.min.js +28 -28
- package/dist/unpkg/worldorbit-viewer.min.js +203 -203
- package/dist/unpkg/worldorbit.js +958 -40
- package/dist/unpkg/worldorbit.min.js +214 -214
- package/package.json +22 -1
- package/packages/core/dist/atlas-edit.js +146 -1
- package/packages/core/dist/atlas-validate.js +105 -10
- package/packages/core/dist/draft-parse.js +341 -16
- package/packages/core/dist/draft.d.ts +2 -1
- package/packages/core/dist/draft.js +25 -3
- package/packages/core/dist/format.js +86 -4
- package/packages/core/dist/index.d.ts +1 -0
- package/packages/core/dist/index.js +1 -0
- package/packages/core/dist/load.js +7 -2
- package/packages/core/dist/normalize.js +1 -0
- package/packages/core/dist/scene.js +11 -2
- package/packages/core/dist/schema.js +11 -1
- package/packages/core/dist/solver.d.ts +26 -0
- package/packages/core/dist/solver.js +27 -0
- package/packages/core/dist/types.d.ts +57 -3
- package/packages/editor/dist/editor.js +844 -719
- package/packages/editor/dist/types.d.ts +2 -1
- package/packages/viewer/dist/index.d.ts +1 -0
- package/packages/viewer/dist/index.js +1 -0
- package/packages/viewer/dist/interactive-2d.d.ts +21 -0
- package/packages/viewer/dist/interactive-2d.js +201 -0
- package/packages/viewer/dist/minimap.js +9 -7
- package/packages/viewer/dist/render.d.ts +1 -1
- package/packages/viewer/dist/render.js +25 -20
- package/packages/viewer/dist/runtime-3d.js +2 -0
- package/packages/viewer/dist/viewer-state.d.ts +1 -1
- package/packages/viewer/dist/viewer-state.js +1 -1
- package/packages/viewer/dist/viewer.js +7 -3
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { createInteractiveViewer2D, renderSceneToSvg, } from "@worldorbit/viewer/interactive-2d";
|
|
2
|
+
export class WorldOrbitEmbeddedView {
|
|
3
|
+
options;
|
|
4
|
+
viewer = null;
|
|
5
|
+
state;
|
|
6
|
+
constructor(options) {
|
|
7
|
+
this.options = {
|
|
8
|
+
...options,
|
|
9
|
+
createViewer: options.createViewer ?? defaultCreateViewer,
|
|
10
|
+
renderStatic: options.renderStatic ?? defaultRenderStatic,
|
|
11
|
+
};
|
|
12
|
+
this.state = {
|
|
13
|
+
interactive: options.interactive,
|
|
14
|
+
destroyed: false,
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
getState() {
|
|
18
|
+
return { ...this.state };
|
|
19
|
+
}
|
|
20
|
+
mount() {
|
|
21
|
+
if (this.state.destroyed) {
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
this.renderCurrent();
|
|
25
|
+
}
|
|
26
|
+
setInteractive(interactive) {
|
|
27
|
+
if (this.state.destroyed || this.state.interactive === interactive) {
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
this.state.interactive = interactive;
|
|
31
|
+
this.renderCurrent();
|
|
32
|
+
}
|
|
33
|
+
resize() {
|
|
34
|
+
if (this.state.destroyed) {
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
if (this.viewer) {
|
|
38
|
+
const viewport = measureViewport(this.options.container, this.options.scene);
|
|
39
|
+
this.viewer.setRenderOptions(viewport);
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
this.renderCurrent();
|
|
43
|
+
}
|
|
44
|
+
destroy() {
|
|
45
|
+
if (this.state.destroyed) {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
this.state.destroyed = true;
|
|
49
|
+
this.destroyViewer();
|
|
50
|
+
clearElement(this.options.container);
|
|
51
|
+
}
|
|
52
|
+
renderCurrent() {
|
|
53
|
+
this.destroyViewer();
|
|
54
|
+
clearElement(this.options.container);
|
|
55
|
+
if (this.state.interactive) {
|
|
56
|
+
const viewport = measureViewport(this.options.container, this.options.scene);
|
|
57
|
+
this.viewer = this.options.createViewer(this.options.container, {
|
|
58
|
+
scene: this.options.scene,
|
|
59
|
+
theme: this.options.theme,
|
|
60
|
+
pointer: this.options.enablePointer,
|
|
61
|
+
touch: this.options.enableTouch,
|
|
62
|
+
...viewport,
|
|
63
|
+
});
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
const viewport = measureViewport(this.options.container, this.options.scene);
|
|
67
|
+
this.options.container.innerHTML = this.options.renderStatic(this.options.scene, {
|
|
68
|
+
theme: this.options.theme,
|
|
69
|
+
...viewport,
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
destroyViewer() {
|
|
73
|
+
this.viewer?.destroy();
|
|
74
|
+
this.viewer = null;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
function defaultCreateViewer(container, options) {
|
|
78
|
+
return createInteractiveViewer2D(container, options.scene, {
|
|
79
|
+
theme: options.theme,
|
|
80
|
+
pointer: options.pointer,
|
|
81
|
+
touch: options.touch,
|
|
82
|
+
selection: true,
|
|
83
|
+
width: options.width,
|
|
84
|
+
height: options.height,
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
function defaultRenderStatic(scene, options) {
|
|
88
|
+
return renderSceneToSvg(scene, {
|
|
89
|
+
theme: options.theme,
|
|
90
|
+
width: options.width,
|
|
91
|
+
height: options.height,
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
function measureViewport(container, scene) {
|
|
95
|
+
const rect = container.getBoundingClientRect();
|
|
96
|
+
const width = sanitizeDimension(container.clientWidth || rect.width) ?? scene.width;
|
|
97
|
+
const height = sanitizeDimension(container.clientHeight || rect.height) ??
|
|
98
|
+
Math.max(Math.round(width * (scene.height / Math.max(scene.width, 1))), 280);
|
|
99
|
+
return { width, height };
|
|
100
|
+
}
|
|
101
|
+
function sanitizeDimension(value) {
|
|
102
|
+
return Number.isFinite(value) && value > 0 ? Math.round(value) : undefined;
|
|
103
|
+
}
|
|
104
|
+
function clearElement(element) {
|
|
105
|
+
if (typeof element.empty === "function") {
|
|
106
|
+
element.empty();
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
element.replaceChildren();
|
|
110
|
+
}
|
|
@@ -8,3 +8,4 @@ export { createEmbedPayload, createWorldOrbitEmbedMarkup, deserializeWorldOrbitE
|
|
|
8
8
|
export { defineWorldOrbitViewerElement } from "./custom-element.js";
|
|
9
9
|
export { createAtlasViewer } from "./atlas-viewer.js";
|
|
10
10
|
export { createInteractiveViewer } from "./viewer.js";
|
|
11
|
+
export { createInteractiveViewer2D } from "./interactive-2d.js";
|
|
@@ -7,3 +7,4 @@ export { createEmbedPayload, createWorldOrbitEmbedMarkup, deserializeWorldOrbitE
|
|
|
7
7
|
export { defineWorldOrbitViewerElement } from "./custom-element.js";
|
|
8
8
|
export { createAtlasViewer } from "./atlas-viewer.js";
|
|
9
9
|
export { createInteractiveViewer } from "./viewer.js";
|
|
10
|
+
export { createInteractiveViewer2D } from "./interactive-2d.js";
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { RenderScene } from "@worldorbit/core/types";
|
|
2
|
+
import type { SvgRenderOptions, ViewerState } from "./types.js";
|
|
3
|
+
export interface InteractiveViewer2DOptions extends Pick<SvgRenderOptions, "width" | "height" | "padding" | "preset" | "theme" | "layers" | "subtitle"> {
|
|
4
|
+
pointer?: boolean;
|
|
5
|
+
touch?: boolean;
|
|
6
|
+
selection?: boolean;
|
|
7
|
+
minScale?: number;
|
|
8
|
+
maxScale?: number;
|
|
9
|
+
fitPadding?: number;
|
|
10
|
+
}
|
|
11
|
+
export interface WorldOrbitViewer2D {
|
|
12
|
+
getState(): ViewerState;
|
|
13
|
+
setState(state: Partial<ViewerState>): void;
|
|
14
|
+
setRenderOptions(options: Partial<InteractiveViewer2DOptions>): void;
|
|
15
|
+
fitToSystem(): void;
|
|
16
|
+
destroy(): void;
|
|
17
|
+
}
|
|
18
|
+
export { renderSceneToSvg } from "./render.js";
|
|
19
|
+
export { resolveTheme } from "./theme.js";
|
|
20
|
+
export type { WorldOrbitTheme } from "./types.js";
|
|
21
|
+
export declare function createInteractiveViewer2D(container: HTMLElement, scene: RenderScene, options?: InteractiveViewer2DOptions): WorldOrbitViewer2D;
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import { renderSceneToSvg, WORLD_LAYER_ID } from "./render.js";
|
|
2
|
+
import { DEFAULT_VIEWER_STATE, composeViewerTransform, fitViewerState, panViewerState, zoomViewerStateAt, } from "./viewer-state.js";
|
|
3
|
+
export { renderSceneToSvg } from "./render.js";
|
|
4
|
+
export { resolveTheme } from "./theme.js";
|
|
5
|
+
const DEFAULT_VIEWER_LIMITS = {
|
|
6
|
+
minScale: 0.2,
|
|
7
|
+
maxScale: 8,
|
|
8
|
+
fitPadding: 48,
|
|
9
|
+
};
|
|
10
|
+
export function createInteractiveViewer2D(container, scene, options = {}) {
|
|
11
|
+
const constraints = {
|
|
12
|
+
minScale: options.minScale ?? DEFAULT_VIEWER_LIMITS.minScale,
|
|
13
|
+
maxScale: options.maxScale ?? DEFAULT_VIEWER_LIMITS.maxScale,
|
|
14
|
+
fitPadding: options.fitPadding ?? DEFAULT_VIEWER_LIMITS.fitPadding,
|
|
15
|
+
};
|
|
16
|
+
const behavior = {
|
|
17
|
+
pointer: options.pointer ?? true,
|
|
18
|
+
touch: options.touch ?? true,
|
|
19
|
+
selection: options.selection ?? true,
|
|
20
|
+
};
|
|
21
|
+
let renderOptions = {
|
|
22
|
+
width: options.width,
|
|
23
|
+
height: options.height,
|
|
24
|
+
padding: options.padding,
|
|
25
|
+
preset: options.preset,
|
|
26
|
+
theme: options.theme,
|
|
27
|
+
layers: options.layers,
|
|
28
|
+
subtitle: options.subtitle,
|
|
29
|
+
pointer: behavior.pointer,
|
|
30
|
+
touch: behavior.touch,
|
|
31
|
+
selection: behavior.selection,
|
|
32
|
+
minScale: constraints.minScale,
|
|
33
|
+
maxScale: constraints.maxScale,
|
|
34
|
+
fitPadding: constraints.fitPadding,
|
|
35
|
+
};
|
|
36
|
+
let state = fitViewerState(scene, DEFAULT_VIEWER_STATE, constraints);
|
|
37
|
+
let svgElement = null;
|
|
38
|
+
let cameraRoot = null;
|
|
39
|
+
let destroyed = false;
|
|
40
|
+
let activePointerId = null;
|
|
41
|
+
let lastPointerClientPoint = null;
|
|
42
|
+
let dragDistance = 0;
|
|
43
|
+
const previousTabIndex = container.getAttribute("tabindex");
|
|
44
|
+
const previousTouchAction = container.style.touchAction;
|
|
45
|
+
if (previousTabIndex === null) {
|
|
46
|
+
container.tabIndex = 0;
|
|
47
|
+
}
|
|
48
|
+
container.classList.add("wo-viewer-container");
|
|
49
|
+
container.style.touchAction = behavior.touch ? "none" : previousTouchAction;
|
|
50
|
+
const handleWheel = (event) => {
|
|
51
|
+
if (!behavior.pointer || destroyed || !svgElement) {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
event.preventDefault();
|
|
55
|
+
container.focus();
|
|
56
|
+
const anchor = getScenePointFromClient(event.clientX, event.clientY);
|
|
57
|
+
const factor = clamp(Math.exp(-event.deltaY * 0.002), 0.6, 1.6);
|
|
58
|
+
updateState(zoomViewerStateAt(scene, state, factor, anchor, constraints));
|
|
59
|
+
};
|
|
60
|
+
const handlePointerDown = (event) => {
|
|
61
|
+
if (destroyed) {
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
const isTouch = event.pointerType === "touch";
|
|
65
|
+
if ((isTouch && !behavior.touch) || (!isTouch && !behavior.pointer)) {
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
if (!isTouch && event.button !== 0) {
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
activePointerId = event.pointerId;
|
|
72
|
+
lastPointerClientPoint = { x: event.clientX, y: event.clientY };
|
|
73
|
+
dragDistance = 0;
|
|
74
|
+
container.setPointerCapture?.(event.pointerId);
|
|
75
|
+
container.focus();
|
|
76
|
+
};
|
|
77
|
+
const handlePointerMove = (event) => {
|
|
78
|
+
if (destroyed || activePointerId !== event.pointerId || !lastPointerClientPoint) {
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
const rect = svgElement?.getBoundingClientRect();
|
|
82
|
+
if (!rect || rect.width <= 0 || rect.height <= 0) {
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
const dx = event.clientX - lastPointerClientPoint.x;
|
|
86
|
+
const dy = event.clientY - lastPointerClientPoint.y;
|
|
87
|
+
lastPointerClientPoint = { x: event.clientX, y: event.clientY };
|
|
88
|
+
dragDistance += Math.hypot(dx, dy);
|
|
89
|
+
updateState(panViewerState(state, dx * (scene.width / rect.width), dy * (scene.height / rect.height)));
|
|
90
|
+
};
|
|
91
|
+
const stopPointer = (event) => {
|
|
92
|
+
if (activePointerId !== event.pointerId) {
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
activePointerId = null;
|
|
96
|
+
lastPointerClientPoint = null;
|
|
97
|
+
container.releasePointerCapture?.(event.pointerId);
|
|
98
|
+
};
|
|
99
|
+
const handleClick = (event) => {
|
|
100
|
+
if (destroyed || !behavior.selection || dragDistance > 6) {
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
const objectEl = event.target?.closest(".wo-object[data-object-id]");
|
|
104
|
+
if (!objectEl) {
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
updateState({
|
|
108
|
+
...state,
|
|
109
|
+
selectedObjectId: objectEl.dataset.objectId ?? null,
|
|
110
|
+
});
|
|
111
|
+
renderSvg();
|
|
112
|
+
};
|
|
113
|
+
container.addEventListener("wheel", handleWheel, { passive: false });
|
|
114
|
+
container.addEventListener("pointerdown", handlePointerDown);
|
|
115
|
+
container.addEventListener("pointermove", handlePointerMove);
|
|
116
|
+
container.addEventListener("pointerup", stopPointer);
|
|
117
|
+
container.addEventListener("pointercancel", stopPointer);
|
|
118
|
+
container.addEventListener("click", handleClick);
|
|
119
|
+
renderSvg();
|
|
120
|
+
return {
|
|
121
|
+
getState() {
|
|
122
|
+
return { ...state };
|
|
123
|
+
},
|
|
124
|
+
setState(nextState) {
|
|
125
|
+
updateState({
|
|
126
|
+
...state,
|
|
127
|
+
...nextState,
|
|
128
|
+
});
|
|
129
|
+
if ("selectedObjectId" in nextState) {
|
|
130
|
+
renderSvg();
|
|
131
|
+
}
|
|
132
|
+
},
|
|
133
|
+
setRenderOptions(nextOptions) {
|
|
134
|
+
renderOptions = {
|
|
135
|
+
...renderOptions,
|
|
136
|
+
...nextOptions,
|
|
137
|
+
};
|
|
138
|
+
renderSvg();
|
|
139
|
+
},
|
|
140
|
+
fitToSystem() {
|
|
141
|
+
updateState(fitViewerState(scene, state, constraints));
|
|
142
|
+
},
|
|
143
|
+
destroy() {
|
|
144
|
+
if (destroyed) {
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
destroyed = true;
|
|
148
|
+
container.removeEventListener("wheel", handleWheel);
|
|
149
|
+
container.removeEventListener("pointerdown", handlePointerDown);
|
|
150
|
+
container.removeEventListener("pointermove", handlePointerMove);
|
|
151
|
+
container.removeEventListener("pointerup", stopPointer);
|
|
152
|
+
container.removeEventListener("pointercancel", stopPointer);
|
|
153
|
+
container.removeEventListener("click", handleClick);
|
|
154
|
+
if (previousTabIndex === null) {
|
|
155
|
+
container.removeAttribute("tabindex");
|
|
156
|
+
}
|
|
157
|
+
container.style.touchAction = previousTouchAction;
|
|
158
|
+
container.replaceChildren();
|
|
159
|
+
svgElement = null;
|
|
160
|
+
cameraRoot = null;
|
|
161
|
+
},
|
|
162
|
+
};
|
|
163
|
+
function renderSvg() {
|
|
164
|
+
if (destroyed) {
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
container.innerHTML = renderSceneToSvg(scene, {
|
|
168
|
+
width: renderOptions.width,
|
|
169
|
+
height: renderOptions.height,
|
|
170
|
+
padding: renderOptions.padding,
|
|
171
|
+
preset: renderOptions.preset,
|
|
172
|
+
theme: renderOptions.theme,
|
|
173
|
+
layers: renderOptions.layers,
|
|
174
|
+
subtitle: renderOptions.subtitle,
|
|
175
|
+
selectedObjectId: state.selectedObjectId,
|
|
176
|
+
});
|
|
177
|
+
svgElement = container.querySelector("svg");
|
|
178
|
+
cameraRoot = container.querySelector(`[data-worldorbit-camera-root="${WORLD_LAYER_ID}"]`);
|
|
179
|
+
applyTransform();
|
|
180
|
+
}
|
|
181
|
+
function applyTransform() {
|
|
182
|
+
cameraRoot?.setAttribute("transform", composeViewerTransform(scene, state));
|
|
183
|
+
}
|
|
184
|
+
function updateState(nextState) {
|
|
185
|
+
state = nextState;
|
|
186
|
+
applyTransform();
|
|
187
|
+
}
|
|
188
|
+
function getScenePointFromClient(clientX, clientY) {
|
|
189
|
+
const rect = svgElement?.getBoundingClientRect();
|
|
190
|
+
if (!rect || rect.width <= 0 || rect.height <= 0) {
|
|
191
|
+
return { x: scene.width / 2, y: scene.height / 2 };
|
|
192
|
+
}
|
|
193
|
+
return {
|
|
194
|
+
x: ((clientX - rect.left) / rect.width) * scene.width,
|
|
195
|
+
y: ((clientY - rect.top) / rect.height) * scene.height,
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
function clamp(value, min, max) {
|
|
200
|
+
return Math.min(Math.max(value, min), max);
|
|
201
|
+
}
|
|
@@ -28,13 +28,13 @@ export function renderViewerMinimap(scene, state, visibleObjects) {
|
|
|
28
28
|
return `<circle cx="${formatNumber(x)}" cy="${formatNumber(y)}" r="${formatNumber(radius)}" fill="${fill}" fill-opacity="0.92" />`;
|
|
29
29
|
})
|
|
30
30
|
.join("");
|
|
31
|
-
return `<div data-worldorbit-minimap="true" style="position:absolute;right:16px;bottom:16px;width:${MINIMAP_WIDTH}px;height:${MINIMAP_HEIGHT}px;padding:8px;border-radius:16px;background:rgba(5, 14, 22, 0.78);border:1px solid rgba(179, 216, 255, 0.16);box-shadow:0 14px 28px rgba(0, 0, 0, 0.24);backdrop-filter:blur(8px);pointer-events:none;">
|
|
32
|
-
<svg width="${MINIMAP_WIDTH}" height="${MINIMAP_HEIGHT}" viewBox="0 0 ${MINIMAP_WIDTH} ${MINIMAP_HEIGHT}" role="presentation" aria-hidden="true">
|
|
33
|
-
<rect x="0.5" y="0.5" width="${MINIMAP_WIDTH - 1}" height="${MINIMAP_HEIGHT - 1}" rx="12" ry="12" fill="rgba(7, 17, 27, 0.85)" stroke="rgba(179, 216, 255, 0.18)" />
|
|
34
|
-
<rect x="${formatNumber(bounds.minX * scale + translateX)}" y="${formatNumber(bounds.minY * scale + translateY)}" width="${formatNumber(bounds.width * scale)}" height="${formatNumber(bounds.height * scale)}" rx="10" ry="10" fill="rgba(163, 209, 255, 0.04)" stroke="rgba(163, 209, 255, 0.16)" />
|
|
35
|
-
${objectsMarkup}
|
|
36
|
-
<rect x="${formatNumber(viewport.minX * scale + translateX)}" y="${formatNumber(viewport.minY * scale + translateY)}" width="${formatNumber(viewport.width * scale)}" height="${formatNumber(viewport.height * scale)}" rx="8" ry="8" fill="rgba(255, 180, 100, 0.09)" stroke="rgba(255, 180, 100, 0.88)" stroke-width="1.4" />
|
|
37
|
-
</svg>
|
|
31
|
+
return `<div data-worldorbit-minimap="true" style="position:absolute;right:16px;bottom:16px;width:${MINIMAP_WIDTH}px;height:${MINIMAP_HEIGHT}px;padding:8px;border-radius:16px;background:rgba(5, 14, 22, 0.78);border:1px solid rgba(179, 216, 255, 0.16);box-shadow:0 14px 28px rgba(0, 0, 0, 0.24);backdrop-filter:blur(8px);pointer-events:none;">
|
|
32
|
+
<svg width="${MINIMAP_WIDTH}" height="${MINIMAP_HEIGHT}" viewBox="0 0 ${MINIMAP_WIDTH} ${MINIMAP_HEIGHT}" role="presentation" aria-hidden="true">
|
|
33
|
+
<rect x="0.5" y="0.5" width="${MINIMAP_WIDTH - 1}" height="${MINIMAP_HEIGHT - 1}" rx="12" ry="12" fill="rgba(7, 17, 27, 0.85)" stroke="rgba(179, 216, 255, 0.18)" />
|
|
34
|
+
<rect x="${formatNumber(bounds.minX * scale + translateX)}" y="${formatNumber(bounds.minY * scale + translateY)}" width="${formatNumber(bounds.width * scale)}" height="${formatNumber(bounds.height * scale)}" rx="10" ry="10" fill="rgba(163, 209, 255, 0.04)" stroke="rgba(163, 209, 255, 0.16)" />
|
|
35
|
+
${objectsMarkup}
|
|
36
|
+
<rect x="${formatNumber(viewport.minX * scale + translateX)}" y="${formatNumber(viewport.minY * scale + translateY)}" width="${formatNumber(viewport.width * scale)}" height="${formatNumber(viewport.height * scale)}" rx="8" ry="8" fill="rgba(255, 180, 100, 0.09)" stroke="rgba(255, 180, 100, 0.88)" stroke-width="1.4" />
|
|
37
|
+
</svg>
|
|
38
38
|
</div>`;
|
|
39
39
|
}
|
|
40
40
|
function minimapColorForObject(type) {
|
|
@@ -52,6 +52,8 @@ function minimapColorForObject(type) {
|
|
|
52
52
|
return "#a7a5b8";
|
|
53
53
|
case "comet":
|
|
54
54
|
return "#9ce7ff";
|
|
55
|
+
case "craft":
|
|
56
|
+
return "#ffb47f";
|
|
55
57
|
case "structure":
|
|
56
58
|
return "#ff7f5f";
|
|
57
59
|
case "phenomenon":
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type RenderScene, type WorldOrbitDocument } from "@worldorbit/core";
|
|
1
|
+
import { type RenderScene, type WorldOrbitDocument } from "@worldorbit/core/types";
|
|
2
2
|
import type { SvgRenderOptions } from "./types.js";
|
|
3
3
|
export declare const WORLD_LAYER_ID = "worldorbit-camera-root";
|
|
4
4
|
export declare function renderSceneToSvg(scene: RenderScene, options?: SvgRenderOptions): string;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { loadWorldOrbitSource
|
|
1
|
+
import { loadWorldOrbitSource } from "@worldorbit/core/load";
|
|
2
|
+
import { renderDocumentToScene } from "@worldorbit/core/scene";
|
|
2
3
|
import { computeVisibleObjectIds } from "./atlas-state.js";
|
|
3
4
|
import { resolveLayers, resolveTheme } from "./theme.js";
|
|
4
5
|
export const WORLD_LAYER_ID = "worldorbit-camera-root";
|
|
@@ -88,14 +89,14 @@ export function renderSceneToSvg(scene, options = {}) {
|
|
|
88
89
|
.wo-bg-glow { fill: url(#wo-bg-glow); }
|
|
89
90
|
.wo-grid { fill: none; stroke: ${theme.guide}; stroke-width: 1; }
|
|
90
91
|
.wo-orbit { fill: none; stroke: ${theme.orbit}; stroke-width: 1.5; }
|
|
91
|
-
.wo-orbit-back { opacity: 0.38; stroke-dasharray: 8 6; }
|
|
92
|
-
.wo-orbit-front { opacity: 0.9; }
|
|
93
|
-
.wo-orbit-band { stroke: ${theme.orbitBand}; stroke-linecap: round; }
|
|
94
|
-
.wo-relation { stroke: ${theme.relation}; stroke-width: 2; stroke-dasharray: 10 6; }
|
|
95
|
-
.wo-event-line { stroke: ${theme.accent}; stroke-width: 1.6; stroke-dasharray: 5 5; opacity: 0.72; }
|
|
96
|
-
.wo-event-node { fill: ${theme.accent}; stroke: ${theme.selected}; stroke-width: 1.4; opacity: 0.92; }
|
|
97
|
-
.wo-event-label { fill: ${theme.accent}; font-family: ${theme.fontFamily}; font-weight: 700; letter-spacing: 0.04em; text-transform: uppercase; }
|
|
98
|
-
.wo-leader { stroke: ${theme.leader}; stroke-width: 1.5; stroke-dasharray: 6 5; }
|
|
92
|
+
.wo-orbit-back { opacity: 0.38; stroke-dasharray: 8 6; }
|
|
93
|
+
.wo-orbit-front { opacity: 0.9; }
|
|
94
|
+
.wo-orbit-band { stroke: ${theme.orbitBand}; stroke-linecap: round; }
|
|
95
|
+
.wo-relation { stroke: ${theme.relation}; stroke-width: 2; stroke-dasharray: 10 6; }
|
|
96
|
+
.wo-event-line { stroke: ${theme.accent}; stroke-width: 1.6; stroke-dasharray: 5 5; opacity: 0.72; }
|
|
97
|
+
.wo-event-node { fill: ${theme.accent}; stroke: ${theme.selected}; stroke-width: 1.4; opacity: 0.92; }
|
|
98
|
+
.wo-event-label { fill: ${theme.accent}; font-family: ${theme.fontFamily}; font-weight: 700; letter-spacing: 0.04em; text-transform: uppercase; }
|
|
99
|
+
.wo-leader { stroke: ${theme.leader}; stroke-width: 1.5; stroke-dasharray: 6 5; }
|
|
99
100
|
.wo-label { fill: ${theme.ink}; font-family: ${theme.fontFamily}; font-weight: 600; letter-spacing: 0.02em; }
|
|
100
101
|
.wo-label-secondary { fill: ${theme.muted}; font-family: ${theme.fontFamily}; font-weight: 500; }
|
|
101
102
|
.wo-title { fill: ${theme.ink}; font: 700 24px ${theme.displayFont}; letter-spacing: 0.06em; text-transform: uppercase; }
|
|
@@ -126,13 +127,13 @@ export function renderSceneToSvg(scene, options = {}) {
|
|
|
126
127
|
<g data-worldorbit-world="true">
|
|
127
128
|
<g data-worldorbit-camera-root="${WORLD_LAYER_ID}" id="${WORLD_LAYER_ID}">
|
|
128
129
|
<g data-worldorbit-world-content="true">
|
|
129
|
-
${layers.orbits ? `<g data-layer-id="orbits-back">${orbitMarkup.back}</g>` : ""}
|
|
130
|
-
${layers.guides ? `<g data-layer-id="guides">${leaderMarkup}</g>` : ""}
|
|
131
|
-
${layers.relations ? `<g data-layer-id="relations">${relationMarkup}</g>` : ""}
|
|
132
|
-
${layers.events ? `<g data-layer-id="events">${eventMarkup}</g>` : ""}
|
|
133
|
-
${layers.objects ? `<g data-layer-id="objects">${objectMarkup}</g>` : ""}
|
|
134
|
-
${layers.orbits ? `<g data-layer-id="orbits-front">${orbitMarkup.front}</g>` : ""}
|
|
135
|
-
${layers.labels ? `<g data-layer-id="labels">${labelMarkup}</g>` : ""}
|
|
130
|
+
${layers.orbits ? `<g data-layer-id="orbits-back">${orbitMarkup.back}</g>` : ""}
|
|
131
|
+
${layers.guides ? `<g data-layer-id="guides">${leaderMarkup}</g>` : ""}
|
|
132
|
+
${layers.relations ? `<g data-layer-id="relations">${relationMarkup}</g>` : ""}
|
|
133
|
+
${layers.events ? `<g data-layer-id="events">${eventMarkup}</g>` : ""}
|
|
134
|
+
${layers.objects ? `<g data-layer-id="objects">${objectMarkup}</g>` : ""}
|
|
135
|
+
${layers.orbits ? `<g data-layer-id="orbits-front">${orbitMarkup.front}</g>` : ""}
|
|
136
|
+
${layers.labels ? `<g data-layer-id="labels">${labelMarkup}</g>` : ""}
|
|
136
137
|
</g>
|
|
137
138
|
</g>
|
|
138
139
|
</g>
|
|
@@ -151,10 +152,10 @@ function renderSceneEventOverlay(scene, event, visibleObjectIds, theme) {
|
|
|
151
152
|
const lineMarkup = participants
|
|
152
153
|
.map((object) => `<line class="wo-event-line" x1="${event.x}" y1="${event.y}" x2="${object.x}" y2="${object.y}" stroke="${escapeAttribute(stroke)}" data-event-id="${escapeAttribute(event.eventId)}" data-object-id="${escapeAttribute(object.objectId)}" />`)
|
|
153
154
|
.join("");
|
|
154
|
-
return `<g class="wo-event" data-render-id="${escapeXml(event.renderId)}" data-event-id="${escapeAttribute(event.eventId)}">
|
|
155
|
-
${lineMarkup}
|
|
156
|
-
<circle class="wo-event-node" cx="${event.x}" cy="${event.y}" r="5" fill="${escapeAttribute(stroke)}" />
|
|
157
|
-
<text class="wo-event-label" x="${event.x}" y="${event.y - 10}" text-anchor="middle" font-size="10">${escapeXml(label)}</text>
|
|
155
|
+
return `<g class="wo-event" data-render-id="${escapeXml(event.renderId)}" data-event-id="${escapeAttribute(event.eventId)}">
|
|
156
|
+
${lineMarkup}
|
|
157
|
+
<circle class="wo-event-node" cx="${event.x}" cy="${event.y}" r="5" fill="${escapeAttribute(stroke)}" />
|
|
158
|
+
<text class="wo-event-label" x="${event.x}" y="${event.y - 10}" text-anchor="middle" font-size="10">${escapeXml(label)}</text>
|
|
158
159
|
</g>`;
|
|
159
160
|
}
|
|
160
161
|
export function renderDocumentToSvg(document, options = {}) {
|
|
@@ -258,6 +259,8 @@ function renderObjectBody(object, x, y, radius, palette, options = {}) {
|
|
|
258
259
|
? `<circle cx="${x}" cy="${y}" r="${radius}" fill="transparent" stroke="${palette.stroke}" stroke-width="1.4" />`
|
|
259
260
|
: `<path d="M ${x - radius * 2} ${y + radius * 1.3} Q ${x - radius * 0.7} ${y + radius * 0.3} ${x - radius * 0.45} ${y}" fill="none" stroke="${tail}" stroke-width="${Math.max(2, radius * 0.8)}" stroke-linecap="round" opacity="0.85" />
|
|
260
261
|
<circle cx="${x}" cy="${y}" r="${radius}" fill="${fill}" stroke="${palette.stroke}" stroke-width="1.4" />`;
|
|
262
|
+
case "craft":
|
|
263
|
+
return `<polygon points="${diamondPoints(x, y, radius * 0.85)}" fill="${fill}" stroke="${palette.stroke}" stroke-width="1.4" />`;
|
|
261
264
|
case "structure":
|
|
262
265
|
return `<polygon points="${diamondPoints(x, y, radius)}" fill="${fill}" stroke="${palette.stroke}" stroke-width="1.4" />`;
|
|
263
266
|
case "phenomenon": {
|
|
@@ -442,6 +445,8 @@ function basePaletteForType(type, kind, theme) {
|
|
|
442
445
|
return { fill: "#9ce7ff", stroke: "#e7fbff" };
|
|
443
446
|
case "ring":
|
|
444
447
|
return { fill: "#e59f7d", stroke: "#fff0d3" };
|
|
448
|
+
case "craft":
|
|
449
|
+
return { fill: "#ffb47f", stroke: "#fff0d3" };
|
|
445
450
|
case "structure":
|
|
446
451
|
return { fill: theme.accentStrong, stroke: "#fff2ea" };
|
|
447
452
|
case "phenomenon":
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type CoordinatePoint, type RenderBounds, type RenderScene } from "@worldorbit/core";
|
|
1
|
+
import { type CoordinatePoint, type RenderBounds, type RenderScene } from "@worldorbit/core/types";
|
|
2
2
|
import type { ViewerState } from "./types.js";
|
|
3
3
|
export interface ViewerConstraints {
|
|
4
4
|
minScale: number;
|
|
@@ -1086,7 +1086,7 @@ export function createInteractiveViewer(container, options) {
|
|
|
1086
1086
|
tooltipRoot.hidden = false;
|
|
1087
1087
|
tooltipRoot.dataset.mode = resolved.mode;
|
|
1088
1088
|
tooltipRoot.classList.toggle("is-pinned", resolved.mode === "pinned");
|
|
1089
|
-
tooltipRoot.style.pointerEvents = "auto";
|
|
1089
|
+
tooltipRoot.style.pointerEvents = resolved.mode === "pinned" ? "auto" : "none";
|
|
1090
1090
|
tooltipRoot.style.visibility = "hidden";
|
|
1091
1091
|
renderTooltipContent(tooltipRoot, tooltipDetails, resolved.mode);
|
|
1092
1092
|
positionTooltip(tooltipRoot, details.renderObject);
|
|
@@ -1732,7 +1732,11 @@ function getClosestObjectId(target) {
|
|
|
1732
1732
|
if (!(target instanceof Element)) {
|
|
1733
1733
|
return null;
|
|
1734
1734
|
}
|
|
1735
|
-
|
|
1735
|
+
const selectionTarget = target.closest("[data-object-id], [data-orbit-object-id]");
|
|
1736
|
+
if (!selectionTarget) {
|
|
1737
|
+
return null;
|
|
1738
|
+
}
|
|
1739
|
+
return selectionTarget.dataset.objectId ?? selectionTarget.dataset.orbitObjectId ?? null;
|
|
1736
1740
|
}
|
|
1737
1741
|
function ensureBrowserEnvironment(container) {
|
|
1738
1742
|
if (typeof window === "undefined" || typeof document === "undefined") {
|
|
@@ -1864,7 +1868,7 @@ function installViewerOverlayStyles() {
|
|
|
1864
1868
|
backdrop-filter: blur(12px);
|
|
1865
1869
|
font: 500 13px/1.5 "Segoe UI Variable", "Segoe UI", sans-serif;
|
|
1866
1870
|
}
|
|
1867
|
-
.wo-viewer-tooltip-root[data-mode="hover"] { pointer-events:
|
|
1871
|
+
.wo-viewer-tooltip-root[data-mode="hover"] { pointer-events: none; }
|
|
1868
1872
|
.wo-viewer-tooltip-root[data-mode="pinned"] { pointer-events: auto; }
|
|
1869
1873
|
.wo-tooltip-card { display: grid; gap: 10px; }
|
|
1870
1874
|
.wo-tooltip-head { display: grid; grid-template-columns: 52px minmax(0, 1fr); gap: 12px; align-items: center; }
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Hanjo Winter
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|