@genome-spy/inspector 0.79.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 ADDED
@@ -0,0 +1,114 @@
1
+ # @genome-spy/inspector
2
+
3
+ The inspector is a DevTools-like side panel for looking at GenomeSpy runtime
4
+ state while developing or debugging visualizations. It shows the view hierarchy,
5
+ encodings, scale/axis/legend resolutions, dataflow, params, and unit mark state.
6
+ The goal is to make the live runtime easier to understand without adding debug
7
+ UI or heavy inspection code to the default Core or App bundles.
8
+
9
+ The initial implementation was largely vibe-coded with Codex. The tool is
10
+ usable for development work, but the package API may still change as the
11
+ integration points are refined.
12
+
13
+ ## Package Integrations
14
+
15
+ ### App
16
+
17
+ GenomeSpy App uses the inspector through the App plugin surface:
18
+
19
+ ```js
20
+ import { genomeSpyInspector } from "@genome-spy/inspector";
21
+
22
+ await embed(element, spec, {
23
+ plugins: [genomeSpyInspector()],
24
+ });
25
+ ```
26
+
27
+ In the App development single-page entry, the inspector is installed
28
+ automatically so it is available from the three-dot menu in the App toolbar
29
+ during local development.
30
+
31
+ The plugin uses App UI hooks to register a menu item and a side panel. It also
32
+ registers an inspector launcher so App-side development commands can open this
33
+ panel directly to a specific inspector view.
34
+
35
+ ### Playground
36
+
37
+ GenomeSpy Playground depends on this package directly. The Playground toolbar
38
+ has an Inspector button that replaces the editor/file pane with the inspector,
39
+ so the plot and inspector are visible side by side.
40
+
41
+ Playground uses the embeddable panel API:
42
+
43
+ ```js
44
+ import { createInspectorPanel } from "@genome-spy/inspector";
45
+
46
+ const inspector = await createInspectorPanel({
47
+ getRootView: () => embedResult.getDebugViewRoot(),
48
+ });
49
+
50
+ inspectorContainer.append(inspector.panel);
51
+ await inspector.session.refresh();
52
+ ```
53
+
54
+ The inspector is refreshed after Playground rebuilds the Core embed from the
55
+ current editor contents.
56
+
57
+ ### Core Embeds
58
+
59
+ Core does not load the inspector by itself. Applications that embed Core can
60
+ install this package and attach the inspector only in development builds or
61
+ behind their own debug UI.
62
+
63
+ The Core embed result exposes a small debug hook:
64
+
65
+ ```js
66
+ const api = await embed(element, spec);
67
+
68
+ api.getDebugViewRoot();
69
+ ```
70
+
71
+ That hook gives the inspector access to the live root view without adding a
72
+ plugin system or loading debug UI into Core.
73
+
74
+ For quick integration, use the floating overlay helper:
75
+
76
+ ```js
77
+ import { embed } from "@genome-spy/core";
78
+ import { attachInspectorOverlay } from "@genome-spy/inspector";
79
+
80
+ const api = await embed(element, spec);
81
+
82
+ await attachInspectorOverlay({
83
+ getRootView: () => api.getDebugViewRoot(),
84
+ });
85
+ ```
86
+
87
+ For applications with their own panels or split layouts, use
88
+ `createInspectorPanel(...)` instead and place the returned `panel` element in
89
+ the application UI.
90
+
91
+ See the embed example:
92
+ [Inspector overlay](../embed-examples/src/inspectorOverlay.html)
93
+ ([source](../embed-examples/src/inspectorOverlay.js)).
94
+
95
+ ## Architecture
96
+
97
+ The inspector is centered around `InspectorSession`, which reads the live
98
+ runtime through a small host object:
99
+
100
+ ```ts
101
+ interface InspectorHost {
102
+ getRootView(): object | undefined;
103
+ highlightView?(view: object | null): void;
104
+ }
105
+ ```
106
+
107
+ The session dynamically imports Core debug snapshot helpers from
108
+ `@genome-spy/core/debug/...` when it refreshes. This keeps debug-only collection
109
+ code out of normal startup paths.
110
+
111
+ `GsInspectorPanel` is a LitElement component that renders a session snapshot.
112
+ `createInspectorPanel(...)` wires a session to the panel. App, Playground, and
113
+ Core embed examples all use the same session and panel components, with only
114
+ small host adapters around them.
@@ -0,0 +1,229 @@
1
+ import { getViewIdentityRegistry as e } from "@genome-spy/core/view/viewIdentityRegistry.js";
2
+ //#region \0rolldown/runtime.js
3
+ var t = Object.defineProperty, n = (e, n) => {
4
+ let r = {};
5
+ for (var i in e) t(r, i, {
6
+ get: e[i],
7
+ enumerable: !0
8
+ });
9
+ return n || t(r, Symbol.toStringTag, { value: "Module" }), r;
10
+ }, r = {
11
+ prefix: "fas",
12
+ iconName: "bug",
13
+ icon: [
14
+ 512,
15
+ 512,
16
+ [],
17
+ "f188",
18
+ "M256 0c53 0 96 43 96 96l0 3.6c0 15.7-12.7 28.4-28.4 28.4l-135.1 0c-15.7 0-28.4-12.7-28.4-28.4l0-3.6c0-53 43-96 96-96zM41.4 105.4c12.5-12.5 32.8-12.5 45.3 0l64 64c.7 .7 1.3 1.4 1.9 2.1c14.2-7.3 30.4-11.4 47.5-11.4l112 0c17.1 0 33.2 4.1 47.5 11.4c.6-.7 1.2-1.4 1.9-2.1l64-64c12.5-12.5 32.8-12.5 45.3 0s12.5 32.8 0 45.3l-64 64c-.7 .7-1.4 1.3-2.1 1.9c6.2 12 10.1 25.3 11.1 39.5l64.3 0c17.7 0 32 14.3 32 32s-14.3 32-32 32l-64 0c0 24.6-5.5 47.8-15.4 68.6c2.2 1.3 4.2 2.9 6 4.8l64 64c12.5 12.5 12.5 32.8 0 45.3s-32.8 12.5-45.3 0l-63.1-63.1c-24.5 21.8-55.8 36.2-90.3 39.6L272 240c0-8.8-7.2-16-16-16s-16 7.2-16 16l0 239.2c-34.5-3.4-65.8-17.8-90.3-39.6L86.6 502.6c-12.5 12.5-32.8 12.5-45.3 0s-12.5-32.8 0-45.3l64-64c1.9-1.9 3.9-3.4 6-4.8C101.5 367.8 96 344.6 96 320l-64 0c-17.7 0-32-14.3-32-32s14.3-32 32-32l64.3 0c1.1-14.1 5-27.5 11.1-39.5c-.7-.6-1.4-1.2-2.1-1.9l-64-64c-12.5-12.5-12.5-32.8 0-45.3z"
19
+ ]
20
+ }, i = /* @__PURE__ */ n({ default: () => a }), a = class extends EventTarget {
21
+ #e;
22
+ #t = !1;
23
+ #n = /* @__PURE__ */ new WeakMap();
24
+ #r = /* @__PURE__ */ new Map();
25
+ #i = {};
26
+ #a = /* @__PURE__ */ new WeakSet();
27
+ #o;
28
+ #s;
29
+ #c;
30
+ #l;
31
+ #u;
32
+ #d = [];
33
+ #f = !1;
34
+ snapshot = {
35
+ rootId: void 0,
36
+ nodes: [],
37
+ resolutions: {
38
+ scales: [],
39
+ axes: [],
40
+ legends: []
41
+ },
42
+ dataflow: {
43
+ sourceIds: [],
44
+ nodes: [],
45
+ collectorCount: 0
46
+ },
47
+ params: { scopes: [] },
48
+ marks: { marks: [] }
49
+ };
50
+ constructor(e) {
51
+ super(), this.#e = e;
52
+ }
53
+ get includeChrome() {
54
+ return this.#t;
55
+ }
56
+ async setIncludeChrome(e) {
57
+ this.#t !== e && (this.#t = e, await this.refresh());
58
+ }
59
+ async refresh() {
60
+ if (this.#f) return;
61
+ let e = this.#y(), [t, n, r, i, a] = await Promise.all([
62
+ this.#p(),
63
+ this.#m(),
64
+ this.#h(),
65
+ this.#g(),
66
+ this.#_()
67
+ ]);
68
+ this.#r = /* @__PURE__ */ new Map(), this.#a = o(e);
69
+ let s = t.createViewDebugSnapshot(e, {
70
+ includeChrome: this.#t,
71
+ getDebugId: (e) => this.#b(e)
72
+ });
73
+ this.snapshot = {
74
+ ...s,
75
+ resolutions: n.createResolutionDebugSnapshot(e, { getDebugId: (e) => this.#b(e) }),
76
+ dataflow: r.createDataflowDebugSnapshot(e?.context.dataFlow, {
77
+ getDebugId: (e) => this.#b(e),
78
+ rootView: e
79
+ }),
80
+ params: i.createParamDebugSnapshot(e, { getDebugId: (e) => this.#b(e) }),
81
+ marks: a.createMarkDebugSnapshot(e, { getDebugId: (e) => this.#b(e) })
82
+ }, this.#v(), this.dispatchEvent(new Event("snapshot"));
83
+ }
84
+ highlightView(e) {
85
+ let t = e ? this.#r.get(e) : null;
86
+ if (this.#e.highlightView) {
87
+ this.#e.highlightView(t ?? null);
88
+ return;
89
+ }
90
+ let n = this.#y();
91
+ n && n.context.highlightView(t ?? null);
92
+ }
93
+ dispose() {
94
+ this.#f = !0;
95
+ for (let e of this.#d.splice(0)) e();
96
+ this.highlightView(void 0);
97
+ }
98
+ #p() {
99
+ return this.#o ??= import("@genome-spy/core/debug/viewDebugSnapshot.js"), this.#o;
100
+ }
101
+ #m() {
102
+ return this.#s ??= import("@genome-spy/core/debug/resolutionDebugSnapshot.js"), this.#s;
103
+ }
104
+ #h() {
105
+ return this.#c ??= import("@genome-spy/core/debug/dataflowDebugSnapshot.js"), this.#c;
106
+ }
107
+ #g() {
108
+ return this.#l ??= import("@genome-spy/core/debug/paramDebugSnapshot.js"), this.#l;
109
+ }
110
+ #_() {
111
+ return this.#u ??= import("@genome-spy/core/debug/markDebugSnapshot.js"), this.#u;
112
+ }
113
+ #v() {
114
+ if (this.#d.length > 0) return;
115
+ let e = this.#y();
116
+ if (!e) return;
117
+ let t = () => {
118
+ this.refresh();
119
+ };
120
+ for (let n of [
121
+ "layoutComputed",
122
+ "subtreeDataReady",
123
+ "dataFlowBuilt"
124
+ ]) e.context.addBroadcastListener(n, t), this.#d.push(() => e.context.removeBroadcastListener(n, t));
125
+ }
126
+ #y() {
127
+ return this.#e.getRootView();
128
+ }
129
+ #b(t) {
130
+ if (this.#a.has(t)) {
131
+ let n = e(this.#y()).getId(t);
132
+ return this.#r.set(n, t), n;
133
+ }
134
+ let n = this.#n.get(t);
135
+ if (n) return this.#r.set(n, t), n;
136
+ let r = s(t), i = (this.#i[r] ?? 0) + 1;
137
+ this.#i[r] = i;
138
+ let a = r + String(i);
139
+ return this.#n.set(t, a), this.#r.set(a, t), a;
140
+ }
141
+ };
142
+ function o(e) {
143
+ let t = /* @__PURE__ */ new WeakSet();
144
+ return e?.visit?.((e) => {
145
+ t.add(e);
146
+ }), t;
147
+ }
148
+ function s(e) {
149
+ return "channel" in e ? "r" : "o";
150
+ }
151
+ //#endregion
152
+ //#region src/inspectorPlugin.js
153
+ function c(e = {}) {
154
+ return {
155
+ name: "@genome-spy/inspector",
156
+ async install(t) {
157
+ if (!t.ui?.registerToolbarMenuItem) throw Error("genomeSpyInspector requires an App UI host.");
158
+ let n, i, o, s = async () => {
159
+ if (i) return i;
160
+ if (!t.ui.registerSidePanel) throw Error("genomeSpyInspector requires side panel support.");
161
+ let [{ GsInspectorPanel: r }] = await Promise.all([import("./inspectorPanel-Dc1i_a0B.js")]);
162
+ return n = new a({ getRootView: () => t.genomeSpy ? t.genomeSpy.viewRoot : void 0 }), o = new r(), o.session = n, o.addEventListener("close", () => {
163
+ i?.hide();
164
+ }), i = t.ui.registerSidePanel({
165
+ id: "genome-spy-inspector",
166
+ element: o,
167
+ preferredWidth: e.preferredWidth ?? "min(46vw, 760px)"
168
+ }), i;
169
+ }, c = async (e = {}) => {
170
+ let t = await s();
171
+ e.panel && o && (o.activePanel = e.panel), t.show(), await n.refresh();
172
+ }, l = t.ui.registerToolbarMenuItem({
173
+ label: "Inspector",
174
+ icon: r,
175
+ callback: () => {
176
+ c().catch(() => {});
177
+ }
178
+ });
179
+ return () => {
180
+ l(), n?.dispose(), n = void 0, i?.dispose(), i = void 0, o?.remove(), o = void 0;
181
+ };
182
+ }
183
+ };
184
+ }
185
+ //#endregion
186
+ //#region src/index.js
187
+ async function l(e, t = {}) {
188
+ let [{ default: n }, { GsInspectorPanel: r }] = await Promise.all([Promise.resolve().then(() => i), import("./inspectorPanel-Dc1i_a0B.js")]), a = new n(e), o = new r();
189
+ return o.session = a, t.activePanel && (o.activePanel = t.activePanel), {
190
+ panel: o,
191
+ session: a,
192
+ dispose: () => {
193
+ a.dispose(), o.remove();
194
+ }
195
+ };
196
+ }
197
+ async function u(e, t = {}) {
198
+ let n = t.container ?? document.body, r = document.createElement("section");
199
+ r.className = "gs-inspector-overlay", Object.assign(r.style, {
200
+ position: "fixed",
201
+ top: "0",
202
+ right: "0",
203
+ bottom: "0",
204
+ zIndex: "2147483647",
205
+ width: t.width ?? "min(46vw, 760px)",
206
+ minWidth: "320px",
207
+ maxWidth: "100vw",
208
+ boxShadow: "0 0 18px rgba(0, 0, 0, 0.35)",
209
+ resize: "horizontal",
210
+ overflow: "hidden"
211
+ });
212
+ let i = await l(e, { activePanel: t.activePanel });
213
+ Object.assign(i.panel.style, {
214
+ display: "block",
215
+ height: "100%",
216
+ minHeight: "0"
217
+ }), r.append(i.panel), n.append(r);
218
+ let a = () => {
219
+ i.dispose(), r.remove();
220
+ };
221
+ return i.panel.addEventListener("close", a, { once: !0 }), await i.session.refresh(), {
222
+ element: r,
223
+ panel: i.panel,
224
+ session: i.session,
225
+ dispose: a
226
+ };
227
+ }
228
+ //#endregion
229
+ export { a as InspectorSession, u as attachInspectorOverlay, l as createInspectorPanel, c as genomeSpyInspector };