Skip to content

Commit 4c8689b

Browse files
committed
Update
1 parent b177271 commit 4c8689b

File tree

2 files changed

+124
-59
lines changed

2 files changed

+124
-59
lines changed

deno.json

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"lock": false,
3+
"compilerOptions": {
4+
"checkJs": true,
5+
"lib": ["dom", "dom.iterable", "esnext"]
6+
},
7+
"lint": {
8+
"rules": {
9+
"exclude": ["prefer-const"]
10+
}
11+
}
12+
}

src/higlass/widget.js

Lines changed: 112 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -1,61 +1,35 @@
1-
import hglib from "https://esm.sh/[email protected]?deps=react@17,react-dom@17,pixi.js@6";
1+
import * as hglib from "https://esm.sh/[email protected]?deps=react@17,react-dom@17,pixi.js@6";
2+
import { v4 } from "https://esm.sh/@lukeed/[email protected]";
23

3-
/**
4-
* @param {{
5-
* xDomain: [number, number],
6-
* yDomain: [number, number],
7-
* }} location
8-
*/
9-
function toPts({ xDomain, yDomain }) {
10-
let [x, xe] = xDomain;
11-
let [y, ye] = yDomain;
12-
return [x, xe, y, ye];
13-
}
14-
15-
async function render({ model, el }) {
16-
let viewconf = model.get("_viewconf");
17-
let options = model.get("_options") ?? {};
18-
let api = await hglib.viewer(el, viewconf, options);
19-
20-
model.on("msg:custom", (msg) => {
21-
msg = JSON.parse(msg);
22-
let [fn, ...args] = msg;
23-
api[fn](...args);
24-
});
25-
26-
if (viewconf.views.length === 1) {
27-
api.on("location", (loc) => {
28-
model.set("location", toPts(loc));
29-
model.save_changes();
30-
}, viewconf.views[0].uid);
31-
} else {
32-
viewconf.views.forEach((view, idx) => {
33-
api.on("location", (loc) => {
34-
let copy = model.get("location").slice();
35-
copy[idx] = toPts(loc);
36-
model.set("location", copy);
37-
model.save_changes();
38-
}, view.uid);
39-
});
40-
}
41-
}
42-
43-
export default { render };
44-
=======
45-
// import hglib from "https://esm.sh/[email protected]?deps=react@17,react-dom@17,pixi.js@6";
46-
import * as hglib from "http://localhost:5173/app/scripts/hglib.jsx";
47-
import { v4 } from "https://esm.sh/@lukeed/uuid@2";
4+
/** @import { HGC, PluginDataFetcherConstructor } from "./types.ts" */
485

496
// Make sure plugins are registered and enabled
50-
window.higlassDataFetchersByType = window.higlassDataFetchersByType || {};
7+
window.higlassDataFetchersByType = window.higlassDataFetchersByType ||
8+
{};
519

10+
/**
11+
* Create a unique identifier.
12+
* @returns {string}
13+
*/
5214
function uid() {
5315
return v4().split("-")[0];
5416
}
5517

18+
/**
19+
* Make an assertion.
20+
*
21+
* @param {unknown} expression - The expression to test.
22+
* @param {string=} msg - The optional message to display if the assertion fails.
23+
* @returns {asserts expression}
24+
* @throws an {@link Error} if `expression` is not truthy.
25+
*/
26+
function assert(expression, msg = "") {
27+
if (!expression) throw new Error(msg);
28+
}
29+
5630
/**
5731
* @template T
58-
* @param {import("npm:@anyiwdget/types").AnyModel} model
32+
* @param {import("npm:@anywidget/types").AnyModel} model
5933
* @param {unknown} payload
6034
* @param {{ timeout?: number }} [options]
6135
* @returns {Promise<{ data: T, buffers: DataView[] }>}
@@ -83,10 +57,26 @@ function send(model, payload, { timeout = 3000 } = {}) {
8357
}
8458

8559
/**
86-
* Detects server { server: 'jupyter' }, and creates a custom data entry for it.
60+
* Transforms the original view config into tracks recognized by the custom data fetcher.
61+
*
62+
* Finds tracks with `server: 'jupyter'`, removes the key, and adds a `data` object
63+
* with `type: dataFetcherId` and the track’s `tilesetUid`.
64+
*
65+
* @param {Record<string, unknown>} viewConfig - The original view configuration.
66+
* @param {string} dataFetcherId - The identifier for Jupyter-based data sources.
67+
* @returns {Record<string, unknown>} A modified deep copy of the view config.
68+
*
8769
* @example
88-
* resolveJupyterServers({ views: [{ tracks: { top: [{ server: 'jupyter', tilesetUid: 'abc' }] } }] }, 'jupyter-123')
89-
* // { views: [{ tracks: { top: [{ tilesetUid: 'abc', data: { type: 'jupyter-123', tilesetUid: 'abc' } }] } }] }
70+
* ```js
71+
* resolveJupyterServers(
72+
* { views: [{ tracks: { top: [{ server: 'jupyter', tilesetUid: 'abc' }] } }] },
73+
* 'jupyter-123'
74+
* );
75+
* // Returns:
76+
* // {
77+
* // views: [{ tracks: { top: [{ tilesetUid: 'abc', data: { type: 'jupyter-123', tilesetUid: 'abc' } }] } }]
78+
* // }
79+
* ```
9080
*/
9181
function resolveJupyterServers(viewConfig, dataFetcherId) {
9282
let copy = JSON.parse(JSON.stringify(viewConfig));
@@ -103,22 +93,26 @@ function resolveJupyterServers(viewConfig, dataFetcherId) {
10393
return copy;
10494
}
10595

106-
function assert(condition, message) {
107-
if (!condition) throw new Error(message);
108-
}
109-
96+
/**
97+
* @param {import("npm:@anywidget/types").AnyModel} model
98+
* @returns{PluginDataFetcherConstructor}
99+
*/
110100
function createDataFetcherForModel(model) {
111-
return function createDataFetcher(hgc, dataConfig, pubSub) {
101+
/**
102+
* @param {HGC} hgc
103+
* @param {Record<string, unknown>} dataConfig
104+
* @param {unknown} pubSub
105+
*/
106+
const DataFetcher = function createDataFetcher(hgc, dataConfig, pubSub) {
112107
let config = { ...dataConfig, server: "jupyter" };
113108
return new hgc.dataFetchers.DataFetcher(config, pubSub, {
114-
async fetchTiles({ id, server, tileIds }) {
109+
async fetchTiles({ tileIds }) {
115110
let { data } = await send(model, { type: "tiles", tileIds });
116111
let result = hgc.services.tileResponseToData(data, "jupyter", tileIds);
117112
return result;
118113
},
119114
async fetchTilesetInfo({ server, tilesetUid }) {
120115
assert(server === "jupyter", "must be a jupyter server");
121-
let url = `${server}-${tilesetUid}`;
122116
let { data } = await send(model, { type: "tileset_info", tilesetUid });
123117
return data;
124118
},
@@ -127,26 +121,85 @@ function createDataFetcherForModel(model) {
127121
},
128122
});
129123
};
124+
125+
return /** @type{any} */ (DataFetcher);
126+
}
127+
128+
/**
129+
* @param {{
130+
* xDomain: [number, number],
131+
* yDomain: [number, number],
132+
* }} location
133+
*/
134+
function toPts({ xDomain, yDomain }) {
135+
let [x, xe] = xDomain;
136+
let [y, ye] = yDomain;
137+
return [x, xe, y, ye];
138+
}
139+
140+
/**
141+
* @param {HTMLElement} el
142+
* @returns {() => void} unlisten
143+
*/
144+
function addEventListenersTo(el) {
145+
let controller = new AbortController();
146+
147+
// prevent right click events from bubbling up to Jupyter/JupyterLab
148+
el.addEventListener("contextmenu", (event) => event.stopPropagation(), {
149+
signal: controller.signal,
150+
});
151+
152+
return () => controller.abort();
130153
}
131154

132155
export default () => {
133156
let id = `jupyter-${uid()}`;
134157
return {
158+
/** @type{import("npm:@anywidget/[email protected]").Initialize<{ _ts: string }>} */
135159
async initialize({ model }) {
136160
let tsId = model.get("_ts");
137161
let tsModel = await model.widget_manager.get_model(
138-
tsId.slice("IPY_MODEL_".length)
162+
tsId.slice("IPY_MODEL_".length),
139163
);
140164
window.higlassDataFetchersByType[tsId] = {
141165
name: id,
142166
dataFetcher: createDataFetcherForModel(tsModel),
143167
};
144168
},
169+
/** @type{import("npm:@anywidget/[email protected]").Render} */
145170
async render({ model, el }) {
171+
/** @type {{ views: Array<{ uid: string }> }} */
146172
let viewconf = model.get("_viewconf");
147173
let options = model.get("_options") ?? {};
148174
let resolved = resolveJupyterServers(viewconf, model.get("_ts"));
149175
let api = await hglib.viewer(el, resolved, options);
176+
177+
let unlisten = addEventListenersTo(el);
178+
179+
model.on("msg:custom", (msg) => {
180+
msg = JSON.parse(msg);
181+
let [fn, ...args] = msg;
182+
api[fn](...args);
183+
});
184+
185+
if (viewconf.views.length === 1) {
186+
api.on("location", (loc) => {
187+
model.set("location", toPts(loc));
188+
model.save_changes();
189+
}, viewconf.views[0].uid);
190+
} else {
191+
viewconf.views.forEach((view, idx) => {
192+
api.on("location", (loc) => {
193+
let copy = model.get("location").slice();
194+
copy[idx] = toPts(loc);
195+
model.set("location", copy);
196+
model.save_changes();
197+
}, view.uid);
198+
});
199+
}
200+
return () => {
201+
unlisten();
202+
};
150203
},
151204
};
152205
};

0 commit comments

Comments
 (0)