@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 +114 -0
- package/dist/index.es.js +229 -0
- package/dist/inspectorPanel-Dc1i_a0B.js +1619 -0
- package/package.json +55 -0
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.
|
package/dist/index.es.js
ADDED
|
@@ -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 };
|