react-msaview-cli 8.0.0 → 8.2.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.
Files changed (2) hide show
  1. package/dist/index.js +558 -344
  2. package/package.json +3 -3
package/dist/index.js CHANGED
@@ -50325,7 +50325,7 @@ var init_legendRows = __esm({
50325
50325
  });
50326
50326
 
50327
50327
  // ../lib/dist/constants.js
50328
- var defaultRowHeight, defaultColWidth, defaultHeight, defaultScrollX, defaultScrollY, defaultCurrentAlignment, defaultShowDomains, defaultShowDomainLegend, defaultHideGaps, defaultAllowedGappyness, defaultSubFeatureRows, subFeatureRowHeight, domainUnderlineHeight, segmentFeatureTypes, segmentShades, defaultDrawMsaLetters, defaultScrollZoom, scrollZoomAxes, defaultScrollZoomAxis, minLetterRowHeight, minLetterColWidth, labelReferenceFontSize, minColWidth, minRowHeight, maxCellSize, defaultBgColor, defaultColorSchemeName, defaultShowColumnStats, defaultDrawLabels, defaultLabelsAlignRight, defaultTreeAreaWidth, defaultTreeWidth, defaultShowBranchLen, defaultDrawTree, defaultDrawNodeBubbles, defaultDrawNodeLabels, defaultShowTreeOverview, defaultOverviewHeight, maxNeighborJoiningRows, rowTintAlpha, cladeHighlightColor, cladeHighlightAlpha, maxInlineSnapshotBytes, rowPanelHeaderHeight, treeScaleBarHeight, defaultFeaturePanelWidth, msaOverlayZIndex, legendZIndex;
50328
+ var defaultRowHeight, defaultColWidth, defaultHeight, defaultScrollX, defaultScrollY, defaultCurrentAlignment, defaultShowDomains, defaultShowDomainLegend, defaultHideGaps, defaultAllowedGappyness, defaultSubFeatureRows, subFeatureRowHeight, domainUnderlineHeight, segmentFeatureTypes, segmentShades, defaultDrawMsaLetters, defaultScrollZoom, scrollZoomAxes, defaultScrollZoomAxis, minLetterRowHeight, minLetterColWidth, minFeatureLabelHeight, labelReferenceFontSize, minColWidth, minRowHeight, maxCellSize, defaultBgColor, defaultColorSchemeName, defaultShowColumnStats, defaultDrawLabels, defaultLabelsAlignRight, defaultTreeAreaWidth, defaultTreeWidth, defaultShowBranchLen, defaultDrawTree, defaultDrawNodeBubbles, defaultDrawNodeLabels, defaultShowTreeOverview, defaultOverviewHeight, maxNeighborJoiningRows, rowTintAlpha, cladeHighlightColor, cladeHighlightAlpha, maxInlineSnapshotBytes, rowPanelHeaderHeight, treeScaleBarHeight, defaultFeaturePanelWidth, msaOverlayZIndex, legendZIndex;
50329
50329
  var init_constants = __esm({
50330
50330
  "../lib/dist/constants.js"() {
50331
50331
  "use strict";
@@ -50357,6 +50357,7 @@ var init_constants = __esm({
50357
50357
  defaultScrollZoomAxis = "both";
50358
50358
  minLetterRowHeight = 8;
50359
50359
  minLetterColWidth = 5;
50360
+ minFeatureLabelHeight = 9;
50360
50361
  labelReferenceFontSize = 16;
50361
50362
  minColWidth = 0.2;
50362
50363
  minRowHeight = 1;
@@ -50746,9 +50747,13 @@ function spanRows(leaves2, bandsByRow) {
50746
50747
  return rows;
50747
50748
  }
50748
50749
  function drawFeatureSpans({ ctx, rows, layout, xOf, colors: colors2, labelOf, labelDrawn, contrastText, xMin, xMax }) {
50750
+ const placed = [];
50749
50751
  for (const { y: y2, bands } of rows) {
50750
50752
  const h2 = layout.height(bands[0].laneCount);
50751
50753
  const headLen = layout.headLength(h2);
50754
+ const rise = layout.headRise?.(h2) ?? 0;
50755
+ const labelled = labelOf !== void 0 && h2 >= minFeatureLabelHeight;
50756
+ placed.length = 0;
50752
50757
  for (const band of bands) {
50753
50758
  const [xStart, xEnd] = xOf(band);
50754
50759
  const w2 = xEnd - xStart;
@@ -50760,37 +50765,54 @@ function drawFeatureSpans({ ctx, rows, layout, xOf, colors: colors2, labelOf, la
50760
50765
  ctx.fillStyle = fill;
50761
50766
  ctx.strokeStyle = stroke;
50762
50767
  const { strand } = band.annotation;
50768
+ const head = strand === void 0 ? 0 : Math.min(headLen, w2);
50763
50769
  if (strand === void 0) {
50764
50770
  ctx.fillRect(xStart, t3, w2, h2);
50765
50771
  ctx.strokeRect(xStart, t3, w2, h2);
50766
50772
  } else {
50767
- drawGeneArrow({ ctx, x: xStart, t: t3, w: w2, h: h2, headLen, strand });
50773
+ drawGeneArrow({ ctx, x: xStart, t: t3, w: w2, h: h2, head, rise, strand });
50774
+ }
50775
+ if (labelled) {
50776
+ placed.push({ band, xStart, w: w2, t: t3, head, fill });
50768
50777
  }
50769
- const label = labelOf?.(band);
50770
- if (label !== void 0) {
50771
- const fontSize2 = Math.min(h2 - 2, 11);
50772
- ctx.font = `${fontSize2}px sans-serif`;
50773
- if (ctx.measureText(label).width + 2 <= w2) {
50778
+ }
50779
+ if (placed.length > 0) {
50780
+ ctx.font = `${Math.min(h2 - 2, 11)}px sans-serif`;
50781
+ ctx.textAlign = "center";
50782
+ ctx.textBaseline = "middle";
50783
+ for (const { band, xStart, w: w2, t: t3, head, fill } of placed) {
50784
+ const label = labelOf(band);
50785
+ if (label !== void 0 && ctx.measureText(label).width + 2 <= w2 - head / 2) {
50774
50786
  labelDrawn?.(band);
50775
50787
  ctx.fillStyle = contrastText(fill);
50776
- ctx.textAlign = "center";
50777
- ctx.textBaseline = "middle";
50778
50788
  ctx.fillText(label, xStart + w2 / 2, t3 + h2 / 2);
50779
50789
  }
50780
50790
  }
50781
50791
  }
50782
50792
  }
50783
50793
  }
50784
- function drawGeneArrow({ ctx, x: x2, t: t3, w: w2, h: h2, headLen, strand }) {
50785
- const dir = strand > 0 ? 1 : -1;
50786
- const bodyStart = strand > 0 ? x2 : x2 + w2;
50787
- const bodyEnd = strand > 0 ? x2 + w2 : x2;
50794
+ function drawGeneArrow({ ctx, x: x2, t: t3, w: w2, h: h2, head, rise, strand }) {
50795
+ const tip = strand > 0 ? x2 + w2 : x2;
50796
+ const tail = strand > 0 ? x2 : x2 + w2;
50797
+ const flange = strand > 0 ? tip - head : tip + head;
50798
+ const bottom2 = t3 + h2;
50799
+ const crown = t3 - rise;
50800
+ const point = (crown + bottom2) / 2;
50788
50801
  ctx.beginPath();
50789
- ctx.moveTo(bodyStart, t3);
50790
- ctx.lineTo(bodyEnd, t3);
50791
- ctx.lineTo(bodyEnd + dir * headLen, t3 + h2 / 2);
50792
- ctx.lineTo(bodyEnd, t3 + h2);
50793
- ctx.lineTo(bodyStart, t3 + h2);
50802
+ if (head >= w2) {
50803
+ ctx.moveTo(tail, crown);
50804
+ ctx.lineTo(tip, point);
50805
+ ctx.lineTo(tail, bottom2);
50806
+ } else {
50807
+ ctx.moveTo(tail, t3);
50808
+ ctx.lineTo(flange, t3);
50809
+ if (rise > 0) {
50810
+ ctx.lineTo(flange, crown);
50811
+ }
50812
+ ctx.lineTo(tip, point);
50813
+ ctx.lineTo(flange, bottom2);
50814
+ ctx.lineTo(tail, bottom2);
50815
+ }
50794
50816
  ctx.closePath();
50795
50817
  ctx.fill();
50796
50818
  ctx.stroke();
@@ -50798,6 +50820,7 @@ function drawGeneArrow({ ctx, x: x2, t: t3, w: w2, h: h2, headLen, strand }) {
50798
50820
  var init_drawFeatureSpans = __esm({
50799
50821
  "../lib/dist/components/msa/drawFeatureSpans.js"() {
50800
50822
  "use strict";
50823
+ init_constants();
50801
50824
  }
50802
50825
  });
50803
50826
 
@@ -50814,9 +50837,8 @@ function renderBoxFeatureCanvasBlock({ model: model2, theme, offsetX, offsetY, c
50814
50837
  ctx.scale(k2, k2);
50815
50838
  ctx.translate(-offsetX, rowHeight / 2 - offsetY);
50816
50839
  const barHeight = domainUnderline ? Math.min(domainUnderlineHeight, rowHeight) : rowHeight;
50817
- const drawSegmentLabels = !featureLabels && !showMsaLetters && !subFeatureRows && barHeight >= 9;
50840
+ const drawSegmentLabels = !featureLabels && !showMsaLetters && !subFeatureRows;
50818
50841
  const labelled = drawSegmentLabels ? /* @__PURE__ */ new Set() : void 0;
50819
- const cull = rowHeight + colWidth;
50820
50842
  drawFeatureSpans({
50821
50843
  ctx,
50822
50844
  rows: spanRows(getVisibleLeaves({ model: model2, offsetY, blockSizeY: by }), domainBands),
@@ -50825,17 +50847,19 @@ function renderBoxFeatureCanvasBlock({ model: model2, theme, offsetX, offsetY, c
50825
50847
  // shares out the row height between them instead
50826
50848
  height: (laneCount) => subFeatureRows ? Math.min(subFeatureRowHeight, rowHeight / laneCount) : barHeight,
50827
50849
  top: (y2, lane, h2) => y2 - rowHeight + (subFeatureRows ? lane * h2 : domainUnderline ? rowHeight - h2 : 0),
50828
- // the head keeps its full size on an underline bar, since a head as short
50829
- // as the bar reads as a nub rather than as a direction
50830
- headLength: (h2) => domainUnderline ? rowHeight / 2 : h2
50850
+ headLength: (h2) => domainUnderline ? rowHeight / 2 : h2,
50851
+ // a head only as tall as an underline bar tapers over ten pixels of
50852
+ // three, which reads as nothing; it takes the lower two fifths of the
50853
+ // row instead, leaving the letters above it
50854
+ headRise: (h2) => domainUnderline ? Math.max(0, rowHeight * 0.4 - h2) : 0
50831
50855
  },
50832
50856
  xOf: (band) => [band.startCol * colWidth, band.endCol * colWidth],
50833
50857
  colors: featureColors,
50834
50858
  labelOf: (band) => featureLabels?.get(band.annotation) ?? (labelled && !labelled.has(band.annotation.accession) ? segmentLabels.get(band.annotation.accession) : void 0),
50835
50859
  labelDrawn: (band) => labelled?.add(band.annotation.accession),
50836
50860
  contrastText: contrastTextFn(theme),
50837
- xMin: offsetX - cull,
50838
- xMax: offsetX + bx + cull
50861
+ xMin: offsetX,
50862
+ xMax: offsetX + bx
50839
50863
  });
50840
50864
  }
50841
50865
  var init_renderBoxFeatureCanvasBlock = __esm({
@@ -51070,16 +51094,15 @@ function renderMSABlock({ model: model2, offsetX, offsetY, ctx, theme, highResSc
51070
51094
  ctx.resetTransform();
51071
51095
  }
51072
51096
  function drawTilesAndText({ model: model2, ctx, theme, visibleLeaves, xStart, xEnd, referenceSeq, drawTiles, rasterTiles }) {
51073
- const { bgColor, columns, colWidth, rowHeight, relativeTo, showMsaLetters, subFeatureRows, domainUnderline, domainBandsByStart } = model2;
51097
+ const { bgColor, columns, colWidth, rowHeight, fontSize: fontSize2, relativeTo, showMsaLetters, subFeatureRows, domainUnderline, domainBandsByStart, featureColors } = model2;
51074
51098
  const tiles = drawTiles && bgColor;
51075
51099
  const paintTiles = tiles && !rasterTiles;
51076
51100
  if (paintTiles || showMsaLetters) {
51077
- const needsColor = paintTiles || showMsaLetters;
51078
51101
  const tileColor = tileColorFn(model2);
51079
51102
  const contrastText = contrastTextFn(theme);
51080
51103
  const offsetXAligned = xStart * colWidth;
51081
51104
  const halfColWidth = colWidth / 2;
51082
- const quarterRowHeight = rowHeight / 4;
51105
+ const baselineOffset = rowHeight / 2 - fontSize2 / 4;
51083
51106
  const overDomains = !drawTiles && !subFeatureRows && !domainUnderline;
51084
51107
  for (let i2 = 0, l1 = visibleLeaves.length; i2 < l1; i2++) {
51085
51108
  const node2 = visibleLeaves[i2];
@@ -51088,14 +51111,14 @@ function drawTilesAndText({ model: model2, ctx, theme, visibleLeaves, xStart, xE
51088
51111
  const str = columns.get(name)?.slice(xStart, xEnd);
51089
51112
  if (str) {
51090
51113
  const tileY = y2 - rowHeight;
51091
- const textY = y2 - quarterRowHeight;
51114
+ const textY = y2 - baselineOffset;
51092
51115
  const bandAt = domainBandCursor(overDomains ? domainBandsByStart.get(name) : void 0);
51093
51116
  for (let j2 = 0, l2 = str.length; j2 < l2; j2++) {
51094
51117
  const col = xStart + j2;
51095
51118
  const letter = str[j2];
51096
51119
  const x2 = j2 * colWidth + offsetXAligned;
51097
51120
  const isMatchingReference = referenceSeq && name !== relativeTo && letter === referenceSeq[j2];
51098
- const color2 = needsColor ? tileColor(col, letter) : void 0;
51121
+ const color2 = tileColor(col, letter);
51099
51122
  if (paintTiles) {
51100
51123
  ctx.fillStyle = isMatchingReference ? theme.palette.action.hover : color2 || theme.palette.background.default;
51101
51124
  ctx.fillRect(x2, tileY, colWidth, rowHeight);
@@ -51104,7 +51127,7 @@ function drawTilesAndText({ model: model2, ctx, theme, visibleLeaves, xStart, xE
51104
51127
  const covering = bandAt(col);
51105
51128
  ctx.fillStyle = covering ? (
51106
51129
  // on top of a domain box: contrast against the box fill
51107
- contrastText(model2.fillPalette[covering.annotation.accession])
51130
+ contrastText(featureColors.get(covering.annotation)?.fill)
51108
51131
  ) : isMatchingReference ? (
51109
51132
  // the dot sits on the faint reference-match wash, which is
51110
51133
  // the theme's background with a little of its text color in it
@@ -51207,9 +51230,9 @@ function renderFeaturePanel({ ctx, model: model2, theme, panel, x: x2, offsetY,
51207
51230
  top: (y2, lane, h2) => y2 - rowHeight + lane * (h2 + rowPadding) + rowPadding / 2,
51208
51231
  headLength: (h2) => h2
51209
51232
  },
51210
- xOf: (span) => [span.xStart, span.xEnd],
51233
+ xOf: (span2) => [span2.xStart, span2.xEnd],
51211
51234
  colors: panel.colors,
51212
- labelOf: (span) => panel.labels?.get(span.annotation),
51235
+ labelOf: (span2) => panel.labels?.get(span2.annotation),
51213
51236
  contrastText: contrastTextFn(theme),
51214
51237
  xMin: 0,
51215
51238
  xMax: panel.width
@@ -51593,7 +51616,7 @@ function drawSequenceLogo({ ctx, colStats, colorScheme, maxBits, textColor, colW
51593
51616
  }
51594
51617
  }
51595
51618
  function drawTrackBlock({ model: model2, ctx, track, offsetX, offsetY = 0, theme, blockSizeXOverride, highResScaleFactorOverride }) {
51596
- const { blockSize, bgColor, colWidth, colStats, colorScheme: modelColorScheme, fontSize: fontSize2, rowHeight, showMsaLetters, alphabetMaxBits, highResScaleFactor } = model2;
51619
+ const { blockSize, colWidth, highResScaleFactor } = model2;
51597
51620
  const { id, kind, height: trackHeight, barColor, arcColor, arcs, customColorScheme, data } = track.model;
51598
51621
  const blockSizeX = blockSizeXOverride ?? blockSize;
51599
51622
  const k2 = highResScaleFactorOverride ?? highResScaleFactor;
@@ -51629,9 +51652,9 @@ function drawTrackBlock({ model: model2, ctx, track, offsetX, offsetY = 0, theme
51629
51652
  case "logo": {
51630
51653
  drawSequenceLogo({
51631
51654
  ctx,
51632
- colStats,
51633
- colorScheme: modelColorScheme,
51634
- maxBits: alphabetMaxBits,
51655
+ colStats: model2.colStats,
51656
+ colorScheme: model2.colorScheme,
51657
+ maxBits: model2.alphabetMaxBits,
51635
51658
  textColor,
51636
51659
  colWidth,
51637
51660
  trackHeight,
@@ -51659,13 +51682,14 @@ function drawTrackBlock({ model: model2, ctx, track, offsetX, offsetY = 0, theme
51659
51682
  break;
51660
51683
  }
51661
51684
  case "text": {
51685
+ const { bgColor, fontSize: fontSize2, rowHeight, showMsaLetters } = model2;
51662
51686
  ctx.textAlign = "center";
51663
51687
  ctx.textBaseline = "middle";
51664
51688
  setFontSize(ctx, fontSize2);
51665
51689
  drawTextTrackContent({
51666
51690
  ctx,
51667
51691
  data,
51668
- colorScheme: customColorScheme ?? modelColorScheme,
51692
+ colorScheme: customColorScheme ?? model2.colorScheme,
51669
51693
  contrastText: contrastTextFn(theme),
51670
51694
  bgColor,
51671
51695
  drawLetters: showMsaLetters,
@@ -67510,6 +67534,113 @@ var init_headlessRenderEnv = __esm({
67510
67534
  }
67511
67535
  });
67512
67536
 
67537
+ // ../lib/dist/expandSpec.js
67538
+ function parseSpan(text2, what) {
67539
+ const match2 = span.exec(text2.trim());
67540
+ if (!match2) {
67541
+ throw new Error(`${what} "${text2}" is not "start", "start-end" or either followed by a label`);
67542
+ }
67543
+ const start = Number(match2[1]);
67544
+ const end = match2[2] === void 0 ? start : Number(match2[2]);
67545
+ return { start, end, label: match2[3] };
67546
+ }
67547
+ function withRow(entry, query) {
67548
+ const { row, ...rest } = entry;
67549
+ const resolved = row === void 0 ? query : row ?? void 0;
67550
+ return resolved === void 0 ? rest : { ...rest, row: resolved };
67551
+ }
67552
+ function expandHighlight(entry, query) {
67553
+ if (typeof entry === "object") {
67554
+ return entry.rows ? entry : withRow(entry, entry.start === void 0 ? void 0 : query);
67555
+ }
67556
+ const { start, end, label } = parseSpan(String(entry), "highlight");
67557
+ const single = start === end && query !== void 0;
67558
+ return {
67559
+ ...query === void 0 ? {} : { row: query },
67560
+ start,
67561
+ end,
67562
+ ...label ? { label } : single ? { label: residueLabel } : {}
67563
+ };
67564
+ }
67565
+ function expandRegion(region, query) {
67566
+ if (typeof region === "string") {
67567
+ const { start, end } = parseSpan(region, "region");
67568
+ const parsed = { start, end };
67569
+ return withRow(parsed, query);
67570
+ }
67571
+ return withRow(region, query);
67572
+ }
67573
+ function slug(name) {
67574
+ return name.toLowerCase().replaceAll(/[^a-z0-9]+/g, "-").replaceAll(/^-|-$/g, "");
67575
+ }
67576
+ function kindOf(track) {
67577
+ if (track.kind) {
67578
+ return track.kind;
67579
+ }
67580
+ if (track.values) {
67581
+ return "bar";
67582
+ }
67583
+ if (track.data !== void 0) {
67584
+ return "text";
67585
+ }
67586
+ if (track.arcs) {
67587
+ return "arc";
67588
+ }
67589
+ throw new Error(`column track "${track.name}" has no values, data or arcs to take its kind from`);
67590
+ }
67591
+ function expandTrack(track, query, ids) {
67592
+ const { start, ...rest } = track;
67593
+ const pad2 = start && start > 1 ? start - 1 : 0;
67594
+ let id = track.id ?? (slug(track.name) || "track");
67595
+ for (let n2 = 2; !track.id && ids.has(id); n2++) {
67596
+ id = `${slug(track.name) || "track"}-${n2}`;
67597
+ }
67598
+ ids.add(id);
67599
+ return {
67600
+ ...withRow(rest, query),
67601
+ id,
67602
+ kind: kindOf(track),
67603
+ ...pad2 && track.values ? { values: [...Array(pad2).fill(0), ...track.values] } : {},
67604
+ ...pad2 && track.data !== void 0 ? { data: " ".repeat(pad2) + track.data } : {}
67605
+ };
67606
+ }
67607
+ function isInlineMsa(msa) {
67608
+ return msa.includes("\n");
67609
+ }
67610
+ function isInlineTree(tree) {
67611
+ return tree.trimStart().startsWith("(");
67612
+ }
67613
+ function uri(location2) {
67614
+ return { uri: location2, locationType: "UriLocation" };
67615
+ }
67616
+ function expandSpec(spec) {
67617
+ const { query, msa, tree, highlights, region, columnTracks, ...rest } = spec;
67618
+ const data = {
67619
+ ...rest.data,
67620
+ ...msa !== void 0 && isInlineMsa(msa) ? { msa } : {},
67621
+ ...tree !== void 0 && isInlineTree(tree) ? { tree } : {}
67622
+ };
67623
+ const ids = new Set((columnTracks ?? []).map((t3) => t3.id).filter((id) => id !== void 0));
67624
+ return {
67625
+ ...rest,
67626
+ ...msa !== void 0 && !isInlineMsa(msa) ? { msaFilehandle: uri(msa) } : {},
67627
+ ...tree !== void 0 && !isInlineTree(tree) ? { treeFilehandle: uri(tree) } : {},
67628
+ ...Object.keys(data).length > 0 ? { data } : {},
67629
+ ...query !== void 0 && rest.relativeTo === void 0 ? { relativeTo: query } : {},
67630
+ ...highlights ? { highlights: highlights.map((h2) => expandHighlight(h2, query)) } : {},
67631
+ ...region === void 0 ? {} : { region: expandRegion(region, query) },
67632
+ ...columnTracks ? { columnTracks: columnTracks.map((t3) => expandTrack(t3, query, ids)) } : {}
67633
+ };
67634
+ }
67635
+ var residueLabel, span;
67636
+ var init_expandSpec = __esm({
67637
+ "../lib/dist/expandSpec.js"() {
67638
+ "use strict";
67639
+ residueLabel = "{residue}{position}";
67640
+ span = /^(\d+)(?:-(\d+))?(?:\s+(.+))?$/;
67641
+ }
67642
+ });
67643
+
67513
67644
  // ../../node_modules/.pnpm/@jbrowse+core@file+vendor-jbrowse+jbrowse-core.tgz_@emotion+styled@11.14.1_@emotion+rea_7090bbe2527c98dca0fac05344a682ca/node_modules/@jbrowse/core/esm/util/environment.js
67514
67645
  var isElectron, isNode, rIC;
67515
67646
  var init_environment = __esm({
@@ -71841,8 +71972,8 @@ var init_mst = __esm({
71841
71972
  if (snap.locationType) {
71842
71973
  return snap;
71843
71974
  } else {
71844
- const { uri: uri2, localPath, blob } = snap;
71845
- const inferredLocationType = uri2 !== void 0 ? "UriLocation" : localPath !== void 0 ? "LocalPathLocation" : blob !== void 0 ? "BlobLocation" : "FileHandleLocation";
71975
+ const { uri: uri3, localPath, blob } = snap;
71976
+ const inferredLocationType = uri3 !== void 0 ? "UriLocation" : localPath !== void 0 ? "LocalPathLocation" : blob !== void 0 ? "BlobLocation" : "FileHandleLocation";
71846
71977
  return { ...snap, locationType: inferredLocationType };
71847
71978
  }
71848
71979
  }
@@ -75509,12 +75640,12 @@ var init_measureText = __esm({
75509
75640
 
75510
75641
  // ../../node_modules/.pnpm/@jbrowse+core@file+vendor-jbrowse+jbrowse-core.tgz_@emotion+styled@11.14.1_@emotion+rea_7090bbe2527c98dca0fac05344a682ca/node_modules/@jbrowse/core/esm/util/getStr.js
75511
75642
  function getUriLink(value) {
75512
- const { uri: uri2, baseUri = "" } = value;
75643
+ const { uri: uri3, baseUri = "" } = value;
75513
75644
  let href;
75514
75645
  try {
75515
- href = new URL(uri2, baseUri).href;
75646
+ href = new URL(uri3, baseUri).href;
75516
75647
  } catch {
75517
- href = uri2;
75648
+ href = uri3;
75518
75649
  }
75519
75650
  return href;
75520
75651
  }
@@ -75836,7 +75967,7 @@ function createMakeStyles(params) {
75836
75967
  function makeStyles2() {
75837
75968
  return function(cssObjectByRuleNameOrGetCssObjectByRuleName) {
75838
75969
  const getCssObjectByRuleName = typeof cssObjectByRuleNameOrGetCssObjectByRuleName === "function" ? cssObjectByRuleNameOrGetCssObjectByRuleName : () => cssObjectByRuleNameOrGetCssObjectByRuleName;
75839
- return function useStyles26(_params, muiStyleOverridesParams) {
75970
+ return function useStyles25(_params, muiStyleOverridesParams) {
75840
75971
  const theme = useTheme5();
75841
75972
  const { css: css2, cx: cx2 } = useCssAndCx();
75842
75973
  const classes2 = (0, import_react14.useMemo)(() => {
@@ -76037,14 +76168,14 @@ async function copyToClipboard(text2, options2 = {}) {
76037
76168
  function execCommandCopy(text2, format2) {
76038
76169
  const selection = document.getSelection();
76039
76170
  const previousRange = selection && selection.rangeCount > 0 ? selection.getRangeAt(0) : void 0;
76040
- const span = document.createElement("span");
76041
- span.textContent = text2;
76042
- span.style.whiteSpace = "pre";
76043
- span.style.position = "fixed";
76044
- span.style.top = "0";
76045
- span.style.left = "0";
76046
- span.style.opacity = "0";
76047
- document.body.append(span);
76171
+ const span2 = document.createElement("span");
76172
+ span2.textContent = text2;
76173
+ span2.style.whiteSpace = "pre";
76174
+ span2.style.position = "fixed";
76175
+ span2.style.top = "0";
76176
+ span2.style.left = "0";
76177
+ span2.style.opacity = "0";
76178
+ document.body.append(span2);
76048
76179
  const listener = (e3) => {
76049
76180
  if (e3.clipboardData) {
76050
76181
  e3.clipboardData.clearData();
@@ -76056,7 +76187,7 @@ function execCommandCopy(text2, format2) {
76056
76187
  let success = false;
76057
76188
  try {
76058
76189
  const range = document.createRange();
76059
- range.selectNodeContents(span);
76190
+ range.selectNodeContents(span2);
76060
76191
  selection?.removeAllRanges();
76061
76192
  selection?.addRange(range);
76062
76193
  success = document.execCommand("copy");
@@ -76068,7 +76199,7 @@ function execCommandCopy(text2, format2) {
76068
76199
  if (previousRange) {
76069
76200
  selection?.addRange(previousRange);
76070
76201
  }
76071
- span.remove();
76202
+ span2.remove();
76072
76203
  }
76073
76204
  return success;
76074
76205
  }
@@ -80013,39 +80144,39 @@ var require_source_map = __commonJS({
80013
80144
  });
80014
80145
 
80015
80146
  // ../../node_modules/.pnpm/@jbrowse+core@file+vendor-jbrowse+jbrowse-core.tgz_@emotion+styled@11.14.1_@emotion+rea_7090bbe2527c98dca0fac05344a682ca/node_modules/@jbrowse/core/esm/ui/mapStackTrace.js
80016
- async function myfetchtext(uri2) {
80017
- const res = await fetch(uri2);
80147
+ async function myfetchtext(uri3) {
80148
+ const res = await fetch(uri3);
80018
80149
  if (!res.ok) {
80019
- throw new Error(`HTTP ${res.status} fetching ${uri2}: ${await res.text()}`);
80150
+ throw new Error(`HTTP ${res.status} fetching ${uri3}: ${await res.text()}`);
80020
80151
  }
80021
80152
  return res.text();
80022
80153
  }
80023
- function sourceMapUrl(script, uri2) {
80154
+ function sourceMapUrl(script, uri3) {
80024
80155
  const mapUri = [...script.matchAll(sourceMappingUrlRe)].at(-1)?.[1]?.trim();
80025
80156
  let url;
80026
80157
  if (mapUri) {
80027
- url = new URL(mapUri, uri2);
80158
+ url = new URL(mapUri, uri3);
80028
80159
  if (!url.search && url.protocol !== "data:") {
80029
- url.search = new URL(uri2).search;
80160
+ url.search = new URL(uri3).search;
80030
80161
  }
80031
80162
  }
80032
80163
  return url?.href;
80033
80164
  }
80034
- async function loadSourceMap(uri2) {
80165
+ async function loadSourceMap(uri3) {
80035
80166
  try {
80036
- const mapUrl = sourceMapUrl(await myfetchtext(uri2), uri2);
80167
+ const mapUrl = sourceMapUrl(await myfetchtext(uri3), uri3);
80037
80168
  return mapUrl ? new import_source_map_js.SourceMapConsumer(JSON.parse(await myfetchtext(mapUrl))) : void 0;
80038
80169
  } catch {
80039
80170
  return void 0;
80040
80171
  }
80041
80172
  }
80042
- function getSourceMap(uri2) {
80043
- const existing = sourceMaps.get(uri2);
80173
+ function getSourceMap(uri3) {
80174
+ const existing = sourceMaps.get(uri3);
80044
80175
  if (existing) {
80045
80176
  return existing;
80046
80177
  } else {
80047
- const map2 = loadSourceMap(uri2);
80048
- sourceMaps.set(uri2, map2);
80178
+ const map2 = loadSourceMap(uri3);
80179
+ sourceMaps.set(uri3, map2);
80049
80180
  return map2;
80050
80181
  }
80051
80182
  }
@@ -83864,10 +83995,6 @@ function useWheelScroll({ ref, onScrollX, onScrollY, onZoom, scrollZoom }) {
83864
83995
  };
83865
83996
  }, [mouseDragging, onScrollX, onScrollY]);
83866
83997
  function onMouseDown(event) {
83867
- const target = event.target;
83868
- if (target.draggable || target.dataset.resizer) {
83869
- return;
83870
- }
83871
83998
  if (event.button === 0) {
83872
83999
  prevX.current = event.clientX;
83873
84000
  prevY.current = event.clientY;
@@ -84198,7 +84325,7 @@ var init_Track = __esm({
84198
84325
  } }, drawLabels ? name : ""),
84199
84326
  import_react37.default.createElement(
84200
84327
  IconButton_default,
84201
- { className: classes2.button, style: {
84328
+ { className: classes2.button, "aria-label": `${name} track menu`, style: {
84202
84329
  width: trackLabelHeight,
84203
84330
  height: trackLabelHeight,
84204
84331
  flexShrink: 0
@@ -84215,7 +84342,7 @@ var init_Track = __esm({
84215
84342
  import_react37.default.createElement(MenuItem_default, { dense: true, onClick: () => {
84216
84343
  model2.toggleTrack(track.model.id);
84217
84344
  closeMenu();
84218
- } }, "Close"),
84345
+ } }, "Hide track"),
84219
84346
  import_react37.default.createElement(MenuItem_default, { dense: true, onClick: () => {
84220
84347
  model2.queueDialog((onClose) => [
84221
84348
  TrackInfoDialog2,
@@ -84838,10 +84965,15 @@ var init_FeatureDialog = __esm({
84838
84965
  );
84839
84966
  });
84840
84967
  FeatureTable = (0, import_mobx_react12.observer)(function({ model: model2 }) {
84841
- const { annotationTypes, annotations, fillPalette } = model2;
84968
+ const { annotationTypes, annotations, featureColors, fillPalette } = model2;
84842
84969
  const counts = /* @__PURE__ */ new Map();
84970
+ const swatches = /* @__PURE__ */ new Map();
84843
84971
  for (const annot of annotations) {
84844
- counts.set(annot.accession, (counts.get(annot.accession) ?? 0) + 1);
84972
+ const { accession } = annot;
84973
+ counts.set(accession, (counts.get(accession) ?? 0) + 1);
84974
+ if (!swatches.has(accession)) {
84975
+ swatches.set(accession, featureColors.get(annot)?.fill ?? fillPalette[accession]);
84976
+ }
84845
84977
  }
84846
84978
  return import_react40.default.createElement(
84847
84979
  import_react40.default.Fragment,
@@ -84880,7 +85012,7 @@ var init_FeatureDialog = __esm({
84880
85012
  import_react40.default.createElement("div", { style: {
84881
85013
  width: 20,
84882
85014
  height: 20,
84883
- background: fillPalette[accession]
85015
+ background: swatches.get(accession)
84884
85016
  } })
84885
85017
  ),
84886
85018
  import_react40.default.createElement(TableCell_default, null, accession),
@@ -85470,6 +85602,9 @@ function parseAll(text2, opts) {
85470
85602
  for (const line2 of lines) {
85471
85603
  let match2;
85472
85604
  if (formatStartRegex.test(line2)) {
85605
+ if (stock) {
85606
+ db.push(stock);
85607
+ }
85473
85608
  stock = createStockholm();
85474
85609
  treeContinues = false;
85475
85610
  } else if (formatEndRegex.test(line2)) {
@@ -86068,7 +86203,7 @@ var init_AnnotationFileDialog = __esm({
86068
86203
  import_react41.default.createElement(
86069
86204
  DialogActions_default,
86070
86205
  null,
86071
- import_react41.default.createElement(Button_default, { variant: "contained", color: "primary", disabled: loading, onClick: () => {
86206
+ import_react41.default.createElement(Button_default, { variant: "contained", color: "primary", disabled: loading || (choice === "file" ? !file : !url.trim()), onClick: () => {
86072
86207
  ;
86073
86208
  (async () => {
86074
86209
  setLoading(true);
@@ -86085,7 +86220,10 @@ var init_AnnotationFileDialog = __esm({
86085
86220
  setLoading(false);
86086
86221
  }
86087
86222
  })();
86088
- } }, "Open annotations")
86223
+ } }, "Open annotations"),
86224
+ import_react41.default.createElement(Button_default, { variant: "contained", color: "secondary", onClick: () => {
86225
+ handleClose();
86226
+ } }, "Cancel")
86089
86227
  )
86090
86228
  );
86091
86229
  });
@@ -86298,12 +86436,12 @@ var init_SimpleField = __esm({
86298
86436
 
86299
86437
  // ../../node_modules/.pnpm/@jbrowse+core@file+vendor-jbrowse+jbrowse-core.tgz_@emotion+styled@11.14.1_@emotion+rea_7090bbe2527c98dca0fac05344a682ca/node_modules/@jbrowse/core/esm/BaseFeatureWidget/BaseFeatureDetail/UriField.js
86300
86438
  function UriField({ value, prefix: prefix2, name }) {
86301
- const { uri: uri2, baseUri = "" } = value;
86439
+ const { uri: uri3, baseUri = "" } = value;
86302
86440
  let href;
86303
86441
  try {
86304
- href = new URL(uri2, baseUri).href;
86442
+ href = new URL(uri3, baseUri).href;
86305
86443
  } catch {
86306
- href = uri2;
86444
+ href = uri3;
86307
86445
  }
86308
86446
  return (0, import_jsx_runtime177.jsx)(SimpleField, { name, prefix: prefix2, value: href });
86309
86447
  }
@@ -90835,13 +90973,13 @@ function measureScrollbarSize(element, scrollbarSize) {
90835
90973
  if (cached !== void 0 && cached.devicePixelRatio === devicePixelRatio2 && cached.measuredDirectly) {
90836
90974
  return cached.size;
90837
90975
  }
90838
- const computed2 = view?.getComputedStyle(htmlElement);
90839
- const canScrollY = computed2?.overflowY === "auto" || computed2?.overflowY === "scroll";
90840
- const canScrollX = computed2?.overflowX === "auto" || computed2?.overflowX === "scroll";
90976
+ const computed3 = view?.getComputedStyle(htmlElement);
90977
+ const canScrollY = computed3?.overflowY === "auto" || computed3?.overflowY === "scroll";
90978
+ const canScrollX = computed3?.overflowX === "auto" || computed3?.overflowX === "scroll";
90841
90979
  const hasScrollY = canScrollY && htmlElement.scrollHeight > htmlElement.clientHeight;
90842
90980
  const hasScrollX = canScrollX && htmlElement.scrollWidth > htmlElement.clientWidth;
90843
- const borderWidth = parseCSSPixelValue(computed2?.borderLeftWidth) + parseCSSPixelValue(computed2?.borderRightWidth);
90844
- const borderHeight = parseCSSPixelValue(computed2?.borderTopWidth) + parseCSSPixelValue(computed2?.borderBottomWidth);
90981
+ const borderWidth = parseCSSPixelValue(computed3?.borderLeftWidth) + parseCSSPixelValue(computed3?.borderRightWidth);
90982
+ const borderHeight = parseCSSPixelValue(computed3?.borderTopWidth) + parseCSSPixelValue(computed3?.borderBottomWidth);
90845
90983
  const directSize = Math.max(hasScrollY ? htmlElement.offsetWidth - htmlElement.clientWidth - borderWidth : 0, hasScrollX ? htmlElement.offsetHeight - htmlElement.clientHeight - borderHeight : 0);
90846
90984
  if (hasScrollY || hasScrollX) {
90847
90985
  const size6 = Math.max(0, directSize);
@@ -90860,8 +90998,8 @@ function measureScrollbarSize(element, scrollbarSize) {
90860
90998
  scrollDiv.style.height = "99px";
90861
90999
  scrollDiv.style.position = "absolute";
90862
91000
  scrollDiv.style.overflow = "scroll";
90863
- if (computed2?.scrollbarWidth) {
90864
- scrollDiv.style.scrollbarWidth = computed2.scrollbarWidth;
91001
+ if (computed3?.scrollbarWidth) {
91002
+ scrollDiv.style.scrollbarWidth = computed3.scrollbarWidth;
90865
91003
  }
90866
91004
  scrollDiv.className = "scrollDiv";
90867
91005
  element.appendChild(scrollDiv);
@@ -111130,21 +111268,21 @@ var init_csvSerializer = __esm({
111130
111268
 
111131
111269
  // ../../node_modules/.pnpm/@mui+x-data-grid@9.10.1_@emotion+react@11.14.0_@types+react@19.3.0_react@19.3.0_support_99c4788b624f401637c043973d33a0af/node_modules/@mui/x-data-grid/hooks/features/clipboard/useGridClipboard.mjs
111132
111270
  function writeToClipboardPolyfill(data) {
111133
- const span = document.createElement("span");
111134
- span.style.whiteSpace = "pre";
111135
- span.style.userSelect = "all";
111136
- span.style.opacity = "0px";
111137
- span.textContent = data;
111138
- document.body.appendChild(span);
111271
+ const span2 = document.createElement("span");
111272
+ span2.style.whiteSpace = "pre";
111273
+ span2.style.userSelect = "all";
111274
+ span2.style.opacity = "0px";
111275
+ span2.textContent = data;
111276
+ document.body.appendChild(span2);
111139
111277
  const range = document.createRange();
111140
- range.selectNode(span);
111278
+ range.selectNode(span2);
111141
111279
  const selection = window.getSelection();
111142
111280
  selection.removeAllRanges();
111143
111281
  selection.addRange(range);
111144
111282
  try {
111145
111283
  document.execCommand("copy");
111146
111284
  } finally {
111147
- document.body.removeChild(span);
111285
+ document.body.removeChild(span2);
111148
111286
  }
111149
111287
  }
111150
111288
  function copyToClipboard2(data) {
@@ -121076,8 +121214,7 @@ var init_SequenceTextArea = __esm({
121076
121214
  },
121077
121215
  dialogContent: {
121078
121216
  background: theme.palette.action.selected,
121079
- margin: 4,
121080
- minWidth: "80em"
121217
+ margin: 4
121081
121218
  }
121082
121219
  }));
121083
121220
  }
@@ -121223,7 +121360,7 @@ function ExportSVGDialog({ model: model2, onClose }) {
121223
121360
  });
121224
121361
  });
121225
121362
  }
121226
- }, exporting ? "Exporting\u2026" : "Submit"),
121363
+ }, exporting ? "Exporting\u2026" : "Export"),
121227
121364
  import_react53.default.createElement(Button_default, { variant: "contained", color: "secondary", onClick: () => {
121228
121365
  onClose();
121229
121366
  } }, "Cancel")
@@ -121444,44 +121581,25 @@ var init_Warning = __esm({
121444
121581
  });
121445
121582
 
121446
121583
  // ../lib/dist/components/header/LoadWarnings.js
121447
- var import_react58, import_mobx_react19, useStyles22, LoadWarnings, LoadWarnings_default;
121584
+ var import_react58, import_mobx_react19, LoadWarnings, LoadWarnings_default;
121448
121585
  var init_LoadWarnings = __esm({
121449
121586
  "../lib/dist/components/header/LoadWarnings.js"() {
121450
121587
  "use strict";
121451
121588
  import_react58 = __toESM(require_react(), 1);
121452
- init_tss_react();
121453
121589
  init_Warning();
121454
121590
  init_material();
121455
121591
  import_mobx_react19 = __toESM(require_dist3(), 1);
121456
- useStyles22 = makeStyles()((theme) => ({
121457
- warning: {
121458
- display: "flex",
121459
- alignItems: "center",
121460
- gap: 4,
121461
- margin: "auto",
121462
- marginLeft: 10,
121463
- whiteSpace: "nowrap",
121464
- color: theme.palette.warning.main,
121465
- cursor: "pointer"
121466
- }
121467
- }));
121468
121592
  LoadWarnings = (0, import_mobx_react19.observer)(function({ model: model2 }) {
121469
121593
  const { warnings } = model2;
121470
- const { classes: classes2 } = useStyles22();
121471
121594
  if (warnings.length === 0) {
121472
121595
  return null;
121473
121596
  }
121474
121597
  return import_react58.default.createElement(
121475
121598
  Tooltip_default,
121476
121599
  { title: `${warnings.join(" ")} (click to dismiss)` },
121477
- import_react58.default.createElement(
121478
- Typography_default,
121479
- { component: "div", className: classes2.warning, onClick: () => {
121480
- model2.clearWarnings();
121481
- } },
121482
- import_react58.default.createElement(Warning_default, { fontSize: "small" }),
121483
- warnings.length > 1 ? `${warnings.length} warnings` : "Warning"
121484
- )
121600
+ import_react58.default.createElement(Button_default, { "aria-label": "Dismiss warnings", color: "warning", size: "small", startIcon: import_react58.default.createElement(Warning_default, { fontSize: "small" }), style: { margin: "auto", marginLeft: 10, whiteSpace: "nowrap" }, onClick: () => {
121601
+ model2.clearWarnings();
121602
+ } }, warnings.length > 1 ? `${warnings.length} warnings` : "Warning")
121485
121603
  );
121486
121604
  });
121487
121605
  LoadWarnings_default = LoadWarnings;
@@ -121534,10 +121652,10 @@ function msaSettingsMenuItems(model2) {
121534
121652
  model2.setDrawMsaLetters(arg2);
121535
121653
  }),
121536
121654
  // inverted: bgColor colors the tile, and off colors the letter
121537
- toggle("Color letters instead of background of tiles", !bgColor, (arg2) => {
121655
+ toggle("Color letters, not cells", !bgColor, (arg2) => {
121538
121656
  model2.setBgColor(!arg2);
121539
121657
  }),
121540
- toggle("Enable hiding gappy columns?", hideGaps, (arg2) => {
121658
+ toggle("Hide gappy columns", hideGaps, (arg2) => {
121541
121659
  model2.setHideGaps(arg2);
121542
121660
  })
121543
121661
  ];
@@ -121695,7 +121813,7 @@ var init_LinkOff = __esm({
121695
121813
  function size4(bytes) {
121696
121814
  return bytes < 1e6 ? `${Math.round(bytes / 1e3)} kB` : `${(bytes / 1e6).toFixed(1)} MB`;
121697
121815
  }
121698
- var import_react62, import_mobx_react23, useStyles23, UnshareableDataWarning, UnshareableDataWarning_default;
121816
+ var import_react62, import_mobx_react23, useStyles22, UnshareableDataWarning, UnshareableDataWarning_default;
121699
121817
  var init_UnshareableDataWarning = __esm({
121700
121818
  "../lib/dist/components/header/UnshareableDataWarning.js"() {
121701
121819
  "use strict";
@@ -121704,7 +121822,7 @@ var init_UnshareableDataWarning = __esm({
121704
121822
  init_LinkOff();
121705
121823
  init_material();
121706
121824
  import_mobx_react23 = __toESM(require_dist3(), 1);
121707
- useStyles23 = makeStyles()((theme) => ({
121825
+ useStyles22 = makeStyles()((theme) => ({
121708
121826
  warning: {
121709
121827
  display: "flex",
121710
121828
  alignItems: "center",
@@ -121718,7 +121836,7 @@ var init_UnshareableDataWarning = __esm({
121718
121836
  }));
121719
121837
  UnshareableDataWarning = (0, import_mobx_react23.observer)(function({ model: model2 }) {
121720
121838
  const { unshareableData } = model2;
121721
- const { classes: classes2 } = useStyles23();
121839
+ const { classes: classes2 } = useStyles22();
121722
121840
  if (unshareableData.length === 0) {
121723
121841
  return null;
121724
121842
  }
@@ -121948,7 +122066,7 @@ var version3;
121948
122066
  var init_version3 = __esm({
121949
122067
  "../lib/dist/version.js"() {
121950
122068
  "use strict";
121951
- version3 = "8.0.0";
122069
+ version3 = "8.2.0";
121952
122070
  }
121953
122071
  });
121954
122072
 
@@ -121972,7 +122090,7 @@ function AboutDialog({ onClose }) {
121972
122090
  "MSAView ",
121973
122091
  version3,
121974
122092
  " (",
121975
- import_react66.default.createElement(Link_default, { href: "https://github.com/gmod/react-msaview" }, "Github"),
122093
+ import_react66.default.createElement(Link_default, { target: "_blank", rel: "noopener", href: "https://github.com/gmod/react-msaview" }, "Github"),
121976
122094
  ")"
121977
122095
  ),
121978
122096
  import_react66.default.createElement(
@@ -121986,11 +122104,11 @@ function AboutDialog({ onClose }) {
121986
122104
  null,
121987
122105
  "We use some color schemes from the",
121988
122106
  " ",
121989
- import_react66.default.createElement(Link_default, { href: "https://github.com/biotite-dev/biotite" }, "biotite"),
122107
+ import_react66.default.createElement(Link_default, { target: "_blank", rel: "noopener", href: "https://github.com/biotite-dev/biotite" }, "biotite"),
121990
122108
  " ",
121991
122109
  "project, and their license is reproduced",
121992
122110
  " ",
121993
- import_react66.default.createElement(Link_default, { href: "https://github.com/biotite-dev/biotite/blob/master/LICENSE.rst" }, "here")
122111
+ import_react66.default.createElement(Link_default, { target: "_blank", rel: "noopener", href: "https://github.com/biotite-dev/biotite/blob/master/LICENSE.rst" }, "here")
121994
122112
  )
121995
122113
  ),
121996
122114
  import_react66.default.createElement(
@@ -122001,7 +122119,7 @@ function AboutDialog({ onClose }) {
122001
122119
  null,
122002
122120
  "See this page for some information on jalview colorings",
122003
122121
  " ",
122004
- import_react66.default.createElement(Link_default, { href: "https://www.jalview.org/help/html/colourSchemes/" }, "here")
122122
+ import_react66.default.createElement(Link_default, { target: "_blank", rel: "noopener", href: "https://www.jalview.org/help/html/colourSchemes/" }, "here")
122005
122123
  )
122006
122124
  ),
122007
122125
  import_react66.default.createElement(
@@ -122012,7 +122130,7 @@ function AboutDialog({ onClose }) {
122012
122130
  null,
122013
122131
  "See this page for some info on the clustal, cinema, maeditor, and lesk color schemes",
122014
122132
  " ",
122015
- import_react66.default.createElement(Link_default, { href: "http://www.bioinformatics.nl/~berndb/aacolour.html" }, "here")
122133
+ import_react66.default.createElement(Link_default, { target: "_blank", rel: "noopener", href: "http://www.bioinformatics.nl/~berndb/aacolour.html" }, "here")
122016
122134
  )
122017
122135
  ),
122018
122136
  import_react66.default.createElement(
@@ -122023,7 +122141,7 @@ function AboutDialog({ onClose }) {
122023
122141
  null,
122024
122142
  "See this paper about the flower color scheme",
122025
122143
  " ",
122026
- import_react66.default.createElement(Link_default, { href: "https://www.ncbi.nlm.nih.gov/pmc/articles/PMC7245768/" }, "here")
122144
+ import_react66.default.createElement(Link_default, { target: "_blank", rel: "noopener", href: "https://www.ncbi.nlm.nih.gov/pmc/articles/PMC7245768/" }, "here")
122027
122145
  )
122028
122146
  )
122029
122147
  )
@@ -122077,7 +122195,7 @@ var init_Header = __esm({
122077
122195
  }, [model2]);
122078
122196
  return import_react67.default.createElement(
122079
122197
  "div",
122080
- { ref, style: { display: "flex" } },
122198
+ { ref, style: { display: "flex", flexWrap: "wrap" } },
122081
122199
  import_react67.default.createElement(FileMenu_default, { model: model2 }),
122082
122200
  import_react67.default.createElement(ColorSchemeMenu_default, { model: model2 }),
122083
122201
  import_react67.default.createElement(TreeSettingsMenu_default, { model: model2 }),
@@ -122479,9 +122597,7 @@ var init_MSACanvas = __esm({
122479
122597
  onZoom,
122480
122598
  scrollZoom
122481
122599
  });
122482
- return import_react74.default.createElement("div", { ref, "data-testid": "msa_canvas", onMouseDown, onMouseUp, onMouseLeave: (event) => {
122483
- event.preventDefault();
122484
- }, style: {
122600
+ return import_react74.default.createElement("div", { ref, "data-testid": "msa_canvas", onMouseDown, onMouseUp, style: {
122485
122601
  position: "relative",
122486
122602
  height: height2,
122487
122603
  width: msaCanvasWidth,
@@ -122664,17 +122780,17 @@ var init_featureFields = __esm({
122664
122780
 
122665
122781
  // ../lib/dist/components/PortalTooltip.js
122666
122782
  function PortalTooltip({ clientX, clientY, children }) {
122667
- const { classes: classes2 } = useStyles24();
122783
+ const { classes: classes2 } = useStyles23();
122668
122784
  return (0, import_react_dom4.createPortal)(import_react78.default.createElement("div", { className: classes2.tooltip, style: { left: clientX + 12, top: clientY + 12 } }, children), document.body);
122669
122785
  }
122670
- var import_react78, import_react_dom4, useStyles24;
122786
+ var import_react78, import_react_dom4, useStyles23;
122671
122787
  var init_PortalTooltip = __esm({
122672
122788
  "../lib/dist/components/PortalTooltip.js"() {
122673
122789
  "use strict";
122674
122790
  import_react78 = __toESM(require_react(), 1);
122675
122791
  init_tss_react();
122676
122792
  import_react_dom4 = __toESM(require_react_dom(), 1);
122677
- useStyles24 = makeStyles()((theme) => ({
122793
+ useStyles23 = makeStyles()((theme) => ({
122678
122794
  tooltip: {
122679
122795
  position: "fixed",
122680
122796
  pointerEvents: "none",
@@ -122700,11 +122816,11 @@ function hoverLines({ model: model2, panel, name, x: x2 }) {
122700
122816
  `${panel.header}: ${model2.rowDataOf(name)?.[panel.field] ?? "(none)"}`
122701
122817
  ];
122702
122818
  }
122703
- const span = panel.spans.get(name)?.findLast((s2) => x2 >= s2.xStart && x2 <= s2.xEnd);
122704
- if (!span) {
122819
+ const span2 = panel.spans.get(name)?.findLast((s2) => x2 >= s2.xStart && x2 <= s2.xEnd);
122820
+ if (!span2) {
122705
122821
  return [];
122706
122822
  }
122707
- const { annotation } = span;
122823
+ const { annotation } = span2;
122708
122824
  const strand = annotation.strand === void 0 ? "" : annotation.strand < 0 ? " (-)" : " (+)";
122709
122825
  const value = panel.field ? featureField(annotation, panel.field) : void 0;
122710
122826
  return [
@@ -122954,7 +123070,7 @@ var init_TreeBranchMenu = __esm({
122954
123070
  { anchorReference: "anchorPosition", anchorPosition: {
122955
123071
  left: node2.x,
122956
123072
  top: node2.y
122957
- }, transitionDuration: 0, keepMounted: true, open: Boolean(node2), onClose },
123073
+ }, transitionDuration: 0, open: true, onClose },
122958
123074
  import_react83.default.createElement(MenuItem_default, { dense: true, disabled: true }, node2.name),
122959
123075
  import_react83.default.createElement(MenuItem_default, { dense: true, onClick: () => {
122960
123076
  model2.toggleCollapsed(node2.id);
@@ -123038,7 +123154,7 @@ var init_TreeNodeMenu = __esm({
123038
123154
  { anchorReference: "anchorPosition", anchorPosition: {
123039
123155
  top: node2.y,
123040
123156
  left: node2.x
123041
- }, transitionDuration: 0, keepMounted: true, open: Boolean(node2), onClose },
123157
+ }, transitionDuration: 0, open: true, onClose },
123042
123158
  import_react85.default.createElement(MenuItem_default, { dense: true, disabled: true }, name),
123043
123159
  import_react85.default.createElement(MenuItem_default, { dense: true, onClick: () => {
123044
123160
  model2.queueDialog((onClose2) => [
@@ -123657,7 +123773,7 @@ var init_useTreeHover = __esm({
123657
123773
  });
123658
123774
 
123659
123775
  // ../lib/dist/components/tree/TreeCanvasBlock.js
123660
- var import_react87, import_mobx_react41, useStyles25, TreeCanvasBlock, TreeCanvasBlock_default;
123776
+ var import_react87, import_mobx_react41, useStyles24, TreeCanvasBlock, TreeCanvasBlock_default;
123661
123777
  var init_TreeCanvasBlock = __esm({
123662
123778
  "../lib/dist/components/tree/TreeCanvasBlock.js"() {
123663
123779
  "use strict";
@@ -123671,16 +123787,16 @@ var init_TreeCanvasBlock = __esm({
123671
123787
  init_TreeNodeMenu();
123672
123788
  init_renderTreeCanvas();
123673
123789
  init_useTreeHover();
123674
- useStyles25 = makeStyles()(() => ({
123790
+ useStyles24 = makeStyles()((theme) => ({
123675
123791
  hover: {
123676
123792
  position: "absolute",
123677
123793
  pointerEvents: "none",
123678
123794
  zIndex: 100,
123679
- background: "rgba(0,0,0,0.1)"
123795
+ background: theme.palette.action.hover
123680
123796
  }
123681
123797
  }));
123682
123798
  TreeCanvasBlock = (0, import_mobx_react41.observer)(function({ model: model2, offsetY }) {
123683
- const { classes: classes2 } = useStyles25();
123799
+ const { classes: classes2 } = useStyles24();
123684
123800
  const theme = useTheme4();
123685
123801
  const [branchMenu, setBranchMenu] = (0, import_react87.useState)();
123686
123802
  const [nodeMenu, setNodeMenu] = (0, import_react87.useState)();
@@ -123803,9 +123919,7 @@ var init_TreeCanvas = __esm({
123803
123919
  });
123804
123920
  return import_react88.default.createElement(
123805
123921
  "div",
123806
- { ref, onMouseDown, onMouseUp, onMouseLeave: (event) => {
123807
- event.preventDefault();
123808
- }, style: {
123922
+ { ref, onMouseDown, onMouseUp, style: {
123809
123923
  height: height2,
123810
123924
  position: "relative",
123811
123925
  width: treeAreaWidth
@@ -124056,7 +124170,7 @@ var init_util10 = __esm({
124056
124170
  });
124057
124171
 
124058
124172
  // ../lib/dist/components/import/ImportFormExamples.js
124059
- function uri(url) {
124173
+ function uri2(url) {
124060
124174
  return url ? { uri: url, locationType: "UriLocation" } : void 0;
124061
124175
  }
124062
124176
  var import_react92, import_mobx_react46, BASE, TREES, examples, ImportFormExamples, ImportFormExamples_default;
@@ -124123,7 +124237,7 @@ var init_ImportFormExamples = __esm({
124123
124237
  if (inline4) {
124124
124238
  model2.setData(inline4);
124125
124239
  } else {
124126
- load(model2, uri(msa), uri(tree), uri(gff));
124240
+ load(model2, uri2(msa), uri2(tree), uri2(gff));
124127
124241
  }
124128
124242
  } catch (e3) {
124129
124243
  console.error(e3);
@@ -124162,7 +124276,7 @@ var init_ImportForm = __esm({
124162
124276
  { style: { width: "50%" } },
124163
124277
  error ? import_react93.default.createElement(ErrorMessage, { error }) : null,
124164
124278
  import_react93.default.createElement(Typography_default, null, "Open an MSA file (FASTA, Stockholm, Clustal, A3M or EMF format) and/or a tree file (Newick format)."),
124165
- import_react93.default.createElement(Typography_default, { color: "error" }, "Note: you can open up just an MSA or just a tree, both are not required. Some MSA files e.g. stockholm format have an embedded tree also and this is fine, and opening a separate tree file is not required.")
124279
+ import_react93.default.createElement(Typography_default, { color: "text.secondary" }, "An MSA alone or a tree alone is enough, and a Stockholm file with an embedded tree needs no separate tree file.")
124166
124280
  ),
124167
124281
  import_react93.default.createElement(
124168
124282
  "div",
@@ -124236,16 +124350,7 @@ var init_Loading2 = __esm({
124236
124350
  return import_react94.default.createElement(
124237
124351
  "div",
124238
124352
  { style: { display: "flex", alignItems: "center", gap: 12, padding: 20 } },
124239
- import_react94.default.createElement(
124240
- "svg",
124241
- { width: "24", height: "24", viewBox: "0 0 24 24", xmlns: "http://www.w3.org/2000/svg" },
124242
- import_react94.default.createElement("style", null, `@keyframes spinner { to { transform: rotate(360deg); } }`),
124243
- import_react94.default.createElement("circle", { cx: "12", cy: "12", r: "10", stroke: "#ccc", strokeWidth: "3", fill: "none" }),
124244
- import_react94.default.createElement("path", { d: "M12 2a10 10 0 0 1 10 10", stroke: "#1976d2", strokeWidth: "3", fill: "none", strokeLinecap: "round", style: {
124245
- animation: "spinner 1s linear infinite",
124246
- transformOrigin: "center"
124247
- } })
124248
- ),
124353
+ import_react94.default.createElement(CircularProgress_default, { size: 24 }),
124249
124354
  import_react94.default.createElement(Typography_default, { variant: "h6" }, status?.msg ?? "Loading..."),
124250
124355
  status?.onCancel ? import_react94.default.createElement(Button_default, { variant: "outlined", size: "small", onClick: () => status.onCancel?.() }, "Cancel") : null
124251
124356
  );
@@ -124272,8 +124377,9 @@ var init_Loading2 = __esm({
124272
124377
  // ../lib/dist/calculateBlocks.js
124273
124378
  function calculateBlocks({ mapSize, blockSize, viewportPos, viewportSize }) {
124274
124379
  const clamped = Math.max(0, Math.min(viewportPos, mapSize - viewportSize));
124380
+ const end = Math.min(clamped + viewportSize, mapSize);
124275
124381
  const minTile = Math.floor(clamped / blockSize);
124276
- const maxTile = Math.floor((clamped + viewportSize - 1) / blockSize);
124382
+ const maxTile = Math.ceil(end / blockSize) - 1;
124277
124383
  const blocks = [];
124278
124384
  for (let tile = minTile; tile <= maxTile; tile++) {
124279
124385
  blocks.push(tile * blockSize);
@@ -125675,9 +125781,30 @@ function featureLabelMap(annotations, field) {
125675
125781
  }
125676
125782
  return labels;
125677
125783
  }
125678
- function featurePanelSpans({ panel, width: width3, rowHeight, colWidth, domainBands, annotationsByRow, shifts }) {
125784
+ function strandpileLanes(rows) {
125785
+ const piled = rows.map(([name, bands]) => {
125786
+ const reverse = packDomainLanes(bands.filter((b2) => b2.annotation.strand === -1));
125787
+ const forward = packDomainLanes(bands.filter((b2) => b2.annotation.strand !== -1));
125788
+ return { name, forward, reverse };
125789
+ });
125790
+ const up = Math.max(0, ...piled.map((p2) => p2.forward[0]?.laneCount ?? 0));
125791
+ const down = Math.max(0, ...piled.map((p2) => p2.reverse[0]?.laneCount ?? 0));
125792
+ const laneCount = up + down;
125793
+ return new Map(piled.map(({ name, forward, reverse }) => [
125794
+ name,
125795
+ [
125796
+ // forward level 0 is the lane just above the line, stacking upward
125797
+ ...forward.map((b2) => ({ ...b2, lane: up - 1 - b2.lane, laneCount })),
125798
+ ...reverse.map((b2) => ({ ...b2, lane: up + b2.lane, laneCount }))
125799
+ ]
125800
+ ]));
125801
+ }
125802
+ function panelLanes(panel, rows) {
125803
+ return panel.position === "strandpile" ? strandpileLanes(rows) : new Map(rows.map(([name, bands]) => [name, packDomainLanes(bands)]));
125804
+ }
125805
+ function featurePanelSpans({ panel, width: width3, colWidth, domainBands, annotationsByRow, shifts }) {
125679
125806
  if (panel.x === "column") {
125680
- return new Map([...domainBands].map(([name, bands]) => [
125807
+ return panelLanes(panel, [...domainBands].map(([name, bands]) => [
125681
125808
  name,
125682
125809
  bands.map((band) => ({
125683
125810
  ...band,
@@ -125705,17 +125832,17 @@ function featurePanelSpans({ panel, width: width3, rowHeight, colWidth, domainBa
125705
125832
  max3 = Math.max(max3, end);
125706
125833
  }
125707
125834
  }
125708
- const drawable = Math.max(1, width3 - rowHeight);
125835
+ const drawable = Math.max(1, width3 - 1);
125709
125836
  const scale = max3 > min3 ? drawable / (max3 - min3) : 0;
125710
- return new Map(shifted.map(([name, features]) => [
125837
+ return panelLanes(panel, shifted.map(([name, features]) => [
125711
125838
  name,
125712
- packDomainLanes(features.map(({ annotation, start, end }) => ({
125839
+ features.map(({ annotation, start, end }) => ({
125713
125840
  annotation,
125714
125841
  xStart: (start - min3) * scale,
125715
125842
  xEnd: (end - min3) * scale,
125716
125843
  startCol: start,
125717
125844
  endCol: end - (end - start) * laneOverlap
125718
- })))
125845
+ }))
125719
125846
  ]));
125720
125847
  }
125721
125848
  function resolveFeatureScale(field, scale, annotations) {
@@ -125975,11 +126102,7 @@ function stateModelFactory() {
125975
126102
  * data from the loaded tree/msa/treeMetadata, generally loaded by
125976
126103
  * autorun
125977
126104
  */
125978
- data: types.optional(DataModelF(), {
125979
- tree: "",
125980
- msa: "",
125981
- treeMetadata: ""
125982
- }),
126105
+ data: types.optional(DataModelF(), {}),
125983
126106
  /**
125984
126107
  * #property
125985
126108
  * the user's explicit hide choices per annotation accession, keyed by
@@ -126009,6 +126132,14 @@ function stateModelFactory() {
126009
126132
  * Persists in the snapshot and the URL.
126010
126133
  */
126011
126134
  highlights: stripDefault2(types.array(types.frozen()), []),
126135
+ /**
126136
+ * #property
126137
+ * where the view opens, in `highlights` coordinates: `{row, start,
126138
+ * end}` zooms onto residues of that row, `{start, end}` onto alignment
126139
+ * columns. Applied once the alignment and any tree file have loaded,
126140
+ * then cleared, so a reloaded session keeps the reader's own scroll.
126141
+ */
126142
+ region: types.frozen(),
126012
126143
  /**
126013
126144
  * #property
126014
126145
  * clades of the tree with a mark drawn over them. `mrca` names tips
@@ -126216,10 +126347,12 @@ function stateModelFactory() {
126216
126347
  /**
126217
126348
  * #action
126218
126349
  * record a non-fatal load problem: a layer that failed to load, a file
126219
- * that failed to parse
126350
+ * that failed to parse. A message already on the list is not added again
126220
126351
  */
126221
126352
  addWarning(warning) {
126222
- self.warnings = [...self.warnings, warning];
126353
+ if (!self.warnings.includes(warning)) {
126354
+ self.warnings = [...self.warnings, warning];
126355
+ }
126223
126356
  },
126224
126357
  /**
126225
126358
  * #action
@@ -126258,6 +126391,12 @@ function stateModelFactory() {
126258
126391
  setHighlightedColumns(columns) {
126259
126392
  self.highlightedColumns = columns;
126260
126393
  },
126394
+ /**
126395
+ * #action
126396
+ */
126397
+ setRegion(region) {
126398
+ self.region = region;
126399
+ },
126261
126400
  /**
126262
126401
  * #action
126263
126402
  */
@@ -126827,7 +126966,7 @@ function stateModelFactory() {
126827
126966
  */
126828
126967
  get insertionPositions() {
126829
126968
  const { blanks, rows } = this;
126830
- if (blanks.length === 0 || !self.hideGapsEffective) {
126969
+ if (blanks.length === 0) {
126831
126970
  return /* @__PURE__ */ new Map();
126832
126971
  }
126833
126972
  const result = /* @__PURE__ */ new Map();
@@ -127048,7 +127187,7 @@ function stateModelFactory() {
127048
127187
  * cladogram mode. The tree's scale bar uses it.
127049
127188
  */
127050
127189
  get pxPerBranchLength() {
127051
- const max3 = maxLength(this.root);
127190
+ const max3 = this.rootToTipLength;
127052
127191
  return this.showBranchLenEffective && max3 ? self.treeWidth / max3 : 0;
127053
127192
  },
127054
127193
  /**
@@ -127091,7 +127230,107 @@ function stateModelFactory() {
127091
127230
  get showTreeText() {
127092
127231
  return self.drawLabels && self.rowHeight >= minLetterRowHeight;
127093
127232
  }
127094
- })).views((self) => ({
127233
+ })).views((self) => {
127234
+ const columnTrackModels = /* @__PURE__ */ new WeakMap();
127235
+ return {
127236
+ /**
127237
+ * #getter
127238
+ * a data track's values or string, projected from its row's residues
127239
+ * onto alignment columns when it names a row
127240
+ */
127241
+ get columnTrackContent() {
127242
+ const { MSA, blanks, hideGapsEffective } = self;
127243
+ const width3 = MSA?.getWidth() ?? 0;
127244
+ const project = (track, items, fill) => {
127245
+ if (!track.row) {
127246
+ return items;
127247
+ }
127248
+ const out = Array.from({ length: width3 }, () => fill);
127249
+ const index3 = self.seqPosIndex(track.row);
127250
+ items.forEach((item, seqPos) => {
127251
+ const col = index3?.[seqPos];
127252
+ if (col !== void 0) {
127253
+ out[col] = item;
127254
+ }
127255
+ });
127256
+ return out;
127257
+ };
127258
+ const skip2 = (items) => hideGapsEffective ? dropBlanks(blanks, items) : items;
127259
+ const resolve = (track, pos) => {
127260
+ const col = track.row ? self.seqPosIndex(track.row)?.[pos - 1] : pos - 1;
127261
+ if (col === void 0 || col < 0 || col >= width3) {
127262
+ return void 0;
127263
+ }
127264
+ return hideGapsEffective ? visibleColsBefore(blanks, col) : col;
127265
+ };
127266
+ return new Map(self.columnTracks.map((track) => {
127267
+ if (track.kind === "arc") {
127268
+ const arcs = (track.arcs ?? []).map((arc) => {
127269
+ const start = resolve(track, Math.min(arc.start, arc.end));
127270
+ const end = resolve(track, Math.max(arc.start, arc.end));
127271
+ return start !== void 0 && end !== void 0 && start < end ? { start, end, color: arc.color } : void 0;
127272
+ }).filter(notEmpty);
127273
+ return [track.id, { arcs }];
127274
+ }
127275
+ if (track.kind === "bar") {
127276
+ const max3 = track.max ?? 1;
127277
+ const values4 = skip2(project(track, track.values ?? [], 0)).map((v2) => Math.min(1, Math.max(0, v2 / max3)));
127278
+ return [track.id, { values: values4 }];
127279
+ }
127280
+ const data = skip2(project(track, (track.data ?? "").split(""), " "));
127281
+ return [track.id, { data: data.join("") }];
127282
+ }));
127283
+ },
127284
+ /**
127285
+ * #method
127286
+ * the height a track draws at: what the user dragged its divider to,
127287
+ * then the height its snapshot asked for, then its kind's default. Only
127288
+ * a text track falls through to rowHeight
127289
+ */
127290
+ trackHeight(kind, heightKey = kind, given) {
127291
+ return self.trackHeights.get(heightKey) ?? given ?? defaultTrackHeights[kind] ?? self.rowHeight;
127292
+ },
127293
+ /**
127294
+ * #method
127295
+ * the track a column track spec draws as, computed once per spec. A
127296
+ * text track's height falls through to rowHeight, and its own computed
127297
+ * confines the vertical zoom to it, so a sibling keeps its object and
127298
+ * its canvas skips the redraw
127299
+ */
127300
+ columnTrackModel(track) {
127301
+ let computedModel = columnTrackModels.get(track);
127302
+ if (!computedModel) {
127303
+ computedModel = (0, import_mobx5.computed)(() => {
127304
+ const heightKey = resizableKinds.has(track.kind) ? ownHeightKey(track.id) : void 0;
127305
+ const content = this.columnTrackContent.get(track.id);
127306
+ return {
127307
+ model: {
127308
+ id: track.id,
127309
+ name: track.name,
127310
+ kind: track.kind,
127311
+ heightKey,
127312
+ height: this.trackHeight(track.kind, heightKey, track.height),
127313
+ barColor: track.color,
127314
+ arcColor: track.color,
127315
+ customColorScheme: track.colors,
127316
+ data: content?.data,
127317
+ arcs: content?.arcs
127318
+ },
127319
+ ReactComponent: TrackBlocks_default
127320
+ };
127321
+ });
127322
+ columnTrackModels.set(track, computedModel);
127323
+ }
127324
+ return computedModel.get();
127325
+ },
127326
+ /**
127327
+ * #getter
127328
+ */
127329
+ get columnTrackModels() {
127330
+ return self.columnTracks.map((track) => this.columnTrackModel(track));
127331
+ }
127332
+ };
127333
+ }).views((self) => ({
127095
127334
  /**
127096
127335
  * #getter
127097
127336
  */
@@ -127172,87 +127411,6 @@ function stateModelFactory() {
127172
127411
  ReactComponent: TrackBlocks_default
127173
127412
  }));
127174
127413
  },
127175
- /**
127176
- * #getter
127177
- * a data track's values or string, projected from its row's residues
127178
- * onto alignment columns when it names a row
127179
- */
127180
- get columnTrackContent() {
127181
- const { MSA, blanks, hideGapsEffective } = self;
127182
- const width3 = MSA?.getWidth() ?? 0;
127183
- const project = (track, items, fill) => {
127184
- if (!track.row) {
127185
- return items;
127186
- }
127187
- const out = Array.from({ length: width3 }, () => fill);
127188
- const index3 = self.seqPosIndex(track.row);
127189
- items.forEach((item, seqPos) => {
127190
- const col = index3?.[seqPos];
127191
- if (col !== void 0) {
127192
- out[col] = item;
127193
- }
127194
- });
127195
- return out;
127196
- };
127197
- const skip2 = (items) => hideGapsEffective ? dropBlanks(blanks, items) : items;
127198
- const resolve = (track, pos) => {
127199
- const col = track.row ? self.seqPosIndex(track.row)?.[pos - 1] : pos - 1;
127200
- if (col === void 0 || col < 0 || col >= width3) {
127201
- return void 0;
127202
- }
127203
- return hideGapsEffective ? visibleColsBefore(blanks, col) : col;
127204
- };
127205
- return new Map(self.columnTracks.map((track) => {
127206
- if (track.kind === "arc") {
127207
- const arcs = (track.arcs ?? []).map((arc) => {
127208
- const start = resolve(track, Math.min(arc.start, arc.end));
127209
- const end = resolve(track, Math.max(arc.start, arc.end));
127210
- return start !== void 0 && end !== void 0 && start < end ? { start, end, color: arc.color } : void 0;
127211
- }).filter(notEmpty);
127212
- return [track.id, { arcs }];
127213
- }
127214
- if (track.kind === "bar") {
127215
- const max3 = track.max ?? 1;
127216
- const values4 = skip2(project(track, track.values ?? [], 0)).map((v2) => Math.min(1, Math.max(0, v2 / max3)));
127217
- return [track.id, { values: values4 }];
127218
- }
127219
- const data = skip2(project(track, (track.data ?? "").split(""), " "));
127220
- return [track.id, { data: data.join("") }];
127221
- }));
127222
- },
127223
- /**
127224
- * #method
127225
- * the height a track draws at: what the user dragged its divider to,
127226
- * then the height its snapshot asked for, then its kind's default. Only
127227
- * a text track falls through to rowHeight, and `??` short-circuits
127228
- * before reading it, so vertical zoom does not rebuild the other tracks
127229
- */
127230
- trackHeight(kind, heightKey = kind, given) {
127231
- return self.trackHeights.get(heightKey) ?? given ?? defaultTrackHeights[kind] ?? self.rowHeight;
127232
- },
127233
- /**
127234
- * #getter
127235
- */
127236
- get columnTrackModels() {
127237
- return self.columnTracks.map((track) => {
127238
- const heightKey = resizableKinds.has(track.kind) ? ownHeightKey(track.id) : void 0;
127239
- return {
127240
- model: {
127241
- id: track.id,
127242
- name: track.name,
127243
- kind: track.kind,
127244
- heightKey,
127245
- height: this.trackHeight(track.kind, heightKey, track.height),
127246
- barColor: track.color,
127247
- arcColor: track.color,
127248
- customColorScheme: track.colors,
127249
- data: this.columnTrackContent.get(track.id)?.data,
127250
- arcs: this.columnTrackContent.get(track.id)?.arcs
127251
- },
127252
- ReactComponent: TrackBlocks_default
127253
- };
127254
- });
127255
- },
127256
127414
  /**
127257
127415
  * #getter
127258
127416
  * the consensus secondary structure as a track, when there is one. A
@@ -127268,7 +127426,7 @@ function stateModelFactory() {
127268
127426
  name: "Base pairs",
127269
127427
  kind: "arc",
127270
127428
  heightKey: "arc",
127271
- height: this.trackHeight("arc"),
127429
+ height: self.trackHeight("arc"),
127272
127430
  arcs
127273
127431
  },
127274
127432
  ReactComponent: TrackBlocks_default
@@ -127313,7 +127471,7 @@ function stateModelFactory() {
127313
127471
  model: {
127314
127472
  ...model2,
127315
127473
  heightKey: resizableKinds.has(model2.kind) ? model2.kind : void 0,
127316
- height: this.trackHeight(model2.kind)
127474
+ height: self.trackHeight(model2.kind)
127317
127475
  },
127318
127476
  ReactComponent: TrackBlocks_default
127319
127477
  }));
@@ -127322,7 +127480,7 @@ function stateModelFactory() {
127322
127480
  return [
127323
127481
  ...this.adapterTrackModels,
127324
127482
  ...this.basePairTrackModels,
127325
- ...this.columnTrackModels,
127483
+ ...self.columnTrackModels,
127326
127484
  // every computed track reads the alignment's columns, and a tree, a
127327
127485
  // GFF and a features panel draw a figure with none
127328
127486
  ...self.numColumns > 0 ? this.computedTrackModels : []
@@ -128063,12 +128221,6 @@ function stateModelFactory() {
128063
128221
  const categorical = createPaletteMap(this.categoricalDomainTypes.map((d3) => d3.accession));
128064
128222
  return { ...segments, ...categorical };
128065
128223
  },
128066
- get strokePalette() {
128067
- return transform(this.fillPalette, ([key, val]) => [
128068
- key,
128069
- outlineColor2(val)
128070
- ]);
128071
- },
128072
128224
  /**
128073
128225
  * #getter
128074
128226
  * the encoding coloring the overlay's spans, undefined when none does,
@@ -128268,6 +128420,19 @@ function stateModelFactory() {
128268
128420
  }
128269
128421
  return runs;
128270
128422
  },
128423
+ /**
128424
+ * #method
128425
+ * a highlight label with `{residue}` and `{position}` filled in from the
128426
+ * row's letter at `start`, which is how the `175` shorthand draws "R175"
128427
+ */
128428
+ fillHighlightLabel(label, row, start) {
128429
+ if (!label.includes("{")) {
128430
+ return label;
128431
+ }
128432
+ const col = row && start !== void 0 ? self.seqPosIndex(row)?.[start - 1] : void 0;
128433
+ const residue = row && col !== void 0 ? self.MSA?.getRow(row)[col] ?? "" : "";
128434
+ return label.replaceAll("{residue}", residue).replaceAll("{position}", start === void 0 ? "" : String(start));
128435
+ },
128271
128436
  /**
128272
128437
  * #getter
128273
128438
  * `highlights` projected onto what is on screen: residue spans go
@@ -128282,7 +128447,10 @@ function stateModelFactory() {
128282
128447
  ...Object.values(transientHighlights).flat()
128283
128448
  ];
128284
128449
  return all.flatMap(({ row, rows, start, end, label, color: color2 }) => {
128285
- const base = { label, color: color2 };
128450
+ const base = {
128451
+ label: label && this.fillHighlightLabel(label, row, start),
128452
+ color: color2
128453
+ };
128286
128454
  if (rows) {
128287
128455
  const rowIndices = rows.map((name) => rowNamesSet.get(name)).filter(notEmpty);
128288
128456
  return rowIndices.length ? [{ ...base, rowIndices }] : [];
@@ -128290,8 +128458,8 @@ function stateModelFactory() {
128290
128458
  if (start === void 0 || end === void 0) {
128291
128459
  return [];
128292
128460
  }
128293
- const span = self.visibleSpan({ row, start, end });
128294
- return span ? [{ ...base, ...span, rowIndices: [] }] : [];
128461
+ const span2 = self.visibleSpan({ row, start, end });
128462
+ return span2 ? [{ ...base, ...span2, rowIndices: [] }] : [];
128295
128463
  });
128296
128464
  },
128297
128465
  /**
@@ -128310,20 +128478,6 @@ function stateModelFactory() {
128310
128478
  propertyConservation
128311
128479
  });
128312
128480
  },
128313
- /**
128314
- * #getter
128315
- * `columnStatsAt` for the hovered column, undefined when nothing is
128316
- * hovered
128317
- */
128318
- get mouseOverColumnStats() {
128319
- const { mouseCol, colStats, conservation, propertyConservation } = self;
128320
- return mouseCol === void 0 ? void 0 : columnStats({
128321
- col: mouseCol,
128322
- colStats,
128323
- conservation,
128324
- propertyConservation
128325
- });
128326
- },
128327
128481
  /**
128328
128482
  * #method
128329
128483
  */
@@ -128379,7 +128533,6 @@ function stateModelFactory() {
128379
128533
  spans: featurePanelSpans({
128380
128534
  panel,
128381
128535
  width: width3,
128382
- rowHeight: self.rowHeight,
128383
128536
  colWidth: self.colWidth,
128384
128537
  domainBands: this.domainBands,
128385
128538
  annotationsByRow: self.annotationsByRow,
@@ -128601,13 +128754,13 @@ function stateModelFactory() {
128601
128754
  * its width, or for a span that resolves to no visible column.
128602
128755
  */
128603
128756
  zoomToRegion(region) {
128604
- const span = self.visibleSpan(region);
128605
- if (!span || !self.viewInitialized) {
128757
+ const span2 = self.visibleSpan(region);
128758
+ if (!span2 || !self.viewInitialized) {
128606
128759
  return;
128607
128760
  }
128608
128761
  (0, import_mobx5.transaction)(() => {
128609
- self.colWidth = clamp3(self.msaCanvasWidth / (span.endCol - span.startCol + 1), minColWidth, maxCellSize);
128610
- self.scrollX = clamp3(-span.startCol * self.colWidth, self.maxScrollX, 0);
128762
+ self.colWidth = clamp3(self.msaCanvasWidth / (span2.endCol - span2.startCol + 1), minColWidth, maxCellSize);
128763
+ self.scrollX = clamp3(-span2.startCol * self.colWidth, self.maxScrollX, 0);
128611
128764
  });
128612
128765
  },
128613
128766
  /**
@@ -128625,7 +128778,7 @@ function stateModelFactory() {
128625
128778
  }
128626
128779
  let cladesSeeded = false;
128627
128780
  addDisposer(self, (0, import_mobx5.autorun)(() => {
128628
- if (cladesSeeded || !self.dataInitialized || self.clades.length === 0) {
128781
+ if (cladesSeeded || !self.dataInitialized || self.treeFilehandle && !self.data.tree || self.clades.length === 0) {
128629
128782
  return;
128630
128783
  }
128631
128784
  cladesSeeded = true;
@@ -128640,6 +128793,13 @@ function stateModelFactory() {
128640
128793
  }
128641
128794
  }
128642
128795
  }));
128796
+ addDisposer(self, (0, import_mobx5.autorun)(() => {
128797
+ const { region } = self;
128798
+ if (region && self.viewInitialized && self.numColumns > 0 && !(self.treeFilehandle && !self.data.tree)) {
128799
+ this.zoomToRegion(region);
128800
+ self.setRegion(void 0);
128801
+ }
128802
+ }));
128643
128803
  if (typeof window !== "undefined" && typeof window.matchMedia === "function") {
128644
128804
  const query = () => window.matchMedia(`(resolution: ${window.devicePixelRatio}dppx)`);
128645
128805
  let mql = query();
@@ -128751,6 +128911,12 @@ function stateModelFactory() {
128751
128911
  self.setAnnotations([]);
128752
128912
  }
128753
128913
  }));
128914
+ addDisposer(self, (0, import_mobx5.autorun)(() => {
128915
+ const { annotations, rowNamesSet } = self;
128916
+ if (annotations.length > 0 && rowNamesSet.size > 0 && !annotations.some((a3) => rowNamesSet.has(a3.id))) {
128917
+ self.addWarning(`0 of ${annotations.length} annotations name a row in this alignment`);
128918
+ }
128919
+ }));
128754
128920
  loadOnFilehandleChange({
128755
128921
  what: "annotations",
128756
128922
  optional: true,
@@ -129156,16 +129322,16 @@ var init_mount = __esm({
129156
129322
  });
129157
129323
 
129158
129324
  // ../lib/dist/element.js
129159
- function location(uri2) {
129160
- return uri2 ? { uri: uri2, locationType: "UriLocation" } : void 0;
129325
+ function location(uri3) {
129326
+ return uri3 ? { uri: uri3, locationType: "UriLocation" } : void 0;
129161
129327
  }
129162
129328
  function numberAttribute(value) {
129163
129329
  const n2 = value ? Number(value) : Number.NaN;
129164
129330
  return Number.isFinite(n2) ? n2 : void 0;
129165
129331
  }
129166
129332
  function parseHighlight(value, row) {
129167
- return (value ?? "").split(",").flatMap((span) => {
129168
- const [start, end] = span.split(":").map(numberAttribute);
129333
+ return (value ?? "").split(",").flatMap((span2) => {
129334
+ const [start, end] = span2.split(":").map(numberAttribute);
129169
129335
  return start === void 0 ? [] : [{ row, start, end: end ?? start }];
129170
129336
  });
129171
129337
  }
@@ -129448,10 +129614,12 @@ __export(dist_exports, {
129448
129614
  MSAView: () => Loading_default,
129449
129615
  MSAViewer: () => MSAViewer,
129450
129616
  defineMsaElement: () => defineMsaElement,
129617
+ expandSpec: () => expandSpec,
129451
129618
  installHeadlessRenderEnv: () => installHeadlessRenderEnv,
129452
129619
  maxNeighborJoiningRows: () => maxNeighborJoiningRows,
129453
129620
  mount: () => mount,
129454
129621
  renderToSvg: () => renderToSvg,
129622
+ residueLabel: () => residueLabel,
129455
129623
  useMsaSvgFigure: () => useMsaSvgFigure
129456
129624
  });
129457
129625
  var init_dist = __esm({
@@ -129460,6 +129628,7 @@ var init_dist = __esm({
129460
129628
  init_renderToSvg();
129461
129629
  init_headlessRenderEnv();
129462
129630
  init_constants();
129631
+ init_expandSpec();
129463
129632
  init_Loading2();
129464
129633
  init_MSAViewer();
129465
129634
  init_mount();
@@ -129768,6 +129937,37 @@ async function runGeneStructure(options2) {
129768
129937
  init_src2();
129769
129938
  import * as fs4 from "node:fs";
129770
129939
 
129940
+ // src/fetchWithRetry.ts
129941
+ var RETRYABLE = /* @__PURE__ */ new Set([408, 425, 429, 500, 502, 503, 504]);
129942
+ function delay(ms) {
129943
+ return new Promise((resolve) => setTimeout(resolve, ms));
129944
+ }
129945
+ function retryAfterMs(response) {
129946
+ const header = response.headers.get("retry-after");
129947
+ const seconds = header ? Number(header) : Number.NaN;
129948
+ return Number.isFinite(seconds) && seconds > 0 ? Math.min(seconds, 60) * 1e3 : void 0;
129949
+ }
129950
+ async function fetchWithRetry(url, { attempts = 4, baseDelayMs = 1e3 } = {}) {
129951
+ let lastError;
129952
+ for (let attempt = 0; attempt < attempts; attempt++) {
129953
+ let wait = baseDelayMs * 2 ** attempt;
129954
+ try {
129955
+ const response = await fetch(url);
129956
+ if (!RETRYABLE.has(response.status)) {
129957
+ return response;
129958
+ }
129959
+ lastError = new Error(`${response.status} ${response.statusText}`);
129960
+ wait = retryAfterMs(response) ?? wait;
129961
+ } catch (e3) {
129962
+ lastError = e3;
129963
+ }
129964
+ if (attempt < attempts - 1) {
129965
+ await delay(wait);
129966
+ }
129967
+ }
129968
+ throw new Error(`${url} failed after ${attempts} attempts: ${lastError}`);
129969
+ }
129970
+
129771
129971
  // src/interpro-cache.ts
129772
129972
  import * as fs3 from "node:fs";
129773
129973
  import * as os from "node:os";
@@ -129807,35 +130007,6 @@ function writeCached(release, database, accession, value) {
129807
130007
  function cacheLocation() {
129808
130008
  return CACHE_DIR;
129809
130009
  }
129810
- var RETRYABLE = /* @__PURE__ */ new Set([408, 425, 429, 500, 502, 503, 504]);
129811
- function delay(ms) {
129812
- return new Promise((resolve) => setTimeout(resolve, ms));
129813
- }
129814
- function retryAfterMs(response) {
129815
- const header = response.headers.get("retry-after");
129816
- const seconds = header ? Number(header) : Number.NaN;
129817
- return Number.isFinite(seconds) && seconds > 0 ? Math.min(seconds, 60) * 1e3 : void 0;
129818
- }
129819
- async function fetchWithRetry(url, { attempts = 4, baseDelayMs = 1e3 } = {}) {
129820
- let lastError;
129821
- for (let attempt = 0; attempt < attempts; attempt++) {
129822
- let wait = baseDelayMs * 2 ** attempt;
129823
- try {
129824
- const response = await fetch(url);
129825
- if (!RETRYABLE.has(response.status)) {
129826
- return response;
129827
- }
129828
- lastError = new Error(`${response.status} ${response.statusText}`);
129829
- wait = retryAfterMs(response) ?? wait;
129830
- } catch (e3) {
129831
- lastError = e3;
129832
- }
129833
- if (attempt < attempts - 1) {
129834
- await delay(wait);
129835
- }
129836
- }
129837
- throw new Error(`${url} failed after ${attempts} attempts: ${lastError}`);
129838
- }
129839
130010
 
129840
130011
  // src/interpro-precomputed.ts
129841
130012
  var API2 = "https://www.ebi.ac.uk/interpro/api";
@@ -129865,12 +130036,27 @@ async function fetchRelease() {
129865
130036
  const json = await res.json();
129866
130037
  return json.databases.interpro.version;
129867
130038
  }
130039
+ async function fetchNotFoundRetried(url, attempts = 3) {
130040
+ for (let attempt = 0; attempt < attempts; attempt++) {
130041
+ const res = await fetchWithRetry(url);
130042
+ if (res.status !== 404) {
130043
+ return res;
130044
+ }
130045
+ if (attempt < attempts - 1) {
130046
+ await new Promise((resolve) => setTimeout(resolve, 1e3 * 2 ** attempt));
130047
+ }
130048
+ }
130049
+ return void 0;
130050
+ }
129868
130051
  async function fetchEntries(accession, database) {
129869
130052
  const results = [];
129870
130053
  let url = `${API2}/entry/${database}/protein/uniprot/${accession}/?page_size=200`;
129871
130054
  while (url) {
129872
- const res = await fetchWithRetry(url);
129873
- if (res.status === 204 || res.status === 404) {
130055
+ const res = await fetchNotFoundRetried(url);
130056
+ if (!res) {
130057
+ return void 0;
130058
+ }
130059
+ if (res.status === 204) {
129874
130060
  break;
129875
130061
  }
129876
130062
  if (!res.ok) {
@@ -129894,6 +130080,7 @@ async function runInterProPrecomputed(options2) {
129894
130080
  `InterPro release ${release}; reading precomputed ${database} matches...`
129895
130081
  );
129896
130082
  const entriesByAccession = /* @__PURE__ */ new Map();
130083
+ const unresolved = [];
129897
130084
  let fetched = 0;
129898
130085
  let cached = 0;
129899
130086
  for (const [i2, accession] of distinct.entries()) {
@@ -129911,14 +130098,23 @@ async function runInterProPrecomputed(options2) {
129911
130098
  ${i2} of ${distinct.length} accessions are cached; re-run to resume from ${accession}.`
129912
130099
  );
129913
130100
  }
129914
- writeCached(release, database, accession, entries3);
129915
- fetched++;
130101
+ if (entries3) {
130102
+ writeCached(release, database, accession, entries3);
130103
+ fetched++;
130104
+ } else {
130105
+ unresolved.push(accession);
130106
+ }
129916
130107
  }
129917
- entriesByAccession.set(accession, entries3);
130108
+ entriesByAccession.set(accession, entries3 ?? []);
130109
+ const count2 = entries3 ? `${entries3.length} ${database} entries` : "no answer";
129918
130110
  console.log(
129919
- ` [${i2 + 1}/${distinct.length}] ${accession}: ${entries3.length} ${database} entries${hit ? " (cached)" : ""}`
130111
+ ` [${i2 + 1}/${distinct.length}] ${accession}: ${count2}${hit ? " (cached)" : ""}`
129920
130112
  );
129921
- if (entries3.length === 0) {
130113
+ if (!entries3) {
130114
+ console.warn(
130115
+ ` the API answered 404 for ${accession} every time; it carries no domains in the GFF, and a re-run tries it again`
130116
+ );
130117
+ } else if (entries3.length === 0) {
129922
130118
  console.warn(
129923
130119
  ` no ${database} matches for ${accession}; check it is a UniProtKB accession and that --database is the right member database`
129924
130120
  );
@@ -129939,6 +130135,11 @@ ${i2} of ${distinct.length} accessions are cached; re-run to resume from ${acces
129939
130135
  )
129940
130136
  );
129941
130137
  console.log(`${fetched} fetched, ${cached} from ${cacheLocation()}`);
130138
+ if (unresolved.length > 0) {
130139
+ console.warn(
130140
+ `${unresolved.length} of ${distinct.length} accessions did not resolve: ${unresolved.join(", ")}. Re-run to try them again.`
130141
+ );
130142
+ }
129942
130143
  if (options2.msaFile) {
129943
130144
  checkLengths(
129944
130145
  options2.msaFile,
@@ -129998,14 +130199,14 @@ ${sequence.seq}`,
129998
130199
  return response.text();
129999
130200
  }
130000
130201
  async function checkStatus(jobId) {
130001
- const response = await fetch(`${BASE_URL}/status/${jobId}`);
130202
+ const response = await fetchWithRetry(`${BASE_URL}/status/${jobId}`);
130002
130203
  if (!response.ok) {
130003
130204
  throw new Error(`Failed to check status: ${response.statusText}`);
130004
130205
  }
130005
130206
  return response.text();
130006
130207
  }
130007
130208
  async function getResults(jobId) {
130008
- const response = await fetch(`${BASE_URL}/result/${jobId}/json`);
130209
+ const response = await fetchWithRetry(`${BASE_URL}/result/${jobId}/json`);
130009
130210
  if (!response.ok) {
130010
130211
  throw new Error(`Failed to get results: ${response.statusText}`);
130011
130212
  }
@@ -130494,6 +130695,19 @@ function readFormat(format2) {
130494
130695
  }
130495
130696
  return format2;
130496
130697
  }
130698
+ function readNumber(name, value, integer2 = false) {
130699
+ const n2 = integer2 ? Number.parseInt(value, 10) : Number.parseFloat(value);
130700
+ if (Number.isNaN(n2)) {
130701
+ throw new Error(`--${name} must be a number, got "${value}"`);
130702
+ }
130703
+ return n2;
130704
+ }
130705
+ function readInt(name, value) {
130706
+ return readNumber(name, value, true);
130707
+ }
130708
+ function readList(value) {
130709
+ return value.split(",").map((s2) => s2.trim());
130710
+ }
130497
130711
  async function main() {
130498
130712
  const { values: values4, positionals } = parseArgs({ allowPositionals: true, options });
130499
130713
  if (values4.help || positionals.length === 0) {
@@ -130515,13 +130729,13 @@ async function main() {
130515
130729
  gffFile: values4.gff,
130516
130730
  outputFile,
130517
130731
  colorScheme: values4["color-scheme"],
130518
- width: parseInt(values4.width, 10),
130519
- height: parseInt(values4.height, 10),
130520
- treeAreaWidth: values4["tree-area-width"] !== void 0 ? parseInt(values4["tree-area-width"], 10) : void 0,
130521
- colWidth: values4["col-width"] !== void 0 ? parseFloat(values4["col-width"]) : void 0,
130522
- rowHeight: values4["row-height"] !== void 0 ? parseFloat(values4["row-height"]) : void 0,
130732
+ width: readInt("width", values4.width),
130733
+ height: readInt("height", values4.height),
130734
+ treeAreaWidth: values4["tree-area-width"] !== void 0 ? readInt("tree-area-width", values4["tree-area-width"]) : void 0,
130735
+ colWidth: values4["col-width"] !== void 0 ? readNumber("col-width", values4["col-width"]) : void 0,
130736
+ rowHeight: values4["row-height"] !== void 0 ? readNumber("row-height", values4["row-height"]) : void 0,
130523
130737
  format: format2,
130524
- tracks: values4.tracks?.split(",").map((s2) => s2.trim()),
130738
+ tracks: values4.tracks !== void 0 ? readList(values4.tracks) : void 0,
130525
130739
  viewport: values4.viewport,
130526
130740
  minimap: values4.minimap
130527
130741
  });
@@ -130542,7 +130756,7 @@ async function main() {
130542
130756
  dockerImage: values4["docker-image"],
130543
130757
  interproscanPath: values4["interproscan-path"],
130544
130758
  dataDir: values4["interproscan-data"],
130545
- programs: values4.programs.split(","),
130759
+ programs: readList(values4.programs),
130546
130760
  email: values4.email,
130547
130761
  format: format2
130548
130762
  });