worldorbit 3.2.0 → 3.2.2
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/dist/browser/core/dist/scene.js +98 -16
- package/dist/browser/core/dist/spatial-scene.js +1 -0
- package/dist/browser/core/dist/types.d.ts +3 -0
- package/dist/browser/editor/dist/editor.js +55 -12
- 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/atlas-state.js +3 -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/render.d.ts +1 -1
- package/dist/browser/viewer/dist/render.js +2 -1
- package/dist/browser/viewer/dist/types.d.ts +1 -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 +2 -0
- package/dist/obsidian-plugin/LICENSE +21 -0
- package/dist/obsidian-plugin/README.md +124 -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/scene.js +98 -16
- package/dist/unpkg/core/dist/spatial-scene.js +1 -0
- package/dist/unpkg/core/dist/types.d.ts +3 -0
- package/dist/unpkg/editor/dist/editor.js +55 -12
- 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/atlas-state.js +3 -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/render.d.ts +1 -1
- package/dist/unpkg/viewer/dist/render.js +2 -1
- package/dist/unpkg/viewer/dist/types.d.ts +1 -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 +2 -0
- package/dist/unpkg/worldorbit-core.min.js +12 -12
- package/dist/unpkg/worldorbit-editor.min.js +342 -342
- package/dist/unpkg/worldorbit-markdown.min.js +23 -23
- package/dist/unpkg/worldorbit-viewer.min.js +197 -197
- package/dist/unpkg/worldorbit.js +297 -21
- package/dist/unpkg/worldorbit.min.js +201 -201
- package/package.json +18 -1
- package/packages/core/dist/scene.js +98 -16
- package/packages/core/dist/spatial-scene.js +1 -0
- package/packages/core/dist/types.d.ts +3 -0
- package/packages/editor/dist/editor.js +55 -12
- package/packages/viewer/dist/atlas-state.js +3 -0
- 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/render.d.ts +1 -1
- package/packages/viewer/dist/render.js +2 -1
- package/packages/viewer/dist/types.d.ts +1 -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 +2 -0
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
export const WORLDORBIT_HELP_TITLE = "WorldOrbit Quick Start";
|
|
2
|
+
export function buildSolarSystemExampleBlock() {
|
|
3
|
+
return [
|
|
4
|
+
"```worldorbit",
|
|
5
|
+
"schema 2.5",
|
|
6
|
+
"",
|
|
7
|
+
"system Sol",
|
|
8
|
+
' title "Solar System"',
|
|
9
|
+
" referencePlane ecliptic",
|
|
10
|
+
"",
|
|
11
|
+
"defaults",
|
|
12
|
+
" view orthographic",
|
|
13
|
+
" preset atlas-card",
|
|
14
|
+
"",
|
|
15
|
+
'group inner-system',
|
|
16
|
+
' label "Inner System"',
|
|
17
|
+
"",
|
|
18
|
+
"object star Sun",
|
|
19
|
+
" mass 1sol",
|
|
20
|
+
"",
|
|
21
|
+
"object planet Mercury",
|
|
22
|
+
" orbit Sun",
|
|
23
|
+
" semiMajor 0.39au",
|
|
24
|
+
" color #b7b1a7",
|
|
25
|
+
" groups inner-system",
|
|
26
|
+
"",
|
|
27
|
+
"object planet Venus",
|
|
28
|
+
" orbit Sun",
|
|
29
|
+
" semiMajor 0.72au",
|
|
30
|
+
" color #d9b37a",
|
|
31
|
+
" groups inner-system",
|
|
32
|
+
"",
|
|
33
|
+
"object planet Earth",
|
|
34
|
+
" orbit Sun",
|
|
35
|
+
" semiMajor 1au",
|
|
36
|
+
" color #6fa8ff",
|
|
37
|
+
" atmosphere nitrogen-oxygen",
|
|
38
|
+
" groups inner-system",
|
|
39
|
+
"",
|
|
40
|
+
"object moon Luna",
|
|
41
|
+
" orbit Earth",
|
|
42
|
+
" distance 384400km",
|
|
43
|
+
"",
|
|
44
|
+
"object planet Mars",
|
|
45
|
+
" orbit Sun",
|
|
46
|
+
" semiMajor 1.52au",
|
|
47
|
+
" color #d97c52",
|
|
48
|
+
" groups inner-system",
|
|
49
|
+
"```",
|
|
50
|
+
].join("\n");
|
|
51
|
+
}
|
|
52
|
+
export function getQuickStartMarkdown() {
|
|
53
|
+
return [
|
|
54
|
+
"Paste this into a note:",
|
|
55
|
+
"",
|
|
56
|
+
"```worldorbit",
|
|
57
|
+
"schema 2.5",
|
|
58
|
+
"",
|
|
59
|
+
"system Sol",
|
|
60
|
+
"",
|
|
61
|
+
"object star Sun",
|
|
62
|
+
"",
|
|
63
|
+
"object planet Earth",
|
|
64
|
+
" orbit Sun",
|
|
65
|
+
" semiMajor 1au",
|
|
66
|
+
"```",
|
|
67
|
+
"",
|
|
68
|
+
"Useful fields:",
|
|
69
|
+
"- `object planet NAME` creates a body",
|
|
70
|
+
"- `orbit TARGET` places it around another object",
|
|
71
|
+
"- `semiMajor 1au` or `distance 384400km` controls placement",
|
|
72
|
+
"- `color #6fa8ff` sets a display color hint",
|
|
73
|
+
"- `radius`, `mass`, `kind`, and `atmosphere` add more detail",
|
|
74
|
+
"",
|
|
75
|
+
"Locked mode keeps scrolling safe in notes. Activate interaction only when you want to pan or zoom the diagram.",
|
|
76
|
+
].join("\n");
|
|
77
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export { WorldOrbitObsidianPlugin } from "./plugin.js";
|
|
2
|
+
export { DEFAULT_SETTINGS } from "./settings.js";
|
|
3
|
+
export { formatDiagnosticLocation, summarizeDiagnostics } from "./diagnostics.js";
|
|
4
|
+
export { buildSolarSystemExampleBlock, getQuickStartMarkdown, WORLDORBIT_HELP_TITLE, } from "./examples.js";
|
|
5
|
+
export { inferFenceContentStartLine, resolveDiagnosticEditorPosition, } from "./positions.js";
|
|
6
|
+
export { createDiagnosticNavigationTarget, navigateToWorldOrbitDiagnostic, resolveFenceNavigationContext, } from "./navigation.js";
|
|
7
|
+
export { createObsidianViewerTheme } from "./theme.js";
|
|
8
|
+
export { WorldOrbitEmbeddedView } from "./viewer-host.js";
|
|
9
|
+
export type * from "./types.js";
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { WorldOrbitObsidianPlugin } from "./plugin.js";
|
|
2
|
+
export { DEFAULT_SETTINGS } from "./settings.js";
|
|
3
|
+
export { formatDiagnosticLocation, summarizeDiagnostics } from "./diagnostics.js";
|
|
4
|
+
export { buildSolarSystemExampleBlock, getQuickStartMarkdown, WORLDORBIT_HELP_TITLE, } from "./examples.js";
|
|
5
|
+
export { inferFenceContentStartLine, resolveDiagnosticEditorPosition, } from "./positions.js";
|
|
6
|
+
export { createDiagnosticNavigationTarget, navigateToWorldOrbitDiagnostic, resolveFenceNavigationContext, } from "./navigation.js";
|
|
7
|
+
export { createObsidianViewerTheme } from "./theme.js";
|
|
8
|
+
export { WorldOrbitEmbeddedView } from "./viewer-host.js";
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { WorldOrbitDiagnostic } from "@worldorbit/core/types";
|
|
2
|
+
import { type App, type MarkdownPostProcessorContext } from "obsidian";
|
|
3
|
+
import type { BlockNavigationContext, DiagnosticNavigationTarget } from "./types.js";
|
|
4
|
+
export declare function resolveFenceNavigationContext(ctx: MarkdownPostProcessorContext, el: HTMLElement): BlockNavigationContext | null;
|
|
5
|
+
export declare function createDiagnosticNavigationTarget(contentStartLine: number | null, diagnostic: WorldOrbitDiagnostic): DiagnosticNavigationTarget;
|
|
6
|
+
export declare function navigateToWorldOrbitDiagnostic(app: App, sourcePath: string, contentStartLine: number, diagnostic: WorldOrbitDiagnostic): Promise<boolean>;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { MarkdownView, TFile, } from "obsidian";
|
|
2
|
+
import { inferFenceContentStartLine, resolveDiagnosticEditorPosition, } from "./positions.js";
|
|
3
|
+
export function resolveFenceNavigationContext(ctx, el) {
|
|
4
|
+
if (!ctx.sourcePath) {
|
|
5
|
+
return null;
|
|
6
|
+
}
|
|
7
|
+
const sectionInfo = ctx.getSectionInfo(el);
|
|
8
|
+
if (!sectionInfo) {
|
|
9
|
+
return null;
|
|
10
|
+
}
|
|
11
|
+
return {
|
|
12
|
+
sourcePath: ctx.sourcePath,
|
|
13
|
+
contentStartLine: inferFenceContentStartLine(sectionInfo),
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
export function createDiagnosticNavigationTarget(contentStartLine, diagnostic) {
|
|
17
|
+
return {
|
|
18
|
+
diagnostic,
|
|
19
|
+
position: contentStartLine === null
|
|
20
|
+
? null
|
|
21
|
+
: resolveDiagnosticEditorPosition(contentStartLine, diagnostic),
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
export async function navigateToWorldOrbitDiagnostic(app, sourcePath, contentStartLine, diagnostic) {
|
|
25
|
+
const position = resolveDiagnosticEditorPosition(contentStartLine, diagnostic);
|
|
26
|
+
if (!position) {
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
const file = app.vault.getAbstractFileByPath(sourcePath);
|
|
30
|
+
if (!(file instanceof TFile)) {
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
const leaf = app.workspace.getMostRecentLeaf() ?? app.workspace.getLeaf(true);
|
|
34
|
+
await leaf.openFile(file);
|
|
35
|
+
const view = leaf.view instanceof MarkdownView
|
|
36
|
+
? leaf.view
|
|
37
|
+
: app.workspace.getActiveViewOfType(MarkdownView);
|
|
38
|
+
if (!view) {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
view.editor.setCursor(position);
|
|
42
|
+
view.editor.focus();
|
|
43
|
+
return true;
|
|
44
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { Plugin } from "obsidian";
|
|
2
|
+
import type { WorldOrbitObsidianPluginSettings } from "./types.js";
|
|
3
|
+
export declare class WorldOrbitObsidianPlugin extends Plugin {
|
|
4
|
+
settings: WorldOrbitObsidianPluginSettings;
|
|
5
|
+
onload(): Promise<void>;
|
|
6
|
+
loadSettings(): Promise<void>;
|
|
7
|
+
saveSettings(): Promise<void>;
|
|
8
|
+
}
|
|
@@ -0,0 +1,508 @@
|
|
|
1
|
+
import { Component, MarkdownRenderChild, Modal, Notice, Platform, Plugin, PluginSettingTab, Setting, setIcon, } from "obsidian";
|
|
2
|
+
import { loadWorldOrbitSourceWithDiagnostics } from "@worldorbit/core/load";
|
|
3
|
+
import { renderDocumentToScene } from "@worldorbit/core/scene";
|
|
4
|
+
import { formatDiagnosticLocation, summarizeDiagnostics } from "./diagnostics.js";
|
|
5
|
+
import { buildSolarSystemExampleBlock, WORLDORBIT_HELP_TITLE, } from "./examples.js";
|
|
6
|
+
import { navigateToWorldOrbitDiagnostic, resolveFenceNavigationContext, } from "./navigation.js";
|
|
7
|
+
import { DEFAULT_SETTINGS } from "./settings.js";
|
|
8
|
+
import { createObsidianViewerTheme } from "./theme.js";
|
|
9
|
+
import { WorldOrbitEmbeddedView } from "./viewer-host.js";
|
|
10
|
+
export class WorldOrbitObsidianPlugin extends Plugin {
|
|
11
|
+
settings = DEFAULT_SETTINGS;
|
|
12
|
+
async onload() {
|
|
13
|
+
await this.loadSettings();
|
|
14
|
+
this.registerMarkdownCodeBlockProcessor("worldorbit", (source, el, ctx) => {
|
|
15
|
+
const child = new WorldOrbitBlockComponent(this.app, this, source, el, ctx);
|
|
16
|
+
ctx.addChild(child);
|
|
17
|
+
});
|
|
18
|
+
this.addCommand({
|
|
19
|
+
id: "insert-solar-system-example",
|
|
20
|
+
name: "Insert Solar System Example",
|
|
21
|
+
editorCheckCallback: (checking, editor) => {
|
|
22
|
+
if (checking) {
|
|
23
|
+
return true;
|
|
24
|
+
}
|
|
25
|
+
insertWorldOrbitExample(editor);
|
|
26
|
+
return true;
|
|
27
|
+
},
|
|
28
|
+
});
|
|
29
|
+
this.addSettingTab(new WorldOrbitSettingTab(this.app, this));
|
|
30
|
+
}
|
|
31
|
+
async loadSettings() {
|
|
32
|
+
const loaded = (await this.loadData());
|
|
33
|
+
this.settings = {
|
|
34
|
+
...DEFAULT_SETTINGS,
|
|
35
|
+
...loaded,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
async saveSettings() {
|
|
39
|
+
await this.saveData(this.settings);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
class WorldOrbitBlockComponent extends MarkdownRenderChild {
|
|
43
|
+
app;
|
|
44
|
+
plugin;
|
|
45
|
+
source;
|
|
46
|
+
context;
|
|
47
|
+
navigationContext = null;
|
|
48
|
+
embeddedView = null;
|
|
49
|
+
resizeObserver = null;
|
|
50
|
+
intersectionObserver = null;
|
|
51
|
+
pendingFrameId = null;
|
|
52
|
+
cleanupComponent = new Component();
|
|
53
|
+
rootEl;
|
|
54
|
+
toolbarEl;
|
|
55
|
+
statusEl;
|
|
56
|
+
actionsEl;
|
|
57
|
+
hostEl;
|
|
58
|
+
diagnosticsEl;
|
|
59
|
+
renderState = "idle";
|
|
60
|
+
loadedSource = null;
|
|
61
|
+
scene = null;
|
|
62
|
+
diagnostics = [];
|
|
63
|
+
constructor(app, plugin, source, containerEl, context) {
|
|
64
|
+
super(containerEl);
|
|
65
|
+
this.app = app;
|
|
66
|
+
this.plugin = plugin;
|
|
67
|
+
this.source = source;
|
|
68
|
+
this.context = context;
|
|
69
|
+
}
|
|
70
|
+
onload() {
|
|
71
|
+
this.renderShell();
|
|
72
|
+
}
|
|
73
|
+
onunload() {
|
|
74
|
+
this.renderState = "destroyed";
|
|
75
|
+
this.cancelPendingRender();
|
|
76
|
+
this.intersectionObserver?.disconnect();
|
|
77
|
+
this.intersectionObserver = null;
|
|
78
|
+
this.resizeObserver?.disconnect();
|
|
79
|
+
this.resizeObserver = null;
|
|
80
|
+
this.destroyEmbeddedView();
|
|
81
|
+
this.cleanupComponent.unload();
|
|
82
|
+
this.rootEl?.detach();
|
|
83
|
+
}
|
|
84
|
+
renderShell() {
|
|
85
|
+
this.containerEl.empty();
|
|
86
|
+
this.navigationContext = resolveFenceNavigationContext(this.context, this.containerEl);
|
|
87
|
+
this.rootEl = this.containerEl.createDiv({ cls: "worldorbit-obsidian-block" });
|
|
88
|
+
this.toolbarEl = this.rootEl.createDiv({ cls: "worldorbit-obsidian-toolbar" });
|
|
89
|
+
this.statusEl = this.toolbarEl.createSpan({
|
|
90
|
+
cls: "worldorbit-obsidian-status",
|
|
91
|
+
text: "Preparing WorldOrbit block...",
|
|
92
|
+
});
|
|
93
|
+
this.actionsEl = this.toolbarEl.createDiv({
|
|
94
|
+
cls: "worldorbit-obsidian-actions",
|
|
95
|
+
});
|
|
96
|
+
this.hostEl = this.rootEl.createDiv({
|
|
97
|
+
cls: "worldorbit-obsidian-host is-locked",
|
|
98
|
+
});
|
|
99
|
+
this.diagnosticsEl = this.rootEl.createDiv({
|
|
100
|
+
cls: "worldorbit-obsidian-diagnostics",
|
|
101
|
+
});
|
|
102
|
+
this.attachResizeObserver();
|
|
103
|
+
if (!this.source.trim()) {
|
|
104
|
+
this.statusEl.setText("Empty WorldOrbit block");
|
|
105
|
+
this.renderState = "idle";
|
|
106
|
+
this.renderActions();
|
|
107
|
+
this.renderEmptyState();
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
this.statusEl.setText("Preview will render when this block becomes visible.");
|
|
111
|
+
this.renderPlaceholder();
|
|
112
|
+
this.renderActions();
|
|
113
|
+
this.scheduleLazyRender();
|
|
114
|
+
}
|
|
115
|
+
scheduleLazyRender() {
|
|
116
|
+
this.intersectionObserver?.disconnect();
|
|
117
|
+
if (typeof IntersectionObserver === "undefined") {
|
|
118
|
+
this.queueRender();
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
this.intersectionObserver = new IntersectionObserver((entries) => {
|
|
122
|
+
if (entries.some((entry) => entry.isIntersecting)) {
|
|
123
|
+
this.intersectionObserver?.disconnect();
|
|
124
|
+
this.intersectionObserver = null;
|
|
125
|
+
this.queueRender();
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
this.intersectionObserver.observe(this.rootEl);
|
|
129
|
+
}
|
|
130
|
+
queueRender() {
|
|
131
|
+
if (this.renderState !== "idle" || this.pendingFrameId !== null) {
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
this.renderState = "queued";
|
|
135
|
+
this.statusEl.setText("Preparing preview...");
|
|
136
|
+
this.pendingFrameId = window.requestAnimationFrame(() => {
|
|
137
|
+
this.pendingFrameId = null;
|
|
138
|
+
this.ensureRendered();
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
ensureRendered() {
|
|
142
|
+
if (this.renderState === "destroyed" || this.scene || this.loadedSource) {
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
const result = loadWorldOrbitSourceWithDiagnostics(this.source);
|
|
146
|
+
this.loadedSource = result.ok ? result.value : null;
|
|
147
|
+
this.diagnostics = this.filterDiagnostics(result.diagnostics);
|
|
148
|
+
this.diagnosticsEl.empty();
|
|
149
|
+
if (!result.ok || !result.value) {
|
|
150
|
+
this.renderState = "rendered-static";
|
|
151
|
+
this.destroyEmbeddedView();
|
|
152
|
+
this.hostEl.empty();
|
|
153
|
+
this.hostEl.createDiv({
|
|
154
|
+
cls: "worldorbit-obsidian-empty",
|
|
155
|
+
text: "WorldOrbit could not be rendered. Check the diagnostics below.",
|
|
156
|
+
});
|
|
157
|
+
this.renderDiagnostics(this.diagnostics, true);
|
|
158
|
+
this.renderActions();
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
this.scene = renderDocumentToScene(result.value.document);
|
|
162
|
+
this.mountStaticScene();
|
|
163
|
+
this.renderDiagnostics(this.diagnostics, false);
|
|
164
|
+
this.renderActions();
|
|
165
|
+
if (this.plugin.settings.embeddedInteraction === "enabled" && !Platform.isMobile) {
|
|
166
|
+
this.mountInteractiveIfNeeded();
|
|
167
|
+
}
|
|
168
|
+
else {
|
|
169
|
+
this.renderState = "rendered-static";
|
|
170
|
+
this.statusEl.setText(this.diagnostics.length ? summarizeDiagnostics(this.diagnostics) : "Rendered");
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
renderActions() {
|
|
174
|
+
this.actionsEl.empty();
|
|
175
|
+
this.createIconButton("Help", "help-circle", () => {
|
|
176
|
+
new WorldOrbitHelpModal(this.app).open();
|
|
177
|
+
});
|
|
178
|
+
if (!this.source.trim()) {
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
if (this.plugin.settings.showFullscreenButton) {
|
|
182
|
+
this.createToolbarButton("Open fullscreen", false, () => {
|
|
183
|
+
this.openFullscreen();
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
if (!this.scene) {
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
const interactive = this.renderState === "interactive-mounted";
|
|
190
|
+
this.createToolbarButton(interactive ? "Lock interaction" : "Activate interaction", !interactive, () => {
|
|
191
|
+
if (interactive) {
|
|
192
|
+
this.mountStaticScene();
|
|
193
|
+
this.renderState = "rendered-static";
|
|
194
|
+
this.hostEl.toggleClass("is-locked", true);
|
|
195
|
+
this.statusEl.setText(this.diagnostics.length ? summarizeDiagnostics(this.diagnostics) : "Rendered");
|
|
196
|
+
this.renderActions();
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
this.mountInteractiveIfNeeded();
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
renderPlaceholder() {
|
|
203
|
+
this.destroyEmbeddedView();
|
|
204
|
+
this.hostEl.empty();
|
|
205
|
+
this.hostEl.createDiv({
|
|
206
|
+
cls: "worldorbit-obsidian-empty",
|
|
207
|
+
text: "Preview loads when the block becomes visible. Use fullscreen to render immediately.",
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
renderEmptyState() {
|
|
211
|
+
this.destroyEmbeddedView();
|
|
212
|
+
this.hostEl.empty();
|
|
213
|
+
this.hostEl.createDiv({
|
|
214
|
+
cls: "worldorbit-obsidian-empty",
|
|
215
|
+
text: "Type or paste a WorldOrbit document to start. Use the help button or the example command for a ready-made block.",
|
|
216
|
+
});
|
|
217
|
+
const code = this.hostEl.createEl("pre", {
|
|
218
|
+
cls: "worldorbit-obsidian-example",
|
|
219
|
+
text: [
|
|
220
|
+
"schema 2.5",
|
|
221
|
+
"",
|
|
222
|
+
"system Sol",
|
|
223
|
+
"",
|
|
224
|
+
"object star Sun",
|
|
225
|
+
"",
|
|
226
|
+
"object planet Earth",
|
|
227
|
+
" orbit Sun",
|
|
228
|
+
" semiMajor 1au",
|
|
229
|
+
].join("\n"),
|
|
230
|
+
});
|
|
231
|
+
code.setAttr("aria-label", "WorldOrbit quick start example");
|
|
232
|
+
}
|
|
233
|
+
mountStaticScene() {
|
|
234
|
+
if (!this.scene) {
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
this.hostEl.empty();
|
|
238
|
+
this.embeddedView?.destroy();
|
|
239
|
+
this.embeddedView = new WorldOrbitEmbeddedView({
|
|
240
|
+
container: this.hostEl,
|
|
241
|
+
scene: this.scene,
|
|
242
|
+
theme: createObsidianViewerTheme(),
|
|
243
|
+
interactive: false,
|
|
244
|
+
enablePointer: true,
|
|
245
|
+
enableTouch: true,
|
|
246
|
+
});
|
|
247
|
+
this.embeddedView.mount();
|
|
248
|
+
this.hostEl.toggleClass("is-locked", true);
|
|
249
|
+
}
|
|
250
|
+
mountInteractiveIfNeeded() {
|
|
251
|
+
if (!this.scene) {
|
|
252
|
+
this.ensureRendered();
|
|
253
|
+
}
|
|
254
|
+
if (!this.scene) {
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
if (!this.embeddedView) {
|
|
258
|
+
this.embeddedView = new WorldOrbitEmbeddedView({
|
|
259
|
+
container: this.hostEl,
|
|
260
|
+
scene: this.scene,
|
|
261
|
+
theme: createObsidianViewerTheme(),
|
|
262
|
+
interactive: true,
|
|
263
|
+
enablePointer: true,
|
|
264
|
+
enableTouch: true,
|
|
265
|
+
});
|
|
266
|
+
this.embeddedView.mount();
|
|
267
|
+
}
|
|
268
|
+
else {
|
|
269
|
+
this.embeddedView.setInteractive(true);
|
|
270
|
+
}
|
|
271
|
+
this.renderState = "interactive-mounted";
|
|
272
|
+
this.hostEl.toggleClass("is-locked", false);
|
|
273
|
+
this.statusEl.setText("Interactive preview active");
|
|
274
|
+
this.renderActions();
|
|
275
|
+
}
|
|
276
|
+
renderDiagnostics(diagnostics, errorState) {
|
|
277
|
+
this.diagnosticsEl.empty();
|
|
278
|
+
if (!diagnostics.length) {
|
|
279
|
+
this.diagnosticsEl.detach();
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
if (!this.diagnosticsEl.parentElement) {
|
|
283
|
+
this.rootEl.appendChild(this.diagnosticsEl);
|
|
284
|
+
}
|
|
285
|
+
for (const diagnostic of diagnostics) {
|
|
286
|
+
const itemEl = this.diagnosticsEl.createDiv({
|
|
287
|
+
cls: "worldorbit-obsidian-diagnostic",
|
|
288
|
+
});
|
|
289
|
+
itemEl.dataset.severity = diagnostic.severity;
|
|
290
|
+
const metaEl = itemEl.createDiv({ cls: "worldorbit-obsidian-diagnostic-meta" });
|
|
291
|
+
metaEl.createSpan({
|
|
292
|
+
cls: "worldorbit-obsidian-diagnostic-badge",
|
|
293
|
+
text: diagnostic.severity,
|
|
294
|
+
});
|
|
295
|
+
metaEl.createSpan({
|
|
296
|
+
text: diagnostic.source,
|
|
297
|
+
});
|
|
298
|
+
metaEl.createSpan({
|
|
299
|
+
text: formatDiagnosticLocation(diagnostic),
|
|
300
|
+
});
|
|
301
|
+
itemEl.createDiv({
|
|
302
|
+
cls: "worldorbit-obsidian-diagnostic-message",
|
|
303
|
+
text: diagnostic.message,
|
|
304
|
+
});
|
|
305
|
+
const canNavigate = Boolean(this.navigationContext && diagnostic.line);
|
|
306
|
+
if (canNavigate) {
|
|
307
|
+
const actionsEl = itemEl.createDiv({
|
|
308
|
+
cls: "worldorbit-obsidian-diagnostic-actions",
|
|
309
|
+
});
|
|
310
|
+
const button = actionsEl.createEl("button", {
|
|
311
|
+
cls: "worldorbit-obsidian-button",
|
|
312
|
+
text: "Go to error",
|
|
313
|
+
});
|
|
314
|
+
button.disabled = !canNavigate;
|
|
315
|
+
this.cleanupComponent.registerDomEvent(button, "click", () => {
|
|
316
|
+
void this.handleDiagnosticNavigation(diagnostic);
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
if (errorState) {
|
|
321
|
+
this.statusEl.setText(summarizeDiagnostics(diagnostics));
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
async openFullscreen() {
|
|
325
|
+
if (!this.scene) {
|
|
326
|
+
this.ensureRendered();
|
|
327
|
+
}
|
|
328
|
+
if (!this.scene) {
|
|
329
|
+
new Notice("WorldOrbit: this block could not be rendered.");
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
new WorldOrbitFullscreenModal(this.app, this.scene).open();
|
|
333
|
+
}
|
|
334
|
+
filterDiagnostics(diagnostics) {
|
|
335
|
+
return this.plugin.settings.showWarnings
|
|
336
|
+
? diagnostics
|
|
337
|
+
: diagnostics.filter((diagnostic) => diagnostic.source !== "validate");
|
|
338
|
+
}
|
|
339
|
+
async handleDiagnosticNavigation(diagnostic) {
|
|
340
|
+
if (!this.navigationContext) {
|
|
341
|
+
new Notice("WorldOrbit: no editor position is available for this block.");
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
344
|
+
const navigated = await navigateToWorldOrbitDiagnostic(this.app, this.navigationContext.sourcePath, this.navigationContext.contentStartLine, diagnostic);
|
|
345
|
+
if (!navigated) {
|
|
346
|
+
new Notice("WorldOrbit: could not focus the diagnostic location.");
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
attachResizeObserver() {
|
|
350
|
+
this.resizeObserver?.disconnect();
|
|
351
|
+
if (typeof ResizeObserver === "undefined") {
|
|
352
|
+
return;
|
|
353
|
+
}
|
|
354
|
+
this.resizeObserver = new ResizeObserver(() => {
|
|
355
|
+
this.embeddedView?.resize();
|
|
356
|
+
});
|
|
357
|
+
this.resizeObserver.observe(this.hostEl);
|
|
358
|
+
}
|
|
359
|
+
cancelPendingRender() {
|
|
360
|
+
if (this.pendingFrameId !== null) {
|
|
361
|
+
window.cancelAnimationFrame(this.pendingFrameId);
|
|
362
|
+
this.pendingFrameId = null;
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
destroyEmbeddedView() {
|
|
366
|
+
this.embeddedView?.destroy();
|
|
367
|
+
this.embeddedView = null;
|
|
368
|
+
}
|
|
369
|
+
createToolbarButton(label, emphasized, onClick) {
|
|
370
|
+
const button = this.actionsEl.createEl("button", {
|
|
371
|
+
cls: `worldorbit-obsidian-button${emphasized ? " mod-cta" : ""}`,
|
|
372
|
+
text: label,
|
|
373
|
+
});
|
|
374
|
+
this.cleanupComponent.registerDomEvent(button, "click", onClick);
|
|
375
|
+
return button;
|
|
376
|
+
}
|
|
377
|
+
createIconButton(label, icon, onClick) {
|
|
378
|
+
const button = this.actionsEl.createEl("button", {
|
|
379
|
+
cls: "worldorbit-obsidian-button worldorbit-obsidian-icon-button",
|
|
380
|
+
attr: {
|
|
381
|
+
"aria-label": label,
|
|
382
|
+
title: label,
|
|
383
|
+
},
|
|
384
|
+
});
|
|
385
|
+
setIcon(button, icon);
|
|
386
|
+
this.cleanupComponent.registerDomEvent(button, "click", onClick);
|
|
387
|
+
return button;
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
class WorldOrbitFullscreenModal extends Modal {
|
|
391
|
+
scene;
|
|
392
|
+
embeddedView = null;
|
|
393
|
+
constructor(app, scene) {
|
|
394
|
+
super(app);
|
|
395
|
+
this.scene = scene;
|
|
396
|
+
}
|
|
397
|
+
onOpen() {
|
|
398
|
+
this.modalEl.addClass("worldorbit-obsidian-modal");
|
|
399
|
+
this.setTitle("WorldOrbit");
|
|
400
|
+
const host = this.contentEl.createDiv({
|
|
401
|
+
cls: "worldorbit-obsidian-modal-host",
|
|
402
|
+
});
|
|
403
|
+
this.embeddedView = new WorldOrbitEmbeddedView({
|
|
404
|
+
container: host,
|
|
405
|
+
scene: this.scene,
|
|
406
|
+
theme: createObsidianViewerTheme(),
|
|
407
|
+
interactive: true,
|
|
408
|
+
enablePointer: true,
|
|
409
|
+
enableTouch: true,
|
|
410
|
+
});
|
|
411
|
+
this.embeddedView.mount();
|
|
412
|
+
}
|
|
413
|
+
onClose() {
|
|
414
|
+
this.embeddedView?.destroy();
|
|
415
|
+
this.embeddedView = null;
|
|
416
|
+
this.contentEl.empty();
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
class WorldOrbitHelpModal extends Modal {
|
|
420
|
+
constructor(app) {
|
|
421
|
+
super(app);
|
|
422
|
+
}
|
|
423
|
+
onOpen() {
|
|
424
|
+
this.setTitle(WORLDORBIT_HELP_TITLE);
|
|
425
|
+
this.contentEl.empty();
|
|
426
|
+
const prose = this.contentEl.createDiv({
|
|
427
|
+
cls: "worldorbit-obsidian-help",
|
|
428
|
+
});
|
|
429
|
+
prose.createEl("p", {
|
|
430
|
+
text: "Paste a fenced worldorbit block into a note to start rendering immediately.",
|
|
431
|
+
});
|
|
432
|
+
prose.createEl("pre", {
|
|
433
|
+
cls: "worldorbit-obsidian-example",
|
|
434
|
+
text: [
|
|
435
|
+
"```worldorbit",
|
|
436
|
+
"schema 2.5",
|
|
437
|
+
"",
|
|
438
|
+
"system Sol",
|
|
439
|
+
"",
|
|
440
|
+
"object star Sun",
|
|
441
|
+
"",
|
|
442
|
+
"object planet Earth",
|
|
443
|
+
" orbit Sun",
|
|
444
|
+
" semiMajor 1au",
|
|
445
|
+
"```",
|
|
446
|
+
].join("\n"),
|
|
447
|
+
});
|
|
448
|
+
const list = prose.createEl("ul");
|
|
449
|
+
for (const item of [
|
|
450
|
+
"`object planet NAME` creates a body",
|
|
451
|
+
"`orbit TARGET` places it around another object",
|
|
452
|
+
"`semiMajor 1au` or `distance 384400km` controls orbit size",
|
|
453
|
+
"`color #6fa8ff`, `radius`, `mass`, and `kind` add detail",
|
|
454
|
+
"Locked mode keeps note scrolling safe until you activate interaction",
|
|
455
|
+
]) {
|
|
456
|
+
list.createEl("li", { text: item });
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
onClose() {
|
|
460
|
+
this.contentEl.empty();
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
class WorldOrbitSettingTab extends PluginSettingTab {
|
|
464
|
+
plugin;
|
|
465
|
+
constructor(app, plugin) {
|
|
466
|
+
super(app, plugin);
|
|
467
|
+
this.plugin = plugin;
|
|
468
|
+
}
|
|
469
|
+
display() {
|
|
470
|
+
const { containerEl } = this;
|
|
471
|
+
containerEl.empty();
|
|
472
|
+
new Setting(containerEl)
|
|
473
|
+
.setName("Embedded interaction")
|
|
474
|
+
.setDesc("Choose whether embedded previews start locked or immediately interactive. Locked mode keeps note scrolling safe until you explicitly activate pan and zoom.")
|
|
475
|
+
.addDropdown((dropdown) => dropdown
|
|
476
|
+
.addOption("locked", "Locked by default")
|
|
477
|
+
.addOption("enabled", "Interactive by default")
|
|
478
|
+
.setValue(this.plugin.settings.embeddedInteraction)
|
|
479
|
+
.onChange(async (value) => {
|
|
480
|
+
this.plugin.settings.embeddedInteraction =
|
|
481
|
+
value;
|
|
482
|
+
await this.plugin.saveSettings();
|
|
483
|
+
}));
|
|
484
|
+
new Setting(containerEl)
|
|
485
|
+
.setName("Show validator diagnostics")
|
|
486
|
+
.setDesc("Display diagnostics produced by the validation phase under rendered WorldOrbit blocks.")
|
|
487
|
+
.addToggle((toggle) => toggle.setValue(this.plugin.settings.showWarnings).onChange(async (value) => {
|
|
488
|
+
this.plugin.settings.showWarnings = value;
|
|
489
|
+
await this.plugin.saveSettings();
|
|
490
|
+
}));
|
|
491
|
+
new Setting(containerEl)
|
|
492
|
+
.setName("Show fullscreen button")
|
|
493
|
+
.setDesc("Offer a larger interactive view for code blocks on small screens.")
|
|
494
|
+
.addToggle((toggle) => toggle.setValue(this.plugin.settings.showFullscreenButton).onChange(async (value) => {
|
|
495
|
+
this.plugin.settings.showFullscreenButton = value;
|
|
496
|
+
await this.plugin.saveSettings();
|
|
497
|
+
}));
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
function insertWorldOrbitExample(editor) {
|
|
501
|
+
const snippet = buildSolarSystemExampleBlock();
|
|
502
|
+
const cursor = editor.getCursor();
|
|
503
|
+
const lineCount = editor.lineCount();
|
|
504
|
+
const needsLeadingBreak = cursor.line > 0;
|
|
505
|
+
const needsTrailingBreak = cursor.line < lineCount - 1;
|
|
506
|
+
const wrapped = `${needsLeadingBreak ? "\n" : ""}${snippet}${needsTrailingBreak ? "\n" : ""}`;
|
|
507
|
+
editor.replaceRange(wrapped, cursor);
|
|
508
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { WorldOrbitDiagnostic } from "@worldorbit/core";
|
|
2
|
+
import type { EditorPosition } from "obsidian";
|
|
3
|
+
export declare function inferFenceContentStartLine(section: {
|
|
4
|
+
text: string;
|
|
5
|
+
lineStart: number;
|
|
6
|
+
}): number;
|
|
7
|
+
export declare function resolveDiagnosticEditorPosition(contentStartLine: number, diagnostic: WorldOrbitDiagnostic): EditorPosition | null;
|