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] " ;
2
3
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" */
48
5
49
6
// Make sure plugins are registered and enabled
50
- window . higlassDataFetchersByType = window . higlassDataFetchersByType || { } ;
7
+ window . higlassDataFetchersByType = window . higlassDataFetchersByType ||
8
+ { } ;
51
9
10
+ /**
11
+ * Create a unique identifier.
12
+ * @returns {string }
13
+ */
52
14
function uid ( ) {
53
15
return v4 ( ) . split ( "-" ) [ 0 ] ;
54
16
}
55
17
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
+
56
30
/**
57
31
* @template T
58
- * @param {import("npm:@anyiwdget /types").AnyModel } model
32
+ * @param {import("npm:@anywidget /types").AnyModel } model
59
33
* @param {unknown } payload
60
34
* @param {{ timeout?: number } } [options]
61
35
* @returns {Promise<{ data: T, buffers: DataView[] }> }
@@ -83,10 +57,26 @@ function send(model, payload, { timeout = 3000 } = {}) {
83
57
}
84
58
85
59
/**
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
+ *
87
69
* @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
+ * ```
90
80
*/
91
81
function resolveJupyterServers ( viewConfig , dataFetcherId ) {
92
82
let copy = JSON . parse ( JSON . stringify ( viewConfig ) ) ;
@@ -103,22 +93,26 @@ function resolveJupyterServers(viewConfig, dataFetcherId) {
103
93
return copy ;
104
94
}
105
95
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
+ */
110
100
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 ) {
112
107
let config = { ...dataConfig , server : "jupyter" } ;
113
108
return new hgc . dataFetchers . DataFetcher ( config , pubSub , {
114
- async fetchTiles ( { id , server , tileIds } ) {
109
+ async fetchTiles ( { tileIds } ) {
115
110
let { data } = await send ( model , { type : "tiles" , tileIds } ) ;
116
111
let result = hgc . services . tileResponseToData ( data , "jupyter" , tileIds ) ;
117
112
return result ;
118
113
} ,
119
114
async fetchTilesetInfo ( { server, tilesetUid } ) {
120
115
assert ( server === "jupyter" , "must be a jupyter server" ) ;
121
- let url = `${ server } -${ tilesetUid } ` ;
122
116
let { data } = await send ( model , { type : "tileset_info" , tilesetUid } ) ;
123
117
return data ;
124
118
} ,
@@ -127,26 +121,85 @@ function createDataFetcherForModel(model) {
127
121
} ,
128
122
} ) ;
129
123
} ;
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 ( ) ;
130
153
}
131
154
132
155
export default ( ) => {
133
156
let id = `jupyter-${ uid ( ) } ` ;
134
157
return {
158
+ /** @type {import("npm:@anywidget/[email protected] ").Initialize<{ _ts: string }> } */
135
159
async initialize ( { model } ) {
136
160
let tsId = model . get ( "_ts" ) ;
137
161
let tsModel = await model . widget_manager . get_model (
138
- tsId . slice ( "IPY_MODEL_" . length )
162
+ tsId . slice ( "IPY_MODEL_" . length ) ,
139
163
) ;
140
164
window . higlassDataFetchersByType [ tsId ] = {
141
165
name : id ,
142
166
dataFetcher : createDataFetcherForModel ( tsModel ) ,
143
167
} ;
144
168
} ,
169
+ /** @type {import("npm:@anywidget/[email protected] ").Render } */
145
170
async render ( { model, el } ) {
171
+ /** @type {{ views: Array<{ uid: string }> } } */
146
172
let viewconf = model . get ( "_viewconf" ) ;
147
173
let options = model . get ( "_options" ) ?? { } ;
148
174
let resolved = resolveJupyterServers ( viewconf , model . get ( "_ts" ) ) ;
149
175
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
+ } ;
150
203
} ,
151
204
} ;
152
205
} ;
0 commit comments