@teselagen/ove 0.8.40 → 0.8.42

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.
@@ -0,0 +1,53 @@
1
+ /**
2
+ * @typedef {"mismatch"|"insertion"|"deletion"|"gap"} DifferenceType
3
+ *
4
+ * @typedef {Object} AlignmentDifference
5
+ * @property {number} position - 0-based column index in the aligned sequence
6
+ * @property {DifferenceType} type
7
+ * @property {string[]} bases - bases at this column for each track (template first)
8
+ */
9
+ /**
10
+ * Group consecutive same-type differences into regions.
11
+ * Mismatches are never grouped — each is its own entry.
12
+ * Insertions, deletions, and gaps that are side-by-side are collapsed into
13
+ * one entry with a `start` and `end` (both inclusive, 0-based).
14
+ *
15
+ * @param {AlignmentDifference[]} differences
16
+ * @returns {Array<AlignmentDifference & { start: number, end: number }>}
17
+ */
18
+ export function groupConsecutiveDifferences(differences: AlignmentDifference[]): Array<AlignmentDifference & {
19
+ start: number;
20
+ end: number;
21
+ }>;
22
+ /**
23
+ * Classify alignment columns into difference types relative to the template track.
24
+ *
25
+ * Template is alignedSeqs[0]. Non-template tracks are alignedSeqs[1+].
26
+ *
27
+ * Classification rules (per column):
28
+ * - "gap" : no non-template track is in its aligned region at this position
29
+ * - "insertion" : template has '-', at least one aligned non-template has a non-gap base
30
+ * - "deletion" : template has a non-gap base, at least one aligned non-template has '-'
31
+ * - "mismatch" : no gaps among aligned tracks, unique base set has more than one member
32
+ *
33
+ * Only tracks whose aligned region covers position i participate in classification.
34
+ * This correctly handles multi-read alignments (e.g. Sanger) where reads cover
35
+ * different sub-ranges of the full alignment.
36
+ *
37
+ * @param {string[]} alignedSeqs - Aligned sequence strings, all same length
38
+ * @returns {AlignmentDifference[]}
39
+ */
40
+ export function findAlignmentDifferences(alignedSeqs: string[]): AlignmentDifference[];
41
+ export default findAlignmentDifferences;
42
+ export type DifferenceType = "mismatch" | "insertion" | "deletion" | "gap";
43
+ export type AlignmentDifference = {
44
+ /**
45
+ * - 0-based column index in the aligned sequence
46
+ */
47
+ position: number;
48
+ type: DifferenceType;
49
+ /**
50
+ * - bases at this column for each track (template first)
51
+ */
52
+ bases: string[];
53
+ };
package/index.cjs.js CHANGED
@@ -90314,17 +90314,20 @@ function snapgeneToJson(_0) {
90314
90314
  const b3 = new fxpExports.XMLParser({
90315
90315
  ignoreAttributes: false,
90316
90316
  attributeNamePrefix: "",
90317
- isArray: /* @__PURE__ */ __name((name2) => name2 === "Feature" || name2 === "Segment", "isArray")
90317
+ isArray: /* @__PURE__ */ __name((name2) => ["Feature", "Segment", "Q", "V"].includes(name2), "isArray")
90318
90318
  }).parse(xml2);
90319
90319
  const { Features: { Feature: Feature2 = [] } = {} } = b3;
90320
90320
  data.features = [];
90321
90321
  Feature2.forEach((feat) => {
90322
+ var _a2, _b2, _c, _d;
90322
90323
  const { directionality, Segment = [], name: name2, type: type2 } = feat;
90324
+ let color2;
90323
90325
  let maxStart = 0;
90324
90326
  let maxEnd = 0;
90325
90327
  const locations = Segment && Segment.map((seg) => {
90326
90328
  if (!seg) throw new Error("invalid feature definition");
90327
90329
  const { range: range2 } = seg;
90330
+ if (seg.color) color2 = seg.color;
90328
90331
  let { start: start2, end: end2 } = getStartAndEndFromRangeString(range2);
90329
90332
  start2 = isProtein2 ? start2 * 3 : start2;
90330
90333
  end2 = isProtein2 ? end2 * 3 + 2 : end2;
@@ -90335,6 +90338,10 @@ function snapgeneToJson(_0) {
90335
90338
  end: end2
90336
90339
  };
90337
90340
  });
90341
+ const colorQual = (_a2 = feat.Q) == null ? void 0 : _a2.find((q2) => q2.name === "color");
90342
+ if (colorQual) {
90343
+ color2 = ((_c = (_b2 = colorQual.V) == null ? void 0 : _b2[0]) == null ? void 0 : _c.text) || ((_d = colorQual.V) == null ? void 0 : _d[0]);
90344
+ }
90338
90345
  data.features.push(__spreadProps(__spreadValues({
90339
90346
  name: name2,
90340
90347
  type: type2
@@ -90342,8 +90349,8 @@ function snapgeneToJson(_0) {
90342
90349
  strand: directionality ? strand_dict[directionality][0] : 1,
90343
90350
  arrowheadType: directionality ? strand_dict[directionality][1] : "NONE",
90344
90351
  start: maxStart,
90345
- end: maxEnd
90346
- // color,
90352
+ end: maxEnd,
90353
+ color: color2
90347
90354
  }));
90348
90355
  });
90349
90356
  } else if (ord(next_byte) === 6) {
@@ -95510,14 +95517,34 @@ function addHighlightedDifferences(alignmentTracks) {
95510
95517
  track.alignmentData.sequence
95511
95518
  );
95512
95519
  const mismatches = matchHighlightRanges.filter(({ isMatch }) => !isMatch);
95520
+ const alignedSeq = track.alignmentData.sequence;
95521
+ const seqLen = alignedSeq.length;
95522
+ const startIndex = seqLen - alignedSeq.replace(/^-+/, "").length;
95523
+ const endIndex = alignedSeq.replace(/-+$/, "").length;
95524
+ const gapRanges = [
95525
+ startIndex > 0 && {
95526
+ start: 0,
95527
+ end: startIndex - 1,
95528
+ differenceType: "gap"
95529
+ },
95530
+ endIndex < seqLen && {
95531
+ start: endIndex,
95532
+ end: seqLen - 1,
95533
+ differenceType: "gap"
95534
+ }
95535
+ ].filter(Boolean);
95513
95536
  return __spreadProps(__spreadValues({}, track), {
95514
95537
  sequenceData: sequenceData2,
95515
95538
  matchHighlightRanges,
95516
- additionalSelectionLayers: matchHighlightRanges.filter(({ isMatch }) => !isMatch).map((range2) => {
95517
- return __spreadProps(__spreadValues(__spreadValues({}, range2), highlightRangeProps), {
95539
+ additionalSelectionLayers: [
95540
+ ...matchHighlightRanges.filter(({ isMatch }) => !isMatch).map((range2) => __spreadProps(__spreadValues(__spreadValues({}, range2), highlightRangeProps), {
95518
95541
  className: "veAlignmentMismatch"
95519
- });
95520
- }),
95542
+ })),
95543
+ ...gapRanges.map((range2) => __spreadProps(__spreadValues(__spreadValues({}, range2), highlightRangeProps), {
95544
+ className: "veAlignmentMismatch"
95545
+ }))
95546
+ ],
95547
+ gapRanges,
95521
95548
  mismatches
95522
95549
  });
95523
95550
  });
@@ -95651,23 +95678,30 @@ function getRangeMatchesBetweenTemplateAndNonTemplate(tempSeq, nonTempSeq) {
95651
95678
  const startIndex = seqLength - nonTempSeqWithoutLeadingDashes.length;
95652
95679
  const endIndex = seqLength - (seqLength - nonTempSeqWithoutTrailingDashes.length);
95653
95680
  for (let index2 = startIndex; index2 < endIndex; index2++) {
95654
- const isMatch = tempSeq[index2].toLowerCase() === nonTempSeq[index2].toLowerCase();
95655
- const previousRange = ranges[ranges.length - 1];
95656
- if (previousRange) {
95657
- if (previousRange.isMatch === isMatch) {
95658
- previousRange.end++;
95681
+ const tempBase = tempSeq[index2].toLowerCase();
95682
+ const nonTempBase = nonTempSeq[index2].toLowerCase();
95683
+ const isMatch = tempBase === nonTempBase;
95684
+ let differenceType = null;
95685
+ if (!isMatch) {
95686
+ if (tempBase === "-") {
95687
+ differenceType = "insertion";
95688
+ } else if (nonTempBase === "-") {
95689
+ differenceType = "deletion";
95659
95690
  } else {
95660
- ranges.push({
95661
- start: index2,
95662
- end: index2,
95663
- isMatch
95664
- });
95691
+ differenceType = "mismatch";
95665
95692
  }
95693
+ }
95694
+ const previousRange = ranges[ranges.length - 1];
95695
+ if (previousRange && previousRange.isMatch === isMatch && previousRange.differenceType === differenceType) {
95696
+ previousRange.end++;
95697
+ } else if (previousRange) {
95698
+ ranges.push({ start: index2, end: index2, isMatch, differenceType });
95666
95699
  } else {
95667
95700
  ranges.push({
95668
95701
  start: startIndex,
95669
95702
  end: startIndex,
95670
- isMatch
95703
+ isMatch,
95704
+ differenceType
95671
95705
  });
95672
95706
  }
95673
95707
  }
@@ -117245,7 +117279,7 @@ function showFileDialog({ multiple = false, onSelect }) {
117245
117279
  input.click();
117246
117280
  }
117247
117281
  __name(showFileDialog, "showFileDialog");
117248
- const version = "0.8.40";
117282
+ const version = "0.8.42";
117249
117283
  const packageJson = {
117250
117284
  version
117251
117285
  };
@@ -125114,11 +125148,13 @@ const _Minimap = class _Minimap extends React.Component {
125114
125148
  dimensions: { width = 200 },
125115
125149
  laneHeight,
125116
125150
  laneSpacing = 1,
125117
- isTrackSelected = []
125151
+ isTrackSelected = [],
125152
+ activeFilterType = "all"
125118
125153
  } = this.props;
125119
125154
  const charWidth2 = this.getCharWidth();
125120
125155
  const {
125121
125156
  matchHighlightRanges: _matchHighlightRanges,
125157
+ gapRanges = [],
125122
125158
  alignmentData: { trimmedRange } = {}
125123
125159
  } = alignmentTracks[i];
125124
125160
  const matchHighlightRanges = !trimmedRange ? _matchHighlightRanges : flatMap(_matchHighlightRanges, (r2) => {
@@ -125145,10 +125181,19 @@ const _Minimap = class _Minimap extends React.Component {
125145
125181
  charWidth2
125146
125182
  );
125147
125183
  const toAdd = `M${xStart},${y2} L${xStart + width2},${y2} L${xStart + width2},${y2 + height} L${xStart},${y2 + height}`;
125148
- if (!range2.isMatch) {
125184
+ if (!range2.isMatch && (activeFilterType === "all" || range2.differenceType === activeFilterType)) {
125149
125185
  redPath += toAdd;
125150
125186
  }
125151
125187
  });
125188
+ if (activeFilterType === "gap") {
125189
+ gapRanges.forEach((range2) => {
125190
+ const { xStart, width: width2 } = getXStartAndWidthFromNonCircularRange(
125191
+ range2,
125192
+ charWidth2
125193
+ );
125194
+ redPath += `M${xStart},${y2} L${xStart + width2},${y2} L${xStart + width2},${y2 + height} L${xStart},${y2 + height}`;
125195
+ });
125196
+ }
125152
125197
  return /* @__PURE__ */ React.createElement(
125153
125198
  "div",
125154
125199
  {
@@ -125178,7 +125223,8 @@ const _Minimap = class _Minimap extends React.Component {
125178
125223
  "scrollAlignmentView",
125179
125224
  "laneHeight",
125180
125225
  "laneSpacing",
125181
- "isTrackSelected"
125226
+ "isTrackSelected",
125227
+ "activeFilterType"
125182
125228
  ].some((key) => props[key] !== newProps[key]))
125183
125229
  return true;
125184
125230
  return false;
@@ -125456,6 +125502,66 @@ function getTrimmedRangesToDisplay({ trimmedRange, seqLen }) {
125456
125502
  return splitRangeIntoTwoPartsIfItIsCircular(inverted, seqLen);
125457
125503
  }
125458
125504
  __name(getTrimmedRangesToDisplay, "getTrimmedRangesToDisplay");
125505
+ function groupConsecutiveDifferences(differences) {
125506
+ const grouped = [];
125507
+ for (const diff of differences) {
125508
+ if (diff.type === "mismatch") {
125509
+ grouped.push(__spreadProps(__spreadValues({}, diff), { start: diff.position, end: diff.position }));
125510
+ continue;
125511
+ }
125512
+ const last2 = grouped[grouped.length - 1];
125513
+ if (last2 && last2.type === diff.type && last2.end === diff.position - 1) {
125514
+ grouped[grouped.length - 1] = __spreadProps(__spreadValues({}, last2), { end: diff.position });
125515
+ } else {
125516
+ grouped.push(__spreadProps(__spreadValues({}, diff), { start: diff.position, end: diff.position }));
125517
+ }
125518
+ }
125519
+ return grouped;
125520
+ }
125521
+ __name(groupConsecutiveDifferences, "groupConsecutiveDifferences");
125522
+ function findAlignmentDifferences(alignedSeqs) {
125523
+ var _a2;
125524
+ if (alignedSeqs.length < 2 || !((_a2 = alignedSeqs[0]) == null ? void 0 : _a2.length)) return [];
125525
+ const template = alignedSeqs[0].toLowerCase();
125526
+ const nonTemplates = alignedSeqs.slice(1).map((s2) => s2.toLowerCase());
125527
+ const trackBounds = nonTemplates.map((seq) => {
125528
+ const withoutLeading = seq.replace(/^-+/, "");
125529
+ const withoutTrailing = seq.replace(/-+$/, "");
125530
+ const start2 = seq.length - withoutLeading.length;
125531
+ const end2 = seq.length - (seq.length - withoutTrailing.length);
125532
+ return { start: start2, end: end2 };
125533
+ });
125534
+ const differences = [];
125535
+ for (let i = 0; i < template.length; i++) {
125536
+ const templateBase = template[i];
125537
+ const allNonTemplateBases = nonTemplates.map((seq) => seq[i]);
125538
+ const bases = [templateBase, ...allNonTemplateBases];
125539
+ const alignedIndices = trackBounds.reduce((acc, { start: start2, end: end2 }, idx) => {
125540
+ if (i >= start2 && i < end2) acc.push(idx);
125541
+ return acc;
125542
+ }, []);
125543
+ if (alignedIndices.length === 0) {
125544
+ differences.push({ position: i, type: "gap", bases });
125545
+ continue;
125546
+ }
125547
+ const alignedBases = alignedIndices.map((idx) => allNonTemplateBases[idx]);
125548
+ const templateIsGap = templateBase === "-";
125549
+ const nonTemplateHasBase = alignedBases.some((b3) => b3 !== "-");
125550
+ const nonTemplateHasGap = alignedBases.some((b3) => b3 === "-");
125551
+ if (templateIsGap && nonTemplateHasBase) {
125552
+ differences.push({ position: i, type: "insertion", bases });
125553
+ } else if (!templateIsGap && nonTemplateHasGap) {
125554
+ differences.push({ position: i, type: "deletion", bases });
125555
+ } else if (!templateIsGap) {
125556
+ const uniqueBases = /* @__PURE__ */ new Set([templateBase, ...alignedBases]);
125557
+ if (uniqueBases.size > 1) {
125558
+ differences.push({ position: i, type: "mismatch", bases });
125559
+ }
125560
+ }
125561
+ }
125562
+ return differences;
125563
+ }
125564
+ __name(findAlignmentDifferences, "findAlignmentDifferences");
125459
125565
  function scrollToAlignmentSelection() {
125460
125566
  const el = document.querySelector(".veCaret");
125461
125567
  if (el) {
@@ -125469,159 +125575,145 @@ function updateCaretPosition({ start: start2, end: end2 }) {
125469
125575
  }
125470
125576
  }
125471
125577
  __name(updateCaretPosition, "updateCaretPosition");
125578
+ const FILTER_OPTIONS = [
125579
+ { value: "all", label: "All" },
125580
+ { value: "mismatch", label: "Mismatches" },
125581
+ { value: "insertion", label: "Insertions" },
125582
+ { value: "deletion", label: "Deletions" },
125583
+ { value: "gap", label: "Gaps" }
125584
+ ];
125472
125585
  function FindMismatches(props) {
125473
- const { alignmentJson, id: id2 } = props;
125586
+ var _a2;
125587
+ const { alignmentJson, id: id2, onFilterChange } = props;
125474
125588
  const alignedSeqs = React.useMemo(
125475
125589
  () => alignmentJson.map((t2) => {
125476
- var _a2;
125477
- return ((_a2 = t2.alignmentData) == null ? void 0 : _a2.sequence) || "";
125590
+ var _a3;
125591
+ return ((_a3 = t2.alignmentData) == null ? void 0 : _a3.sequence) || "";
125478
125592
  }),
125479
125593
  [alignmentJson]
125480
125594
  );
125481
- const mismatches = React.useMemo(() => {
125482
- const result = [{ position: 0, bases: [""] }];
125483
- if (alignedSeqs.length > 1 && alignedSeqs[0].length) {
125484
- for (let i = 0; i < alignedSeqs[0].length; i++) {
125485
- const bases = alignedSeqs.map((seq) => seq[i]);
125486
- const uniqueBases = new Set(bases);
125487
- if (uniqueBases.size > 1 && !uniqueBases.has("-")) {
125488
- result.push({ position: i, bases });
125489
- }
125490
- }
125491
- }
125492
- return result;
125493
- }, [alignedSeqs]);
125595
+ const [activeFilter, setActiveFilter] = React.useState("all");
125596
+ const allDifferences = React.useMemo(
125597
+ () => groupConsecutiveDifferences(findAlignmentDifferences(alignedSeqs)),
125598
+ [alignedSeqs]
125599
+ );
125600
+ const countsByType = React.useMemo(() => {
125601
+ const counts = { all: 0, mismatch: 0, insertion: 0, deletion: 0, gap: 0 };
125602
+ allDifferences.forEach((d2) => {
125603
+ counts[d2.type] = (counts[d2.type] || 0) + 1;
125604
+ counts.all++;
125605
+ });
125606
+ return counts;
125607
+ }, [allDifferences]);
125608
+ const differences = React.useMemo(() => {
125609
+ const filtered = activeFilter === "all" ? allDifferences : allDifferences.filter((d2) => d2.type === activeFilter);
125610
+ return [{ position: -1, start: -1, end: -1, bases: [""] }, ...filtered];
125611
+ }, [allDifferences, activeFilter]);
125494
125612
  const currentCaretPosition = reactRedux.useSelector(
125495
125613
  (state2) => {
125496
- var _a2;
125497
- return (_a2 = state2.VectorEditor.__allEditorsOptions.alignments[id2]) == null ? void 0 : _a2.caretPosition;
125614
+ var _a3;
125615
+ return (_a3 = state2.VectorEditor.__allEditorsOptions.alignments[id2]) == null ? void 0 : _a3.caretPosition;
125498
125616
  }
125499
125617
  );
125500
125618
  const [currentIdx, setCurrentIdx] = React.useState(0);
125501
- const [disablePrev, setDisablePrev] = React.useState(true);
125502
- const [disableNext, setDisableNext] = React.useState(false);
125503
- const currentMismatch = mismatches[currentIdx];
125504
- const handleButtonsState = React.useCallback(
125505
- (caret) => {
125506
- if (mismatches.length <= 1) {
125507
- setDisablePrev(true);
125508
- setDisableNext(true);
125509
- return;
125510
- }
125511
- const firstMismatchPos = mismatches[1].position;
125512
- const lastMismatchPos = mismatches[mismatches.length - 1].position;
125513
- setDisablePrev(caret <= firstMismatchPos);
125514
- setDisableNext(caret >= lastMismatchPos);
125515
- },
125516
- [mismatches]
125517
- );
125619
+ const currentDiff = differences[currentIdx];
125620
+ const disablePrev = currentIdx <= 1;
125621
+ const disableNext = currentIdx >= differences.length - 1;
125622
+ React.useEffect(() => {
125623
+ setCurrentIdx(0);
125624
+ }, [activeFilter]);
125625
+ React.useEffect(() => {
125626
+ onFilterChange == null ? void 0 : onFilterChange({ activeFilter });
125627
+ }, [activeFilter, onFilterChange]);
125518
125628
  React.useEffect(() => {
125519
125629
  if (currentCaretPosition !== -1) {
125520
- const mismatchIdx = mismatches.findIndex(
125521
- (m2) => m2.position === currentCaretPosition || m2.position === currentCaretPosition - 1
125630
+ const diffIdx = differences.findIndex(
125631
+ (d2, i) => i > 0 && currentCaretPosition >= d2.start && currentCaretPosition <= d2.end + 1
125522
125632
  );
125523
- if (mismatchIdx !== -1 && mismatchIdx !== currentIdx) {
125524
- handleButtonsState(currentCaretPosition);
125525
- setCurrentIdx(mismatchIdx);
125633
+ if (diffIdx !== -1 && diffIdx !== currentIdx) {
125634
+ setCurrentIdx(diffIdx);
125526
125635
  }
125527
125636
  }
125528
- }, [currentCaretPosition, mismatches, currentIdx, handleButtonsState]);
125529
- const updateView = /* @__PURE__ */ __name((mismatch) => {
125530
- const idx = mismatches.indexOf(mismatch);
125531
- const position2 = mismatch.position;
125532
- handleButtonsState(position2);
125637
+ }, [currentCaretPosition, differences, currentIdx]);
125638
+ const updateView = /* @__PURE__ */ __name((diff) => {
125639
+ const idx = differences.indexOf(diff);
125640
+ const { start: start2, end: end2 } = diff;
125533
125641
  setCurrentIdx(idx);
125534
- updateCaretPosition({ start: position2, end: position2 });
125642
+ updateCaretPosition({ start: start2, end: end2 });
125535
125643
  setTimeout(() => {
125536
125644
  scrollToAlignmentSelection();
125537
125645
  }, 0);
125538
125646
  }, "updateView");
125539
- const prevMismatch = /* @__PURE__ */ __name(() => {
125540
- if (currentIdx > 1) {
125541
- const newIdx = Math.max(0, currentIdx - 1);
125542
- let prev = mismatches[newIdx];
125543
- if (currentCaretPosition > 0) {
125544
- prev = [...mismatches].reverse().find((m2) => m2.position < currentCaretPosition);
125545
- }
125546
- if (prev) {
125547
- updateView(prev);
125548
- }
125549
- }
125550
- }, "prevMismatch");
125551
- const nextMismatch = /* @__PURE__ */ __name(() => {
125552
- if (currentIdx < mismatches.length - 1) {
125553
- const newIdx = Math.min(mismatches.length - 1, currentIdx + 1);
125554
- let next = mismatches[newIdx];
125555
- if (currentCaretPosition > 0) {
125556
- next = mismatches.find(
125557
- (m2) => m2.position > currentCaretPosition && m2.position > 1
125558
- );
125559
- }
125560
- if (next) {
125561
- updateView(next);
125647
+ const prevDifference = /* @__PURE__ */ __name(() => {
125648
+ var _a3, _b2;
125649
+ const pivot = currentCaretPosition >= 0 ? currentCaretPosition : (_b2 = (_a3 = differences[currentIdx]) == null ? void 0 : _a3.start) != null ? _b2 : 0;
125650
+ const prev = [...differences].reverse().find((d2) => d2.start >= 0 && d2.start < pivot);
125651
+ if (prev) updateView(prev);
125652
+ }, "prevDifference");
125653
+ const nextDifference = /* @__PURE__ */ __name(() => {
125654
+ var _a3, _b2;
125655
+ const pivot = currentCaretPosition >= 0 ? currentCaretPosition : (_b2 = (_a3 = differences[currentIdx]) == null ? void 0 : _a3.start) != null ? _b2 : -1;
125656
+ const next = differences.find((d2) => d2.start > pivot && d2.start >= 0);
125657
+ if (next) updateView(next);
125658
+ }, "nextDifference");
125659
+ const noDifferences = differences.length <= 1;
125660
+ const activeOption = FILTER_OPTIONS.find((o2) => o2.value === activeFilter);
125661
+ const activeLabel = (_a2 = activeOption == null ? void 0 : activeOption.label) != null ? _a2 : "Differences";
125662
+ const filterMenu = /* @__PURE__ */ React.createElement(core.Menu, null, FILTER_OPTIONS.map(({ value, label }) => {
125663
+ var _a3;
125664
+ const count2 = (_a3 = countsByType[value]) != null ? _a3 : 0;
125665
+ const isActive2 = activeFilter === value;
125666
+ return /* @__PURE__ */ React.createElement(
125667
+ core.MenuItem,
125668
+ {
125669
+ key: value,
125670
+ active: isActive2,
125671
+ onClick: /* @__PURE__ */ __name(() => setActiveFilter(value), "onClick"),
125672
+ text: /* @__PURE__ */ React.createElement("span", { className: "veDiffMenuItem-inner" }, label, /* @__PURE__ */ React.createElement(core.Tag, { round: true, minimal: true, style: { marginLeft: 6 } }, count2))
125562
125673
  }
125563
- }
125564
- }, "nextMismatch");
125565
- return /* @__PURE__ */ React.createElement(
125566
- "div",
125674
+ );
125675
+ }));
125676
+ return /* @__PURE__ */ React.createElement("div", { className: "veDiffNavigator" }, /* @__PURE__ */ React.createElement(
125677
+ core.Popover,
125567
125678
  {
125568
- style: {
125569
- display: "flex",
125570
- flexDirection: "row",
125571
- justifyContent: "center",
125572
- alignItems: "center",
125573
- gap: 10
125574
- }
125575
- },
125576
- mismatches.length === 1 ? /* @__PURE__ */ React.createElement("span", { style: { fontStyle: "italic", color: "grey" } }, "no mismatches") : /* @__PURE__ */ React.createElement("div", { style: { display: "flex", flexDirection: "column" } }, /* @__PURE__ */ React.createElement(
125577
- "div",
125578
- {
125579
- style: {
125580
- display: "flex",
125581
- alignItems: "center"
125582
- }
125583
- },
125584
- /* @__PURE__ */ React.createElement("strong", null, "Mismatches"),
125585
- /* @__PURE__ */ React.createElement("div", { style: { display: "flex", gap: 2 } }, /* @__PURE__ */ React.createElement(
125586
- core.Button,
125587
- {
125588
- intent: "primary",
125589
- icon: "arrow-left",
125590
- "data-tip": "Previous Mismatch",
125591
- onClick: prevMismatch,
125592
- disabled: disablePrev,
125593
- small: true,
125594
- minimal: true
125595
- }
125596
- ), /* @__PURE__ */ React.createElement(
125679
+ minimal: true,
125680
+ position: core.Position.BOTTOM_LEFT,
125681
+ content: filterMenu,
125682
+ target: /* @__PURE__ */ React.createElement(
125597
125683
  core.Button,
125598
125684
  {
125599
- intent: "primary",
125600
- icon: "arrow-right",
125601
- "data-tip": "Next Mismatch",
125602
- onClick: nextMismatch,
125603
- disabled: disableNext,
125685
+ minimal: true,
125686
+ "data-tip": "Filter Difference Type",
125604
125687
  small: true,
125605
- minimal: true
125606
- }
125607
- ))
125608
- ), /* @__PURE__ */ React.createElement(
125609
- "span",
125610
- {
125611
- style: {
125612
- fontSize: "0.8em",
125613
- color: "grey",
125614
- lineHeight: "0.8em"
125615
- }
125616
- },
125617
- currentMismatch.position > 1 && /* @__PURE__ */ React.createElement("span", null, "Position: ", currentMismatch.position + 1, " | "),
125618
- "(",
125619
- currentIdx,
125620
- " of ",
125621
- mismatches.length - 1,
125622
- ")"
125623
- ))
125624
- );
125688
+ rightIcon: "caret-down",
125689
+ className: "veDiffFilter-trigger"
125690
+ },
125691
+ activeLabel
125692
+ )
125693
+ }
125694
+ ), noDifferences ? /* @__PURE__ */ React.createElement("span", { className: "veDiffNav-empty" }, "no", " ", activeFilter === "all" ? "differences" : activeLabel.toLowerCase()) : /* @__PURE__ */ React.createElement("div", { className: "veDiffNav" }, /* @__PURE__ */ React.createElement(
125695
+ core.Button,
125696
+ {
125697
+ minimal: true,
125698
+ small: true,
125699
+ "data-tip": "Previous Difference",
125700
+ icon: "arrow-left",
125701
+ intent: core.Intent.PRIMARY,
125702
+ onClick: prevDifference,
125703
+ disabled: disablePrev
125704
+ }
125705
+ ), /* @__PURE__ */ React.createElement("div", { className: "veDiffNav-center" }, /* @__PURE__ */ React.createElement("span", { className: "veDiffNav-fraction" }, currentIdx, /* @__PURE__ */ React.createElement("span", { className: "veDiffNav-sep" }, "/"), differences.length - 1), (currentDiff == null ? void 0 : currentDiff.start) > -1 && /* @__PURE__ */ React.createElement("span", { className: "veDiffNav-pos" }, ":", currentDiff.start + 1)), /* @__PURE__ */ React.createElement(
125706
+ core.Button,
125707
+ {
125708
+ minimal: true,
125709
+ small: true,
125710
+ "data-tip": "Next Difference",
125711
+ icon: "arrow-right",
125712
+ intent: core.Intent.PRIMARY,
125713
+ onClick: nextDifference,
125714
+ disabled: disableNext
125715
+ }
125716
+ )));
125625
125717
  }
125626
125718
  __name(FindMismatches, "FindMismatches");
125627
125719
  function getGapMap(sequence2) {
@@ -126310,17 +126402,18 @@ const AlignmentVisibilityTool = pure(/* @__PURE__ */ __name(function AlignmentVi
126310
126402
  minimal: true,
126311
126403
  position: "bottom",
126312
126404
  content: /* @__PURE__ */ React.createElement(VisibilityOptions$2, __spreadValues({}, props)),
126313
- target: /* @__PURE__ */ React.createElement(core.Tooltip, { content: "Visibility Options" }, /* @__PURE__ */ React.createElement(
126405
+ target: /* @__PURE__ */ React.createElement(
126314
126406
  core.Button,
126315
126407
  {
126316
126408
  className: "tg-alignment-visibility-toggle",
126317
126409
  small: true,
126410
+ "data-tip": "Visibility Options",
126318
126411
  rightIcon: "caret-down",
126319
126412
  intent: core.Intent.PRIMARY,
126320
126413
  minimal: true,
126321
126414
  icon: "eye-open"
126322
126415
  }
126323
- ))
126416
+ )
126324
126417
  }
126325
126418
  );
126326
126419
  }, "AlignmentVisibilityTool"));
@@ -140108,6 +140201,10 @@ const AlignmentView = /* @__PURE__ */ __name((props) => {
140108
140201
  const [tempTrimAfter, setTempTrimAfter] = React.useState({});
140109
140202
  const [tempTrimmingCaret, setTempTrimmingCaret] = React.useState({});
140110
140203
  const [searchMatchLayers, setSearchMatchLayers] = React.useState([]);
140204
+ const [activeFilterType, setActiveFilterType] = React.useState("all");
140205
+ const handleFilterChange = React.useCallback(({ activeFilter }) => {
140206
+ setActiveFilterType(activeFilter);
140207
+ }, []);
140111
140208
  const bindOutsideChangeHelper = React.useRef({});
140112
140209
  const alignmentHolder = React.useRef(null);
140113
140210
  const alignmentHolderTop = React.useRef(null);
@@ -140833,7 +140930,9 @@ ${seqDataToCopy}\r
140833
140930
  chromatogramData
140834
140931
  }) : linearViewOptions)), {
140835
140932
  additionalSelectionLayers: [
140836
- ...additionalSelectionLayers || [],
140933
+ ...i !== 0 ? (additionalSelectionLayers || []).filter(
140934
+ (layer) => activeFilterType === "all" ? layer.differenceType !== "gap" : layer.differenceType === activeFilterType
140935
+ ) : additionalSelectionLayers || [],
140837
140936
  ...searchMatchLayers || []
140838
140937
  ],
140839
140938
  dimensions: {
@@ -141492,7 +141591,14 @@ ${seqDataToCopy}\r
141492
141591
  setSearchMatchLayers
141493
141592
  }
141494
141593
  ),
141495
- /* @__PURE__ */ React.createElement(FindMismatches, { alignmentJson: alignmentTracks, id: id2 }),
141594
+ /* @__PURE__ */ React.createElement(
141595
+ FindMismatches,
141596
+ {
141597
+ alignmentJson: alignmentTracks,
141598
+ id: id2,
141599
+ onFilterChange: handleFilterChange
141600
+ }
141601
+ ),
141496
141602
  additionalTopEl,
141497
141603
  saveMessage && /* @__PURE__ */ React.createElement(
141498
141604
  "div",
@@ -141593,6 +141699,7 @@ ${seqDataToCopy}\r
141593
141699
  }
141594
141700
  )),
141595
141701
  alignmentTracks,
141702
+ activeFilterType,
141596
141703
  dimensions: {
141597
141704
  width: Math.max(width, 10) || 10
141598
141705
  },