@teselagen/ove 0.8.39 → 0.8.41

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.cjs.js CHANGED
@@ -95510,14 +95510,34 @@ function addHighlightedDifferences(alignmentTracks) {
95510
95510
  track.alignmentData.sequence
95511
95511
  );
95512
95512
  const mismatches = matchHighlightRanges.filter(({ isMatch }) => !isMatch);
95513
+ const alignedSeq = track.alignmentData.sequence;
95514
+ const seqLen = alignedSeq.length;
95515
+ const startIndex = seqLen - alignedSeq.replace(/^-+/, "").length;
95516
+ const endIndex = alignedSeq.replace(/-+$/, "").length;
95517
+ const gapRanges = [
95518
+ startIndex > 0 && {
95519
+ start: 0,
95520
+ end: startIndex - 1,
95521
+ differenceType: "gap"
95522
+ },
95523
+ endIndex < seqLen && {
95524
+ start: endIndex,
95525
+ end: seqLen - 1,
95526
+ differenceType: "gap"
95527
+ }
95528
+ ].filter(Boolean);
95513
95529
  return __spreadProps(__spreadValues({}, track), {
95514
95530
  sequenceData: sequenceData2,
95515
95531
  matchHighlightRanges,
95516
- additionalSelectionLayers: matchHighlightRanges.filter(({ isMatch }) => !isMatch).map((range2) => {
95517
- return __spreadProps(__spreadValues(__spreadValues({}, range2), highlightRangeProps), {
95532
+ additionalSelectionLayers: [
95533
+ ...matchHighlightRanges.filter(({ isMatch }) => !isMatch).map((range2) => __spreadProps(__spreadValues(__spreadValues({}, range2), highlightRangeProps), {
95518
95534
  className: "veAlignmentMismatch"
95519
- });
95520
- }),
95535
+ })),
95536
+ ...gapRanges.map((range2) => __spreadProps(__spreadValues(__spreadValues({}, range2), highlightRangeProps), {
95537
+ className: "veAlignmentMismatch"
95538
+ }))
95539
+ ],
95540
+ gapRanges,
95521
95541
  mismatches
95522
95542
  });
95523
95543
  });
@@ -95651,23 +95671,30 @@ function getRangeMatchesBetweenTemplateAndNonTemplate(tempSeq, nonTempSeq) {
95651
95671
  const startIndex = seqLength - nonTempSeqWithoutLeadingDashes.length;
95652
95672
  const endIndex = seqLength - (seqLength - nonTempSeqWithoutTrailingDashes.length);
95653
95673
  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++;
95674
+ const tempBase = tempSeq[index2].toLowerCase();
95675
+ const nonTempBase = nonTempSeq[index2].toLowerCase();
95676
+ const isMatch = tempBase === nonTempBase;
95677
+ let differenceType = null;
95678
+ if (!isMatch) {
95679
+ if (tempBase === "-") {
95680
+ differenceType = "insertion";
95681
+ } else if (nonTempBase === "-") {
95682
+ differenceType = "deletion";
95659
95683
  } else {
95660
- ranges.push({
95661
- start: index2,
95662
- end: index2,
95663
- isMatch
95664
- });
95684
+ differenceType = "mismatch";
95665
95685
  }
95686
+ }
95687
+ const previousRange = ranges[ranges.length - 1];
95688
+ if (previousRange && previousRange.isMatch === isMatch && previousRange.differenceType === differenceType) {
95689
+ previousRange.end++;
95690
+ } else if (previousRange) {
95691
+ ranges.push({ start: index2, end: index2, isMatch, differenceType });
95666
95692
  } else {
95667
95693
  ranges.push({
95668
95694
  start: startIndex,
95669
95695
  end: startIndex,
95670
- isMatch
95696
+ isMatch,
95697
+ differenceType
95671
95698
  });
95672
95699
  }
95673
95700
  }
@@ -117245,7 +117272,7 @@ function showFileDialog({ multiple = false, onSelect }) {
117245
117272
  input.click();
117246
117273
  }
117247
117274
  __name(showFileDialog, "showFileDialog");
117248
- const version = "0.8.39";
117275
+ const version = "0.8.41";
117249
117276
  const packageJson = {
117250
117277
  version
117251
117278
  };
@@ -125114,11 +125141,13 @@ const _Minimap = class _Minimap extends React.Component {
125114
125141
  dimensions: { width = 200 },
125115
125142
  laneHeight,
125116
125143
  laneSpacing = 1,
125117
- isTrackSelected = []
125144
+ isTrackSelected = [],
125145
+ activeFilterType = "all"
125118
125146
  } = this.props;
125119
125147
  const charWidth2 = this.getCharWidth();
125120
125148
  const {
125121
125149
  matchHighlightRanges: _matchHighlightRanges,
125150
+ gapRanges = [],
125122
125151
  alignmentData: { trimmedRange } = {}
125123
125152
  } = alignmentTracks[i];
125124
125153
  const matchHighlightRanges = !trimmedRange ? _matchHighlightRanges : flatMap(_matchHighlightRanges, (r2) => {
@@ -125145,10 +125174,19 @@ const _Minimap = class _Minimap extends React.Component {
125145
125174
  charWidth2
125146
125175
  );
125147
125176
  const toAdd = `M${xStart},${y2} L${xStart + width2},${y2} L${xStart + width2},${y2 + height} L${xStart},${y2 + height}`;
125148
- if (!range2.isMatch) {
125177
+ if (!range2.isMatch && (activeFilterType === "all" || range2.differenceType === activeFilterType)) {
125149
125178
  redPath += toAdd;
125150
125179
  }
125151
125180
  });
125181
+ if (activeFilterType === "gap") {
125182
+ gapRanges.forEach((range2) => {
125183
+ const { xStart, width: width2 } = getXStartAndWidthFromNonCircularRange(
125184
+ range2,
125185
+ charWidth2
125186
+ );
125187
+ redPath += `M${xStart},${y2} L${xStart + width2},${y2} L${xStart + width2},${y2 + height} L${xStart},${y2 + height}`;
125188
+ });
125189
+ }
125152
125190
  return /* @__PURE__ */ React.createElement(
125153
125191
  "div",
125154
125192
  {
@@ -125178,7 +125216,8 @@ const _Minimap = class _Minimap extends React.Component {
125178
125216
  "scrollAlignmentView",
125179
125217
  "laneHeight",
125180
125218
  "laneSpacing",
125181
- "isTrackSelected"
125219
+ "isTrackSelected",
125220
+ "activeFilterType"
125182
125221
  ].some((key) => props[key] !== newProps[key]))
125183
125222
  return true;
125184
125223
  return false;
@@ -125456,6 +125495,899 @@ function getTrimmedRangesToDisplay({ trimmedRange, seqLen }) {
125456
125495
  return splitRangeIntoTwoPartsIfItIsCircular(inverted, seqLen);
125457
125496
  }
125458
125497
  __name(getTrimmedRangesToDisplay, "getTrimmedRangesToDisplay");
125498
+ function groupConsecutiveDifferences(differences) {
125499
+ const grouped = [];
125500
+ for (const diff of differences) {
125501
+ if (diff.type === "mismatch") {
125502
+ grouped.push(__spreadProps(__spreadValues({}, diff), { start: diff.position, end: diff.position }));
125503
+ continue;
125504
+ }
125505
+ const last2 = grouped[grouped.length - 1];
125506
+ if (last2 && last2.type === diff.type && last2.end === diff.position - 1) {
125507
+ grouped[grouped.length - 1] = __spreadProps(__spreadValues({}, last2), { end: diff.position });
125508
+ } else {
125509
+ grouped.push(__spreadProps(__spreadValues({}, diff), { start: diff.position, end: diff.position }));
125510
+ }
125511
+ }
125512
+ return grouped;
125513
+ }
125514
+ __name(groupConsecutiveDifferences, "groupConsecutiveDifferences");
125515
+ function findAlignmentDifferences(alignedSeqs) {
125516
+ var _a2;
125517
+ if (alignedSeqs.length < 2 || !((_a2 = alignedSeqs[0]) == null ? void 0 : _a2.length)) return [];
125518
+ const template = alignedSeqs[0].toLowerCase();
125519
+ const nonTemplates = alignedSeqs.slice(1).map((s2) => s2.toLowerCase());
125520
+ const trackBounds = nonTemplates.map((seq) => {
125521
+ const withoutLeading = seq.replace(/^-+/, "");
125522
+ const withoutTrailing = seq.replace(/-+$/, "");
125523
+ const start2 = seq.length - withoutLeading.length;
125524
+ const end2 = seq.length - (seq.length - withoutTrailing.length);
125525
+ return { start: start2, end: end2 };
125526
+ });
125527
+ const differences = [];
125528
+ for (let i = 0; i < template.length; i++) {
125529
+ const templateBase = template[i];
125530
+ const allNonTemplateBases = nonTemplates.map((seq) => seq[i]);
125531
+ const bases = [templateBase, ...allNonTemplateBases];
125532
+ const alignedIndices = trackBounds.reduce((acc, { start: start2, end: end2 }, idx) => {
125533
+ if (i >= start2 && i < end2) acc.push(idx);
125534
+ return acc;
125535
+ }, []);
125536
+ if (alignedIndices.length === 0) {
125537
+ differences.push({ position: i, type: "gap", bases });
125538
+ continue;
125539
+ }
125540
+ const alignedBases = alignedIndices.map((idx) => allNonTemplateBases[idx]);
125541
+ const templateIsGap = templateBase === "-";
125542
+ const nonTemplateHasBase = alignedBases.some((b3) => b3 !== "-");
125543
+ const nonTemplateHasGap = alignedBases.some((b3) => b3 === "-");
125544
+ if (templateIsGap && nonTemplateHasBase) {
125545
+ differences.push({ position: i, type: "insertion", bases });
125546
+ } else if (!templateIsGap && nonTemplateHasGap) {
125547
+ differences.push({ position: i, type: "deletion", bases });
125548
+ } else if (!templateIsGap) {
125549
+ const uniqueBases = /* @__PURE__ */ new Set([templateBase, ...alignedBases]);
125550
+ if (uniqueBases.size > 1) {
125551
+ differences.push({ position: i, type: "mismatch", bases });
125552
+ }
125553
+ }
125554
+ }
125555
+ return differences;
125556
+ }
125557
+ __name(findAlignmentDifferences, "findAlignmentDifferences");
125558
+ function scrollToAlignmentSelection() {
125559
+ const el = document.querySelector(".veCaret");
125560
+ if (el) {
125561
+ el.scrollIntoView({ inline: "center", block: "nearest" });
125562
+ }
125563
+ }
125564
+ __name(scrollToAlignmentSelection, "scrollToAlignmentSelection");
125565
+ function updateCaretPosition({ start: start2, end: end2 }) {
125566
+ if (window.updateAlignmentSelection) {
125567
+ window.updateAlignmentSelection({ start: start2, end: end2 });
125568
+ }
125569
+ }
125570
+ __name(updateCaretPosition, "updateCaretPosition");
125571
+ const FILTER_OPTIONS = [
125572
+ { value: "all", label: "All" },
125573
+ { value: "mismatch", label: "Mismatches" },
125574
+ { value: "insertion", label: "Insertions" },
125575
+ { value: "deletion", label: "Deletions" },
125576
+ { value: "gap", label: "Gaps" }
125577
+ ];
125578
+ function FindMismatches(props) {
125579
+ var _a2;
125580
+ const { alignmentJson, id: id2, onFilterChange } = props;
125581
+ const alignedSeqs = React.useMemo(
125582
+ () => alignmentJson.map((t2) => {
125583
+ var _a3;
125584
+ return ((_a3 = t2.alignmentData) == null ? void 0 : _a3.sequence) || "";
125585
+ }),
125586
+ [alignmentJson]
125587
+ );
125588
+ const [activeFilter, setActiveFilter] = React.useState("all");
125589
+ const allDifferences = React.useMemo(
125590
+ () => groupConsecutiveDifferences(findAlignmentDifferences(alignedSeqs)),
125591
+ [alignedSeqs]
125592
+ );
125593
+ const countsByType = React.useMemo(() => {
125594
+ const counts = { all: 0, mismatch: 0, insertion: 0, deletion: 0, gap: 0 };
125595
+ allDifferences.forEach((d2) => {
125596
+ counts[d2.type] = (counts[d2.type] || 0) + 1;
125597
+ counts.all++;
125598
+ });
125599
+ return counts;
125600
+ }, [allDifferences]);
125601
+ const differences = React.useMemo(() => {
125602
+ const filtered = activeFilter === "all" ? allDifferences : allDifferences.filter((d2) => d2.type === activeFilter);
125603
+ return [{ position: -1, start: -1, end: -1, bases: [""] }, ...filtered];
125604
+ }, [allDifferences, activeFilter]);
125605
+ const currentCaretPosition = reactRedux.useSelector(
125606
+ (state2) => {
125607
+ var _a3;
125608
+ return (_a3 = state2.VectorEditor.__allEditorsOptions.alignments[id2]) == null ? void 0 : _a3.caretPosition;
125609
+ }
125610
+ );
125611
+ const [currentIdx, setCurrentIdx] = React.useState(0);
125612
+ const currentDiff = differences[currentIdx];
125613
+ const disablePrev = currentIdx <= 1;
125614
+ const disableNext = currentIdx >= differences.length - 1;
125615
+ React.useEffect(() => {
125616
+ setCurrentIdx(0);
125617
+ }, [activeFilter]);
125618
+ React.useEffect(() => {
125619
+ onFilterChange == null ? void 0 : onFilterChange({ activeFilter });
125620
+ }, [activeFilter, onFilterChange]);
125621
+ React.useEffect(() => {
125622
+ if (currentCaretPosition !== -1) {
125623
+ const diffIdx = differences.findIndex(
125624
+ (d2, i) => i > 0 && currentCaretPosition >= d2.start && currentCaretPosition <= d2.end + 1
125625
+ );
125626
+ if (diffIdx !== -1 && diffIdx !== currentIdx) {
125627
+ setCurrentIdx(diffIdx);
125628
+ }
125629
+ }
125630
+ }, [currentCaretPosition, differences, currentIdx]);
125631
+ const updateView = /* @__PURE__ */ __name((diff) => {
125632
+ const idx = differences.indexOf(diff);
125633
+ const { start: start2, end: end2 } = diff;
125634
+ setCurrentIdx(idx);
125635
+ updateCaretPosition({ start: start2, end: end2 });
125636
+ setTimeout(() => {
125637
+ scrollToAlignmentSelection();
125638
+ }, 0);
125639
+ }, "updateView");
125640
+ const prevDifference = /* @__PURE__ */ __name(() => {
125641
+ var _a3, _b2;
125642
+ const pivot = currentCaretPosition >= 0 ? currentCaretPosition : (_b2 = (_a3 = differences[currentIdx]) == null ? void 0 : _a3.start) != null ? _b2 : 0;
125643
+ const prev = [...differences].reverse().find((d2) => d2.start >= 0 && d2.start < pivot);
125644
+ if (prev) updateView(prev);
125645
+ }, "prevDifference");
125646
+ const nextDifference = /* @__PURE__ */ __name(() => {
125647
+ var _a3, _b2;
125648
+ const pivot = currentCaretPosition >= 0 ? currentCaretPosition : (_b2 = (_a3 = differences[currentIdx]) == null ? void 0 : _a3.start) != null ? _b2 : -1;
125649
+ const next = differences.find((d2) => d2.start > pivot && d2.start >= 0);
125650
+ if (next) updateView(next);
125651
+ }, "nextDifference");
125652
+ const noDifferences = differences.length <= 1;
125653
+ const activeOption = FILTER_OPTIONS.find((o2) => o2.value === activeFilter);
125654
+ const activeLabel = (_a2 = activeOption == null ? void 0 : activeOption.label) != null ? _a2 : "Differences";
125655
+ const filterMenu = /* @__PURE__ */ React.createElement(core.Menu, null, FILTER_OPTIONS.map(({ value, label }) => {
125656
+ var _a3;
125657
+ const count2 = (_a3 = countsByType[value]) != null ? _a3 : 0;
125658
+ const isActive2 = activeFilter === value;
125659
+ return /* @__PURE__ */ React.createElement(
125660
+ core.MenuItem,
125661
+ {
125662
+ key: value,
125663
+ active: isActive2,
125664
+ onClick: /* @__PURE__ */ __name(() => setActiveFilter(value), "onClick"),
125665
+ text: /* @__PURE__ */ React.createElement("span", { className: "veDiffMenuItem-inner" }, label, /* @__PURE__ */ React.createElement(core.Tag, { round: true, minimal: true, style: { marginLeft: 6 } }, count2))
125666
+ }
125667
+ );
125668
+ }));
125669
+ return /* @__PURE__ */ React.createElement("div", { className: "veDiffNavigator" }, /* @__PURE__ */ React.createElement(
125670
+ core.Popover,
125671
+ {
125672
+ minimal: true,
125673
+ position: core.Position.BOTTOM_LEFT,
125674
+ content: filterMenu,
125675
+ target: /* @__PURE__ */ React.createElement(
125676
+ core.Button,
125677
+ {
125678
+ minimal: true,
125679
+ "data-tip": "Filter Difference Type",
125680
+ small: true,
125681
+ rightIcon: "caret-down",
125682
+ className: "veDiffFilter-trigger"
125683
+ },
125684
+ activeLabel
125685
+ )
125686
+ }
125687
+ ), noDifferences ? /* @__PURE__ */ React.createElement("span", { className: "veDiffNav-empty" }, "no", " ", activeFilter === "all" ? "differences" : activeLabel.toLowerCase()) : /* @__PURE__ */ React.createElement("div", { className: "veDiffNav" }, /* @__PURE__ */ React.createElement(
125688
+ core.Button,
125689
+ {
125690
+ minimal: true,
125691
+ small: true,
125692
+ "data-tip": "Previous Difference",
125693
+ icon: "arrow-left",
125694
+ intent: core.Intent.PRIMARY,
125695
+ onClick: prevDifference,
125696
+ disabled: disablePrev
125697
+ }
125698
+ ), /* @__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(
125699
+ core.Button,
125700
+ {
125701
+ minimal: true,
125702
+ small: true,
125703
+ "data-tip": "Next Difference",
125704
+ icon: "arrow-right",
125705
+ intent: core.Intent.PRIMARY,
125706
+ onClick: nextDifference,
125707
+ disabled: disableNext
125708
+ }
125709
+ )));
125710
+ }
125711
+ __name(FindMismatches, "FindMismatches");
125712
+ function getGapMap(sequence2) {
125713
+ const gapMap = [0];
125714
+ sequence2.split("").forEach((char) => {
125715
+ if (char === "-") {
125716
+ gapMap[Math.max(0, gapMap.length - 1)] = (gapMap[Math.max(0, gapMap.length - 1)] || 0) + 1;
125717
+ } else {
125718
+ gapMap.push(gapMap[gapMap.length - 1] || 0);
125719
+ }
125720
+ });
125721
+ return gapMap;
125722
+ }
125723
+ __name(getGapMap, "getGapMap");
125724
+ const MATCH_COLOR = "gold";
125725
+ const CURRENT_MATCH_COLOR = "green";
125726
+ const MISMATCH_COLOR = "red";
125727
+ const ANNOTATION_TYPES = ["features", "parts", "primers"];
125728
+ const initialSearchState = {
125729
+ searchText: "",
125730
+ matches: [],
125731
+ currentMatchIndex: 0,
125732
+ searched: false,
125733
+ featureMatches: [],
125734
+ dnaOrAA: "DNA",
125735
+ ambiguousOrLiteral: "LITERAL",
125736
+ mismatchesAllowed: 0
125737
+ };
125738
+ function searchReducer(state2, action2) {
125739
+ switch (action2.type) {
125740
+ case "SET_SEARCH_TEXT":
125741
+ return __spreadProps(__spreadValues({}, state2), { searchText: action2.payload });
125742
+ case "SET_MATCHES":
125743
+ return __spreadProps(__spreadValues({}, state2), {
125744
+ matches: action2.payload.matches,
125745
+ currentMatchIndex: action2.payload.currentMatchIndex
125746
+ });
125747
+ case "SET_CURRENT_MATCH_INDEX":
125748
+ return __spreadProps(__spreadValues({}, state2), { currentMatchIndex: action2.payload });
125749
+ case "SET_SEARCHED":
125750
+ return __spreadProps(__spreadValues({}, state2), { searched: action2.payload });
125751
+ case "SEARCH_COMPLETE":
125752
+ return __spreadProps(__spreadValues({}, state2), {
125753
+ matches: action2.payload.matches,
125754
+ currentMatchIndex: action2.payload.currentMatchIndex,
125755
+ searched: action2.payload.searched
125756
+ });
125757
+ case "SET_FEATURE_MATCHES":
125758
+ return __spreadProps(__spreadValues({}, state2), { featureMatches: action2.payload });
125759
+ case "SET_DNA_OR_AA":
125760
+ return __spreadProps(__spreadValues({}, state2), { dnaOrAA: action2.payload });
125761
+ case "SET_AMBIGUOUS_OR_LITERAL":
125762
+ return __spreadProps(__spreadValues({}, state2), { ambiguousOrLiteral: action2.payload });
125763
+ case "SET_MISMATCHES_ALLOWED":
125764
+ return __spreadProps(__spreadValues({}, state2), { mismatchesAllowed: Math.max(0, action2.payload) });
125765
+ case "RESET":
125766
+ return __spreadValues({}, initialSearchState);
125767
+ default:
125768
+ return state2;
125769
+ }
125770
+ }
125771
+ __name(searchReducer, "searchReducer");
125772
+ function AlignmentSearchBar(props) {
125773
+ const { alignmentTracks = [], setSearchMatchLayers } = props;
125774
+ const [searchState, dispatch] = React.useReducer(searchReducer, initialSearchState);
125775
+ const {
125776
+ searchText,
125777
+ matches,
125778
+ currentMatchIndex,
125779
+ searched,
125780
+ featureMatches,
125781
+ dnaOrAA,
125782
+ ambiguousOrLiteral,
125783
+ mismatchesAllowed
125784
+ } = searchState;
125785
+ const debouncedSearch = React.useRef(
125786
+ debounce$1((text2, search2, featureSearch) => {
125787
+ search2(text2);
125788
+ featureSearch(text2);
125789
+ }, 50)
125790
+ ).current;
125791
+ React.useEffect(() => {
125792
+ return () => {
125793
+ debouncedSearch.cancel();
125794
+ };
125795
+ }, [debouncedSearch]);
125796
+ const [highlightAll, setHighlightAll] = React.useState(false);
125797
+ const [isExpanded, setIsExpanded] = React.useState(false);
125798
+ const [isOpen, setIsOpen] = React.useState(false);
125799
+ const [isPopoverOpen, setIsPopoverOpen] = React.useState(false);
125800
+ const handleToggleExpanded = React.useCallback(() => {
125801
+ setIsExpanded((prev) => {
125802
+ const next = !prev;
125803
+ if (!next) setIsPopoverOpen(true);
125804
+ return next;
125805
+ });
125806
+ }, [setIsPopoverOpen]);
125807
+ React.useEffect(() => {
125808
+ dispatch({ type: "RESET" });
125809
+ if (setSearchMatchLayers) setSearchMatchLayers([]);
125810
+ }, [setSearchMatchLayers]);
125811
+ const buildMatchLayers = React.useCallback(
125812
+ (allMatches, activeIndex) => {
125813
+ if (!setSearchMatchLayers) return;
125814
+ if (!allMatches.length) {
125815
+ setSearchMatchLayers([]);
125816
+ return;
125817
+ }
125818
+ const makeMismatchLayers = /* @__PURE__ */ __name((match) => (match.mismatchAlignmentPositions || []).map((pos) => ({
125819
+ start: pos,
125820
+ end: pos,
125821
+ color: MISMATCH_COLOR,
125822
+ className: "veSearchMismatch",
125823
+ ignoreGaps: true,
125824
+ hideCarets: true
125825
+ })), "makeMismatchLayers");
125826
+ const layers = highlightAll ? allMatches.flatMap((match, i) => [
125827
+ {
125828
+ start: match.alignmentStart,
125829
+ end: match.alignmentEnd,
125830
+ color: i === activeIndex ? CURRENT_MATCH_COLOR : MATCH_COLOR,
125831
+ className: i === activeIndex ? "veSearchLayerActive" : "veSearchLayer",
125832
+ ignoreGaps: true
125833
+ },
125834
+ ...makeMismatchLayers(match)
125835
+ ]) : [
125836
+ {
125837
+ start: allMatches[activeIndex].alignmentStart,
125838
+ end: allMatches[activeIndex].alignmentEnd,
125839
+ color: CURRENT_MATCH_COLOR,
125840
+ className: "veSearchLayerActive",
125841
+ ignoreGaps: true
125842
+ },
125843
+ ...makeMismatchLayers(allMatches[activeIndex])
125844
+ ];
125845
+ setSearchMatchLayers(layers);
125846
+ },
125847
+ [setSearchMatchLayers, highlightAll]
125848
+ );
125849
+ const navigateTo = React.useCallback(
125850
+ (allMatches, index2) => {
125851
+ const match = allMatches[index2];
125852
+ if (!match) return;
125853
+ updateCaretPosition({
125854
+ start: match.alignmentStart,
125855
+ end: match.alignmentEnd
125856
+ });
125857
+ setTimeout(() => {
125858
+ scrollToAlignmentSelection();
125859
+ }, 0);
125860
+ buildMatchLayers(allMatches, index2);
125861
+ },
125862
+ [buildMatchLayers]
125863
+ );
125864
+ const runSearch = React.useCallback(
125865
+ (text2) => {
125866
+ const query = text2.trim();
125867
+ if (!query) {
125868
+ dispatch({
125869
+ type: "SEARCH_COMPLETE",
125870
+ payload: { matches: [], currentMatchIndex: 0, searched: false }
125871
+ });
125872
+ if (setSearchMatchLayers) setSearchMatchLayers([]);
125873
+ return;
125874
+ }
125875
+ const allMatches = [];
125876
+ alignmentTracks.slice(0, 1).forEach((track, trackIndex) => {
125877
+ var _a2, _b2;
125878
+ const rawSeq = ((_a2 = track.sequenceData) == null ? void 0 : _a2.sequence) || "";
125879
+ const alignedSeq = ((_b2 = track.alignmentData) == null ? void 0 : _b2.sequence) || "";
125880
+ const gapMap = getGapMap(alignedSeq);
125881
+ const gapOffset = /* @__PURE__ */ __name((n2) => {
125882
+ var _a3, _b3;
125883
+ return (_b3 = (_a3 = gapMap[n2]) != null ? _a3 : gapMap[gapMap.length - 1]) != null ? _b3 : 0;
125884
+ }, "gapOffset");
125885
+ let seqMatches = [];
125886
+ if (dnaOrAA === "DNA" && ambiguousOrLiteral === "LITERAL" && mismatchesAllowed > 0) {
125887
+ const approxMatches = findApproxMatches(
125888
+ query.toLowerCase(),
125889
+ rawSeq.toLowerCase(),
125890
+ mismatchesAllowed,
125891
+ false
125892
+ );
125893
+ seqMatches = approxMatches.map((m2) => ({
125894
+ start: m2.index,
125895
+ end: m2.index + m2.match.length - 1,
125896
+ mismatchPositions: m2.mismatchPositions
125897
+ }));
125898
+ } else {
125899
+ seqMatches = findSequenceMatches(rawSeq, query, {
125900
+ isCircular: false,
125901
+ isAmbiguous: ambiguousOrLiteral === "AMBIGUOUS",
125902
+ isProteinSearch: dnaOrAA !== "DNA",
125903
+ searchReverseStrand: dnaOrAA === "DNA"
125904
+ });
125905
+ }
125906
+ const hitsToProcess = query.length < 2 ? seqMatches.slice(0, 1) : seqMatches;
125907
+ hitsToProcess.forEach(({ start: start2, end: end2, mismatchPositions }) => {
125908
+ const alignmentStart = start2 + gapOffset(start2);
125909
+ const alignmentEnd = end2 + gapOffset(end2);
125910
+ const mismatchAlignmentPositions = (mismatchPositions || []).map(
125911
+ (p2) => {
125912
+ const absPos = start2 + p2;
125913
+ return absPos + gapOffset(absPos);
125914
+ }
125915
+ );
125916
+ allMatches.push({
125917
+ trackIndex,
125918
+ alignmentStart,
125919
+ alignmentEnd,
125920
+ mismatchAlignmentPositions
125921
+ });
125922
+ });
125923
+ });
125924
+ const results = query.length < 2 ? allMatches.slice(0, 1) : allMatches;
125925
+ dispatch({
125926
+ type: "SEARCH_COMPLETE",
125927
+ payload: { matches: results, currentMatchIndex: 0, searched: true }
125928
+ });
125929
+ if (results.length) {
125930
+ navigateTo(results, 0);
125931
+ } else {
125932
+ if (setSearchMatchLayers) setSearchMatchLayers([]);
125933
+ }
125934
+ },
125935
+ [
125936
+ alignmentTracks,
125937
+ navigateTo,
125938
+ dnaOrAA,
125939
+ ambiguousOrLiteral,
125940
+ mismatchesAllowed,
125941
+ setSearchMatchLayers
125942
+ ]
125943
+ );
125944
+ const runFeatureSearch = React.useCallback(
125945
+ (text2) => {
125946
+ const query = text2.trim().toLowerCase();
125947
+ if (!query) {
125948
+ dispatch({ type: "SET_FEATURE_MATCHES", payload: [] });
125949
+ return;
125950
+ }
125951
+ const allMatches = [];
125952
+ alignmentTracks.slice(0, 1).forEach((track, trackIndex) => {
125953
+ const { sequenceData: sequenceData2, alignmentData } = track;
125954
+ const alignedSeq = (alignmentData == null ? void 0 : alignmentData.sequence) || "";
125955
+ const gapMap = getGapMap(alignedSeq);
125956
+ const gapOffset = /* @__PURE__ */ __name((n2) => {
125957
+ var _a2, _b2;
125958
+ return (_b2 = (_a2 = gapMap[n2]) != null ? _a2 : gapMap[gapMap.length - 1]) != null ? _b2 : 0;
125959
+ }, "gapOffset");
125960
+ const trackName = (alignmentData == null ? void 0 : alignmentData.name) || (sequenceData2 == null ? void 0 : sequenceData2.name) || (sequenceData2 == null ? void 0 : sequenceData2.id) || "";
125961
+ ANNOTATION_TYPES.forEach((type2) => {
125962
+ const anns = sequenceData2 == null ? void 0 : sequenceData2[type2];
125963
+ if (!anns) return;
125964
+ const annsArray = Array.isArray(anns) ? anns : Object.values(anns);
125965
+ annsArray.forEach((ann) => {
125966
+ if (!ann.name) return;
125967
+ if (ann.name.toLowerCase().includes(query)) {
125968
+ const alignmentStart = ann.start + gapOffset(ann.start);
125969
+ const alignmentEnd = ann.end + gapOffset(ann.end);
125970
+ allMatches.push({
125971
+ trackIndex,
125972
+ trackName,
125973
+ type: type2,
125974
+ annotation: ann,
125975
+ alignmentStart,
125976
+ alignmentEnd
125977
+ });
125978
+ }
125979
+ });
125980
+ });
125981
+ });
125982
+ dispatch({ type: "SET_FEATURE_MATCHES", payload: allMatches });
125983
+ },
125984
+ [alignmentTracks]
125985
+ );
125986
+ const goToPrev = React.useCallback(() => {
125987
+ if (!matches.length) return;
125988
+ const newIndex = currentMatchIndex === 0 ? matches.length - 1 : currentMatchIndex - 1;
125989
+ dispatch({ type: "SET_CURRENT_MATCH_INDEX", payload: newIndex });
125990
+ navigateTo(matches, newIndex);
125991
+ }, [matches, currentMatchIndex, navigateTo]);
125992
+ const goToNext = React.useCallback(() => {
125993
+ if (!matches.length) return;
125994
+ const newIndex = currentMatchIndex === matches.length - 1 ? 0 : currentMatchIndex + 1;
125995
+ dispatch({ type: "SET_CURRENT_MATCH_INDEX", payload: newIndex });
125996
+ navigateTo(matches, newIndex);
125997
+ }, [matches, currentMatchIndex, navigateTo]);
125998
+ const handleKeyDown = React.useCallback(
125999
+ (e) => {
126000
+ if (e.key === "Escape") {
126001
+ setIsOpen(false);
126002
+ }
126003
+ if (e.key === "Enter") {
126004
+ if (e.shiftKey) {
126005
+ goToPrev();
126006
+ } else {
126007
+ goToNext();
126008
+ }
126009
+ e.preventDefault();
126010
+ e.stopPropagation();
126011
+ }
126012
+ },
126013
+ [goToPrev, goToNext]
126014
+ );
126015
+ React.useEffect(() => {
126016
+ if (!searched || !searchText.trim()) return;
126017
+ runSearch(searchText);
126018
+ runFeatureSearch(searchText);
126019
+ }, [
126020
+ dnaOrAA,
126021
+ ambiguousOrLiteral,
126022
+ mismatchesAllowed,
126023
+ runSearch,
126024
+ runFeatureSearch,
126025
+ searched,
126026
+ searchText
126027
+ ]);
126028
+ React.useEffect(() => {
126029
+ if (searchText.trim().length < 1) setHighlightAll(false);
126030
+ }, [searchText]);
126031
+ const prevHighlightAll = React.useRef(highlightAll);
126032
+ React.useEffect(() => {
126033
+ if (prevHighlightAll.current !== highlightAll) {
126034
+ prevHighlightAll.current = highlightAll;
126035
+ if (matches.length) buildMatchLayers(matches, currentMatchIndex);
126036
+ }
126037
+ }, [highlightAll, matches, currentMatchIndex, buildMatchLayers]);
126038
+ const hasMatches = matches.length > 0;
126039
+ const handleChange = React.useCallback(
126040
+ (e) => {
126041
+ const value = e.target.value;
126042
+ dispatch({ type: "SET_SEARCH_TEXT", payload: value });
126043
+ debouncedSearch(value, runSearch, runFeatureSearch);
126044
+ },
126045
+ [debouncedSearch, runSearch, runFeatureSearch]
126046
+ );
126047
+ const handleFeatureClick = React.useCallback((featureMatch) => {
126048
+ updateCaretPosition({
126049
+ start: featureMatch.alignmentStart,
126050
+ end: featureMatch.alignmentEnd
126051
+ });
126052
+ setTimeout(() => {
126053
+ scrollToAlignmentSelection();
126054
+ }, 0);
126055
+ }, []);
126056
+ const matchCounter = /* @__PURE__ */ React.createElement(
126057
+ "span",
126058
+ {
126059
+ style: {
126060
+ marginRight: 3,
126061
+ color: "lightgrey",
126062
+ fontSize: "0.9em",
126063
+ whiteSpace: "nowrap"
126064
+ }
126065
+ },
126066
+ hasMatches ? currentMatchIndex + 1 : 0,
126067
+ "/",
126068
+ matches.length
126069
+ );
126070
+ const inlineNavEl = /* @__PURE__ */ React.createElement("span", { style: { display: "flex", alignItems: "center" } }, !isExpanded && /* @__PURE__ */ React.createElement(
126071
+ core.Popover,
126072
+ {
126073
+ autoFocus: false,
126074
+ enforceFocus: false,
126075
+ isOpen: isPopoverOpen,
126076
+ onInteraction: setIsPopoverOpen,
126077
+ position: core.Position.TOP,
126078
+ content: /* @__PURE__ */ React.createElement(
126079
+ "div",
126080
+ {
126081
+ className: "ve-find-options-popover",
126082
+ style: {
126083
+ display: "flex",
126084
+ flexDirection: "column",
126085
+ paddingLeft: 20,
126086
+ paddingBottom: 10,
126087
+ paddingTop: 10,
126088
+ paddingRight: 20,
126089
+ gap: 6
126090
+ }
126091
+ },
126092
+ /* @__PURE__ */ React.createElement(
126093
+ FindOptionsPanel,
126094
+ {
126095
+ dnaOrAA,
126096
+ ambiguousOrLiteral,
126097
+ mismatchesAllowed,
126098
+ searchText,
126099
+ matches,
126100
+ dispatch,
126101
+ highlightAll,
126102
+ setHighlightAll,
126103
+ isExpanded,
126104
+ onToggleExpanded: handleToggleExpanded
126105
+ }
126106
+ )
126107
+ ),
126108
+ target: /* @__PURE__ */ React.createElement(core.Button, { minimal: true, icon: "wrench", "data-tip": "Options" })
126109
+ }
126110
+ ), matchCounter, /* @__PURE__ */ React.createElement(
126111
+ core.Button,
126112
+ {
126113
+ minimal: true,
126114
+ small: true,
126115
+ icon: "caret-left",
126116
+ "data-tip": "Previous",
126117
+ disabled: !hasMatches,
126118
+ onClick: goToPrev
126119
+ }
126120
+ ), /* @__PURE__ */ React.createElement(
126121
+ core.Button,
126122
+ {
126123
+ minimal: true,
126124
+ small: true,
126125
+ icon: "caret-right",
126126
+ "data-tip": "Next",
126127
+ disabled: !hasMatches,
126128
+ onClick: goToNext
126129
+ }
126130
+ ), /* @__PURE__ */ React.createElement(
126131
+ core.Button,
126132
+ {
126133
+ minimal: true,
126134
+ small: true,
126135
+ "data-tip": "Close (Esc)",
126136
+ icon: "small-cross",
126137
+ onClick: /* @__PURE__ */ __name(() => setIsOpen(false), "onClick")
126138
+ }
126139
+ ));
126140
+ const expandedNavEl = /* @__PURE__ */ React.createElement("span", { style: { display: "flex", alignItems: "center" } }, matchCounter, /* @__PURE__ */ React.createElement(
126141
+ core.Button,
126142
+ {
126143
+ minimal: true,
126144
+ small: true,
126145
+ icon: "caret-up",
126146
+ disabled: !hasMatches,
126147
+ onClick: goToPrev
126148
+ }
126149
+ ), /* @__PURE__ */ React.createElement(
126150
+ core.Button,
126151
+ {
126152
+ minimal: true,
126153
+ small: true,
126154
+ icon: "caret-down",
126155
+ disabled: !hasMatches,
126156
+ onClick: goToNext
126157
+ }
126158
+ ));
126159
+ if (!isOpen) {
126160
+ return /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement(
126161
+ core.Button,
126162
+ {
126163
+ minimal: true,
126164
+ small: true,
126165
+ intent: "primary",
126166
+ icon: "search",
126167
+ rightIcon: "caret-right",
126168
+ "data-tip": "Search",
126169
+ onClick: /* @__PURE__ */ __name(() => setIsOpen(true), "onClick")
126170
+ }
126171
+ ));
126172
+ }
126173
+ const annotationPopoverOpen = searched && featureMatches.length > 0;
126174
+ const inputEl = /* @__PURE__ */ React.createElement(
126175
+ core.InputGroup,
126176
+ {
126177
+ className: "tg-find-tool-input alignment-search-bar",
126178
+ leftIcon: "search",
126179
+ placeholder: "Search...",
126180
+ autoFocus: true,
126181
+ value: searchText,
126182
+ onChange: handleChange,
126183
+ onKeyDown: handleKeyDown,
126184
+ rightElement: inlineNavEl
126185
+ }
126186
+ );
126187
+ return /* @__PURE__ */ React.createElement("div", { style: { position: "relative" } }, !isExpanded && /* @__PURE__ */ React.createElement(
126188
+ core.Popover,
126189
+ {
126190
+ autoFocus: false,
126191
+ enforceFocus: false,
126192
+ modifiers: {
126193
+ arrow: false
126194
+ },
126195
+ position: core.Position.BOTTOM,
126196
+ isOpen: annotationPopoverOpen,
126197
+ content: /* @__PURE__ */ React.createElement(
126198
+ AnnotationResultsComp,
126199
+ {
126200
+ featureMatches,
126201
+ onClickMatch: handleFeatureClick
126202
+ }
126203
+ ),
126204
+ target: inputEl
126205
+ }
126206
+ ), isExpanded && /* @__PURE__ */ React.createElement(
126207
+ "div",
126208
+ {
126209
+ style: {
126210
+ position: "absolute",
126211
+ top: 0,
126212
+ left: 0,
126213
+ padding: 10,
126214
+ paddingBottom: 25,
126215
+ display: "flex",
126216
+ alignItems: "flex-start",
126217
+ gap: 10,
126218
+ zIndex: 5e4,
126219
+ background: "white",
126220
+ boxShadow: "0 2px 8px rgba(0,0,0,0.2)",
126221
+ borderRadius: 3
126222
+ }
126223
+ },
126224
+ /* @__PURE__ */ React.createElement("div", { style: { display: "flex", flexDirection: "column", gap: 6 } }, /* @__PURE__ */ React.createElement(
126225
+ core.TextArea,
126226
+ {
126227
+ autoFocus: true,
126228
+ placeholder: "Search sequences and annotations...",
126229
+ value: searchText,
126230
+ onChange: handleChange,
126231
+ onKeyDown: handleKeyDown,
126232
+ style: { resize: "vertical", width: 350, height: 190 }
126233
+ }
126234
+ ), annotationPopoverOpen && /* @__PURE__ */ React.createElement(
126235
+ AnnotationResultsComp,
126236
+ {
126237
+ featureMatches,
126238
+ onClickMatch: handleFeatureClick
126239
+ }
126240
+ )),
126241
+ /* @__PURE__ */ React.createElement("div", { style: { display: "flex", flexDirection: "column", gap: 5 } }, expandedNavEl, /* @__PURE__ */ React.createElement(
126242
+ FindOptionsPanel,
126243
+ {
126244
+ dnaOrAA,
126245
+ ambiguousOrLiteral,
126246
+ mismatchesAllowed,
126247
+ searchText,
126248
+ matches,
126249
+ dispatch,
126250
+ highlightAll,
126251
+ setHighlightAll,
126252
+ isExpanded,
126253
+ onToggleExpanded: handleToggleExpanded
126254
+ }
126255
+ )),
126256
+ /* @__PURE__ */ React.createElement(
126257
+ core.Button,
126258
+ {
126259
+ minimal: true,
126260
+ style: { position: "absolute", bottom: 0, right: 0 },
126261
+ onClick: /* @__PURE__ */ __name(() => setIsOpen(false), "onClick"),
126262
+ icon: "cross"
126263
+ }
126264
+ )
126265
+ ));
126266
+ }
126267
+ __name(AlignmentSearchBar, "AlignmentSearchBar");
126268
+ function AnnotationResultsComp({ featureMatches, onClickMatch }) {
126269
+ const byType = {};
126270
+ ANNOTATION_TYPES.forEach((type2) => {
126271
+ byType[type2] = [];
126272
+ });
126273
+ featureMatches.forEach((match) => {
126274
+ if (byType[match.type]) {
126275
+ byType[match.type].push(match);
126276
+ }
126277
+ });
126278
+ const featureColorMap = getFeatureToColorMap({ includeHidden: true });
126279
+ return /* @__PURE__ */ React.createElement("div", { className: "veAnnotationFindMatches" }, ANNOTATION_TYPES.map((type2) => {
126280
+ const anns = byType[type2];
126281
+ if (!anns.length) return null;
126282
+ const showing = anns.slice(0, 10);
126283
+ return /* @__PURE__ */ React.createElement("div", { key: type2 }, /* @__PURE__ */ React.createElement("div", { className: "veAnnotationFoundType" }, anns.length, " ", getSingular(type2), " match", anns.length > 1 ? "es" : null, anns.length > 10 ? ` (only showing 10)` : null, ":"), /* @__PURE__ */ React.createElement("div", null, showing.map((match, i) => {
126284
+ const { annotation } = match;
126285
+ const annotationColor = type2 === "parts" ? "#ac68cc" : annotation.color || featureColorMap[annotation.type];
126286
+ return /* @__PURE__ */ React.createElement(
126287
+ "div",
126288
+ {
126289
+ key: i,
126290
+ onClick: /* @__PURE__ */ __name(() => onClickMatch(match), "onClick"),
126291
+ className: "veAnnotationFoundResult"
126292
+ },
126293
+ /* @__PURE__ */ React.createElement("div", { style: { display: "flex", alignItems: "center" } }, /* @__PURE__ */ React.createElement(
126294
+ "div",
126295
+ {
126296
+ style: {
126297
+ background: annotationColor,
126298
+ height: 15,
126299
+ width: 15,
126300
+ marginRight: 3
126301
+ }
126302
+ }
126303
+ ), annotation.name),
126304
+ /* @__PURE__ */ React.createElement("div", { className: "veAnnotationFoundResultRange" }, annotation.start + 1, "-", annotation.end + 1)
126305
+ );
126306
+ })));
126307
+ }));
126308
+ }
126309
+ __name(AnnotationResultsComp, "AnnotationResultsComp");
126310
+ function FindOptionsPanel({
126311
+ dnaOrAA,
126312
+ ambiguousOrLiteral,
126313
+ mismatchesAllowed,
126314
+ searchText,
126315
+ matches,
126316
+ dispatch,
126317
+ highlightAll,
126318
+ setHighlightAll,
126319
+ isExpanded,
126320
+ onToggleExpanded
126321
+ }) {
126322
+ return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(
126323
+ TgHTMLSelect,
126324
+ {
126325
+ options: [
126326
+ { label: "DNA", value: "DNA" },
126327
+ { label: "Amino Acids", value: "AA" }
126328
+ ],
126329
+ value: dnaOrAA,
126330
+ onChange: /* @__PURE__ */ __name((e) => dispatch({ type: "SET_DNA_OR_AA", payload: e.target.value }), "onChange")
126331
+ }
126332
+ ), /* @__PURE__ */ React.createElement("div", { style: { display: "flex" } }, /* @__PURE__ */ React.createElement(
126333
+ TgHTMLSelect,
126334
+ {
126335
+ options: [
126336
+ { label: "Literal", value: "LITERAL" },
126337
+ { label: "Ambiguous", value: "AMBIGUOUS" }
126338
+ ],
126339
+ value: ambiguousOrLiteral,
126340
+ onChange: /* @__PURE__ */ __name((e) => dispatch({
126341
+ type: "SET_AMBIGUOUS_OR_LITERAL",
126342
+ payload: e.target.value
126343
+ }), "onChange")
126344
+ }
126345
+ ), /* @__PURE__ */ React.createElement(InfoHelper, { style: { marginLeft: 10 } }, /* @__PURE__ */ React.createElement("div", null, "Ambiguous substitutions:", /* @__PURE__ */ React.createElement("div", { style: { display: "flex", fontSize: 12 } }, /* @__PURE__ */ React.createElement("div", { style: { marginRight: 20 } }, /* @__PURE__ */ React.createElement("div", { style: { fontSize: 14, marginBottom: 4, marginTop: 5 } }, "DNA:"), /* @__PURE__ */ React.createElement("div", null, "M: AC"), /* @__PURE__ */ React.createElement("div", null, "R: AG"), /* @__PURE__ */ React.createElement("div", null, "W: AT"), /* @__PURE__ */ React.createElement("div", null, "S: CG"), /* @__PURE__ */ React.createElement("div", null, "Y: CT"), /* @__PURE__ */ React.createElement("div", null, "K: GT"), /* @__PURE__ */ React.createElement("div", null, "V: ACG"), /* @__PURE__ */ React.createElement("div", null, "H: ACT"), /* @__PURE__ */ React.createElement("div", null, "D: AGT"), /* @__PURE__ */ React.createElement("div", null, "B: CGT"), /* @__PURE__ */ React.createElement("div", null, "X: GATC"), /* @__PURE__ */ React.createElement("div", null, "N: GATC"), /* @__PURE__ */ React.createElement("div", null, "*: any")), /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("div", { style: { fontSize: 14, marginBottom: 4, marginTop: 5 } }, "AA:"), /* @__PURE__ */ React.createElement("div", null, "B: ND"), /* @__PURE__ */ React.createElement("div", null, "J: IL"), /* @__PURE__ */ React.createElement("div", null, "X: ACDEFGHIKLMNPQRSTVWY"), /* @__PURE__ */ React.createElement("div", null, "Z: QE"), /* @__PURE__ */ React.createElement("div", null, "*: any")))))), /* @__PURE__ */ React.createElement(
126346
+ "div",
126347
+ {
126348
+ style: {
126349
+ marginTop: "8px",
126350
+ display: "flex",
126351
+ flexDirection: "row",
126352
+ gap: "3px",
126353
+ alignItems: "center"
126354
+ }
126355
+ },
126356
+ /* @__PURE__ */ React.createElement("label", null, "Mismatches Allowed:"),
126357
+ /* @__PURE__ */ React.createElement(
126358
+ core.NumericInput,
126359
+ {
126360
+ min: 0,
126361
+ max: 10,
126362
+ className: "tg-mismatches-allowed-input",
126363
+ style: { width: "60px" },
126364
+ value: mismatchesAllowed,
126365
+ disabled: dnaOrAA !== "DNA" || ambiguousOrLiteral !== "LITERAL",
126366
+ onValueChange: /* @__PURE__ */ __name((value) => dispatch({
126367
+ type: "SET_MISMATCHES_ALLOWED",
126368
+ payload: Number.parseInt(value, 10) || 0
126369
+ }), "onValueChange")
126370
+ }
126371
+ ),
126372
+ /* @__PURE__ */ React.createElement(InfoHelper, { style: { marginLeft: 10 } }, /* @__PURE__ */ React.createElement("div", null, "Number of mismatches allowed when searching DNA sequences with literal matching.", /* @__PURE__ */ React.createElement("br", null), /* @__PURE__ */ React.createElement("br", null), "Higher values may slow down search performance."))
126373
+ ), /* @__PURE__ */ React.createElement(
126374
+ core.Switch,
126375
+ {
126376
+ checked: highlightAll,
126377
+ onChange: /* @__PURE__ */ __name(() => setHighlightAll((v2) => !v2), "onChange"),
126378
+ disabled: searchText.trim().length < 2 || matches.length > MAX_MATCHES_DISPLAYED
126379
+ },
126380
+ /* @__PURE__ */ React.createElement(
126381
+ core.Tooltip,
126382
+ {
126383
+ disabled: matches.length <= MAX_MATCHES_DISPLAYED,
126384
+ content: `Disabled because there are >${MAX_MATCHES_DISPLAYED} matches`
126385
+ },
126386
+ "Highlight All"
126387
+ )
126388
+ ), /* @__PURE__ */ React.createElement(core.Switch, { checked: isExpanded, onChange: onToggleExpanded }, "Expanded"));
126389
+ }
126390
+ __name(FindOptionsPanel, "FindOptionsPanel");
125459
126391
  const AlignmentVisibilityTool = pure(/* @__PURE__ */ __name(function AlignmentVisibilityTool2(props) {
125460
126392
  return /* @__PURE__ */ React.createElement(
125461
126393
  core.Popover,
@@ -125463,17 +126395,18 @@ const AlignmentVisibilityTool = pure(/* @__PURE__ */ __name(function AlignmentVi
125463
126395
  minimal: true,
125464
126396
  position: "bottom",
125465
126397
  content: /* @__PURE__ */ React.createElement(VisibilityOptions$2, __spreadValues({}, props)),
125466
- target: /* @__PURE__ */ React.createElement(core.Tooltip, { content: "Visibility Options" }, /* @__PURE__ */ React.createElement(
126398
+ target: /* @__PURE__ */ React.createElement(
125467
126399
  core.Button,
125468
126400
  {
125469
126401
  className: "tg-alignment-visibility-toggle",
125470
126402
  small: true,
126403
+ "data-tip": "Visibility Options",
125471
126404
  rightIcon: "caret-down",
125472
126405
  intent: core.Intent.PRIMARY,
125473
126406
  minimal: true,
125474
126407
  icon: "eye-open"
125475
126408
  }
125476
- ))
126409
+ )
125477
126410
  }
125478
126411
  );
125479
126412
  }, "AlignmentVisibilityTool"));
@@ -138639,7 +139572,6 @@ const aminoAcidShortNames = {
138639
139572
  const PropertySidePanel = /* @__PURE__ */ __name(({ properties: properties2, setProperties, style: style2 }) => {
138640
139573
  const sidebarRef = React.useRef(null);
138641
139574
  const [mismatchesCount, setMismatchesCount] = React.useState(0);
138642
- const [mismatchesInRange, setMismatchesInRange] = React.useState(0);
138643
139575
  const { track, isOpen, selection, isPairwise } = properties2;
138644
139576
  const getSequenceInRegion = React.useCallback(() => {
138645
139577
  var _a2, _b2;
@@ -138658,6 +139590,30 @@ const PropertySidePanel = /* @__PURE__ */ __name(({ properties: properties2, set
138658
139590
  if (!Array.isArray(tr)) return [];
138659
139591
  return isPairwise ? tr.filter((m2) => (m2 == null ? void 0 : m2.color) === "red") : tr;
138660
139592
  }, [track, mismatchKey, isPairwise]);
139593
+ const mismatchSchema = React.useMemo(
139594
+ () => ({
139595
+ fields: [
139596
+ {
139597
+ path: "start",
139598
+ type: "number",
139599
+ displayName: "Start",
139600
+ render: /* @__PURE__ */ __name((val2) => val2 + 1, "render")
139601
+ },
139602
+ {
139603
+ path: "end",
139604
+ type: "number",
139605
+ displayName: "End",
139606
+ render: /* @__PURE__ */ __name((val2) => val2 + 1, "render")
139607
+ }
139608
+ ]
139609
+ }),
139610
+ []
139611
+ );
139612
+ const mismatchEntities = React.useMemo(() => {
139613
+ return (trackMismatches || []).map((m2, i) => __spreadProps(__spreadValues({}, m2), {
139614
+ id: i.toString()
139615
+ }));
139616
+ }, [trackMismatches]);
138661
139617
  React.useEffect(() => {
138662
139618
  if (!isOpen || sidebarRef.current === null || !track) {
138663
139619
  return;
@@ -138675,21 +139631,6 @@ const PropertySidePanel = /* @__PURE__ */ __name(({ properties: properties2, set
138675
139631
  }
138676
139632
  });
138677
139633
  setMismatchesCount(mismatchCount);
138678
- setMismatchesInRange(mismatchCount);
138679
- if (selection && selection.start > -1 && selection.end > -1) {
138680
- let count2 = 0;
138681
- trackMismatches == null ? void 0 : trackMismatches.forEach((tm) => {
138682
- if (tm === null || tm.start === null || tm.end === null) {
138683
- return;
138684
- }
138685
- const overlapStart = Math.max(tm.start, selection.start);
138686
- const overlapEnd = Math.min(tm.end, selection.end);
138687
- if (overlapEnd >= overlapStart) {
138688
- count2 += overlapEnd - overlapStart + 1;
138689
- }
138690
- });
138691
- setMismatchesInRange(count2);
138692
- }
138693
139634
  }, [isOpen, track, selection, trackMismatches]);
138694
139635
  const aminoFreq = React.useMemo(() => {
138695
139636
  var _a2, _b2;
@@ -138725,7 +139666,7 @@ const PropertySidePanel = /* @__PURE__ */ __name(({ properties: properties2, set
138725
139666
  width: "100%"
138726
139667
  }
138727
139668
  }
138728
- ), /* @__PURE__ */ React.createElement("h5", null, "Track Properties"), /* @__PURE__ */ React.createElement("div", { className: "bp3-tab-panel" }, /* @__PURE__ */ React.createElement(RowItem, { item: name2, title: "Name" }), /* @__PURE__ */ React.createElement(RowItem, { item: isProtein2 ? proteinSize : size, title: "Length" }), /* @__PURE__ */ React.createElement(
139669
+ ), /* @__PURE__ */ React.createElement(HeaderItem, { title: "Track Properties" }), /* @__PURE__ */ React.createElement("div", { className: "bp3-tab-panel" }, /* @__PURE__ */ React.createElement(RowItem, { item: name2, title: "Name" }), /* @__PURE__ */ React.createElement(RowItem, { item: isProtein2 ? proteinSize : size, title: "Length" }), /* @__PURE__ */ React.createElement(
138729
139670
  RowItem,
138730
139671
  {
138731
139672
  item: molecularWeight == null ? void 0 : molecularWeight.toFixed(2),
@@ -138739,18 +139680,45 @@ const PropertySidePanel = /* @__PURE__ */ __name(({ properties: properties2, set
138739
139680
  title: "Extinction Coefficient"
138740
139681
  }
138741
139682
  )), /* @__PURE__ */ React.createElement(
138742
- RowItem,
138743
- {
138744
- item: `${mismatchesInRange}/${mismatchesCount}`,
138745
- title: "Mismatches"
138746
- }
138747
- ), /* @__PURE__ */ React.createElement(
138748
139683
  RowItem,
138749
139684
  {
138750
139685
  item: selection && selection.start > -1 ? /* @__PURE__ */ React.createElement("span", null, selection.start + 1, " - ", selection.end + 1) : /* @__PURE__ */ React.createElement("span", null, "1 - ", isProtein2 ? proteinSize : size),
138751
139686
  title: "Region"
138752
139687
  }
138753
- )), /* @__PURE__ */ React.createElement("h5", null, isProtein2 ? "Amino Acid" : "Base Pair", " Frequencies"), /* @__PURE__ */ React.createElement("div", { className: "sidebar-table" }, /* @__PURE__ */ React.createElement("div", { className: "sidebar-row" }, /* @__PURE__ */ React.createElement("div", { className: "sidebar-cell" }, isProtein2 ? "Amino Acid" : "Base"), /* @__PURE__ */ React.createElement("div", { className: "sidebar-cell" }, "Count"), /* @__PURE__ */ React.createElement("div", { className: "sidebar-cell" }, "Percentage")), frequencyEntries.map(([aa, data], idx) => {
139688
+ ), /* @__PURE__ */ React.createElement(HeaderItem, { title: `Mismatches (${mismatchesCount})` }), trackMismatches && trackMismatches.length > 0 && /* @__PURE__ */ React.createElement(
139689
+ "div",
139690
+ {
139691
+ style: {
139692
+ margin: "0px 10px"
139693
+ }
139694
+ },
139695
+ /* @__PURE__ */ React.createElement(
139696
+ WrappedDT,
139697
+ {
139698
+ formName: "mismatchesTable",
139699
+ isSimple: true,
139700
+ noHeader: true,
139701
+ noFooter: true,
139702
+ withSearch: false,
139703
+ noPadding: true,
139704
+ compact: true,
139705
+ maxHeight: 150,
139706
+ entities: mismatchEntities,
139707
+ schema: mismatchSchema,
139708
+ onRowClick: /* @__PURE__ */ __name((e, row) => {
139709
+ updateCaretPosition({ start: row.start, end: row.end });
139710
+ setTimeout(() => {
139711
+ scrollToAlignmentSelection();
139712
+ }, 0);
139713
+ }, "onRowClick")
139714
+ }
139715
+ )
139716
+ )), /* @__PURE__ */ React.createElement(
139717
+ HeaderItem,
139718
+ {
139719
+ title: `${isProtein2 ? "Amino Acid" : "Base Pair"} Frequencies`
139720
+ }
139721
+ ), /* @__PURE__ */ React.createElement("div", { className: "sidebar-table" }, /* @__PURE__ */ React.createElement("div", { className: "sidebar-row" }, /* @__PURE__ */ React.createElement("div", { className: "sidebar-cell" }, isProtein2 ? "Amino Acid" : "Base"), /* @__PURE__ */ React.createElement("div", { className: "sidebar-cell" }, "Count"), /* @__PURE__ */ React.createElement("div", { className: "sidebar-cell" }, "Percentage")), frequencyEntries.map(([aa, data], idx) => {
138754
139722
  return /* @__PURE__ */ React.createElement("div", { className: `sidebar-row property-amino-acid-${idx}` }, /* @__PURE__ */ React.createElement("div", { className: "sidebar-cell" }, aa, " ", isProtein2 ? `(${aminoAcidShortNames[aa]})` : ""), /* @__PURE__ */ React.createElement("div", { className: "sidebar-cell" }, data.count), /* @__PURE__ */ React.createElement("div", { className: "sidebar-cell" }, data.percentage.toFixed(1), "%"));
138755
139723
  })));
138756
139724
  } else {
@@ -138807,11 +139775,29 @@ const PropertySidePanel = /* @__PURE__ */ __name(({ properties: properties2, set
138807
139775
  );
138808
139776
  }, "PropertySidePanel");
138809
139777
  function RowItem({ item, title, units }) {
138810
- if (!item) return;
139778
+ if (item == null) {
139779
+ return null;
139780
+ }
138811
139781
  const propertyClass = title.split(" ").join("-").toLowerCase();
138812
- return /* @__PURE__ */ React.createElement("div", { className: `ve-flex-row property-${propertyClass}` }, /* @__PURE__ */ React.createElement("div", { className: "ve-column-left" }, title), /* @__PURE__ */ React.createElement("div", { className: "ve-column-right" }, item, " ", units != null ? units : ""));
139782
+ return /* @__PURE__ */ React.createElement("div", { className: `ve-flex-row property-${propertyClass}` }, /* @__PURE__ */ React.createElement("div", { style: { fontWeight: "bold" }, className: "ve-column-left" }, title), /* @__PURE__ */ React.createElement("div", { className: "ve-column-right" }, item, " ", units != null ? units : ""));
138813
139783
  }
138814
139784
  __name(RowItem, "RowItem");
139785
+ const HeaderItem = /* @__PURE__ */ __name(({ title }) => {
139786
+ return /* @__PURE__ */ React.createElement(
139787
+ "h5",
139788
+ {
139789
+ style: {
139790
+ margin: 0,
139791
+ fontSize: 15,
139792
+ fontWeight: "bold",
139793
+ textAlign: "center",
139794
+ padding: "5px 0",
139795
+ borderBottom: "1px solid #f1f1f1"
139796
+ }
139797
+ },
139798
+ title
139799
+ );
139800
+ }, "HeaderItem");
138815
139801
  function calculatePairwiseIdentity(seq1, seq2, excludeGaps = true) {
138816
139802
  if (seq1.length !== seq2.length) {
138817
139803
  throw new Error("Sequences must be aligned (same length)");
@@ -139207,6 +140193,11 @@ const AlignmentView = /* @__PURE__ */ __name((props) => {
139207
140193
  const [tempTrimBefore, setTempTrimBefore] = React.useState({});
139208
140194
  const [tempTrimAfter, setTempTrimAfter] = React.useState({});
139209
140195
  const [tempTrimmingCaret, setTempTrimmingCaret] = React.useState({});
140196
+ const [searchMatchLayers, setSearchMatchLayers] = React.useState([]);
140197
+ const [activeFilterType, setActiveFilterType] = React.useState("all");
140198
+ const handleFilterChange = React.useCallback(({ activeFilter }) => {
140199
+ setActiveFilterType(activeFilter);
140200
+ }, []);
139210
140201
  const bindOutsideChangeHelper = React.useRef({});
139211
140202
  const alignmentHolder = React.useRef(null);
139212
140203
  const alignmentHolderTop = React.useRef(null);
@@ -139931,7 +140922,12 @@ ${seqDataToCopy}\r
139931
140922
  alignmentData,
139932
140923
  chromatogramData
139933
140924
  }) : linearViewOptions)), {
139934
- additionalSelectionLayers,
140925
+ additionalSelectionLayers: [
140926
+ ...i !== 0 ? (additionalSelectionLayers || []).filter(
140927
+ (layer) => activeFilterType === "all" ? layer.differenceType !== "gap" : layer.differenceType === activeFilterType
140928
+ ) : additionalSelectionLayers || [],
140929
+ ...searchMatchLayers || []
140930
+ ],
139935
140931
  dimensions: {
139936
140932
  width: linearViewWidth
139937
140933
  },
@@ -140459,7 +141455,7 @@ ${seqDataToCopy}\r
140459
141455
  display: "flex",
140460
141456
  minHeight: "32px",
140461
141457
  width: "100%",
140462
- flexWrap: "nowrap",
141458
+ flexWrap: "wrap",
140463
141459
  flexDirection: "row",
140464
141460
  flex: "0 0 auto"
140465
141461
  },
@@ -140580,6 +141576,22 @@ ${seqDataToCopy}\r
140580
141576
  currentPairwiseAlignmentIndex
140581
141577
  }, alignmentVisibilityToolOptions)
140582
141578
  ),
141579
+ /* @__PURE__ */ React.createElement(
141580
+ AlignmentSearchBar,
141581
+ {
141582
+ alignmentTracks,
141583
+ id: id2,
141584
+ setSearchMatchLayers
141585
+ }
141586
+ ),
141587
+ /* @__PURE__ */ React.createElement(
141588
+ FindMismatches,
141589
+ {
141590
+ alignmentJson: alignmentTracks,
141591
+ id: id2,
141592
+ onFilterChange: handleFilterChange
141593
+ }
141594
+ ),
140583
141595
  additionalTopEl,
140584
141596
  saveMessage && /* @__PURE__ */ React.createElement(
140585
141597
  "div",
@@ -140680,6 +141692,7 @@ ${seqDataToCopy}\r
140680
141692
  }
140681
141693
  )),
140682
141694
  alignmentTracks,
141695
+ activeFilterType,
140683
141696
  dimensions: {
140684
141697
  width: Math.max(width, 10) || 10
140685
141698
  },
@@ -145869,108 +146882,6 @@ const schema = {
145869
146882
  ]
145870
146883
  };
145871
146884
  const DigestTool$1 = withEditorInteractions(DigestTool);
145872
- const _Mismatches = class _Mismatches extends React.Component {
145873
- constructor() {
145874
- super(...arguments);
145875
- __publicField(this, "getGapMap", /* @__PURE__ */ __name((sequence2) => {
145876
- const gapMap = [0];
145877
- sequence2.split("").forEach((char) => {
145878
- if (char === "-") {
145879
- gapMap[Math.max(0, gapMap.length - 1)] = (gapMap[Math.max(0, gapMap.length - 1)] || 0) + 1;
145880
- } else {
145881
- gapMap.push(gapMap[gapMap.length - 1] || 0);
145882
- }
145883
- });
145884
- return gapMap;
145885
- }, "getGapMap"));
145886
- __publicField(this, "getMismatchList", /* @__PURE__ */ __name((alignmentData, mismatches) => {
145887
- const mismatchList = [];
145888
- let getGaps = /* @__PURE__ */ __name(() => ({
145889
- gapsBefore: 0,
145890
- gapsInside: 0
145891
- }), "getGaps");
145892
- const gapMap = this.getGapMap(alignmentData.sequence);
145893
- getGaps = /* @__PURE__ */ __name((rangeOrCaretPosition) => {
145894
- if (typeof rangeOrCaretPosition !== "object") {
145895
- return {
145896
- gapsBefore: gapMap[Math.min(rangeOrCaretPosition, gapMap.length - 1)]
145897
- };
145898
- }
145899
- const { start: start2, end: end2 } = rangeOrCaretPosition;
145900
- const toReturn = {
145901
- gapsBefore: gapMap[start2],
145902
- gapsInside: gapMap[Math.min(end2, gapMap.length - 1)] - gapMap[Math.min(start2, gapMap.length - 1)]
145903
- };
145904
- return toReturn;
145905
- }, "getGaps");
145906
- const gapsBeforeSequence = getGaps(0).gapsBefore;
145907
- for (let mismatchI = 0; mismatchI < mismatches.length; mismatchI++) {
145908
- const mismatchEnd = mismatches[mismatchI].end;
145909
- const mismatchStart = mismatches[mismatchI].start;
145910
- const mismatchDifference = mismatchEnd - mismatchStart;
145911
- if (mismatchDifference === 0) {
145912
- mismatchList.push({
145913
- mismatches: mismatchStart + 1 - gapsBeforeSequence,
145914
- start: mismatchStart - gapsBeforeSequence,
145915
- end: mismatchStart - gapsBeforeSequence
145916
- });
145917
- } else {
145918
- for (let innerI = 0; innerI <= mismatchDifference; innerI++) {
145919
- mismatchList.push({
145920
- mismatches: mismatchStart + innerI + 1 - gapsBeforeSequence,
145921
- start: mismatchStart + innerI - gapsBeforeSequence,
145922
- end: mismatchStart + innerI - gapsBeforeSequence
145923
- });
145924
- }
145925
- }
145926
- }
145927
- return mismatchList;
145928
- }, "getMismatchList"));
145929
- }
145930
- UNSAFE_componentWillMount() {
145931
- const { alignmentData, mismatches } = this.props;
145932
- const mismatchList = this.getMismatchList(alignmentData, mismatches);
145933
- const schema2 = {
145934
- fields: [{ path: "mismatches", type: "number" }]
145935
- };
145936
- this.setState({ mismatchList, schema: schema2 });
145937
- }
145938
- render() {
145939
- const { mismatchList, schema: schema2 } = this.state;
145940
- let tableOfMismatches;
145941
- if (mismatchList.length === 0) {
145942
- tableOfMismatches = null;
145943
- } else {
145944
- tableOfMismatches = /* @__PURE__ */ React.createElement(
145945
- WrappedDT,
145946
- {
145947
- maxHeight: 168,
145948
- formName: "mismatchesTable",
145949
- isSimple: true,
145950
- compact: true,
145951
- noRouter: true,
145952
- schema: schema2,
145953
- entities: mismatchList
145954
- }
145955
- );
145956
- }
145957
- return /* @__PURE__ */ React.createElement("div", { style: { maxHeight: 180.8, overflowY: "scroll" } }, /* @__PURE__ */ React.createElement(
145958
- "div",
145959
- {
145960
- style: {
145961
- // margin: 10,
145962
- display: "flex",
145963
- flexDirection: "column",
145964
- alignItems: "center"
145965
- }
145966
- },
145967
- /* @__PURE__ */ React.createElement("div", { style: { width: 100, margin: 4 } }, tableOfMismatches)
145968
- ));
145969
- }
145970
- };
145971
- __name(_Mismatches, "Mismatches");
145972
- let Mismatches = _Mismatches;
145973
- const Mismatches$1 = withSelectedEntities("mismatchesTable")(Mismatches);
145974
146885
  function PCRTool(props) {
145975
146886
  const {
145976
146887
  sequenceData: sequenceData2,
@@ -146142,7 +147053,7 @@ const _panelMap = {
146142
147053
  comp: PropertiesDialog$1,
146143
147054
  panelSpecificProps: ["PropertiesProps"]
146144
147055
  },
146145
- mismatches: Mismatches$1
147056
+ mismatches: FindMismatches
146146
147057
  };
146147
147058
  const reorder = /* @__PURE__ */ __name((list2, startIndex, endIndex) => {
146148
147059
  const result = Array.from(list2);
@@ -148972,18 +149883,6 @@ __name(createAlignmentView, "createAlignmentView");
148972
149883
  window.createVectorEditor = createVectorEditor;
148973
149884
  window.createAlignmentView = createAlignmentView;
148974
149885
  window.createVersionHistoryView = createVersionHistoryView;
148975
- function getGapMap(sequence2) {
148976
- const gapMap = [0];
148977
- sequence2.split("").forEach((char) => {
148978
- if (char === "-") {
148979
- gapMap[Math.max(0, gapMap.length - 1)] = (gapMap[Math.max(0, gapMap.length - 1)] || 0) + 1;
148980
- } else {
148981
- gapMap.push(gapMap[gapMap.length - 1] || 0);
148982
- }
148983
- });
148984
- return gapMap;
148985
- }
148986
- __name(getGapMap, "getGapMap");
148987
149886
  exports.getGaps = () => ({
148988
149887
  gapsBefore: 0,
148989
149888
  gapsInside: 0