react-msaview-cli 8.0.0 → 8.1.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 +355 -288
  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
@@ -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,
@@ -75836,7 +75860,7 @@ function createMakeStyles(params) {
75836
75860
  function makeStyles2() {
75837
75861
  return function(cssObjectByRuleNameOrGetCssObjectByRuleName) {
75838
75862
  const getCssObjectByRuleName = typeof cssObjectByRuleNameOrGetCssObjectByRuleName === "function" ? cssObjectByRuleNameOrGetCssObjectByRuleName : () => cssObjectByRuleNameOrGetCssObjectByRuleName;
75839
- return function useStyles26(_params, muiStyleOverridesParams) {
75863
+ return function useStyles25(_params, muiStyleOverridesParams) {
75840
75864
  const theme = useTheme5();
75841
75865
  const { css: css2, cx: cx2 } = useCssAndCx();
75842
75866
  const classes2 = (0, import_react14.useMemo)(() => {
@@ -83864,10 +83888,6 @@ function useWheelScroll({ ref, onScrollX, onScrollY, onZoom, scrollZoom }) {
83864
83888
  };
83865
83889
  }, [mouseDragging, onScrollX, onScrollY]);
83866
83890
  function onMouseDown(event) {
83867
- const target = event.target;
83868
- if (target.draggable || target.dataset.resizer) {
83869
- return;
83870
- }
83871
83891
  if (event.button === 0) {
83872
83892
  prevX.current = event.clientX;
83873
83893
  prevY.current = event.clientY;
@@ -84198,7 +84218,7 @@ var init_Track = __esm({
84198
84218
  } }, drawLabels ? name : ""),
84199
84219
  import_react37.default.createElement(
84200
84220
  IconButton_default,
84201
- { className: classes2.button, style: {
84221
+ { className: classes2.button, "aria-label": `${name} track menu`, style: {
84202
84222
  width: trackLabelHeight,
84203
84223
  height: trackLabelHeight,
84204
84224
  flexShrink: 0
@@ -84215,7 +84235,7 @@ var init_Track = __esm({
84215
84235
  import_react37.default.createElement(MenuItem_default, { dense: true, onClick: () => {
84216
84236
  model2.toggleTrack(track.model.id);
84217
84237
  closeMenu();
84218
- } }, "Close"),
84238
+ } }, "Hide track"),
84219
84239
  import_react37.default.createElement(MenuItem_default, { dense: true, onClick: () => {
84220
84240
  model2.queueDialog((onClose) => [
84221
84241
  TrackInfoDialog2,
@@ -84838,10 +84858,15 @@ var init_FeatureDialog = __esm({
84838
84858
  );
84839
84859
  });
84840
84860
  FeatureTable = (0, import_mobx_react12.observer)(function({ model: model2 }) {
84841
- const { annotationTypes, annotations, fillPalette } = model2;
84861
+ const { annotationTypes, annotations, featureColors, fillPalette } = model2;
84842
84862
  const counts = /* @__PURE__ */ new Map();
84863
+ const swatches = /* @__PURE__ */ new Map();
84843
84864
  for (const annot of annotations) {
84844
- counts.set(annot.accession, (counts.get(annot.accession) ?? 0) + 1);
84865
+ const { accession } = annot;
84866
+ counts.set(accession, (counts.get(accession) ?? 0) + 1);
84867
+ if (!swatches.has(accession)) {
84868
+ swatches.set(accession, featureColors.get(annot)?.fill ?? fillPalette[accession]);
84869
+ }
84845
84870
  }
84846
84871
  return import_react40.default.createElement(
84847
84872
  import_react40.default.Fragment,
@@ -84880,7 +84905,7 @@ var init_FeatureDialog = __esm({
84880
84905
  import_react40.default.createElement("div", { style: {
84881
84906
  width: 20,
84882
84907
  height: 20,
84883
- background: fillPalette[accession]
84908
+ background: swatches.get(accession)
84884
84909
  } })
84885
84910
  ),
84886
84911
  import_react40.default.createElement(TableCell_default, null, accession),
@@ -85470,6 +85495,9 @@ function parseAll(text2, opts) {
85470
85495
  for (const line2 of lines) {
85471
85496
  let match2;
85472
85497
  if (formatStartRegex.test(line2)) {
85498
+ if (stock) {
85499
+ db.push(stock);
85500
+ }
85473
85501
  stock = createStockholm();
85474
85502
  treeContinues = false;
85475
85503
  } else if (formatEndRegex.test(line2)) {
@@ -86068,7 +86096,7 @@ var init_AnnotationFileDialog = __esm({
86068
86096
  import_react41.default.createElement(
86069
86097
  DialogActions_default,
86070
86098
  null,
86071
- import_react41.default.createElement(Button_default, { variant: "contained", color: "primary", disabled: loading, onClick: () => {
86099
+ import_react41.default.createElement(Button_default, { variant: "contained", color: "primary", disabled: loading || (choice === "file" ? !file : !url.trim()), onClick: () => {
86072
86100
  ;
86073
86101
  (async () => {
86074
86102
  setLoading(true);
@@ -86085,7 +86113,10 @@ var init_AnnotationFileDialog = __esm({
86085
86113
  setLoading(false);
86086
86114
  }
86087
86115
  })();
86088
- } }, "Open annotations")
86116
+ } }, "Open annotations"),
86117
+ import_react41.default.createElement(Button_default, { variant: "contained", color: "secondary", onClick: () => {
86118
+ handleClose();
86119
+ } }, "Cancel")
86089
86120
  )
86090
86121
  );
86091
86122
  });
@@ -90835,13 +90866,13 @@ function measureScrollbarSize(element, scrollbarSize) {
90835
90866
  if (cached !== void 0 && cached.devicePixelRatio === devicePixelRatio2 && cached.measuredDirectly) {
90836
90867
  return cached.size;
90837
90868
  }
90838
- const computed2 = view?.getComputedStyle(htmlElement);
90839
- const canScrollY = computed2?.overflowY === "auto" || computed2?.overflowY === "scroll";
90840
- const canScrollX = computed2?.overflowX === "auto" || computed2?.overflowX === "scroll";
90869
+ const computed3 = view?.getComputedStyle(htmlElement);
90870
+ const canScrollY = computed3?.overflowY === "auto" || computed3?.overflowY === "scroll";
90871
+ const canScrollX = computed3?.overflowX === "auto" || computed3?.overflowX === "scroll";
90841
90872
  const hasScrollY = canScrollY && htmlElement.scrollHeight > htmlElement.clientHeight;
90842
90873
  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);
90874
+ const borderWidth = parseCSSPixelValue(computed3?.borderLeftWidth) + parseCSSPixelValue(computed3?.borderRightWidth);
90875
+ const borderHeight = parseCSSPixelValue(computed3?.borderTopWidth) + parseCSSPixelValue(computed3?.borderBottomWidth);
90845
90876
  const directSize = Math.max(hasScrollY ? htmlElement.offsetWidth - htmlElement.clientWidth - borderWidth : 0, hasScrollX ? htmlElement.offsetHeight - htmlElement.clientHeight - borderHeight : 0);
90846
90877
  if (hasScrollY || hasScrollX) {
90847
90878
  const size6 = Math.max(0, directSize);
@@ -90860,8 +90891,8 @@ function measureScrollbarSize(element, scrollbarSize) {
90860
90891
  scrollDiv.style.height = "99px";
90861
90892
  scrollDiv.style.position = "absolute";
90862
90893
  scrollDiv.style.overflow = "scroll";
90863
- if (computed2?.scrollbarWidth) {
90864
- scrollDiv.style.scrollbarWidth = computed2.scrollbarWidth;
90894
+ if (computed3?.scrollbarWidth) {
90895
+ scrollDiv.style.scrollbarWidth = computed3.scrollbarWidth;
90865
90896
  }
90866
90897
  scrollDiv.className = "scrollDiv";
90867
90898
  element.appendChild(scrollDiv);
@@ -121076,8 +121107,7 @@ var init_SequenceTextArea = __esm({
121076
121107
  },
121077
121108
  dialogContent: {
121078
121109
  background: theme.palette.action.selected,
121079
- margin: 4,
121080
- minWidth: "80em"
121110
+ margin: 4
121081
121111
  }
121082
121112
  }));
121083
121113
  }
@@ -121223,7 +121253,7 @@ function ExportSVGDialog({ model: model2, onClose }) {
121223
121253
  });
121224
121254
  });
121225
121255
  }
121226
- }, exporting ? "Exporting\u2026" : "Submit"),
121256
+ }, exporting ? "Exporting\u2026" : "Export"),
121227
121257
  import_react53.default.createElement(Button_default, { variant: "contained", color: "secondary", onClick: () => {
121228
121258
  onClose();
121229
121259
  } }, "Cancel")
@@ -121444,44 +121474,25 @@ var init_Warning = __esm({
121444
121474
  });
121445
121475
 
121446
121476
  // ../lib/dist/components/header/LoadWarnings.js
121447
- var import_react58, import_mobx_react19, useStyles22, LoadWarnings, LoadWarnings_default;
121477
+ var import_react58, import_mobx_react19, LoadWarnings, LoadWarnings_default;
121448
121478
  var init_LoadWarnings = __esm({
121449
121479
  "../lib/dist/components/header/LoadWarnings.js"() {
121450
121480
  "use strict";
121451
121481
  import_react58 = __toESM(require_react(), 1);
121452
- init_tss_react();
121453
121482
  init_Warning();
121454
121483
  init_material();
121455
121484
  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
121485
  LoadWarnings = (0, import_mobx_react19.observer)(function({ model: model2 }) {
121469
121486
  const { warnings } = model2;
121470
- const { classes: classes2 } = useStyles22();
121471
121487
  if (warnings.length === 0) {
121472
121488
  return null;
121473
121489
  }
121474
121490
  return import_react58.default.createElement(
121475
121491
  Tooltip_default,
121476
121492
  { 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
- )
121493
+ 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: () => {
121494
+ model2.clearWarnings();
121495
+ } }, warnings.length > 1 ? `${warnings.length} warnings` : "Warning")
121485
121496
  );
121486
121497
  });
121487
121498
  LoadWarnings_default = LoadWarnings;
@@ -121534,10 +121545,10 @@ function msaSettingsMenuItems(model2) {
121534
121545
  model2.setDrawMsaLetters(arg2);
121535
121546
  }),
121536
121547
  // inverted: bgColor colors the tile, and off colors the letter
121537
- toggle("Color letters instead of background of tiles", !bgColor, (arg2) => {
121548
+ toggle("Color letters, not cells", !bgColor, (arg2) => {
121538
121549
  model2.setBgColor(!arg2);
121539
121550
  }),
121540
- toggle("Enable hiding gappy columns?", hideGaps, (arg2) => {
121551
+ toggle("Hide gappy columns", hideGaps, (arg2) => {
121541
121552
  model2.setHideGaps(arg2);
121542
121553
  })
121543
121554
  ];
@@ -121695,7 +121706,7 @@ var init_LinkOff = __esm({
121695
121706
  function size4(bytes) {
121696
121707
  return bytes < 1e6 ? `${Math.round(bytes / 1e3)} kB` : `${(bytes / 1e6).toFixed(1)} MB`;
121697
121708
  }
121698
- var import_react62, import_mobx_react23, useStyles23, UnshareableDataWarning, UnshareableDataWarning_default;
121709
+ var import_react62, import_mobx_react23, useStyles22, UnshareableDataWarning, UnshareableDataWarning_default;
121699
121710
  var init_UnshareableDataWarning = __esm({
121700
121711
  "../lib/dist/components/header/UnshareableDataWarning.js"() {
121701
121712
  "use strict";
@@ -121704,7 +121715,7 @@ var init_UnshareableDataWarning = __esm({
121704
121715
  init_LinkOff();
121705
121716
  init_material();
121706
121717
  import_mobx_react23 = __toESM(require_dist3(), 1);
121707
- useStyles23 = makeStyles()((theme) => ({
121718
+ useStyles22 = makeStyles()((theme) => ({
121708
121719
  warning: {
121709
121720
  display: "flex",
121710
121721
  alignItems: "center",
@@ -121718,7 +121729,7 @@ var init_UnshareableDataWarning = __esm({
121718
121729
  }));
121719
121730
  UnshareableDataWarning = (0, import_mobx_react23.observer)(function({ model: model2 }) {
121720
121731
  const { unshareableData } = model2;
121721
- const { classes: classes2 } = useStyles23();
121732
+ const { classes: classes2 } = useStyles22();
121722
121733
  if (unshareableData.length === 0) {
121723
121734
  return null;
121724
121735
  }
@@ -121948,7 +121959,7 @@ var version3;
121948
121959
  var init_version3 = __esm({
121949
121960
  "../lib/dist/version.js"() {
121950
121961
  "use strict";
121951
- version3 = "8.0.0";
121962
+ version3 = "8.1.0";
121952
121963
  }
121953
121964
  });
121954
121965
 
@@ -121972,7 +121983,7 @@ function AboutDialog({ onClose }) {
121972
121983
  "MSAView ",
121973
121984
  version3,
121974
121985
  " (",
121975
- import_react66.default.createElement(Link_default, { href: "https://github.com/gmod/react-msaview" }, "Github"),
121986
+ import_react66.default.createElement(Link_default, { target: "_blank", rel: "noopener", href: "https://github.com/gmod/react-msaview" }, "Github"),
121976
121987
  ")"
121977
121988
  ),
121978
121989
  import_react66.default.createElement(
@@ -121986,11 +121997,11 @@ function AboutDialog({ onClose }) {
121986
121997
  null,
121987
121998
  "We use some color schemes from the",
121988
121999
  " ",
121989
- import_react66.default.createElement(Link_default, { href: "https://github.com/biotite-dev/biotite" }, "biotite"),
122000
+ import_react66.default.createElement(Link_default, { target: "_blank", rel: "noopener", href: "https://github.com/biotite-dev/biotite" }, "biotite"),
121990
122001
  " ",
121991
122002
  "project, and their license is reproduced",
121992
122003
  " ",
121993
- import_react66.default.createElement(Link_default, { href: "https://github.com/biotite-dev/biotite/blob/master/LICENSE.rst" }, "here")
122004
+ import_react66.default.createElement(Link_default, { target: "_blank", rel: "noopener", href: "https://github.com/biotite-dev/biotite/blob/master/LICENSE.rst" }, "here")
121994
122005
  )
121995
122006
  ),
121996
122007
  import_react66.default.createElement(
@@ -122001,7 +122012,7 @@ function AboutDialog({ onClose }) {
122001
122012
  null,
122002
122013
  "See this page for some information on jalview colorings",
122003
122014
  " ",
122004
- import_react66.default.createElement(Link_default, { href: "https://www.jalview.org/help/html/colourSchemes/" }, "here")
122015
+ import_react66.default.createElement(Link_default, { target: "_blank", rel: "noopener", href: "https://www.jalview.org/help/html/colourSchemes/" }, "here")
122005
122016
  )
122006
122017
  ),
122007
122018
  import_react66.default.createElement(
@@ -122012,7 +122023,7 @@ function AboutDialog({ onClose }) {
122012
122023
  null,
122013
122024
  "See this page for some info on the clustal, cinema, maeditor, and lesk color schemes",
122014
122025
  " ",
122015
- import_react66.default.createElement(Link_default, { href: "http://www.bioinformatics.nl/~berndb/aacolour.html" }, "here")
122026
+ import_react66.default.createElement(Link_default, { target: "_blank", rel: "noopener", href: "http://www.bioinformatics.nl/~berndb/aacolour.html" }, "here")
122016
122027
  )
122017
122028
  ),
122018
122029
  import_react66.default.createElement(
@@ -122023,7 +122034,7 @@ function AboutDialog({ onClose }) {
122023
122034
  null,
122024
122035
  "See this paper about the flower color scheme",
122025
122036
  " ",
122026
- import_react66.default.createElement(Link_default, { href: "https://www.ncbi.nlm.nih.gov/pmc/articles/PMC7245768/" }, "here")
122037
+ import_react66.default.createElement(Link_default, { target: "_blank", rel: "noopener", href: "https://www.ncbi.nlm.nih.gov/pmc/articles/PMC7245768/" }, "here")
122027
122038
  )
122028
122039
  )
122029
122040
  )
@@ -122077,7 +122088,7 @@ var init_Header = __esm({
122077
122088
  }, [model2]);
122078
122089
  return import_react67.default.createElement(
122079
122090
  "div",
122080
- { ref, style: { display: "flex" } },
122091
+ { ref, style: { display: "flex", flexWrap: "wrap" } },
122081
122092
  import_react67.default.createElement(FileMenu_default, { model: model2 }),
122082
122093
  import_react67.default.createElement(ColorSchemeMenu_default, { model: model2 }),
122083
122094
  import_react67.default.createElement(TreeSettingsMenu_default, { model: model2 }),
@@ -122479,9 +122490,7 @@ var init_MSACanvas = __esm({
122479
122490
  onZoom,
122480
122491
  scrollZoom
122481
122492
  });
122482
- return import_react74.default.createElement("div", { ref, "data-testid": "msa_canvas", onMouseDown, onMouseUp, onMouseLeave: (event) => {
122483
- event.preventDefault();
122484
- }, style: {
122493
+ return import_react74.default.createElement("div", { ref, "data-testid": "msa_canvas", onMouseDown, onMouseUp, style: {
122485
122494
  position: "relative",
122486
122495
  height: height2,
122487
122496
  width: msaCanvasWidth,
@@ -122664,17 +122673,17 @@ var init_featureFields = __esm({
122664
122673
 
122665
122674
  // ../lib/dist/components/PortalTooltip.js
122666
122675
  function PortalTooltip({ clientX, clientY, children }) {
122667
- const { classes: classes2 } = useStyles24();
122676
+ const { classes: classes2 } = useStyles23();
122668
122677
  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
122678
  }
122670
- var import_react78, import_react_dom4, useStyles24;
122679
+ var import_react78, import_react_dom4, useStyles23;
122671
122680
  var init_PortalTooltip = __esm({
122672
122681
  "../lib/dist/components/PortalTooltip.js"() {
122673
122682
  "use strict";
122674
122683
  import_react78 = __toESM(require_react(), 1);
122675
122684
  init_tss_react();
122676
122685
  import_react_dom4 = __toESM(require_react_dom(), 1);
122677
- useStyles24 = makeStyles()((theme) => ({
122686
+ useStyles23 = makeStyles()((theme) => ({
122678
122687
  tooltip: {
122679
122688
  position: "fixed",
122680
122689
  pointerEvents: "none",
@@ -122954,7 +122963,7 @@ var init_TreeBranchMenu = __esm({
122954
122963
  { anchorReference: "anchorPosition", anchorPosition: {
122955
122964
  left: node2.x,
122956
122965
  top: node2.y
122957
- }, transitionDuration: 0, keepMounted: true, open: Boolean(node2), onClose },
122966
+ }, transitionDuration: 0, open: true, onClose },
122958
122967
  import_react83.default.createElement(MenuItem_default, { dense: true, disabled: true }, node2.name),
122959
122968
  import_react83.default.createElement(MenuItem_default, { dense: true, onClick: () => {
122960
122969
  model2.toggleCollapsed(node2.id);
@@ -123038,7 +123047,7 @@ var init_TreeNodeMenu = __esm({
123038
123047
  { anchorReference: "anchorPosition", anchorPosition: {
123039
123048
  top: node2.y,
123040
123049
  left: node2.x
123041
- }, transitionDuration: 0, keepMounted: true, open: Boolean(node2), onClose },
123050
+ }, transitionDuration: 0, open: true, onClose },
123042
123051
  import_react85.default.createElement(MenuItem_default, { dense: true, disabled: true }, name),
123043
123052
  import_react85.default.createElement(MenuItem_default, { dense: true, onClick: () => {
123044
123053
  model2.queueDialog((onClose2) => [
@@ -123657,7 +123666,7 @@ var init_useTreeHover = __esm({
123657
123666
  });
123658
123667
 
123659
123668
  // ../lib/dist/components/tree/TreeCanvasBlock.js
123660
- var import_react87, import_mobx_react41, useStyles25, TreeCanvasBlock, TreeCanvasBlock_default;
123669
+ var import_react87, import_mobx_react41, useStyles24, TreeCanvasBlock, TreeCanvasBlock_default;
123661
123670
  var init_TreeCanvasBlock = __esm({
123662
123671
  "../lib/dist/components/tree/TreeCanvasBlock.js"() {
123663
123672
  "use strict";
@@ -123671,16 +123680,16 @@ var init_TreeCanvasBlock = __esm({
123671
123680
  init_TreeNodeMenu();
123672
123681
  init_renderTreeCanvas();
123673
123682
  init_useTreeHover();
123674
- useStyles25 = makeStyles()(() => ({
123683
+ useStyles24 = makeStyles()((theme) => ({
123675
123684
  hover: {
123676
123685
  position: "absolute",
123677
123686
  pointerEvents: "none",
123678
123687
  zIndex: 100,
123679
- background: "rgba(0,0,0,0.1)"
123688
+ background: theme.palette.action.hover
123680
123689
  }
123681
123690
  }));
123682
123691
  TreeCanvasBlock = (0, import_mobx_react41.observer)(function({ model: model2, offsetY }) {
123683
- const { classes: classes2 } = useStyles25();
123692
+ const { classes: classes2 } = useStyles24();
123684
123693
  const theme = useTheme4();
123685
123694
  const [branchMenu, setBranchMenu] = (0, import_react87.useState)();
123686
123695
  const [nodeMenu, setNodeMenu] = (0, import_react87.useState)();
@@ -123803,9 +123812,7 @@ var init_TreeCanvas = __esm({
123803
123812
  });
123804
123813
  return import_react88.default.createElement(
123805
123814
  "div",
123806
- { ref, onMouseDown, onMouseUp, onMouseLeave: (event) => {
123807
- event.preventDefault();
123808
- }, style: {
123815
+ { ref, onMouseDown, onMouseUp, style: {
123809
123816
  height: height2,
123810
123817
  position: "relative",
123811
123818
  width: treeAreaWidth
@@ -124162,7 +124169,7 @@ var init_ImportForm = __esm({
124162
124169
  { style: { width: "50%" } },
124163
124170
  error ? import_react93.default.createElement(ErrorMessage, { error }) : null,
124164
124171
  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.")
124172
+ 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
124173
  ),
124167
124174
  import_react93.default.createElement(
124168
124175
  "div",
@@ -124236,16 +124243,7 @@ var init_Loading2 = __esm({
124236
124243
  return import_react94.default.createElement(
124237
124244
  "div",
124238
124245
  { 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
- ),
124246
+ import_react94.default.createElement(CircularProgress_default, { size: 24 }),
124249
124247
  import_react94.default.createElement(Typography_default, { variant: "h6" }, status?.msg ?? "Loading..."),
124250
124248
  status?.onCancel ? import_react94.default.createElement(Button_default, { variant: "outlined", size: "small", onClick: () => status.onCancel?.() }, "Cancel") : null
124251
124249
  );
@@ -124272,8 +124270,9 @@ var init_Loading2 = __esm({
124272
124270
  // ../lib/dist/calculateBlocks.js
124273
124271
  function calculateBlocks({ mapSize, blockSize, viewportPos, viewportSize }) {
124274
124272
  const clamped = Math.max(0, Math.min(viewportPos, mapSize - viewportSize));
124273
+ const end = Math.min(clamped + viewportSize, mapSize);
124275
124274
  const minTile = Math.floor(clamped / blockSize);
124276
- const maxTile = Math.floor((clamped + viewportSize - 1) / blockSize);
124275
+ const maxTile = Math.ceil(end / blockSize) - 1;
124277
124276
  const blocks = [];
124278
124277
  for (let tile = minTile; tile <= maxTile; tile++) {
124279
124278
  blocks.push(tile * blockSize);
@@ -125675,9 +125674,30 @@ function featureLabelMap(annotations, field) {
125675
125674
  }
125676
125675
  return labels;
125677
125676
  }
125678
- function featurePanelSpans({ panel, width: width3, rowHeight, colWidth, domainBands, annotationsByRow, shifts }) {
125677
+ function strandpileLanes(rows) {
125678
+ const piled = rows.map(([name, bands]) => {
125679
+ const reverse = packDomainLanes(bands.filter((b2) => b2.annotation.strand === -1));
125680
+ const forward = packDomainLanes(bands.filter((b2) => b2.annotation.strand !== -1));
125681
+ return { name, forward, reverse };
125682
+ });
125683
+ const up = Math.max(0, ...piled.map((p2) => p2.forward[0]?.laneCount ?? 0));
125684
+ const down = Math.max(0, ...piled.map((p2) => p2.reverse[0]?.laneCount ?? 0));
125685
+ const laneCount = up + down;
125686
+ return new Map(piled.map(({ name, forward, reverse }) => [
125687
+ name,
125688
+ [
125689
+ // forward level 0 is the lane just above the line, stacking upward
125690
+ ...forward.map((b2) => ({ ...b2, lane: up - 1 - b2.lane, laneCount })),
125691
+ ...reverse.map((b2) => ({ ...b2, lane: up + b2.lane, laneCount }))
125692
+ ]
125693
+ ]));
125694
+ }
125695
+ function panelLanes(panel, rows) {
125696
+ return panel.position === "strandpile" ? strandpileLanes(rows) : new Map(rows.map(([name, bands]) => [name, packDomainLanes(bands)]));
125697
+ }
125698
+ function featurePanelSpans({ panel, width: width3, colWidth, domainBands, annotationsByRow, shifts }) {
125679
125699
  if (panel.x === "column") {
125680
- return new Map([...domainBands].map(([name, bands]) => [
125700
+ return panelLanes(panel, [...domainBands].map(([name, bands]) => [
125681
125701
  name,
125682
125702
  bands.map((band) => ({
125683
125703
  ...band,
@@ -125705,17 +125725,17 @@ function featurePanelSpans({ panel, width: width3, rowHeight, colWidth, domainBa
125705
125725
  max3 = Math.max(max3, end);
125706
125726
  }
125707
125727
  }
125708
- const drawable = Math.max(1, width3 - rowHeight);
125728
+ const drawable = Math.max(1, width3 - 1);
125709
125729
  const scale = max3 > min3 ? drawable / (max3 - min3) : 0;
125710
- return new Map(shifted.map(([name, features]) => [
125730
+ return panelLanes(panel, shifted.map(([name, features]) => [
125711
125731
  name,
125712
- packDomainLanes(features.map(({ annotation, start, end }) => ({
125732
+ features.map(({ annotation, start, end }) => ({
125713
125733
  annotation,
125714
125734
  xStart: (start - min3) * scale,
125715
125735
  xEnd: (end - min3) * scale,
125716
125736
  startCol: start,
125717
125737
  endCol: end - (end - start) * laneOverlap
125718
- })))
125738
+ }))
125719
125739
  ]));
125720
125740
  }
125721
125741
  function resolveFeatureScale(field, scale, annotations) {
@@ -125975,11 +125995,7 @@ function stateModelFactory() {
125975
125995
  * data from the loaded tree/msa/treeMetadata, generally loaded by
125976
125996
  * autorun
125977
125997
  */
125978
- data: types.optional(DataModelF(), {
125979
- tree: "",
125980
- msa: "",
125981
- treeMetadata: ""
125982
- }),
125998
+ data: types.optional(DataModelF(), {}),
125983
125999
  /**
125984
126000
  * #property
125985
126001
  * the user's explicit hide choices per annotation accession, keyed by
@@ -126216,10 +126232,12 @@ function stateModelFactory() {
126216
126232
  /**
126217
126233
  * #action
126218
126234
  * record a non-fatal load problem: a layer that failed to load, a file
126219
- * that failed to parse
126235
+ * that failed to parse. A message already on the list is not added again
126220
126236
  */
126221
126237
  addWarning(warning) {
126222
- self.warnings = [...self.warnings, warning];
126238
+ if (!self.warnings.includes(warning)) {
126239
+ self.warnings = [...self.warnings, warning];
126240
+ }
126223
126241
  },
126224
126242
  /**
126225
126243
  * #action
@@ -126827,7 +126845,7 @@ function stateModelFactory() {
126827
126845
  */
126828
126846
  get insertionPositions() {
126829
126847
  const { blanks, rows } = this;
126830
- if (blanks.length === 0 || !self.hideGapsEffective) {
126848
+ if (blanks.length === 0) {
126831
126849
  return /* @__PURE__ */ new Map();
126832
126850
  }
126833
126851
  const result = /* @__PURE__ */ new Map();
@@ -127048,7 +127066,7 @@ function stateModelFactory() {
127048
127066
  * cladogram mode. The tree's scale bar uses it.
127049
127067
  */
127050
127068
  get pxPerBranchLength() {
127051
- const max3 = maxLength(this.root);
127069
+ const max3 = this.rootToTipLength;
127052
127070
  return this.showBranchLenEffective && max3 ? self.treeWidth / max3 : 0;
127053
127071
  },
127054
127072
  /**
@@ -127091,7 +127109,107 @@ function stateModelFactory() {
127091
127109
  get showTreeText() {
127092
127110
  return self.drawLabels && self.rowHeight >= minLetterRowHeight;
127093
127111
  }
127094
- })).views((self) => ({
127112
+ })).views((self) => {
127113
+ const columnTrackModels = /* @__PURE__ */ new WeakMap();
127114
+ return {
127115
+ /**
127116
+ * #getter
127117
+ * a data track's values or string, projected from its row's residues
127118
+ * onto alignment columns when it names a row
127119
+ */
127120
+ get columnTrackContent() {
127121
+ const { MSA, blanks, hideGapsEffective } = self;
127122
+ const width3 = MSA?.getWidth() ?? 0;
127123
+ const project = (track, items, fill) => {
127124
+ if (!track.row) {
127125
+ return items;
127126
+ }
127127
+ const out = Array.from({ length: width3 }, () => fill);
127128
+ const index3 = self.seqPosIndex(track.row);
127129
+ items.forEach((item, seqPos) => {
127130
+ const col = index3?.[seqPos];
127131
+ if (col !== void 0) {
127132
+ out[col] = item;
127133
+ }
127134
+ });
127135
+ return out;
127136
+ };
127137
+ const skip2 = (items) => hideGapsEffective ? dropBlanks(blanks, items) : items;
127138
+ const resolve = (track, pos) => {
127139
+ const col = track.row ? self.seqPosIndex(track.row)?.[pos - 1] : pos - 1;
127140
+ if (col === void 0 || col < 0 || col >= width3) {
127141
+ return void 0;
127142
+ }
127143
+ return hideGapsEffective ? visibleColsBefore(blanks, col) : col;
127144
+ };
127145
+ return new Map(self.columnTracks.map((track) => {
127146
+ if (track.kind === "arc") {
127147
+ const arcs = (track.arcs ?? []).map((arc) => {
127148
+ const start = resolve(track, Math.min(arc.start, arc.end));
127149
+ const end = resolve(track, Math.max(arc.start, arc.end));
127150
+ return start !== void 0 && end !== void 0 && start < end ? { start, end, color: arc.color } : void 0;
127151
+ }).filter(notEmpty);
127152
+ return [track.id, { arcs }];
127153
+ }
127154
+ if (track.kind === "bar") {
127155
+ const max3 = track.max ?? 1;
127156
+ const values4 = skip2(project(track, track.values ?? [], 0)).map((v2) => Math.min(1, Math.max(0, v2 / max3)));
127157
+ return [track.id, { values: values4 }];
127158
+ }
127159
+ const data = skip2(project(track, (track.data ?? "").split(""), " "));
127160
+ return [track.id, { data: data.join("") }];
127161
+ }));
127162
+ },
127163
+ /**
127164
+ * #method
127165
+ * the height a track draws at: what the user dragged its divider to,
127166
+ * then the height its snapshot asked for, then its kind's default. Only
127167
+ * a text track falls through to rowHeight
127168
+ */
127169
+ trackHeight(kind, heightKey = kind, given) {
127170
+ return self.trackHeights.get(heightKey) ?? given ?? defaultTrackHeights[kind] ?? self.rowHeight;
127171
+ },
127172
+ /**
127173
+ * #method
127174
+ * the track a column track spec draws as, computed once per spec. A
127175
+ * text track's height falls through to rowHeight, and its own computed
127176
+ * confines the vertical zoom to it, so a sibling keeps its object and
127177
+ * its canvas skips the redraw
127178
+ */
127179
+ columnTrackModel(track) {
127180
+ let computedModel = columnTrackModels.get(track);
127181
+ if (!computedModel) {
127182
+ computedModel = (0, import_mobx5.computed)(() => {
127183
+ const heightKey = resizableKinds.has(track.kind) ? ownHeightKey(track.id) : void 0;
127184
+ const content = this.columnTrackContent.get(track.id);
127185
+ return {
127186
+ model: {
127187
+ id: track.id,
127188
+ name: track.name,
127189
+ kind: track.kind,
127190
+ heightKey,
127191
+ height: this.trackHeight(track.kind, heightKey, track.height),
127192
+ barColor: track.color,
127193
+ arcColor: track.color,
127194
+ customColorScheme: track.colors,
127195
+ data: content?.data,
127196
+ arcs: content?.arcs
127197
+ },
127198
+ ReactComponent: TrackBlocks_default
127199
+ };
127200
+ });
127201
+ columnTrackModels.set(track, computedModel);
127202
+ }
127203
+ return computedModel.get();
127204
+ },
127205
+ /**
127206
+ * #getter
127207
+ */
127208
+ get columnTrackModels() {
127209
+ return self.columnTracks.map((track) => this.columnTrackModel(track));
127210
+ }
127211
+ };
127212
+ }).views((self) => ({
127095
127213
  /**
127096
127214
  * #getter
127097
127215
  */
@@ -127172,87 +127290,6 @@ function stateModelFactory() {
127172
127290
  ReactComponent: TrackBlocks_default
127173
127291
  }));
127174
127292
  },
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
127293
  /**
127257
127294
  * #getter
127258
127295
  * the consensus secondary structure as a track, when there is one. A
@@ -127268,7 +127305,7 @@ function stateModelFactory() {
127268
127305
  name: "Base pairs",
127269
127306
  kind: "arc",
127270
127307
  heightKey: "arc",
127271
- height: this.trackHeight("arc"),
127308
+ height: self.trackHeight("arc"),
127272
127309
  arcs
127273
127310
  },
127274
127311
  ReactComponent: TrackBlocks_default
@@ -127313,7 +127350,7 @@ function stateModelFactory() {
127313
127350
  model: {
127314
127351
  ...model2,
127315
127352
  heightKey: resizableKinds.has(model2.kind) ? model2.kind : void 0,
127316
- height: this.trackHeight(model2.kind)
127353
+ height: self.trackHeight(model2.kind)
127317
127354
  },
127318
127355
  ReactComponent: TrackBlocks_default
127319
127356
  }));
@@ -127322,7 +127359,7 @@ function stateModelFactory() {
127322
127359
  return [
127323
127360
  ...this.adapterTrackModels,
127324
127361
  ...this.basePairTrackModels,
127325
- ...this.columnTrackModels,
127362
+ ...self.columnTrackModels,
127326
127363
  // every computed track reads the alignment's columns, and a tree, a
127327
127364
  // GFF and a features panel draw a figure with none
127328
127365
  ...self.numColumns > 0 ? this.computedTrackModels : []
@@ -128063,12 +128100,6 @@ function stateModelFactory() {
128063
128100
  const categorical = createPaletteMap(this.categoricalDomainTypes.map((d3) => d3.accession));
128064
128101
  return { ...segments, ...categorical };
128065
128102
  },
128066
- get strokePalette() {
128067
- return transform(this.fillPalette, ([key, val]) => [
128068
- key,
128069
- outlineColor2(val)
128070
- ]);
128071
- },
128072
128103
  /**
128073
128104
  * #getter
128074
128105
  * the encoding coloring the overlay's spans, undefined when none does,
@@ -128310,20 +128341,6 @@ function stateModelFactory() {
128310
128341
  propertyConservation
128311
128342
  });
128312
128343
  },
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
128344
  /**
128328
128345
  * #method
128329
128346
  */
@@ -128379,7 +128396,6 @@ function stateModelFactory() {
128379
128396
  spans: featurePanelSpans({
128380
128397
  panel,
128381
128398
  width: width3,
128382
- rowHeight: self.rowHeight,
128383
128399
  colWidth: self.colWidth,
128384
128400
  domainBands: this.domainBands,
128385
128401
  annotationsByRow: self.annotationsByRow,
@@ -128625,7 +128641,7 @@ function stateModelFactory() {
128625
128641
  }
128626
128642
  let cladesSeeded = false;
128627
128643
  addDisposer(self, (0, import_mobx5.autorun)(() => {
128628
- if (cladesSeeded || !self.dataInitialized || self.clades.length === 0) {
128644
+ if (cladesSeeded || !self.dataInitialized || self.treeFilehandle && !self.data.tree || self.clades.length === 0) {
128629
128645
  return;
128630
128646
  }
128631
128647
  cladesSeeded = true;
@@ -128751,6 +128767,12 @@ function stateModelFactory() {
128751
128767
  self.setAnnotations([]);
128752
128768
  }
128753
128769
  }));
128770
+ addDisposer(self, (0, import_mobx5.autorun)(() => {
128771
+ const { annotations, rowNamesSet } = self;
128772
+ if (annotations.length > 0 && rowNamesSet.size > 0 && !annotations.some((a3) => rowNamesSet.has(a3.id))) {
128773
+ self.addWarning(`0 of ${annotations.length} annotations name a row in this alignment`);
128774
+ }
128775
+ }));
128754
128776
  loadOnFilehandleChange({
128755
128777
  what: "annotations",
128756
128778
  optional: true,
@@ -129768,6 +129790,37 @@ async function runGeneStructure(options2) {
129768
129790
  init_src2();
129769
129791
  import * as fs4 from "node:fs";
129770
129792
 
129793
+ // src/fetchWithRetry.ts
129794
+ var RETRYABLE = /* @__PURE__ */ new Set([408, 425, 429, 500, 502, 503, 504]);
129795
+ function delay(ms) {
129796
+ return new Promise((resolve) => setTimeout(resolve, ms));
129797
+ }
129798
+ function retryAfterMs(response) {
129799
+ const header = response.headers.get("retry-after");
129800
+ const seconds = header ? Number(header) : Number.NaN;
129801
+ return Number.isFinite(seconds) && seconds > 0 ? Math.min(seconds, 60) * 1e3 : void 0;
129802
+ }
129803
+ async function fetchWithRetry(url, { attempts = 4, baseDelayMs = 1e3 } = {}) {
129804
+ let lastError;
129805
+ for (let attempt = 0; attempt < attempts; attempt++) {
129806
+ let wait = baseDelayMs * 2 ** attempt;
129807
+ try {
129808
+ const response = await fetch(url);
129809
+ if (!RETRYABLE.has(response.status)) {
129810
+ return response;
129811
+ }
129812
+ lastError = new Error(`${response.status} ${response.statusText}`);
129813
+ wait = retryAfterMs(response) ?? wait;
129814
+ } catch (e3) {
129815
+ lastError = e3;
129816
+ }
129817
+ if (attempt < attempts - 1) {
129818
+ await delay(wait);
129819
+ }
129820
+ }
129821
+ throw new Error(`${url} failed after ${attempts} attempts: ${lastError}`);
129822
+ }
129823
+
129771
129824
  // src/interpro-cache.ts
129772
129825
  import * as fs3 from "node:fs";
129773
129826
  import * as os from "node:os";
@@ -129807,35 +129860,6 @@ function writeCached(release, database, accession, value) {
129807
129860
  function cacheLocation() {
129808
129861
  return CACHE_DIR;
129809
129862
  }
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
129863
 
129840
129864
  // src/interpro-precomputed.ts
129841
129865
  var API2 = "https://www.ebi.ac.uk/interpro/api";
@@ -129865,12 +129889,27 @@ async function fetchRelease() {
129865
129889
  const json = await res.json();
129866
129890
  return json.databases.interpro.version;
129867
129891
  }
129892
+ async function fetchNotFoundRetried(url, attempts = 3) {
129893
+ for (let attempt = 0; attempt < attempts; attempt++) {
129894
+ const res = await fetchWithRetry(url);
129895
+ if (res.status !== 404) {
129896
+ return res;
129897
+ }
129898
+ if (attempt < attempts - 1) {
129899
+ await new Promise((resolve) => setTimeout(resolve, 1e3 * 2 ** attempt));
129900
+ }
129901
+ }
129902
+ return void 0;
129903
+ }
129868
129904
  async function fetchEntries(accession, database) {
129869
129905
  const results = [];
129870
129906
  let url = `${API2}/entry/${database}/protein/uniprot/${accession}/?page_size=200`;
129871
129907
  while (url) {
129872
- const res = await fetchWithRetry(url);
129873
- if (res.status === 204 || res.status === 404) {
129908
+ const res = await fetchNotFoundRetried(url);
129909
+ if (!res) {
129910
+ return void 0;
129911
+ }
129912
+ if (res.status === 204) {
129874
129913
  break;
129875
129914
  }
129876
129915
  if (!res.ok) {
@@ -129894,6 +129933,7 @@ async function runInterProPrecomputed(options2) {
129894
129933
  `InterPro release ${release}; reading precomputed ${database} matches...`
129895
129934
  );
129896
129935
  const entriesByAccession = /* @__PURE__ */ new Map();
129936
+ const unresolved = [];
129897
129937
  let fetched = 0;
129898
129938
  let cached = 0;
129899
129939
  for (const [i2, accession] of distinct.entries()) {
@@ -129911,14 +129951,23 @@ async function runInterProPrecomputed(options2) {
129911
129951
  ${i2} of ${distinct.length} accessions are cached; re-run to resume from ${accession}.`
129912
129952
  );
129913
129953
  }
129914
- writeCached(release, database, accession, entries3);
129915
- fetched++;
129954
+ if (entries3) {
129955
+ writeCached(release, database, accession, entries3);
129956
+ fetched++;
129957
+ } else {
129958
+ unresolved.push(accession);
129959
+ }
129916
129960
  }
129917
- entriesByAccession.set(accession, entries3);
129961
+ entriesByAccession.set(accession, entries3 ?? []);
129962
+ const count2 = entries3 ? `${entries3.length} ${database} entries` : "no answer";
129918
129963
  console.log(
129919
- ` [${i2 + 1}/${distinct.length}] ${accession}: ${entries3.length} ${database} entries${hit ? " (cached)" : ""}`
129964
+ ` [${i2 + 1}/${distinct.length}] ${accession}: ${count2}${hit ? " (cached)" : ""}`
129920
129965
  );
129921
- if (entries3.length === 0) {
129966
+ if (!entries3) {
129967
+ console.warn(
129968
+ ` the API answered 404 for ${accession} every time; it carries no domains in the GFF, and a re-run tries it again`
129969
+ );
129970
+ } else if (entries3.length === 0) {
129922
129971
  console.warn(
129923
129972
  ` no ${database} matches for ${accession}; check it is a UniProtKB accession and that --database is the right member database`
129924
129973
  );
@@ -129939,6 +129988,11 @@ ${i2} of ${distinct.length} accessions are cached; re-run to resume from ${acces
129939
129988
  )
129940
129989
  );
129941
129990
  console.log(`${fetched} fetched, ${cached} from ${cacheLocation()}`);
129991
+ if (unresolved.length > 0) {
129992
+ console.warn(
129993
+ `${unresolved.length} of ${distinct.length} accessions did not resolve: ${unresolved.join(", ")}. Re-run to try them again.`
129994
+ );
129995
+ }
129942
129996
  if (options2.msaFile) {
129943
129997
  checkLengths(
129944
129998
  options2.msaFile,
@@ -129998,14 +130052,14 @@ ${sequence.seq}`,
129998
130052
  return response.text();
129999
130053
  }
130000
130054
  async function checkStatus(jobId) {
130001
- const response = await fetch(`${BASE_URL}/status/${jobId}`);
130055
+ const response = await fetchWithRetry(`${BASE_URL}/status/${jobId}`);
130002
130056
  if (!response.ok) {
130003
130057
  throw new Error(`Failed to check status: ${response.statusText}`);
130004
130058
  }
130005
130059
  return response.text();
130006
130060
  }
130007
130061
  async function getResults(jobId) {
130008
- const response = await fetch(`${BASE_URL}/result/${jobId}/json`);
130062
+ const response = await fetchWithRetry(`${BASE_URL}/result/${jobId}/json`);
130009
130063
  if (!response.ok) {
130010
130064
  throw new Error(`Failed to get results: ${response.statusText}`);
130011
130065
  }
@@ -130494,6 +130548,19 @@ function readFormat(format2) {
130494
130548
  }
130495
130549
  return format2;
130496
130550
  }
130551
+ function readNumber(name, value, integer2 = false) {
130552
+ const n2 = integer2 ? Number.parseInt(value, 10) : Number.parseFloat(value);
130553
+ if (Number.isNaN(n2)) {
130554
+ throw new Error(`--${name} must be a number, got "${value}"`);
130555
+ }
130556
+ return n2;
130557
+ }
130558
+ function readInt(name, value) {
130559
+ return readNumber(name, value, true);
130560
+ }
130561
+ function readList(value) {
130562
+ return value.split(",").map((s2) => s2.trim());
130563
+ }
130497
130564
  async function main() {
130498
130565
  const { values: values4, positionals } = parseArgs({ allowPositionals: true, options });
130499
130566
  if (values4.help || positionals.length === 0) {
@@ -130515,13 +130582,13 @@ async function main() {
130515
130582
  gffFile: values4.gff,
130516
130583
  outputFile,
130517
130584
  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,
130585
+ width: readInt("width", values4.width),
130586
+ height: readInt("height", values4.height),
130587
+ treeAreaWidth: values4["tree-area-width"] !== void 0 ? readInt("tree-area-width", values4["tree-area-width"]) : void 0,
130588
+ colWidth: values4["col-width"] !== void 0 ? readNumber("col-width", values4["col-width"]) : void 0,
130589
+ rowHeight: values4["row-height"] !== void 0 ? readNumber("row-height", values4["row-height"]) : void 0,
130523
130590
  format: format2,
130524
- tracks: values4.tracks?.split(",").map((s2) => s2.trim()),
130591
+ tracks: values4.tracks !== void 0 ? readList(values4.tracks) : void 0,
130525
130592
  viewport: values4.viewport,
130526
130593
  minimap: values4.minimap
130527
130594
  });
@@ -130542,7 +130609,7 @@ async function main() {
130542
130609
  dockerImage: values4["docker-image"],
130543
130610
  interproscanPath: values4["interproscan-path"],
130544
130611
  dataDir: values4["interproscan-data"],
130545
- programs: values4.programs.split(","),
130612
+ programs: readList(values4.programs),
130546
130613
  email: values4.email,
130547
130614
  format: format2
130548
130615
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-msaview-cli",
3
- "version": "8.0.0",
3
+ "version": "8.1.0",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "repository": {
@@ -20,8 +20,8 @@
20
20
  "@types/node": "^26.5.1",
21
21
  "esbuild": "^0.28.2",
22
22
  "mobx-react": "^10.0.2",
23
- "msa-parsers": "8.0.0",
24
- "react-msaview": "8.0.0"
23
+ "msa-parsers": "8.1.0",
24
+ "react-msaview": "8.1.0"
25
25
  },
26
26
  "dependencies": {
27
27
  "jsdom": "^30.0.1"