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,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;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export function inferFenceContentStartLine(section) {
|
|
2
|
+
const lines = section.text.split(/\r?\n/);
|
|
3
|
+
const openerIndex = lines.findIndex((line) => /^\s*(```+|~~~+)\s*worldorbit(?:\s+.*)?$/i.test(line));
|
|
4
|
+
return section.lineStart + (openerIndex >= 0 ? openerIndex + 1 : 1);
|
|
5
|
+
}
|
|
6
|
+
export function resolveDiagnosticEditorPosition(contentStartLine, diagnostic) {
|
|
7
|
+
if (!diagnostic.line || diagnostic.line < 1) {
|
|
8
|
+
return null;
|
|
9
|
+
}
|
|
10
|
+
return {
|
|
11
|
+
line: contentStartLine + diagnostic.line - 1,
|
|
12
|
+
ch: Math.max((diagnostic.column ?? 1) - 1, 0),
|
|
13
|
+
};
|
|
14
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { resolveTheme } from "@worldorbit/viewer/interactive-2d";
|
|
2
|
+
export function createObsidianViewerTheme() {
|
|
3
|
+
const base = resolveTheme("atlas");
|
|
4
|
+
return {
|
|
5
|
+
...base,
|
|
6
|
+
name: "obsidian",
|
|
7
|
+
backgroundStart: "var(--background-primary, #10131a)",
|
|
8
|
+
backgroundEnd: "var(--background-secondary, #171b24)",
|
|
9
|
+
backgroundGlow: "var(--interactive-accent-hover, rgba(143, 202, 255, 0.18))",
|
|
10
|
+
panel: "var(--background-secondary-alt, rgba(10, 16, 24, 0.92))",
|
|
11
|
+
panelLine: "var(--background-modifier-border, rgba(168, 207, 242, 0.2))",
|
|
12
|
+
relation: "var(--text-accent-hover, rgba(240, 180, 100, 0.42))",
|
|
13
|
+
orbit: "var(--text-faint, rgba(163, 209, 255, 0.24))",
|
|
14
|
+
guide: "var(--background-modifier-border-hover, rgba(255, 255, 255, 0.08))",
|
|
15
|
+
leader: "var(--text-muted, rgba(225, 238, 255, 0.4))",
|
|
16
|
+
ink: "var(--text-normal, #e8f0ff)",
|
|
17
|
+
muted: "var(--text-muted, rgba(232, 240, 255, 0.7))",
|
|
18
|
+
accent: "var(--interactive-accent, #f0b464)",
|
|
19
|
+
accentStrong: "var(--text-accent-hover, #ff7f5f)",
|
|
20
|
+
selected: "var(--color-cyan, rgba(255, 214, 139, 0.92))",
|
|
21
|
+
starCore: "var(--color-yellow, #ffcc67)",
|
|
22
|
+
starStroke: "var(--text-normal, rgba(255, 245, 203, 0.85))",
|
|
23
|
+
starGlow: "var(--color-orange, #ffe8a3)",
|
|
24
|
+
objectSpecular: "var(--text-normal, #f5f8ff)",
|
|
25
|
+
selectionHalo: "var(--interactive-accent, rgba(255, 214, 139, 0.9))",
|
|
26
|
+
atmosphere: "var(--color-cyan, rgba(143, 202, 255, 0.4))",
|
|
27
|
+
cometTail: "var(--color-cyan, rgba(193, 243, 255, 0.7))",
|
|
28
|
+
fontFamily: "var(--font-interface, \"Segoe UI\", sans-serif)",
|
|
29
|
+
displayFont: "var(--font-text, var(--font-interface, \"Segoe UI\", sans-serif))",
|
|
30
|
+
};
|
|
31
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { EditorPosition } from "obsidian";
|
|
2
|
+
import type { RenderScene, WorldOrbitDiagnostic } from "@worldorbit/core/types";
|
|
3
|
+
import type { WorldOrbitTheme, WorldOrbitViewer2D } from "@worldorbit/viewer/interactive-2d";
|
|
4
|
+
export type EmbeddedInteractionMode = "locked" | "enabled";
|
|
5
|
+
export interface WorldOrbitObsidianPluginSettings {
|
|
6
|
+
embeddedInteraction: EmbeddedInteractionMode;
|
|
7
|
+
showWarnings: boolean;
|
|
8
|
+
showFullscreenButton: boolean;
|
|
9
|
+
}
|
|
10
|
+
export interface BlockNavigationContext {
|
|
11
|
+
sourcePath: string;
|
|
12
|
+
contentStartLine: number;
|
|
13
|
+
}
|
|
14
|
+
export interface WorldOrbitEmbeddedViewOptions {
|
|
15
|
+
container: HTMLElement;
|
|
16
|
+
scene: RenderScene;
|
|
17
|
+
theme: WorldOrbitTheme;
|
|
18
|
+
interactive: boolean;
|
|
19
|
+
enablePointer: boolean;
|
|
20
|
+
enableTouch: boolean;
|
|
21
|
+
createViewer?: (container: HTMLElement, options: {
|
|
22
|
+
scene: RenderScene;
|
|
23
|
+
theme: WorldOrbitTheme;
|
|
24
|
+
pointer: boolean;
|
|
25
|
+
touch: boolean;
|
|
26
|
+
width?: number;
|
|
27
|
+
height?: number;
|
|
28
|
+
}) => WorldOrbitViewer2D;
|
|
29
|
+
renderStatic?: (scene: RenderScene, options: {
|
|
30
|
+
theme: WorldOrbitTheme;
|
|
31
|
+
width?: number;
|
|
32
|
+
height?: number;
|
|
33
|
+
}) => string;
|
|
34
|
+
}
|
|
35
|
+
export interface WorldOrbitEmbeddedViewState {
|
|
36
|
+
interactive: boolean;
|
|
37
|
+
destroyed: boolean;
|
|
38
|
+
}
|
|
39
|
+
export interface DiagnosticNavigationTarget {
|
|
40
|
+
diagnostic: WorldOrbitDiagnostic;
|
|
41
|
+
position: EditorPosition | null;
|
|
42
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { WorldOrbitEmbeddedViewOptions, WorldOrbitEmbeddedViewState } from "./types.js";
|
|
2
|
+
export declare class WorldOrbitEmbeddedView {
|
|
3
|
+
private readonly options;
|
|
4
|
+
private viewer;
|
|
5
|
+
private state;
|
|
6
|
+
constructor(options: WorldOrbitEmbeddedViewOptions);
|
|
7
|
+
getState(): WorldOrbitEmbeddedViewState;
|
|
8
|
+
mount(): void;
|
|
9
|
+
setInteractive(interactive: boolean): void;
|
|
10
|
+
resize(): void;
|
|
11
|
+
destroy(): void;
|
|
12
|
+
private renderCurrent;
|
|
13
|
+
private destroyViewer;
|
|
14
|
+
}
|