igv 3.2.4 → 3.2.6

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/dist/igv.js CHANGED
@@ -8629,6 +8629,7 @@
8629
8629
  }
8630
8630
 
8631
8631
 
8632
+ let promise;
8632
8633
  /**
8633
8634
  * Return a promise for an access token for the given scope. If the user hasn't authorized the scope request it
8634
8635
  *
@@ -8647,22 +8648,27 @@
8647
8648
  return google.igv.tokenResponse.access_token
8648
8649
  } else {
8649
8650
  const tokenClient = google.igv.tokenClient;
8650
- return new Promise((resolve, reject) => {
8651
- try {
8652
- // Settle this promise in the response callback for requestAccessToken()
8653
- tokenClient.callback = (tokenResponse) => {
8654
- if (tokenResponse.error !== undefined) {
8655
- reject(tokenResponse);
8656
- }
8657
- google.igv.tokenResponse = tokenResponse;
8658
- google.igv.tokenExpiresAt = Date.now() + tokenResponse.expires_in * 1000;
8659
- resolve(tokenResponse.access_token);
8660
- };
8661
- tokenClient.requestAccessToken({scope});
8662
- } catch (err) {
8663
- console.log(err);
8664
- }
8665
- })
8651
+ if(!promise) {
8652
+ promise = new Promise((resolve, reject) => {
8653
+ try {
8654
+ // Settle this promise in the response callback for requestAccessToken()
8655
+ tokenClient.callback = (tokenResponse) => {
8656
+ if (tokenResponse.error !== undefined) {
8657
+ reject(tokenResponse);
8658
+ }
8659
+ google.igv.tokenResponse = tokenResponse;
8660
+ google.igv.tokenExpiresAt = Date.now() + tokenResponse.expires_in * 1000;
8661
+ console.log("Access token expires at " + new Date(google.igv.tokenExpiresAt));
8662
+ resolve(tokenResponse.access_token);
8663
+ };
8664
+ console.log("Requesting access token");
8665
+ tokenClient.requestAccessToken({scope});
8666
+ } catch (err) {
8667
+ console.log(err);
8668
+ }
8669
+ });
8670
+ }
8671
+ return promise
8666
8672
  }
8667
8673
  }
8668
8674
 
@@ -10946,7 +10952,7 @@
10946
10952
  return list;
10947
10953
  }
10948
10954
 
10949
- /*! @license DOMPurify 3.2.4 | (c) Cure53 and other contributors | Released under the Apache license 2.0 and Mozilla Public License 2.0 | github.com/cure53/DOMPurify/blob/3.2.4/LICENSE */
10955
+ /*! @license DOMPurify 3.2.5 | (c) Cure53 and other contributors | Released under the Apache license 2.0 and Mozilla Public License 2.0 | github.com/cure53/DOMPurify/blob/3.2.5/LICENSE */
10950
10956
 
10951
10957
  const {
10952
10958
  entries,
@@ -11006,6 +11012,9 @@
11006
11012
  */
11007
11013
  function unapply(func) {
11008
11014
  return function (thisArg) {
11015
+ if (thisArg instanceof RegExp) {
11016
+ thisArg.lastIndex = 0;
11017
+ }
11009
11018
  for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
11010
11019
  args[_key - 1] = arguments[_key];
11011
11020
  }
@@ -11244,7 +11253,7 @@
11244
11253
  function createDOMPurify() {
11245
11254
  let window = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : getGlobal();
11246
11255
  const DOMPurify = root => createDOMPurify(root);
11247
- DOMPurify.version = '3.2.4';
11256
+ DOMPurify.version = '3.2.5';
11248
11257
  DOMPurify.removed = [];
11249
11258
  if (!window || !window.document || window.document.nodeType !== NODE_TYPE.document || !window.Element) {
11250
11259
  // Not running in a browser, provide a factory function
@@ -11849,7 +11858,7 @@
11849
11858
  allowedTags: ALLOWED_TAGS
11850
11859
  });
11851
11860
  /* Detect mXSS attempts abusing namespace confusion */
11852
- if (currentNode.hasChildNodes() && !_isNode(currentNode.firstElementChild) && regExpTest(/<[/\w]/g, currentNode.innerHTML) && regExpTest(/<[/\w]/g, currentNode.textContent)) {
11861
+ if (currentNode.hasChildNodes() && !_isNode(currentNode.firstElementChild) && regExpTest(/<[/\w!]/g, currentNode.innerHTML) && regExpTest(/<[/\w!]/g, currentNode.textContent)) {
11853
11862
  _forceRemove(currentNode);
11854
11863
  return true;
11855
11864
  }
@@ -16343,7 +16352,10 @@
16343
16352
  }
16344
16353
  } else {
16345
16354
  // All directives that could change the format, and thus decoder, should have been read by now.
16346
- this.setDecoder(header.format);
16355
+ // Set the decoder, unless it is explicitly set in the track configuration (not common)
16356
+ if(!this.config.decode) {
16357
+ this.setDecoder(header.format);
16358
+ }
16347
16359
 
16348
16360
  // If the line can be parsed as a feature assume we are beyond the header, if any
16349
16361
  const tokens = line.split(this.delimiter || "\t");
@@ -30852,6 +30864,7 @@
30852
30864
  }
30853
30865
 
30854
30866
  const DEFAULT_GENOMES_URL = "https://igv.org/genomes/genomes.json";
30867
+ const BACKUP_GENOMES_URL = "https://raw.githubusercontent.com/igvteam/igv-genomes/refs/heads/main/dist/genomes.json";
30855
30868
 
30856
30869
  const GenomeUtils = {
30857
30870
 
@@ -30859,34 +30872,42 @@
30859
30872
 
30860
30873
  if (!GenomeUtils.KNOWN_GENOMES) {
30861
30874
 
30862
- const table = {};
30875
+ let table = {};
30876
+
30877
+ const processJson = (jsonArray, table) => {
30878
+ jsonArray.forEach(function (json) {
30879
+ table[json.id] = json;
30880
+ });
30881
+ return table
30882
+ };
30863
30883
 
30864
30884
  // Get default genomes
30865
30885
  if (config.loadDefaultGenomes !== false) {
30866
- const url = DEFAULT_GENOMES_URL;
30867
- const jsonArray = await igvxhr.loadJson(url, {timeout: 5000});
30868
- processJson(jsonArray);
30886
+ try {
30887
+ const jsonArray = await igvxhr.loadJson(DEFAULT_GENOMES_URL, {timeout: 2000});
30888
+ processJson(jsonArray, table);
30889
+ } catch (error) {
30890
+ try {
30891
+ console.error("Error initializing default genomes:", error);
30892
+ const jsonArray = await igvxhr.loadJson(BACKUP_GENOMES_URL, {timeout: 2000});
30893
+ processJson(jsonArray, table);
30894
+ } catch (e) {
30895
+ console.error("Error initializing backup genomes:", error);
30896
+ }
30897
+ }
30869
30898
  }
30870
30899
 
30871
- // Add user-defined genomes
30900
+ // Append user-defined genomes, which might override defaults
30872
30901
  const genomeList = config.genomeList || config.genomes;
30873
30902
  if (genomeList) {
30874
30903
  if (typeof genomeList === 'string') {
30875
30904
  const jsonArray = await igvxhr.loadJson(genomeList, {});
30876
- processJson(jsonArray);
30905
+ processJson(jsonArray, table);
30877
30906
  } else {
30878
- processJson(genomeList);
30907
+ processJson(genomeList, table);
30879
30908
  }
30880
30909
  }
30881
-
30882
30910
  GenomeUtils.KNOWN_GENOMES = table;
30883
-
30884
- function processJson(jsonArray) {
30885
- jsonArray.forEach(function (json) {
30886
- table[json.id] = json;
30887
- });
30888
- return table
30889
- }
30890
30911
  }
30891
30912
  },
30892
30913
 
@@ -30912,7 +30933,7 @@
30912
30933
  } else if (idOrConfig.genome) {
30913
30934
  genomeID = idOrConfig.genome;
30914
30935
  } else if (idOrConfig.id !== undefined && !(idOrConfig.fastaURL || idOrConfig.twobitURL)) {
30915
- // Backward compatibilityz
30936
+ // Backward compatibility
30916
30937
  genomeID = idOrConfig.id;
30917
30938
  }
30918
30939
 
@@ -47613,7 +47634,7 @@
47613
47634
  if (!this.colorBy) {
47614
47635
  this.colorBy = this.hasPairs ? "unexpectedPair" : "none";
47615
47636
  }
47616
-
47637
+
47617
47638
  let pixelTop = options.pixelTop - BAMTrack.coverageTrackHeight;
47618
47639
  if (this.top) {
47619
47640
  ctx.translate(0, this.top);
@@ -48320,7 +48341,12 @@
48320
48341
  this.trackView.repaintViews();
48321
48342
  }
48322
48343
 
48323
- return {name: undefined, element: createCheckbox(menuItem.label, showCheck), click: clickHandler, init: undefined}
48344
+ return {
48345
+ name: undefined,
48346
+ element: createCheckbox(menuItem.label, showCheck),
48347
+ click: clickHandler,
48348
+ init: undefined
48349
+ }
48324
48350
  }
48325
48351
 
48326
48352
 
@@ -48367,7 +48393,12 @@
48367
48393
  }
48368
48394
  }
48369
48395
 
48370
- return {name: undefined, element: createCheckbox(menuItem.label, showCheck), dialog: clickHandler, init: undefined}
48396
+ return {
48397
+ name: undefined,
48398
+ element: createCheckbox(menuItem.label, showCheck),
48399
+ dialog: clickHandler,
48400
+ init: undefined
48401
+ }
48371
48402
 
48372
48403
  }
48373
48404
 
@@ -48489,38 +48520,88 @@
48489
48520
  });
48490
48521
  }
48491
48522
 
48523
+ list.push('<hr/>');
48524
+ const softClips = clickedAlignment.softClippedBlocks();
48492
48525
  list.push({
48493
48526
  label: 'View read sequence',
48494
48527
  click: () => {
48495
- const seqstring = clickedAlignment.seq; //.map(b => String.fromCharCode(b)).join("");
48496
- if (!seqstring || "*" === seqstring) {
48497
- this.browser.alert.present("Read sequence: *");
48498
- } else {
48499
- this.browser.alert.present(seqstring);
48500
- }
48528
+ const seqstring = clickedAlignment.seq;
48529
+ this.browser.alert.present(seqstring && seqstring !== "*" ? seqstring : "Read sequence: *");
48501
48530
  }
48502
48531
  });
48503
48532
 
48533
+ if (softClips.left && softClips.left.len > 0) {
48534
+ list.push({
48535
+ label: 'View left soft-clipped sequence',
48536
+ click: () => {
48537
+ const clippedSequence = clickedAlignment.seq.substring(softClips.left.seqOffset, softClips.left.seqOffset + softClips.left.len);
48538
+ this.browser.alert.present(clippedSequence);
48539
+ }
48540
+ });
48541
+ }
48542
+
48543
+ if (softClips.right && softClips.right.len > 0) {
48544
+ list.push({
48545
+ label: 'View right soft-clipped sequence',
48546
+ click: () => {
48547
+ const clippedSequence = clickedAlignment.seq.substring(softClips.right.seqOffset, softClips.right.seqOffset + softClips.right.len);
48548
+ this.browser.alert.present(clippedSequence);
48549
+ }
48550
+ });
48551
+ }
48552
+
48553
+ list.push('<hr/>');
48554
+
48504
48555
  if (isSecureContext()) {
48505
48556
  list.push({
48506
48557
  label: 'Copy read sequence',
48507
48558
  click: async () => {
48508
- const seq = clickedAlignment.seq; //.map(b => String.fromCharCode(b)).join("");
48509
48559
  try {
48510
- await navigator.clipboard.writeText(seq);
48560
+ await navigator.clipboard.writeText(clickedAlignment.seq);
48511
48561
  } catch (e) {
48512
48562
  console.error(e);
48513
48563
  this.browser.alert.present(`error copying sequence to clipboard ${e}`);
48514
48564
  }
48515
-
48516
48565
  }
48517
48566
  });
48567
+
48568
+ if (softClips.left && softClips.left.len > 0) {
48569
+ list.push({
48570
+ label: 'Copy left soft-clipped sequence',
48571
+ click: async () => {
48572
+ try {
48573
+ const clippedSequence = clickedAlignment.seq.substring(softClips.left.seqOffset, softClips.left.seqOffset + softClips.left.len);
48574
+ await navigator.clipboard.writeText(clippedSequence);
48575
+ } catch (e) {
48576
+ console.error(e);
48577
+ this.browser.alert.present(`error copying sequence to clipboard ${e}`);
48578
+ }
48579
+ }
48580
+ });
48581
+ }
48582
+
48583
+ if (softClips.right && softClips.right.len > 0) {
48584
+ list.push({
48585
+ label: 'Copy right soft-clipped sequence',
48586
+ click: async () => {
48587
+ try {
48588
+ const clippedSequence = clickedAlignment.seq.substring(softClips.right.seqOffset, softClips.right.seqOffset + softClips.right.len);
48589
+ await navigator.clipboard.writeText(clippedSequence);
48590
+ } catch (e) {
48591
+ console.error(e);
48592
+ this.browser.alert.present(`error copying sequence to clipboard ${e}`);
48593
+ }
48594
+ }
48595
+ });
48596
+ }
48518
48597
  }
48519
48598
 
48520
- // TODO if genome supports blat
48599
+ // TODO test if genome supports blat
48521
48600
  const seqstring = clickedAlignment.seq;
48522
48601
  if (seqstring && "*" !== seqstring) {
48523
48602
 
48603
+ list.push('<hr/>');
48604
+
48524
48605
  if (seqstring.length < maxSequenceSize$1) {
48525
48606
  list.push({
48526
48607
  label: 'BLAT read sequence',
@@ -48538,7 +48619,7 @@
48538
48619
  list.push({
48539
48620
  label: 'BLAT left soft-clipped sequence',
48540
48621
  click: () => {
48541
- const clippedSequence = seqstring.substr(softClips.left.seqOffset, softClips.left.len);
48622
+ const clippedSequence = seqstring.substring(softClips.left.seqOffset, softClips.left.seqOffset + softClips.left.len);
48542
48623
  const sequence = clickedAlignment.isNegativeStrand() ? reverseComplementSequence(clippedSequence) : clippedSequence;
48543
48624
  const name = `${clickedAlignment.readName} - blat left clip`;
48544
48625
  const title = `${this.name} - ${name}`;
@@ -48550,7 +48631,7 @@
48550
48631
  list.push({
48551
48632
  label: 'BLAT right soft-clipped sequence',
48552
48633
  click: () => {
48553
- const clippedSequence = seqstring.substr(softClips.right.seqOffset, softClips.right.len);
48634
+ const clippedSequence = seqstring.substring(softClips.right.seqOffset, softClips.right.seqOffset + softClips.right.len);
48554
48635
  const sequence = clickedAlignment.isNegativeStrand() ? reverseComplementSequence(clippedSequence) : clippedSequence;
48555
48636
  const name = `${clickedAlignment.readName} - blat right clip`;
48556
48637
  const title = `${this.name} - ${name}`;
@@ -48599,7 +48680,7 @@
48599
48680
  const offsetY = y - this.top;
48600
48681
  const genomicLocation = clickState.genomicLocation;
48601
48682
 
48602
- if(features.packedGroups) {
48683
+ if (features.packedGroups) {
48603
48684
  let minGroupY = Number.MAX_VALUE;
48604
48685
  for (let group of features.packedGroups.values()) {
48605
48686
  minGroupY = Math.min(minGroupY, group.pixelTop);
@@ -68825,6 +68906,115 @@ ${indent}columns: ${matrix.columns}
68825
68906
  }
68826
68907
  }
68827
68908
 
68909
+ /**
68910
+ * Configurable properties
68911
+ * Locus {chr, start, end}
68912
+ * url - url to image. Later url to webservice to fetch image
68913
+ */
68914
+
68915
+ class ImageTrack extends TrackBase {
68916
+
68917
+ static defaults = {}
68918
+
68919
+ constructor(config, browser) {
68920
+ super(config, browser);
68921
+ }
68922
+
68923
+ init(config) {
68924
+
68925
+ super.init(config);
68926
+
68927
+ if (!config.images) {
68928
+ throw Error("images are required")
68929
+ }
68930
+
68931
+ this.locus = config.locus;
68932
+ this.type = "image";
68933
+ this.resolutionAware = true;
68934
+ }
68935
+
68936
+
68937
+ async postInit() {
68938
+
68939
+ this._images = [];
68940
+
68941
+ for (let i of this.config.images) {
68942
+ const img = new Image();
68943
+ img.onload = () => {
68944
+ i.img = img;
68945
+ i.bpPerPixel = (i.end - i.start) / img.width;
68946
+ this._images.push(i);
68947
+ };
68948
+ img.onerror = (err) => {
68949
+ console.error(err);
68950
+ };
68951
+ //if (img.complete) { //cached image
68952
+ // img.onload()
68953
+ // }
68954
+ img.src = i.src;
68955
+ }
68956
+
68957
+ }
68958
+
68959
+ computePixelHeight(features) {
68960
+ return features ? features.height : 0
68961
+ }
68962
+
68963
+
68964
+ menuItemList() {
68965
+
68966
+ const menuItems = [];
68967
+
68968
+ return menuItems
68969
+ }
68970
+
68971
+
68972
+ async getFeatures(chr, start, end, bpPerPixel) {
68973
+ // Return image. Scaled or not?
68974
+ return this.selectImage(chr, start, end, bpPerPixel)
68975
+ }
68976
+
68977
+ selectImage(chr, start, end, bpPerPixel) {
68978
+
68979
+ // Select the highest resolution image containing the interval. If no image contains the interval return
68980
+ // the lowest resolution image if it overlaps
68981
+ if(this._images.length == 0) {
68982
+ return null
68983
+ }
68984
+ this._images.sort((a, b) => a.bpPerPixel < b.bpPerPixel ? -1 : 1);
68985
+ for(let i of this._images) {
68986
+ if(i.bpPerPixel > bpPerPixel) {
68987
+ return i
68988
+ }
68989
+ }
68990
+ const lowRes = this._images[this._images.length-1];
68991
+ if(lowRes.chr === chr) {
68992
+ return lowRes
68993
+ } else {
68994
+ return null
68995
+ }
68996
+ }
68997
+
68998
+ draw({context, pixelTop, pixelWidth, pixelHeight, features, bpPerPixel, bpStart}) {
68999
+
69000
+ const image = features.img;
69001
+ if (image) {
69002
+ const nw = image.width;
69003
+ const nh = image.height;
69004
+ const imageBpPerPixel = (features.end - features.start) / nw;
69005
+ const scale = imageBpPerPixel / bpPerPixel;
69006
+ const x = (features.start - bpStart) / bpPerPixel;
69007
+ context.drawImage(image, x, 0, scale * nw, nh);
69008
+ }
69009
+
69010
+ }
69011
+
69012
+ get supportsWholeGenome() {
69013
+ return false
69014
+ }
69015
+
69016
+ }
69017
+
68828
69018
  //import CNVPytorTrack from "./CNVpytor/cnvpytorTrack.js"
68829
69019
 
68830
69020
 
@@ -68850,7 +69040,8 @@ ${indent}columns: ${matrix.columns}
68850
69040
  ['gcnv', (config, browser) => new GCNVTrack(config, browser)],
68851
69041
  ['junction', (config, browser) => new SpliceJunctionTrack(config, browser)],
68852
69042
  ['blat', (config, browser) => new BlatTrack(config, browser)],
68853
- ['cnvpytor', (config, browser) => new CNVPytorTrack(config, browser)]
69043
+ ['cnvpytor', (config, browser) => new CNVPytorTrack(config, browser)],
69044
+ ['image', (config, browser) => new ImageTrack(config, browser)]
68854
69045
  ]);
68855
69046
 
68856
69047
 
@@ -69328,7 +69519,7 @@ ${indent}columns: ${matrix.columns}
69328
69519
  })
69329
69520
  }
69330
69521
 
69331
- const _version = "3.2.4";
69522
+ const _version = "3.2.6";
69332
69523
  function version() {
69333
69524
  return _version
69334
69525
  }
@@ -73871,7 +74062,7 @@ ${indent}columns: ${matrix.columns}
73871
74062
  async loadTrackList(configList) {
73872
74063
 
73873
74064
  // Impose an order if not specified
73874
- let order = 0;
74065
+ let order = this.trackViews.length + 1;
73875
74066
  for (let c of configList) {
73876
74067
  if (c.order === undefined) {
73877
74068
  c.order = order++;