@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.es.js CHANGED
@@ -95492,14 +95492,34 @@ function addHighlightedDifferences(alignmentTracks) {
95492
95492
  track.alignmentData.sequence
95493
95493
  );
95494
95494
  const mismatches = matchHighlightRanges.filter(({ isMatch }) => !isMatch);
95495
+ const alignedSeq = track.alignmentData.sequence;
95496
+ const seqLen = alignedSeq.length;
95497
+ const startIndex = seqLen - alignedSeq.replace(/^-+/, "").length;
95498
+ const endIndex = alignedSeq.replace(/-+$/, "").length;
95499
+ const gapRanges = [
95500
+ startIndex > 0 && {
95501
+ start: 0,
95502
+ end: startIndex - 1,
95503
+ differenceType: "gap"
95504
+ },
95505
+ endIndex < seqLen && {
95506
+ start: endIndex,
95507
+ end: seqLen - 1,
95508
+ differenceType: "gap"
95509
+ }
95510
+ ].filter(Boolean);
95495
95511
  return __spreadProps(__spreadValues({}, track), {
95496
95512
  sequenceData: sequenceData2,
95497
95513
  matchHighlightRanges,
95498
- additionalSelectionLayers: matchHighlightRanges.filter(({ isMatch }) => !isMatch).map((range2) => {
95499
- return __spreadProps(__spreadValues(__spreadValues({}, range2), highlightRangeProps), {
95514
+ additionalSelectionLayers: [
95515
+ ...matchHighlightRanges.filter(({ isMatch }) => !isMatch).map((range2) => __spreadProps(__spreadValues(__spreadValues({}, range2), highlightRangeProps), {
95500
95516
  className: "veAlignmentMismatch"
95501
- });
95502
- }),
95517
+ })),
95518
+ ...gapRanges.map((range2) => __spreadProps(__spreadValues(__spreadValues({}, range2), highlightRangeProps), {
95519
+ className: "veAlignmentMismatch"
95520
+ }))
95521
+ ],
95522
+ gapRanges,
95503
95523
  mismatches
95504
95524
  });
95505
95525
  });
@@ -95633,23 +95653,30 @@ function getRangeMatchesBetweenTemplateAndNonTemplate(tempSeq, nonTempSeq) {
95633
95653
  const startIndex = seqLength - nonTempSeqWithoutLeadingDashes.length;
95634
95654
  const endIndex = seqLength - (seqLength - nonTempSeqWithoutTrailingDashes.length);
95635
95655
  for (let index2 = startIndex; index2 < endIndex; index2++) {
95636
- const isMatch = tempSeq[index2].toLowerCase() === nonTempSeq[index2].toLowerCase();
95637
- const previousRange = ranges[ranges.length - 1];
95638
- if (previousRange) {
95639
- if (previousRange.isMatch === isMatch) {
95640
- previousRange.end++;
95656
+ const tempBase = tempSeq[index2].toLowerCase();
95657
+ const nonTempBase = nonTempSeq[index2].toLowerCase();
95658
+ const isMatch = tempBase === nonTempBase;
95659
+ let differenceType = null;
95660
+ if (!isMatch) {
95661
+ if (tempBase === "-") {
95662
+ differenceType = "insertion";
95663
+ } else if (nonTempBase === "-") {
95664
+ differenceType = "deletion";
95641
95665
  } else {
95642
- ranges.push({
95643
- start: index2,
95644
- end: index2,
95645
- isMatch
95646
- });
95666
+ differenceType = "mismatch";
95647
95667
  }
95668
+ }
95669
+ const previousRange = ranges[ranges.length - 1];
95670
+ if (previousRange && previousRange.isMatch === isMatch && previousRange.differenceType === differenceType) {
95671
+ previousRange.end++;
95672
+ } else if (previousRange) {
95673
+ ranges.push({ start: index2, end: index2, isMatch, differenceType });
95648
95674
  } else {
95649
95675
  ranges.push({
95650
95676
  start: startIndex,
95651
95677
  end: startIndex,
95652
- isMatch
95678
+ isMatch,
95679
+ differenceType
95653
95680
  });
95654
95681
  }
95655
95682
  }
@@ -117227,7 +117254,7 @@ function showFileDialog({ multiple = false, onSelect }) {
117227
117254
  input.click();
117228
117255
  }
117229
117256
  __name(showFileDialog, "showFileDialog");
117230
- const version = "0.8.39";
117257
+ const version = "0.8.41";
117231
117258
  const packageJson = {
117232
117259
  version
117233
117260
  };
@@ -125096,11 +125123,13 @@ const _Minimap = class _Minimap extends React__default.Component {
125096
125123
  dimensions: { width = 200 },
125097
125124
  laneHeight,
125098
125125
  laneSpacing = 1,
125099
- isTrackSelected = []
125126
+ isTrackSelected = [],
125127
+ activeFilterType = "all"
125100
125128
  } = this.props;
125101
125129
  const charWidth2 = this.getCharWidth();
125102
125130
  const {
125103
125131
  matchHighlightRanges: _matchHighlightRanges,
125132
+ gapRanges = [],
125104
125133
  alignmentData: { trimmedRange } = {}
125105
125134
  } = alignmentTracks[i];
125106
125135
  const matchHighlightRanges = !trimmedRange ? _matchHighlightRanges : flatMap(_matchHighlightRanges, (r2) => {
@@ -125127,10 +125156,19 @@ const _Minimap = class _Minimap extends React__default.Component {
125127
125156
  charWidth2
125128
125157
  );
125129
125158
  const toAdd = `M${xStart},${y2} L${xStart + width2},${y2} L${xStart + width2},${y2 + height} L${xStart},${y2 + height}`;
125130
- if (!range2.isMatch) {
125159
+ if (!range2.isMatch && (activeFilterType === "all" || range2.differenceType === activeFilterType)) {
125131
125160
  redPath += toAdd;
125132
125161
  }
125133
125162
  });
125163
+ if (activeFilterType === "gap") {
125164
+ gapRanges.forEach((range2) => {
125165
+ const { xStart, width: width2 } = getXStartAndWidthFromNonCircularRange(
125166
+ range2,
125167
+ charWidth2
125168
+ );
125169
+ redPath += `M${xStart},${y2} L${xStart + width2},${y2} L${xStart + width2},${y2 + height} L${xStart},${y2 + height}`;
125170
+ });
125171
+ }
125134
125172
  return /* @__PURE__ */ React__default.createElement(
125135
125173
  "div",
125136
125174
  {
@@ -125160,7 +125198,8 @@ const _Minimap = class _Minimap extends React__default.Component {
125160
125198
  "scrollAlignmentView",
125161
125199
  "laneHeight",
125162
125200
  "laneSpacing",
125163
- "isTrackSelected"
125201
+ "isTrackSelected",
125202
+ "activeFilterType"
125164
125203
  ].some((key) => props[key] !== newProps[key]))
125165
125204
  return true;
125166
125205
  return false;
@@ -125438,6 +125477,899 @@ function getTrimmedRangesToDisplay({ trimmedRange, seqLen }) {
125438
125477
  return splitRangeIntoTwoPartsIfItIsCircular(inverted, seqLen);
125439
125478
  }
125440
125479
  __name(getTrimmedRangesToDisplay, "getTrimmedRangesToDisplay");
125480
+ function groupConsecutiveDifferences(differences) {
125481
+ const grouped = [];
125482
+ for (const diff of differences) {
125483
+ if (diff.type === "mismatch") {
125484
+ grouped.push(__spreadProps(__spreadValues({}, diff), { start: diff.position, end: diff.position }));
125485
+ continue;
125486
+ }
125487
+ const last2 = grouped[grouped.length - 1];
125488
+ if (last2 && last2.type === diff.type && last2.end === diff.position - 1) {
125489
+ grouped[grouped.length - 1] = __spreadProps(__spreadValues({}, last2), { end: diff.position });
125490
+ } else {
125491
+ grouped.push(__spreadProps(__spreadValues({}, diff), { start: diff.position, end: diff.position }));
125492
+ }
125493
+ }
125494
+ return grouped;
125495
+ }
125496
+ __name(groupConsecutiveDifferences, "groupConsecutiveDifferences");
125497
+ function findAlignmentDifferences(alignedSeqs) {
125498
+ var _a2;
125499
+ if (alignedSeqs.length < 2 || !((_a2 = alignedSeqs[0]) == null ? void 0 : _a2.length)) return [];
125500
+ const template = alignedSeqs[0].toLowerCase();
125501
+ const nonTemplates = alignedSeqs.slice(1).map((s2) => s2.toLowerCase());
125502
+ const trackBounds = nonTemplates.map((seq) => {
125503
+ const withoutLeading = seq.replace(/^-+/, "");
125504
+ const withoutTrailing = seq.replace(/-+$/, "");
125505
+ const start2 = seq.length - withoutLeading.length;
125506
+ const end2 = seq.length - (seq.length - withoutTrailing.length);
125507
+ return { start: start2, end: end2 };
125508
+ });
125509
+ const differences = [];
125510
+ for (let i = 0; i < template.length; i++) {
125511
+ const templateBase = template[i];
125512
+ const allNonTemplateBases = nonTemplates.map((seq) => seq[i]);
125513
+ const bases = [templateBase, ...allNonTemplateBases];
125514
+ const alignedIndices = trackBounds.reduce((acc, { start: start2, end: end2 }, idx) => {
125515
+ if (i >= start2 && i < end2) acc.push(idx);
125516
+ return acc;
125517
+ }, []);
125518
+ if (alignedIndices.length === 0) {
125519
+ differences.push({ position: i, type: "gap", bases });
125520
+ continue;
125521
+ }
125522
+ const alignedBases = alignedIndices.map((idx) => allNonTemplateBases[idx]);
125523
+ const templateIsGap = templateBase === "-";
125524
+ const nonTemplateHasBase = alignedBases.some((b3) => b3 !== "-");
125525
+ const nonTemplateHasGap = alignedBases.some((b3) => b3 === "-");
125526
+ if (templateIsGap && nonTemplateHasBase) {
125527
+ differences.push({ position: i, type: "insertion", bases });
125528
+ } else if (!templateIsGap && nonTemplateHasGap) {
125529
+ differences.push({ position: i, type: "deletion", bases });
125530
+ } else if (!templateIsGap) {
125531
+ const uniqueBases = /* @__PURE__ */ new Set([templateBase, ...alignedBases]);
125532
+ if (uniqueBases.size > 1) {
125533
+ differences.push({ position: i, type: "mismatch", bases });
125534
+ }
125535
+ }
125536
+ }
125537
+ return differences;
125538
+ }
125539
+ __name(findAlignmentDifferences, "findAlignmentDifferences");
125540
+ function scrollToAlignmentSelection() {
125541
+ const el = document.querySelector(".veCaret");
125542
+ if (el) {
125543
+ el.scrollIntoView({ inline: "center", block: "nearest" });
125544
+ }
125545
+ }
125546
+ __name(scrollToAlignmentSelection, "scrollToAlignmentSelection");
125547
+ function updateCaretPosition({ start: start2, end: end2 }) {
125548
+ if (window.updateAlignmentSelection) {
125549
+ window.updateAlignmentSelection({ start: start2, end: end2 });
125550
+ }
125551
+ }
125552
+ __name(updateCaretPosition, "updateCaretPosition");
125553
+ const FILTER_OPTIONS = [
125554
+ { value: "all", label: "All" },
125555
+ { value: "mismatch", label: "Mismatches" },
125556
+ { value: "insertion", label: "Insertions" },
125557
+ { value: "deletion", label: "Deletions" },
125558
+ { value: "gap", label: "Gaps" }
125559
+ ];
125560
+ function FindMismatches(props) {
125561
+ var _a2;
125562
+ const { alignmentJson, id: id2, onFilterChange } = props;
125563
+ const alignedSeqs = useMemo$1(
125564
+ () => alignmentJson.map((t2) => {
125565
+ var _a3;
125566
+ return ((_a3 = t2.alignmentData) == null ? void 0 : _a3.sequence) || "";
125567
+ }),
125568
+ [alignmentJson]
125569
+ );
125570
+ const [activeFilter, setActiveFilter] = React__default.useState("all");
125571
+ const allDifferences = useMemo$1(
125572
+ () => groupConsecutiveDifferences(findAlignmentDifferences(alignedSeqs)),
125573
+ [alignedSeqs]
125574
+ );
125575
+ const countsByType = useMemo$1(() => {
125576
+ const counts = { all: 0, mismatch: 0, insertion: 0, deletion: 0, gap: 0 };
125577
+ allDifferences.forEach((d2) => {
125578
+ counts[d2.type] = (counts[d2.type] || 0) + 1;
125579
+ counts.all++;
125580
+ });
125581
+ return counts;
125582
+ }, [allDifferences]);
125583
+ const differences = useMemo$1(() => {
125584
+ const filtered = activeFilter === "all" ? allDifferences : allDifferences.filter((d2) => d2.type === activeFilter);
125585
+ return [{ position: -1, start: -1, end: -1, bases: [""] }, ...filtered];
125586
+ }, [allDifferences, activeFilter]);
125587
+ const currentCaretPosition = useSelector(
125588
+ (state2) => {
125589
+ var _a3;
125590
+ return (_a3 = state2.VectorEditor.__allEditorsOptions.alignments[id2]) == null ? void 0 : _a3.caretPosition;
125591
+ }
125592
+ );
125593
+ const [currentIdx, setCurrentIdx] = React__default.useState(0);
125594
+ const currentDiff = differences[currentIdx];
125595
+ const disablePrev = currentIdx <= 1;
125596
+ const disableNext = currentIdx >= differences.length - 1;
125597
+ useEffect(() => {
125598
+ setCurrentIdx(0);
125599
+ }, [activeFilter]);
125600
+ useEffect(() => {
125601
+ onFilterChange == null ? void 0 : onFilterChange({ activeFilter });
125602
+ }, [activeFilter, onFilterChange]);
125603
+ useEffect(() => {
125604
+ if (currentCaretPosition !== -1) {
125605
+ const diffIdx = differences.findIndex(
125606
+ (d2, i) => i > 0 && currentCaretPosition >= d2.start && currentCaretPosition <= d2.end + 1
125607
+ );
125608
+ if (diffIdx !== -1 && diffIdx !== currentIdx) {
125609
+ setCurrentIdx(diffIdx);
125610
+ }
125611
+ }
125612
+ }, [currentCaretPosition, differences, currentIdx]);
125613
+ const updateView = /* @__PURE__ */ __name((diff) => {
125614
+ const idx = differences.indexOf(diff);
125615
+ const { start: start2, end: end2 } = diff;
125616
+ setCurrentIdx(idx);
125617
+ updateCaretPosition({ start: start2, end: end2 });
125618
+ setTimeout(() => {
125619
+ scrollToAlignmentSelection();
125620
+ }, 0);
125621
+ }, "updateView");
125622
+ const prevDifference = /* @__PURE__ */ __name(() => {
125623
+ var _a3, _b2;
125624
+ const pivot = currentCaretPosition >= 0 ? currentCaretPosition : (_b2 = (_a3 = differences[currentIdx]) == null ? void 0 : _a3.start) != null ? _b2 : 0;
125625
+ const prev = [...differences].reverse().find((d2) => d2.start >= 0 && d2.start < pivot);
125626
+ if (prev) updateView(prev);
125627
+ }, "prevDifference");
125628
+ const nextDifference = /* @__PURE__ */ __name(() => {
125629
+ var _a3, _b2;
125630
+ const pivot = currentCaretPosition >= 0 ? currentCaretPosition : (_b2 = (_a3 = differences[currentIdx]) == null ? void 0 : _a3.start) != null ? _b2 : -1;
125631
+ const next = differences.find((d2) => d2.start > pivot && d2.start >= 0);
125632
+ if (next) updateView(next);
125633
+ }, "nextDifference");
125634
+ const noDifferences = differences.length <= 1;
125635
+ const activeOption = FILTER_OPTIONS.find((o2) => o2.value === activeFilter);
125636
+ const activeLabel = (_a2 = activeOption == null ? void 0 : activeOption.label) != null ? _a2 : "Differences";
125637
+ const filterMenu = /* @__PURE__ */ React__default.createElement(Menu, null, FILTER_OPTIONS.map(({ value, label }) => {
125638
+ var _a3;
125639
+ const count2 = (_a3 = countsByType[value]) != null ? _a3 : 0;
125640
+ const isActive2 = activeFilter === value;
125641
+ return /* @__PURE__ */ React__default.createElement(
125642
+ MenuItem,
125643
+ {
125644
+ key: value,
125645
+ active: isActive2,
125646
+ onClick: /* @__PURE__ */ __name(() => setActiveFilter(value), "onClick"),
125647
+ text: /* @__PURE__ */ React__default.createElement("span", { className: "veDiffMenuItem-inner" }, label, /* @__PURE__ */ React__default.createElement(Tag, { round: true, minimal: true, style: { marginLeft: 6 } }, count2))
125648
+ }
125649
+ );
125650
+ }));
125651
+ return /* @__PURE__ */ React__default.createElement("div", { className: "veDiffNavigator" }, /* @__PURE__ */ React__default.createElement(
125652
+ Popover,
125653
+ {
125654
+ minimal: true,
125655
+ position: Position.BOTTOM_LEFT,
125656
+ content: filterMenu,
125657
+ target: /* @__PURE__ */ React__default.createElement(
125658
+ Button,
125659
+ {
125660
+ minimal: true,
125661
+ "data-tip": "Filter Difference Type",
125662
+ small: true,
125663
+ rightIcon: "caret-down",
125664
+ className: "veDiffFilter-trigger"
125665
+ },
125666
+ activeLabel
125667
+ )
125668
+ }
125669
+ ), noDifferences ? /* @__PURE__ */ React__default.createElement("span", { className: "veDiffNav-empty" }, "no", " ", activeFilter === "all" ? "differences" : activeLabel.toLowerCase()) : /* @__PURE__ */ React__default.createElement("div", { className: "veDiffNav" }, /* @__PURE__ */ React__default.createElement(
125670
+ Button,
125671
+ {
125672
+ minimal: true,
125673
+ small: true,
125674
+ "data-tip": "Previous Difference",
125675
+ icon: "arrow-left",
125676
+ intent: Intent.PRIMARY,
125677
+ onClick: prevDifference,
125678
+ disabled: disablePrev
125679
+ }
125680
+ ), /* @__PURE__ */ React__default.createElement("div", { className: "veDiffNav-center" }, /* @__PURE__ */ React__default.createElement("span", { className: "veDiffNav-fraction" }, currentIdx, /* @__PURE__ */ React__default.createElement("span", { className: "veDiffNav-sep" }, "/"), differences.length - 1), (currentDiff == null ? void 0 : currentDiff.start) > -1 && /* @__PURE__ */ React__default.createElement("span", { className: "veDiffNav-pos" }, ":", currentDiff.start + 1)), /* @__PURE__ */ React__default.createElement(
125681
+ Button,
125682
+ {
125683
+ minimal: true,
125684
+ small: true,
125685
+ "data-tip": "Next Difference",
125686
+ icon: "arrow-right",
125687
+ intent: Intent.PRIMARY,
125688
+ onClick: nextDifference,
125689
+ disabled: disableNext
125690
+ }
125691
+ )));
125692
+ }
125693
+ __name(FindMismatches, "FindMismatches");
125694
+ function getGapMap(sequence2) {
125695
+ const gapMap = [0];
125696
+ sequence2.split("").forEach((char) => {
125697
+ if (char === "-") {
125698
+ gapMap[Math.max(0, gapMap.length - 1)] = (gapMap[Math.max(0, gapMap.length - 1)] || 0) + 1;
125699
+ } else {
125700
+ gapMap.push(gapMap[gapMap.length - 1] || 0);
125701
+ }
125702
+ });
125703
+ return gapMap;
125704
+ }
125705
+ __name(getGapMap, "getGapMap");
125706
+ const MATCH_COLOR = "gold";
125707
+ const CURRENT_MATCH_COLOR = "green";
125708
+ const MISMATCH_COLOR = "red";
125709
+ const ANNOTATION_TYPES = ["features", "parts", "primers"];
125710
+ const initialSearchState = {
125711
+ searchText: "",
125712
+ matches: [],
125713
+ currentMatchIndex: 0,
125714
+ searched: false,
125715
+ featureMatches: [],
125716
+ dnaOrAA: "DNA",
125717
+ ambiguousOrLiteral: "LITERAL",
125718
+ mismatchesAllowed: 0
125719
+ };
125720
+ function searchReducer(state2, action2) {
125721
+ switch (action2.type) {
125722
+ case "SET_SEARCH_TEXT":
125723
+ return __spreadProps(__spreadValues({}, state2), { searchText: action2.payload });
125724
+ case "SET_MATCHES":
125725
+ return __spreadProps(__spreadValues({}, state2), {
125726
+ matches: action2.payload.matches,
125727
+ currentMatchIndex: action2.payload.currentMatchIndex
125728
+ });
125729
+ case "SET_CURRENT_MATCH_INDEX":
125730
+ return __spreadProps(__spreadValues({}, state2), { currentMatchIndex: action2.payload });
125731
+ case "SET_SEARCHED":
125732
+ return __spreadProps(__spreadValues({}, state2), { searched: action2.payload });
125733
+ case "SEARCH_COMPLETE":
125734
+ return __spreadProps(__spreadValues({}, state2), {
125735
+ matches: action2.payload.matches,
125736
+ currentMatchIndex: action2.payload.currentMatchIndex,
125737
+ searched: action2.payload.searched
125738
+ });
125739
+ case "SET_FEATURE_MATCHES":
125740
+ return __spreadProps(__spreadValues({}, state2), { featureMatches: action2.payload });
125741
+ case "SET_DNA_OR_AA":
125742
+ return __spreadProps(__spreadValues({}, state2), { dnaOrAA: action2.payload });
125743
+ case "SET_AMBIGUOUS_OR_LITERAL":
125744
+ return __spreadProps(__spreadValues({}, state2), { ambiguousOrLiteral: action2.payload });
125745
+ case "SET_MISMATCHES_ALLOWED":
125746
+ return __spreadProps(__spreadValues({}, state2), { mismatchesAllowed: Math.max(0, action2.payload) });
125747
+ case "RESET":
125748
+ return __spreadValues({}, initialSearchState);
125749
+ default:
125750
+ return state2;
125751
+ }
125752
+ }
125753
+ __name(searchReducer, "searchReducer");
125754
+ function AlignmentSearchBar(props) {
125755
+ const { alignmentTracks = [], setSearchMatchLayers } = props;
125756
+ const [searchState, dispatch] = useReducer(searchReducer, initialSearchState);
125757
+ const {
125758
+ searchText,
125759
+ matches,
125760
+ currentMatchIndex,
125761
+ searched,
125762
+ featureMatches,
125763
+ dnaOrAA,
125764
+ ambiguousOrLiteral,
125765
+ mismatchesAllowed
125766
+ } = searchState;
125767
+ const debouncedSearch = useRef(
125768
+ debounce$1((text2, search2, featureSearch) => {
125769
+ search2(text2);
125770
+ featureSearch(text2);
125771
+ }, 50)
125772
+ ).current;
125773
+ useEffect(() => {
125774
+ return () => {
125775
+ debouncedSearch.cancel();
125776
+ };
125777
+ }, [debouncedSearch]);
125778
+ const [highlightAll, setHighlightAll] = useState(false);
125779
+ const [isExpanded, setIsExpanded] = useState(false);
125780
+ const [isOpen, setIsOpen] = useState(false);
125781
+ const [isPopoverOpen, setIsPopoverOpen] = useState(false);
125782
+ const handleToggleExpanded = useCallback$1(() => {
125783
+ setIsExpanded((prev) => {
125784
+ const next = !prev;
125785
+ if (!next) setIsPopoverOpen(true);
125786
+ return next;
125787
+ });
125788
+ }, [setIsPopoverOpen]);
125789
+ useEffect(() => {
125790
+ dispatch({ type: "RESET" });
125791
+ if (setSearchMatchLayers) setSearchMatchLayers([]);
125792
+ }, [setSearchMatchLayers]);
125793
+ const buildMatchLayers = useCallback$1(
125794
+ (allMatches, activeIndex) => {
125795
+ if (!setSearchMatchLayers) return;
125796
+ if (!allMatches.length) {
125797
+ setSearchMatchLayers([]);
125798
+ return;
125799
+ }
125800
+ const makeMismatchLayers = /* @__PURE__ */ __name((match) => (match.mismatchAlignmentPositions || []).map((pos) => ({
125801
+ start: pos,
125802
+ end: pos,
125803
+ color: MISMATCH_COLOR,
125804
+ className: "veSearchMismatch",
125805
+ ignoreGaps: true,
125806
+ hideCarets: true
125807
+ })), "makeMismatchLayers");
125808
+ const layers = highlightAll ? allMatches.flatMap((match, i) => [
125809
+ {
125810
+ start: match.alignmentStart,
125811
+ end: match.alignmentEnd,
125812
+ color: i === activeIndex ? CURRENT_MATCH_COLOR : MATCH_COLOR,
125813
+ className: i === activeIndex ? "veSearchLayerActive" : "veSearchLayer",
125814
+ ignoreGaps: true
125815
+ },
125816
+ ...makeMismatchLayers(match)
125817
+ ]) : [
125818
+ {
125819
+ start: allMatches[activeIndex].alignmentStart,
125820
+ end: allMatches[activeIndex].alignmentEnd,
125821
+ color: CURRENT_MATCH_COLOR,
125822
+ className: "veSearchLayerActive",
125823
+ ignoreGaps: true
125824
+ },
125825
+ ...makeMismatchLayers(allMatches[activeIndex])
125826
+ ];
125827
+ setSearchMatchLayers(layers);
125828
+ },
125829
+ [setSearchMatchLayers, highlightAll]
125830
+ );
125831
+ const navigateTo = useCallback$1(
125832
+ (allMatches, index2) => {
125833
+ const match = allMatches[index2];
125834
+ if (!match) return;
125835
+ updateCaretPosition({
125836
+ start: match.alignmentStart,
125837
+ end: match.alignmentEnd
125838
+ });
125839
+ setTimeout(() => {
125840
+ scrollToAlignmentSelection();
125841
+ }, 0);
125842
+ buildMatchLayers(allMatches, index2);
125843
+ },
125844
+ [buildMatchLayers]
125845
+ );
125846
+ const runSearch = useCallback$1(
125847
+ (text2) => {
125848
+ const query = text2.trim();
125849
+ if (!query) {
125850
+ dispatch({
125851
+ type: "SEARCH_COMPLETE",
125852
+ payload: { matches: [], currentMatchIndex: 0, searched: false }
125853
+ });
125854
+ if (setSearchMatchLayers) setSearchMatchLayers([]);
125855
+ return;
125856
+ }
125857
+ const allMatches = [];
125858
+ alignmentTracks.slice(0, 1).forEach((track, trackIndex) => {
125859
+ var _a2, _b2;
125860
+ const rawSeq = ((_a2 = track.sequenceData) == null ? void 0 : _a2.sequence) || "";
125861
+ const alignedSeq = ((_b2 = track.alignmentData) == null ? void 0 : _b2.sequence) || "";
125862
+ const gapMap = getGapMap(alignedSeq);
125863
+ const gapOffset = /* @__PURE__ */ __name((n2) => {
125864
+ var _a3, _b3;
125865
+ return (_b3 = (_a3 = gapMap[n2]) != null ? _a3 : gapMap[gapMap.length - 1]) != null ? _b3 : 0;
125866
+ }, "gapOffset");
125867
+ let seqMatches = [];
125868
+ if (dnaOrAA === "DNA" && ambiguousOrLiteral === "LITERAL" && mismatchesAllowed > 0) {
125869
+ const approxMatches = findApproxMatches(
125870
+ query.toLowerCase(),
125871
+ rawSeq.toLowerCase(),
125872
+ mismatchesAllowed,
125873
+ false
125874
+ );
125875
+ seqMatches = approxMatches.map((m2) => ({
125876
+ start: m2.index,
125877
+ end: m2.index + m2.match.length - 1,
125878
+ mismatchPositions: m2.mismatchPositions
125879
+ }));
125880
+ } else {
125881
+ seqMatches = findSequenceMatches(rawSeq, query, {
125882
+ isCircular: false,
125883
+ isAmbiguous: ambiguousOrLiteral === "AMBIGUOUS",
125884
+ isProteinSearch: dnaOrAA !== "DNA",
125885
+ searchReverseStrand: dnaOrAA === "DNA"
125886
+ });
125887
+ }
125888
+ const hitsToProcess = query.length < 2 ? seqMatches.slice(0, 1) : seqMatches;
125889
+ hitsToProcess.forEach(({ start: start2, end: end2, mismatchPositions }) => {
125890
+ const alignmentStart = start2 + gapOffset(start2);
125891
+ const alignmentEnd = end2 + gapOffset(end2);
125892
+ const mismatchAlignmentPositions = (mismatchPositions || []).map(
125893
+ (p2) => {
125894
+ const absPos = start2 + p2;
125895
+ return absPos + gapOffset(absPos);
125896
+ }
125897
+ );
125898
+ allMatches.push({
125899
+ trackIndex,
125900
+ alignmentStart,
125901
+ alignmentEnd,
125902
+ mismatchAlignmentPositions
125903
+ });
125904
+ });
125905
+ });
125906
+ const results = query.length < 2 ? allMatches.slice(0, 1) : allMatches;
125907
+ dispatch({
125908
+ type: "SEARCH_COMPLETE",
125909
+ payload: { matches: results, currentMatchIndex: 0, searched: true }
125910
+ });
125911
+ if (results.length) {
125912
+ navigateTo(results, 0);
125913
+ } else {
125914
+ if (setSearchMatchLayers) setSearchMatchLayers([]);
125915
+ }
125916
+ },
125917
+ [
125918
+ alignmentTracks,
125919
+ navigateTo,
125920
+ dnaOrAA,
125921
+ ambiguousOrLiteral,
125922
+ mismatchesAllowed,
125923
+ setSearchMatchLayers
125924
+ ]
125925
+ );
125926
+ const runFeatureSearch = useCallback$1(
125927
+ (text2) => {
125928
+ const query = text2.trim().toLowerCase();
125929
+ if (!query) {
125930
+ dispatch({ type: "SET_FEATURE_MATCHES", payload: [] });
125931
+ return;
125932
+ }
125933
+ const allMatches = [];
125934
+ alignmentTracks.slice(0, 1).forEach((track, trackIndex) => {
125935
+ const { sequenceData: sequenceData2, alignmentData } = track;
125936
+ const alignedSeq = (alignmentData == null ? void 0 : alignmentData.sequence) || "";
125937
+ const gapMap = getGapMap(alignedSeq);
125938
+ const gapOffset = /* @__PURE__ */ __name((n2) => {
125939
+ var _a2, _b2;
125940
+ return (_b2 = (_a2 = gapMap[n2]) != null ? _a2 : gapMap[gapMap.length - 1]) != null ? _b2 : 0;
125941
+ }, "gapOffset");
125942
+ const trackName = (alignmentData == null ? void 0 : alignmentData.name) || (sequenceData2 == null ? void 0 : sequenceData2.name) || (sequenceData2 == null ? void 0 : sequenceData2.id) || "";
125943
+ ANNOTATION_TYPES.forEach((type2) => {
125944
+ const anns = sequenceData2 == null ? void 0 : sequenceData2[type2];
125945
+ if (!anns) return;
125946
+ const annsArray = Array.isArray(anns) ? anns : Object.values(anns);
125947
+ annsArray.forEach((ann) => {
125948
+ if (!ann.name) return;
125949
+ if (ann.name.toLowerCase().includes(query)) {
125950
+ const alignmentStart = ann.start + gapOffset(ann.start);
125951
+ const alignmentEnd = ann.end + gapOffset(ann.end);
125952
+ allMatches.push({
125953
+ trackIndex,
125954
+ trackName,
125955
+ type: type2,
125956
+ annotation: ann,
125957
+ alignmentStart,
125958
+ alignmentEnd
125959
+ });
125960
+ }
125961
+ });
125962
+ });
125963
+ });
125964
+ dispatch({ type: "SET_FEATURE_MATCHES", payload: allMatches });
125965
+ },
125966
+ [alignmentTracks]
125967
+ );
125968
+ const goToPrev = useCallback$1(() => {
125969
+ if (!matches.length) return;
125970
+ const newIndex = currentMatchIndex === 0 ? matches.length - 1 : currentMatchIndex - 1;
125971
+ dispatch({ type: "SET_CURRENT_MATCH_INDEX", payload: newIndex });
125972
+ navigateTo(matches, newIndex);
125973
+ }, [matches, currentMatchIndex, navigateTo]);
125974
+ const goToNext = useCallback$1(() => {
125975
+ if (!matches.length) return;
125976
+ const newIndex = currentMatchIndex === matches.length - 1 ? 0 : currentMatchIndex + 1;
125977
+ dispatch({ type: "SET_CURRENT_MATCH_INDEX", payload: newIndex });
125978
+ navigateTo(matches, newIndex);
125979
+ }, [matches, currentMatchIndex, navigateTo]);
125980
+ const handleKeyDown = useCallback$1(
125981
+ (e) => {
125982
+ if (e.key === "Escape") {
125983
+ setIsOpen(false);
125984
+ }
125985
+ if (e.key === "Enter") {
125986
+ if (e.shiftKey) {
125987
+ goToPrev();
125988
+ } else {
125989
+ goToNext();
125990
+ }
125991
+ e.preventDefault();
125992
+ e.stopPropagation();
125993
+ }
125994
+ },
125995
+ [goToPrev, goToNext]
125996
+ );
125997
+ useEffect(() => {
125998
+ if (!searched || !searchText.trim()) return;
125999
+ runSearch(searchText);
126000
+ runFeatureSearch(searchText);
126001
+ }, [
126002
+ dnaOrAA,
126003
+ ambiguousOrLiteral,
126004
+ mismatchesAllowed,
126005
+ runSearch,
126006
+ runFeatureSearch,
126007
+ searched,
126008
+ searchText
126009
+ ]);
126010
+ useEffect(() => {
126011
+ if (searchText.trim().length < 1) setHighlightAll(false);
126012
+ }, [searchText]);
126013
+ const prevHighlightAll = useRef(highlightAll);
126014
+ useEffect(() => {
126015
+ if (prevHighlightAll.current !== highlightAll) {
126016
+ prevHighlightAll.current = highlightAll;
126017
+ if (matches.length) buildMatchLayers(matches, currentMatchIndex);
126018
+ }
126019
+ }, [highlightAll, matches, currentMatchIndex, buildMatchLayers]);
126020
+ const hasMatches = matches.length > 0;
126021
+ const handleChange = useCallback$1(
126022
+ (e) => {
126023
+ const value = e.target.value;
126024
+ dispatch({ type: "SET_SEARCH_TEXT", payload: value });
126025
+ debouncedSearch(value, runSearch, runFeatureSearch);
126026
+ },
126027
+ [debouncedSearch, runSearch, runFeatureSearch]
126028
+ );
126029
+ const handleFeatureClick = useCallback$1((featureMatch) => {
126030
+ updateCaretPosition({
126031
+ start: featureMatch.alignmentStart,
126032
+ end: featureMatch.alignmentEnd
126033
+ });
126034
+ setTimeout(() => {
126035
+ scrollToAlignmentSelection();
126036
+ }, 0);
126037
+ }, []);
126038
+ const matchCounter = /* @__PURE__ */ React__default.createElement(
126039
+ "span",
126040
+ {
126041
+ style: {
126042
+ marginRight: 3,
126043
+ color: "lightgrey",
126044
+ fontSize: "0.9em",
126045
+ whiteSpace: "nowrap"
126046
+ }
126047
+ },
126048
+ hasMatches ? currentMatchIndex + 1 : 0,
126049
+ "/",
126050
+ matches.length
126051
+ );
126052
+ const inlineNavEl = /* @__PURE__ */ React__default.createElement("span", { style: { display: "flex", alignItems: "center" } }, !isExpanded && /* @__PURE__ */ React__default.createElement(
126053
+ Popover,
126054
+ {
126055
+ autoFocus: false,
126056
+ enforceFocus: false,
126057
+ isOpen: isPopoverOpen,
126058
+ onInteraction: setIsPopoverOpen,
126059
+ position: Position.TOP,
126060
+ content: /* @__PURE__ */ React__default.createElement(
126061
+ "div",
126062
+ {
126063
+ className: "ve-find-options-popover",
126064
+ style: {
126065
+ display: "flex",
126066
+ flexDirection: "column",
126067
+ paddingLeft: 20,
126068
+ paddingBottom: 10,
126069
+ paddingTop: 10,
126070
+ paddingRight: 20,
126071
+ gap: 6
126072
+ }
126073
+ },
126074
+ /* @__PURE__ */ React__default.createElement(
126075
+ FindOptionsPanel,
126076
+ {
126077
+ dnaOrAA,
126078
+ ambiguousOrLiteral,
126079
+ mismatchesAllowed,
126080
+ searchText,
126081
+ matches,
126082
+ dispatch,
126083
+ highlightAll,
126084
+ setHighlightAll,
126085
+ isExpanded,
126086
+ onToggleExpanded: handleToggleExpanded
126087
+ }
126088
+ )
126089
+ ),
126090
+ target: /* @__PURE__ */ React__default.createElement(Button, { minimal: true, icon: "wrench", "data-tip": "Options" })
126091
+ }
126092
+ ), matchCounter, /* @__PURE__ */ React__default.createElement(
126093
+ Button,
126094
+ {
126095
+ minimal: true,
126096
+ small: true,
126097
+ icon: "caret-left",
126098
+ "data-tip": "Previous",
126099
+ disabled: !hasMatches,
126100
+ onClick: goToPrev
126101
+ }
126102
+ ), /* @__PURE__ */ React__default.createElement(
126103
+ Button,
126104
+ {
126105
+ minimal: true,
126106
+ small: true,
126107
+ icon: "caret-right",
126108
+ "data-tip": "Next",
126109
+ disabled: !hasMatches,
126110
+ onClick: goToNext
126111
+ }
126112
+ ), /* @__PURE__ */ React__default.createElement(
126113
+ Button,
126114
+ {
126115
+ minimal: true,
126116
+ small: true,
126117
+ "data-tip": "Close (Esc)",
126118
+ icon: "small-cross",
126119
+ onClick: /* @__PURE__ */ __name(() => setIsOpen(false), "onClick")
126120
+ }
126121
+ ));
126122
+ const expandedNavEl = /* @__PURE__ */ React__default.createElement("span", { style: { display: "flex", alignItems: "center" } }, matchCounter, /* @__PURE__ */ React__default.createElement(
126123
+ Button,
126124
+ {
126125
+ minimal: true,
126126
+ small: true,
126127
+ icon: "caret-up",
126128
+ disabled: !hasMatches,
126129
+ onClick: goToPrev
126130
+ }
126131
+ ), /* @__PURE__ */ React__default.createElement(
126132
+ Button,
126133
+ {
126134
+ minimal: true,
126135
+ small: true,
126136
+ icon: "caret-down",
126137
+ disabled: !hasMatches,
126138
+ onClick: goToNext
126139
+ }
126140
+ ));
126141
+ if (!isOpen) {
126142
+ return /* @__PURE__ */ React__default.createElement("div", null, /* @__PURE__ */ React__default.createElement(
126143
+ Button,
126144
+ {
126145
+ minimal: true,
126146
+ small: true,
126147
+ intent: "primary",
126148
+ icon: "search",
126149
+ rightIcon: "caret-right",
126150
+ "data-tip": "Search",
126151
+ onClick: /* @__PURE__ */ __name(() => setIsOpen(true), "onClick")
126152
+ }
126153
+ ));
126154
+ }
126155
+ const annotationPopoverOpen = searched && featureMatches.length > 0;
126156
+ const inputEl = /* @__PURE__ */ React__default.createElement(
126157
+ InputGroup,
126158
+ {
126159
+ className: "tg-find-tool-input alignment-search-bar",
126160
+ leftIcon: "search",
126161
+ placeholder: "Search...",
126162
+ autoFocus: true,
126163
+ value: searchText,
126164
+ onChange: handleChange,
126165
+ onKeyDown: handleKeyDown,
126166
+ rightElement: inlineNavEl
126167
+ }
126168
+ );
126169
+ return /* @__PURE__ */ React__default.createElement("div", { style: { position: "relative" } }, !isExpanded && /* @__PURE__ */ React__default.createElement(
126170
+ Popover,
126171
+ {
126172
+ autoFocus: false,
126173
+ enforceFocus: false,
126174
+ modifiers: {
126175
+ arrow: false
126176
+ },
126177
+ position: Position.BOTTOM,
126178
+ isOpen: annotationPopoverOpen,
126179
+ content: /* @__PURE__ */ React__default.createElement(
126180
+ AnnotationResultsComp,
126181
+ {
126182
+ featureMatches,
126183
+ onClickMatch: handleFeatureClick
126184
+ }
126185
+ ),
126186
+ target: inputEl
126187
+ }
126188
+ ), isExpanded && /* @__PURE__ */ React__default.createElement(
126189
+ "div",
126190
+ {
126191
+ style: {
126192
+ position: "absolute",
126193
+ top: 0,
126194
+ left: 0,
126195
+ padding: 10,
126196
+ paddingBottom: 25,
126197
+ display: "flex",
126198
+ alignItems: "flex-start",
126199
+ gap: 10,
126200
+ zIndex: 5e4,
126201
+ background: "white",
126202
+ boxShadow: "0 2px 8px rgba(0,0,0,0.2)",
126203
+ borderRadius: 3
126204
+ }
126205
+ },
126206
+ /* @__PURE__ */ React__default.createElement("div", { style: { display: "flex", flexDirection: "column", gap: 6 } }, /* @__PURE__ */ React__default.createElement(
126207
+ TextArea,
126208
+ {
126209
+ autoFocus: true,
126210
+ placeholder: "Search sequences and annotations...",
126211
+ value: searchText,
126212
+ onChange: handleChange,
126213
+ onKeyDown: handleKeyDown,
126214
+ style: { resize: "vertical", width: 350, height: 190 }
126215
+ }
126216
+ ), annotationPopoverOpen && /* @__PURE__ */ React__default.createElement(
126217
+ AnnotationResultsComp,
126218
+ {
126219
+ featureMatches,
126220
+ onClickMatch: handleFeatureClick
126221
+ }
126222
+ )),
126223
+ /* @__PURE__ */ React__default.createElement("div", { style: { display: "flex", flexDirection: "column", gap: 5 } }, expandedNavEl, /* @__PURE__ */ React__default.createElement(
126224
+ FindOptionsPanel,
126225
+ {
126226
+ dnaOrAA,
126227
+ ambiguousOrLiteral,
126228
+ mismatchesAllowed,
126229
+ searchText,
126230
+ matches,
126231
+ dispatch,
126232
+ highlightAll,
126233
+ setHighlightAll,
126234
+ isExpanded,
126235
+ onToggleExpanded: handleToggleExpanded
126236
+ }
126237
+ )),
126238
+ /* @__PURE__ */ React__default.createElement(
126239
+ Button,
126240
+ {
126241
+ minimal: true,
126242
+ style: { position: "absolute", bottom: 0, right: 0 },
126243
+ onClick: /* @__PURE__ */ __name(() => setIsOpen(false), "onClick"),
126244
+ icon: "cross"
126245
+ }
126246
+ )
126247
+ ));
126248
+ }
126249
+ __name(AlignmentSearchBar, "AlignmentSearchBar");
126250
+ function AnnotationResultsComp({ featureMatches, onClickMatch }) {
126251
+ const byType = {};
126252
+ ANNOTATION_TYPES.forEach((type2) => {
126253
+ byType[type2] = [];
126254
+ });
126255
+ featureMatches.forEach((match) => {
126256
+ if (byType[match.type]) {
126257
+ byType[match.type].push(match);
126258
+ }
126259
+ });
126260
+ const featureColorMap = getFeatureToColorMap({ includeHidden: true });
126261
+ return /* @__PURE__ */ React__default.createElement("div", { className: "veAnnotationFindMatches" }, ANNOTATION_TYPES.map((type2) => {
126262
+ const anns = byType[type2];
126263
+ if (!anns.length) return null;
126264
+ const showing = anns.slice(0, 10);
126265
+ return /* @__PURE__ */ React__default.createElement("div", { key: type2 }, /* @__PURE__ */ React__default.createElement("div", { className: "veAnnotationFoundType" }, anns.length, " ", getSingular(type2), " match", anns.length > 1 ? "es" : null, anns.length > 10 ? ` (only showing 10)` : null, ":"), /* @__PURE__ */ React__default.createElement("div", null, showing.map((match, i) => {
126266
+ const { annotation } = match;
126267
+ const annotationColor = type2 === "parts" ? "#ac68cc" : annotation.color || featureColorMap[annotation.type];
126268
+ return /* @__PURE__ */ React__default.createElement(
126269
+ "div",
126270
+ {
126271
+ key: i,
126272
+ onClick: /* @__PURE__ */ __name(() => onClickMatch(match), "onClick"),
126273
+ className: "veAnnotationFoundResult"
126274
+ },
126275
+ /* @__PURE__ */ React__default.createElement("div", { style: { display: "flex", alignItems: "center" } }, /* @__PURE__ */ React__default.createElement(
126276
+ "div",
126277
+ {
126278
+ style: {
126279
+ background: annotationColor,
126280
+ height: 15,
126281
+ width: 15,
126282
+ marginRight: 3
126283
+ }
126284
+ }
126285
+ ), annotation.name),
126286
+ /* @__PURE__ */ React__default.createElement("div", { className: "veAnnotationFoundResultRange" }, annotation.start + 1, "-", annotation.end + 1)
126287
+ );
126288
+ })));
126289
+ }));
126290
+ }
126291
+ __name(AnnotationResultsComp, "AnnotationResultsComp");
126292
+ function FindOptionsPanel({
126293
+ dnaOrAA,
126294
+ ambiguousOrLiteral,
126295
+ mismatchesAllowed,
126296
+ searchText,
126297
+ matches,
126298
+ dispatch,
126299
+ highlightAll,
126300
+ setHighlightAll,
126301
+ isExpanded,
126302
+ onToggleExpanded
126303
+ }) {
126304
+ return /* @__PURE__ */ React__default.createElement(React__default.Fragment, null, /* @__PURE__ */ React__default.createElement(
126305
+ TgHTMLSelect,
126306
+ {
126307
+ options: [
126308
+ { label: "DNA", value: "DNA" },
126309
+ { label: "Amino Acids", value: "AA" }
126310
+ ],
126311
+ value: dnaOrAA,
126312
+ onChange: /* @__PURE__ */ __name((e) => dispatch({ type: "SET_DNA_OR_AA", payload: e.target.value }), "onChange")
126313
+ }
126314
+ ), /* @__PURE__ */ React__default.createElement("div", { style: { display: "flex" } }, /* @__PURE__ */ React__default.createElement(
126315
+ TgHTMLSelect,
126316
+ {
126317
+ options: [
126318
+ { label: "Literal", value: "LITERAL" },
126319
+ { label: "Ambiguous", value: "AMBIGUOUS" }
126320
+ ],
126321
+ value: ambiguousOrLiteral,
126322
+ onChange: /* @__PURE__ */ __name((e) => dispatch({
126323
+ type: "SET_AMBIGUOUS_OR_LITERAL",
126324
+ payload: e.target.value
126325
+ }), "onChange")
126326
+ }
126327
+ ), /* @__PURE__ */ React__default.createElement(InfoHelper, { style: { marginLeft: 10 } }, /* @__PURE__ */ React__default.createElement("div", null, "Ambiguous substitutions:", /* @__PURE__ */ React__default.createElement("div", { style: { display: "flex", fontSize: 12 } }, /* @__PURE__ */ React__default.createElement("div", { style: { marginRight: 20 } }, /* @__PURE__ */ React__default.createElement("div", { style: { fontSize: 14, marginBottom: 4, marginTop: 5 } }, "DNA:"), /* @__PURE__ */ React__default.createElement("div", null, "M: AC"), /* @__PURE__ */ React__default.createElement("div", null, "R: AG"), /* @__PURE__ */ React__default.createElement("div", null, "W: AT"), /* @__PURE__ */ React__default.createElement("div", null, "S: CG"), /* @__PURE__ */ React__default.createElement("div", null, "Y: CT"), /* @__PURE__ */ React__default.createElement("div", null, "K: GT"), /* @__PURE__ */ React__default.createElement("div", null, "V: ACG"), /* @__PURE__ */ React__default.createElement("div", null, "H: ACT"), /* @__PURE__ */ React__default.createElement("div", null, "D: AGT"), /* @__PURE__ */ React__default.createElement("div", null, "B: CGT"), /* @__PURE__ */ React__default.createElement("div", null, "X: GATC"), /* @__PURE__ */ React__default.createElement("div", null, "N: GATC"), /* @__PURE__ */ React__default.createElement("div", null, "*: any")), /* @__PURE__ */ React__default.createElement("div", null, /* @__PURE__ */ React__default.createElement("div", { style: { fontSize: 14, marginBottom: 4, marginTop: 5 } }, "AA:"), /* @__PURE__ */ React__default.createElement("div", null, "B: ND"), /* @__PURE__ */ React__default.createElement("div", null, "J: IL"), /* @__PURE__ */ React__default.createElement("div", null, "X: ACDEFGHIKLMNPQRSTVWY"), /* @__PURE__ */ React__default.createElement("div", null, "Z: QE"), /* @__PURE__ */ React__default.createElement("div", null, "*: any")))))), /* @__PURE__ */ React__default.createElement(
126328
+ "div",
126329
+ {
126330
+ style: {
126331
+ marginTop: "8px",
126332
+ display: "flex",
126333
+ flexDirection: "row",
126334
+ gap: "3px",
126335
+ alignItems: "center"
126336
+ }
126337
+ },
126338
+ /* @__PURE__ */ React__default.createElement("label", null, "Mismatches Allowed:"),
126339
+ /* @__PURE__ */ React__default.createElement(
126340
+ NumericInput,
126341
+ {
126342
+ min: 0,
126343
+ max: 10,
126344
+ className: "tg-mismatches-allowed-input",
126345
+ style: { width: "60px" },
126346
+ value: mismatchesAllowed,
126347
+ disabled: dnaOrAA !== "DNA" || ambiguousOrLiteral !== "LITERAL",
126348
+ onValueChange: /* @__PURE__ */ __name((value) => dispatch({
126349
+ type: "SET_MISMATCHES_ALLOWED",
126350
+ payload: Number.parseInt(value, 10) || 0
126351
+ }), "onValueChange")
126352
+ }
126353
+ ),
126354
+ /* @__PURE__ */ React__default.createElement(InfoHelper, { style: { marginLeft: 10 } }, /* @__PURE__ */ React__default.createElement("div", null, "Number of mismatches allowed when searching DNA sequences with literal matching.", /* @__PURE__ */ React__default.createElement("br", null), /* @__PURE__ */ React__default.createElement("br", null), "Higher values may slow down search performance."))
126355
+ ), /* @__PURE__ */ React__default.createElement(
126356
+ Switch,
126357
+ {
126358
+ checked: highlightAll,
126359
+ onChange: /* @__PURE__ */ __name(() => setHighlightAll((v2) => !v2), "onChange"),
126360
+ disabled: searchText.trim().length < 2 || matches.length > MAX_MATCHES_DISPLAYED
126361
+ },
126362
+ /* @__PURE__ */ React__default.createElement(
126363
+ Tooltip,
126364
+ {
126365
+ disabled: matches.length <= MAX_MATCHES_DISPLAYED,
126366
+ content: `Disabled because there are >${MAX_MATCHES_DISPLAYED} matches`
126367
+ },
126368
+ "Highlight All"
126369
+ )
126370
+ ), /* @__PURE__ */ React__default.createElement(Switch, { checked: isExpanded, onChange: onToggleExpanded }, "Expanded"));
126371
+ }
126372
+ __name(FindOptionsPanel, "FindOptionsPanel");
125441
126373
  const AlignmentVisibilityTool = pure(/* @__PURE__ */ __name(function AlignmentVisibilityTool2(props) {
125442
126374
  return /* @__PURE__ */ React__default.createElement(
125443
126375
  Popover,
@@ -125445,17 +126377,18 @@ const AlignmentVisibilityTool = pure(/* @__PURE__ */ __name(function AlignmentVi
125445
126377
  minimal: true,
125446
126378
  position: "bottom",
125447
126379
  content: /* @__PURE__ */ React__default.createElement(VisibilityOptions$2, __spreadValues({}, props)),
125448
- target: /* @__PURE__ */ React__default.createElement(Tooltip, { content: "Visibility Options" }, /* @__PURE__ */ React__default.createElement(
126380
+ target: /* @__PURE__ */ React__default.createElement(
125449
126381
  Button,
125450
126382
  {
125451
126383
  className: "tg-alignment-visibility-toggle",
125452
126384
  small: true,
126385
+ "data-tip": "Visibility Options",
125453
126386
  rightIcon: "caret-down",
125454
126387
  intent: Intent.PRIMARY,
125455
126388
  minimal: true,
125456
126389
  icon: "eye-open"
125457
126390
  }
125458
- ))
126391
+ )
125459
126392
  }
125460
126393
  );
125461
126394
  }, "AlignmentVisibilityTool"));
@@ -138621,7 +139554,6 @@ const aminoAcidShortNames = {
138621
139554
  const PropertySidePanel = /* @__PURE__ */ __name(({ properties: properties2, setProperties, style: style2 }) => {
138622
139555
  const sidebarRef = React__default.useRef(null);
138623
139556
  const [mismatchesCount, setMismatchesCount] = React__default.useState(0);
138624
- const [mismatchesInRange, setMismatchesInRange] = React__default.useState(0);
138625
139557
  const { track, isOpen, selection, isPairwise } = properties2;
138626
139558
  const getSequenceInRegion = useCallback$1(() => {
138627
139559
  var _a2, _b2;
@@ -138640,6 +139572,30 @@ const PropertySidePanel = /* @__PURE__ */ __name(({ properties: properties2, set
138640
139572
  if (!Array.isArray(tr)) return [];
138641
139573
  return isPairwise ? tr.filter((m2) => (m2 == null ? void 0 : m2.color) === "red") : tr;
138642
139574
  }, [track, mismatchKey, isPairwise]);
139575
+ const mismatchSchema = useMemo$1(
139576
+ () => ({
139577
+ fields: [
139578
+ {
139579
+ path: "start",
139580
+ type: "number",
139581
+ displayName: "Start",
139582
+ render: /* @__PURE__ */ __name((val2) => val2 + 1, "render")
139583
+ },
139584
+ {
139585
+ path: "end",
139586
+ type: "number",
139587
+ displayName: "End",
139588
+ render: /* @__PURE__ */ __name((val2) => val2 + 1, "render")
139589
+ }
139590
+ ]
139591
+ }),
139592
+ []
139593
+ );
139594
+ const mismatchEntities = useMemo$1(() => {
139595
+ return (trackMismatches || []).map((m2, i) => __spreadProps(__spreadValues({}, m2), {
139596
+ id: i.toString()
139597
+ }));
139598
+ }, [trackMismatches]);
138643
139599
  useEffect(() => {
138644
139600
  if (!isOpen || sidebarRef.current === null || !track) {
138645
139601
  return;
@@ -138657,21 +139613,6 @@ const PropertySidePanel = /* @__PURE__ */ __name(({ properties: properties2, set
138657
139613
  }
138658
139614
  });
138659
139615
  setMismatchesCount(mismatchCount);
138660
- setMismatchesInRange(mismatchCount);
138661
- if (selection && selection.start > -1 && selection.end > -1) {
138662
- let count2 = 0;
138663
- trackMismatches == null ? void 0 : trackMismatches.forEach((tm) => {
138664
- if (tm === null || tm.start === null || tm.end === null) {
138665
- return;
138666
- }
138667
- const overlapStart = Math.max(tm.start, selection.start);
138668
- const overlapEnd = Math.min(tm.end, selection.end);
138669
- if (overlapEnd >= overlapStart) {
138670
- count2 += overlapEnd - overlapStart + 1;
138671
- }
138672
- });
138673
- setMismatchesInRange(count2);
138674
- }
138675
139616
  }, [isOpen, track, selection, trackMismatches]);
138676
139617
  const aminoFreq = useMemo$1(() => {
138677
139618
  var _a2, _b2;
@@ -138707,7 +139648,7 @@ const PropertySidePanel = /* @__PURE__ */ __name(({ properties: properties2, set
138707
139648
  width: "100%"
138708
139649
  }
138709
139650
  }
138710
- ), /* @__PURE__ */ React__default.createElement("h5", null, "Track Properties"), /* @__PURE__ */ React__default.createElement("div", { className: "bp3-tab-panel" }, /* @__PURE__ */ React__default.createElement(RowItem, { item: name2, title: "Name" }), /* @__PURE__ */ React__default.createElement(RowItem, { item: isProtein2 ? proteinSize : size, title: "Length" }), /* @__PURE__ */ React__default.createElement(
139651
+ ), /* @__PURE__ */ React__default.createElement(HeaderItem, { title: "Track Properties" }), /* @__PURE__ */ React__default.createElement("div", { className: "bp3-tab-panel" }, /* @__PURE__ */ React__default.createElement(RowItem, { item: name2, title: "Name" }), /* @__PURE__ */ React__default.createElement(RowItem, { item: isProtein2 ? proteinSize : size, title: "Length" }), /* @__PURE__ */ React__default.createElement(
138711
139652
  RowItem,
138712
139653
  {
138713
139654
  item: molecularWeight == null ? void 0 : molecularWeight.toFixed(2),
@@ -138721,18 +139662,45 @@ const PropertySidePanel = /* @__PURE__ */ __name(({ properties: properties2, set
138721
139662
  title: "Extinction Coefficient"
138722
139663
  }
138723
139664
  )), /* @__PURE__ */ React__default.createElement(
138724
- RowItem,
138725
- {
138726
- item: `${mismatchesInRange}/${mismatchesCount}`,
138727
- title: "Mismatches"
138728
- }
138729
- ), /* @__PURE__ */ React__default.createElement(
138730
139665
  RowItem,
138731
139666
  {
138732
139667
  item: selection && selection.start > -1 ? /* @__PURE__ */ React__default.createElement("span", null, selection.start + 1, " - ", selection.end + 1) : /* @__PURE__ */ React__default.createElement("span", null, "1 - ", isProtein2 ? proteinSize : size),
138733
139668
  title: "Region"
138734
139669
  }
138735
- )), /* @__PURE__ */ React__default.createElement("h5", null, isProtein2 ? "Amino Acid" : "Base Pair", " Frequencies"), /* @__PURE__ */ React__default.createElement("div", { className: "sidebar-table" }, /* @__PURE__ */ React__default.createElement("div", { className: "sidebar-row" }, /* @__PURE__ */ React__default.createElement("div", { className: "sidebar-cell" }, isProtein2 ? "Amino Acid" : "Base"), /* @__PURE__ */ React__default.createElement("div", { className: "sidebar-cell" }, "Count"), /* @__PURE__ */ React__default.createElement("div", { className: "sidebar-cell" }, "Percentage")), frequencyEntries.map(([aa, data], idx) => {
139670
+ ), /* @__PURE__ */ React__default.createElement(HeaderItem, { title: `Mismatches (${mismatchesCount})` }), trackMismatches && trackMismatches.length > 0 && /* @__PURE__ */ React__default.createElement(
139671
+ "div",
139672
+ {
139673
+ style: {
139674
+ margin: "0px 10px"
139675
+ }
139676
+ },
139677
+ /* @__PURE__ */ React__default.createElement(
139678
+ WrappedDT,
139679
+ {
139680
+ formName: "mismatchesTable",
139681
+ isSimple: true,
139682
+ noHeader: true,
139683
+ noFooter: true,
139684
+ withSearch: false,
139685
+ noPadding: true,
139686
+ compact: true,
139687
+ maxHeight: 150,
139688
+ entities: mismatchEntities,
139689
+ schema: mismatchSchema,
139690
+ onRowClick: /* @__PURE__ */ __name((e, row) => {
139691
+ updateCaretPosition({ start: row.start, end: row.end });
139692
+ setTimeout(() => {
139693
+ scrollToAlignmentSelection();
139694
+ }, 0);
139695
+ }, "onRowClick")
139696
+ }
139697
+ )
139698
+ )), /* @__PURE__ */ React__default.createElement(
139699
+ HeaderItem,
139700
+ {
139701
+ title: `${isProtein2 ? "Amino Acid" : "Base Pair"} Frequencies`
139702
+ }
139703
+ ), /* @__PURE__ */ React__default.createElement("div", { className: "sidebar-table" }, /* @__PURE__ */ React__default.createElement("div", { className: "sidebar-row" }, /* @__PURE__ */ React__default.createElement("div", { className: "sidebar-cell" }, isProtein2 ? "Amino Acid" : "Base"), /* @__PURE__ */ React__default.createElement("div", { className: "sidebar-cell" }, "Count"), /* @__PURE__ */ React__default.createElement("div", { className: "sidebar-cell" }, "Percentage")), frequencyEntries.map(([aa, data], idx) => {
138736
139704
  return /* @__PURE__ */ React__default.createElement("div", { className: `sidebar-row property-amino-acid-${idx}` }, /* @__PURE__ */ React__default.createElement("div", { className: "sidebar-cell" }, aa, " ", isProtein2 ? `(${aminoAcidShortNames[aa]})` : ""), /* @__PURE__ */ React__default.createElement("div", { className: "sidebar-cell" }, data.count), /* @__PURE__ */ React__default.createElement("div", { className: "sidebar-cell" }, data.percentage.toFixed(1), "%"));
138737
139705
  })));
138738
139706
  } else {
@@ -138789,11 +139757,29 @@ const PropertySidePanel = /* @__PURE__ */ __name(({ properties: properties2, set
138789
139757
  );
138790
139758
  }, "PropertySidePanel");
138791
139759
  function RowItem({ item, title, units }) {
138792
- if (!item) return;
139760
+ if (item == null) {
139761
+ return null;
139762
+ }
138793
139763
  const propertyClass = title.split(" ").join("-").toLowerCase();
138794
- return /* @__PURE__ */ React__default.createElement("div", { className: `ve-flex-row property-${propertyClass}` }, /* @__PURE__ */ React__default.createElement("div", { className: "ve-column-left" }, title), /* @__PURE__ */ React__default.createElement("div", { className: "ve-column-right" }, item, " ", units != null ? units : ""));
139764
+ return /* @__PURE__ */ React__default.createElement("div", { className: `ve-flex-row property-${propertyClass}` }, /* @__PURE__ */ React__default.createElement("div", { style: { fontWeight: "bold" }, className: "ve-column-left" }, title), /* @__PURE__ */ React__default.createElement("div", { className: "ve-column-right" }, item, " ", units != null ? units : ""));
138795
139765
  }
138796
139766
  __name(RowItem, "RowItem");
139767
+ const HeaderItem = /* @__PURE__ */ __name(({ title }) => {
139768
+ return /* @__PURE__ */ React__default.createElement(
139769
+ "h5",
139770
+ {
139771
+ style: {
139772
+ margin: 0,
139773
+ fontSize: 15,
139774
+ fontWeight: "bold",
139775
+ textAlign: "center",
139776
+ padding: "5px 0",
139777
+ borderBottom: "1px solid #f1f1f1"
139778
+ }
139779
+ },
139780
+ title
139781
+ );
139782
+ }, "HeaderItem");
138797
139783
  function calculatePairwiseIdentity(seq1, seq2, excludeGaps = true) {
138798
139784
  if (seq1.length !== seq2.length) {
138799
139785
  throw new Error("Sequences must be aligned (same length)");
@@ -139189,6 +140175,11 @@ const AlignmentView = /* @__PURE__ */ __name((props) => {
139189
140175
  const [tempTrimBefore, setTempTrimBefore] = useState({});
139190
140176
  const [tempTrimAfter, setTempTrimAfter] = useState({});
139191
140177
  const [tempTrimmingCaret, setTempTrimmingCaret] = useState({});
140178
+ const [searchMatchLayers, setSearchMatchLayers] = React__default.useState([]);
140179
+ const [activeFilterType, setActiveFilterType] = useState("all");
140180
+ const handleFilterChange = useCallback$1(({ activeFilter }) => {
140181
+ setActiveFilterType(activeFilter);
140182
+ }, []);
139192
140183
  const bindOutsideChangeHelper = useRef({});
139193
140184
  const alignmentHolder = useRef(null);
139194
140185
  const alignmentHolderTop = useRef(null);
@@ -139913,7 +140904,12 @@ ${seqDataToCopy}\r
139913
140904
  alignmentData,
139914
140905
  chromatogramData
139915
140906
  }) : linearViewOptions)), {
139916
- additionalSelectionLayers,
140907
+ additionalSelectionLayers: [
140908
+ ...i !== 0 ? (additionalSelectionLayers || []).filter(
140909
+ (layer) => activeFilterType === "all" ? layer.differenceType !== "gap" : layer.differenceType === activeFilterType
140910
+ ) : additionalSelectionLayers || [],
140911
+ ...searchMatchLayers || []
140912
+ ],
139917
140913
  dimensions: {
139918
140914
  width: linearViewWidth
139919
140915
  },
@@ -140441,7 +141437,7 @@ ${seqDataToCopy}\r
140441
141437
  display: "flex",
140442
141438
  minHeight: "32px",
140443
141439
  width: "100%",
140444
- flexWrap: "nowrap",
141440
+ flexWrap: "wrap",
140445
141441
  flexDirection: "row",
140446
141442
  flex: "0 0 auto"
140447
141443
  },
@@ -140562,6 +141558,22 @@ ${seqDataToCopy}\r
140562
141558
  currentPairwiseAlignmentIndex
140563
141559
  }, alignmentVisibilityToolOptions)
140564
141560
  ),
141561
+ /* @__PURE__ */ React__default.createElement(
141562
+ AlignmentSearchBar,
141563
+ {
141564
+ alignmentTracks,
141565
+ id: id2,
141566
+ setSearchMatchLayers
141567
+ }
141568
+ ),
141569
+ /* @__PURE__ */ React__default.createElement(
141570
+ FindMismatches,
141571
+ {
141572
+ alignmentJson: alignmentTracks,
141573
+ id: id2,
141574
+ onFilterChange: handleFilterChange
141575
+ }
141576
+ ),
140565
141577
  additionalTopEl,
140566
141578
  saveMessage && /* @__PURE__ */ React__default.createElement(
140567
141579
  "div",
@@ -140662,6 +141674,7 @@ ${seqDataToCopy}\r
140662
141674
  }
140663
141675
  )),
140664
141676
  alignmentTracks,
141677
+ activeFilterType,
140665
141678
  dimensions: {
140666
141679
  width: Math.max(width, 10) || 10
140667
141680
  },
@@ -145851,108 +146864,6 @@ const schema = {
145851
146864
  ]
145852
146865
  };
145853
146866
  const DigestTool$1 = withEditorInteractions(DigestTool);
145854
- const _Mismatches = class _Mismatches extends React__default.Component {
145855
- constructor() {
145856
- super(...arguments);
145857
- __publicField(this, "getGapMap", /* @__PURE__ */ __name((sequence2) => {
145858
- const gapMap = [0];
145859
- sequence2.split("").forEach((char) => {
145860
- if (char === "-") {
145861
- gapMap[Math.max(0, gapMap.length - 1)] = (gapMap[Math.max(0, gapMap.length - 1)] || 0) + 1;
145862
- } else {
145863
- gapMap.push(gapMap[gapMap.length - 1] || 0);
145864
- }
145865
- });
145866
- return gapMap;
145867
- }, "getGapMap"));
145868
- __publicField(this, "getMismatchList", /* @__PURE__ */ __name((alignmentData, mismatches) => {
145869
- const mismatchList = [];
145870
- let getGaps2 = /* @__PURE__ */ __name(() => ({
145871
- gapsBefore: 0,
145872
- gapsInside: 0
145873
- }), "getGaps");
145874
- const gapMap = this.getGapMap(alignmentData.sequence);
145875
- getGaps2 = /* @__PURE__ */ __name((rangeOrCaretPosition) => {
145876
- if (typeof rangeOrCaretPosition !== "object") {
145877
- return {
145878
- gapsBefore: gapMap[Math.min(rangeOrCaretPosition, gapMap.length - 1)]
145879
- };
145880
- }
145881
- const { start: start2, end: end2 } = rangeOrCaretPosition;
145882
- const toReturn = {
145883
- gapsBefore: gapMap[start2],
145884
- gapsInside: gapMap[Math.min(end2, gapMap.length - 1)] - gapMap[Math.min(start2, gapMap.length - 1)]
145885
- };
145886
- return toReturn;
145887
- }, "getGaps");
145888
- const gapsBeforeSequence = getGaps2(0).gapsBefore;
145889
- for (let mismatchI = 0; mismatchI < mismatches.length; mismatchI++) {
145890
- const mismatchEnd = mismatches[mismatchI].end;
145891
- const mismatchStart = mismatches[mismatchI].start;
145892
- const mismatchDifference = mismatchEnd - mismatchStart;
145893
- if (mismatchDifference === 0) {
145894
- mismatchList.push({
145895
- mismatches: mismatchStart + 1 - gapsBeforeSequence,
145896
- start: mismatchStart - gapsBeforeSequence,
145897
- end: mismatchStart - gapsBeforeSequence
145898
- });
145899
- } else {
145900
- for (let innerI = 0; innerI <= mismatchDifference; innerI++) {
145901
- mismatchList.push({
145902
- mismatches: mismatchStart + innerI + 1 - gapsBeforeSequence,
145903
- start: mismatchStart + innerI - gapsBeforeSequence,
145904
- end: mismatchStart + innerI - gapsBeforeSequence
145905
- });
145906
- }
145907
- }
145908
- }
145909
- return mismatchList;
145910
- }, "getMismatchList"));
145911
- }
145912
- UNSAFE_componentWillMount() {
145913
- const { alignmentData, mismatches } = this.props;
145914
- const mismatchList = this.getMismatchList(alignmentData, mismatches);
145915
- const schema2 = {
145916
- fields: [{ path: "mismatches", type: "number" }]
145917
- };
145918
- this.setState({ mismatchList, schema: schema2 });
145919
- }
145920
- render() {
145921
- const { mismatchList, schema: schema2 } = this.state;
145922
- let tableOfMismatches;
145923
- if (mismatchList.length === 0) {
145924
- tableOfMismatches = null;
145925
- } else {
145926
- tableOfMismatches = /* @__PURE__ */ React__default.createElement(
145927
- WrappedDT,
145928
- {
145929
- maxHeight: 168,
145930
- formName: "mismatchesTable",
145931
- isSimple: true,
145932
- compact: true,
145933
- noRouter: true,
145934
- schema: schema2,
145935
- entities: mismatchList
145936
- }
145937
- );
145938
- }
145939
- return /* @__PURE__ */ React__default.createElement("div", { style: { maxHeight: 180.8, overflowY: "scroll" } }, /* @__PURE__ */ React__default.createElement(
145940
- "div",
145941
- {
145942
- style: {
145943
- // margin: 10,
145944
- display: "flex",
145945
- flexDirection: "column",
145946
- alignItems: "center"
145947
- }
145948
- },
145949
- /* @__PURE__ */ React__default.createElement("div", { style: { width: 100, margin: 4 } }, tableOfMismatches)
145950
- ));
145951
- }
145952
- };
145953
- __name(_Mismatches, "Mismatches");
145954
- let Mismatches = _Mismatches;
145955
- const Mismatches$1 = withSelectedEntities("mismatchesTable")(Mismatches);
145956
146867
  function PCRTool(props) {
145957
146868
  const {
145958
146869
  sequenceData: sequenceData2,
@@ -146124,7 +147035,7 @@ const _panelMap = {
146124
147035
  comp: PropertiesDialog$1,
146125
147036
  panelSpecificProps: ["PropertiesProps"]
146126
147037
  },
146127
- mismatches: Mismatches$1
147038
+ mismatches: FindMismatches
146128
147039
  };
146129
147040
  const reorder = /* @__PURE__ */ __name((list2, startIndex, endIndex) => {
146130
147041
  const result = Array.from(list2);
@@ -148954,18 +149865,6 @@ __name(createAlignmentView, "createAlignmentView");
148954
149865
  window.createVectorEditor = createVectorEditor;
148955
149866
  window.createAlignmentView = createAlignmentView;
148956
149867
  window.createVersionHistoryView = createVersionHistoryView;
148957
- function getGapMap(sequence2) {
148958
- const gapMap = [0];
148959
- sequence2.split("").forEach((char) => {
148960
- if (char === "-") {
148961
- gapMap[Math.max(0, gapMap.length - 1)] = (gapMap[Math.max(0, gapMap.length - 1)] || 0) + 1;
148962
- } else {
148963
- gapMap.push(gapMap[gapMap.length - 1] || 0);
148964
- }
148965
- });
148966
- return gapMap;
148967
- }
148968
- __name(getGapMap, "getGapMap");
148969
149868
  let getGaps = /* @__PURE__ */ __name(() => ({
148970
149869
  gapsBefore: 0,
148971
149870
  gapsInside: 0