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+ */
5214function 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 */
9181function 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+ */
110100function 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
132155export 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