@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.umd.js CHANGED
@@ -124411,14 +124411,34 @@ ${seq.sequence}
124411
124411
  track.alignmentData.sequence
124412
124412
  );
124413
124413
  const mismatches = matchHighlightRanges.filter(({ isMatch }) => !isMatch);
124414
+ const alignedSeq = track.alignmentData.sequence;
124415
+ const seqLen = alignedSeq.length;
124416
+ const startIndex = seqLen - alignedSeq.replace(/^-+/, "").length;
124417
+ const endIndex = alignedSeq.replace(/-+$/, "").length;
124418
+ const gapRanges = [
124419
+ startIndex > 0 && {
124420
+ start: 0,
124421
+ end: startIndex - 1,
124422
+ differenceType: "gap"
124423
+ },
124424
+ endIndex < seqLen && {
124425
+ start: endIndex,
124426
+ end: seqLen - 1,
124427
+ differenceType: "gap"
124428
+ }
124429
+ ].filter(Boolean);
124414
124430
  return __spreadProps(__spreadValues({}, track), {
124415
124431
  sequenceData: sequenceData2,
124416
124432
  matchHighlightRanges,
124417
- additionalSelectionLayers: matchHighlightRanges.filter(({ isMatch }) => !isMatch).map((range2) => {
124418
- return __spreadProps(__spreadValues(__spreadValues({}, range2), highlightRangeProps), {
124433
+ additionalSelectionLayers: [
124434
+ ...matchHighlightRanges.filter(({ isMatch }) => !isMatch).map((range2) => __spreadProps(__spreadValues(__spreadValues({}, range2), highlightRangeProps), {
124419
124435
  className: "veAlignmentMismatch"
124420
- });
124421
- }),
124436
+ })),
124437
+ ...gapRanges.map((range2) => __spreadProps(__spreadValues(__spreadValues({}, range2), highlightRangeProps), {
124438
+ className: "veAlignmentMismatch"
124439
+ }))
124440
+ ],
124441
+ gapRanges,
124422
124442
  mismatches
124423
124443
  });
124424
124444
  });
@@ -124552,23 +124572,30 @@ ${seq.sequence}
124552
124572
  const startIndex = seqLength - nonTempSeqWithoutLeadingDashes.length;
124553
124573
  const endIndex = seqLength - (seqLength - nonTempSeqWithoutTrailingDashes.length);
124554
124574
  for (let index2 = startIndex; index2 < endIndex; index2++) {
124555
- const isMatch = tempSeq[index2].toLowerCase() === nonTempSeq[index2].toLowerCase();
124556
- const previousRange = ranges[ranges.length - 1];
124557
- if (previousRange) {
124558
- if (previousRange.isMatch === isMatch) {
124559
- previousRange.end++;
124575
+ const tempBase = tempSeq[index2].toLowerCase();
124576
+ const nonTempBase = nonTempSeq[index2].toLowerCase();
124577
+ const isMatch = tempBase === nonTempBase;
124578
+ let differenceType = null;
124579
+ if (!isMatch) {
124580
+ if (tempBase === "-") {
124581
+ differenceType = "insertion";
124582
+ } else if (nonTempBase === "-") {
124583
+ differenceType = "deletion";
124560
124584
  } else {
124561
- ranges.push({
124562
- start: index2,
124563
- end: index2,
124564
- isMatch
124565
- });
124585
+ differenceType = "mismatch";
124566
124586
  }
124587
+ }
124588
+ const previousRange = ranges[ranges.length - 1];
124589
+ if (previousRange && previousRange.isMatch === isMatch && previousRange.differenceType === differenceType) {
124590
+ previousRange.end++;
124591
+ } else if (previousRange) {
124592
+ ranges.push({ start: index2, end: index2, isMatch, differenceType });
124567
124593
  } else {
124568
124594
  ranges.push({
124569
124595
  start: startIndex,
124570
124596
  end: startIndex,
124571
- isMatch
124597
+ isMatch,
124598
+ differenceType
124572
124599
  });
124573
124600
  }
124574
124601
  }
@@ -145343,7 +145370,7 @@ Part of ${annotation.translationType} Translation from BPs ${annotation.start +
145343
145370
  input.click();
145344
145371
  }
145345
145372
  __name(showFileDialog, "showFileDialog");
145346
- const version = "0.8.39";
145373
+ const version = "0.8.41";
145347
145374
  const packageJson = {
145348
145375
  version
145349
145376
  };
@@ -151610,11 +151637,13 @@ Part of ${annotation.translationType} Translation from BPs ${annotation.start +
151610
151637
  dimensions: { width = 200 },
151611
151638
  laneHeight,
151612
151639
  laneSpacing = 1,
151613
- isTrackSelected = []
151640
+ isTrackSelected = [],
151641
+ activeFilterType = "all"
151614
151642
  } = this.props;
151615
151643
  const charWidth2 = this.getCharWidth();
151616
151644
  const {
151617
151645
  matchHighlightRanges: _matchHighlightRanges,
151646
+ gapRanges = [],
151618
151647
  alignmentData: { trimmedRange } = {}
151619
151648
  } = alignmentTracks[i2];
151620
151649
  const matchHighlightRanges = !trimmedRange ? _matchHighlightRanges : flatMap(_matchHighlightRanges, (r2) => {
@@ -151641,10 +151670,19 @@ Part of ${annotation.translationType} Translation from BPs ${annotation.start +
151641
151670
  charWidth2
151642
151671
  );
151643
151672
  const toAdd = `M${xStart},${y2} L${xStart + width2},${y2} L${xStart + width2},${y2 + height} L${xStart},${y2 + height}`;
151644
- if (!range2.isMatch) {
151673
+ if (!range2.isMatch && (activeFilterType === "all" || range2.differenceType === activeFilterType)) {
151645
151674
  redPath += toAdd;
151646
151675
  }
151647
151676
  });
151677
+ if (activeFilterType === "gap") {
151678
+ gapRanges.forEach((range2) => {
151679
+ const { xStart, width: width2 } = getXStartAndWidthFromNonCircularRange(
151680
+ range2,
151681
+ charWidth2
151682
+ );
151683
+ redPath += `M${xStart},${y2} L${xStart + width2},${y2} L${xStart + width2},${y2 + height} L${xStart},${y2 + height}`;
151684
+ });
151685
+ }
151648
151686
  return /* @__PURE__ */ React.createElement(
151649
151687
  "div",
151650
151688
  {
@@ -151674,7 +151712,8 @@ Part of ${annotation.translationType} Translation from BPs ${annotation.start +
151674
151712
  "scrollAlignmentView",
151675
151713
  "laneHeight",
151676
151714
  "laneSpacing",
151677
- "isTrackSelected"
151715
+ "isTrackSelected",
151716
+ "activeFilterType"
151678
151717
  ].some((key) => props[key] !== newProps[key]))
151679
151718
  return true;
151680
151719
  return false;
@@ -151952,6 +151991,899 @@ Part of ${annotation.translationType} Translation from BPs ${annotation.start +
151952
151991
  return splitRangeIntoTwoPartsIfItIsCircular(inverted, seqLen);
151953
151992
  }
151954
151993
  __name(getTrimmedRangesToDisplay, "getTrimmedRangesToDisplay");
151994
+ function groupConsecutiveDifferences(differences) {
151995
+ const grouped = [];
151996
+ for (const diff of differences) {
151997
+ if (diff.type === "mismatch") {
151998
+ grouped.push(__spreadProps(__spreadValues({}, diff), { start: diff.position, end: diff.position }));
151999
+ continue;
152000
+ }
152001
+ const last2 = grouped[grouped.length - 1];
152002
+ if (last2 && last2.type === diff.type && last2.end === diff.position - 1) {
152003
+ grouped[grouped.length - 1] = __spreadProps(__spreadValues({}, last2), { end: diff.position });
152004
+ } else {
152005
+ grouped.push(__spreadProps(__spreadValues({}, diff), { start: diff.position, end: diff.position }));
152006
+ }
152007
+ }
152008
+ return grouped;
152009
+ }
152010
+ __name(groupConsecutiveDifferences, "groupConsecutiveDifferences");
152011
+ function findAlignmentDifferences(alignedSeqs) {
152012
+ var _a2;
152013
+ if (alignedSeqs.length < 2 || !((_a2 = alignedSeqs[0]) == null ? void 0 : _a2.length)) return [];
152014
+ const template = alignedSeqs[0].toLowerCase();
152015
+ const nonTemplates = alignedSeqs.slice(1).map((s2) => s2.toLowerCase());
152016
+ const trackBounds = nonTemplates.map((seq) => {
152017
+ const withoutLeading = seq.replace(/^-+/, "");
152018
+ const withoutTrailing = seq.replace(/-+$/, "");
152019
+ const start2 = seq.length - withoutLeading.length;
152020
+ const end2 = seq.length - (seq.length - withoutTrailing.length);
152021
+ return { start: start2, end: end2 };
152022
+ });
152023
+ const differences = [];
152024
+ for (let i2 = 0; i2 < template.length; i2++) {
152025
+ const templateBase = template[i2];
152026
+ const allNonTemplateBases = nonTemplates.map((seq) => seq[i2]);
152027
+ const bases = [templateBase, ...allNonTemplateBases];
152028
+ const alignedIndices = trackBounds.reduce((acc, { start: start2, end: end2 }, idx) => {
152029
+ if (i2 >= start2 && i2 < end2) acc.push(idx);
152030
+ return acc;
152031
+ }, []);
152032
+ if (alignedIndices.length === 0) {
152033
+ differences.push({ position: i2, type: "gap", bases });
152034
+ continue;
152035
+ }
152036
+ const alignedBases = alignedIndices.map((idx) => allNonTemplateBases[idx]);
152037
+ const templateIsGap = templateBase === "-";
152038
+ const nonTemplateHasBase = alignedBases.some((b3) => b3 !== "-");
152039
+ const nonTemplateHasGap = alignedBases.some((b3) => b3 === "-");
152040
+ if (templateIsGap && nonTemplateHasBase) {
152041
+ differences.push({ position: i2, type: "insertion", bases });
152042
+ } else if (!templateIsGap && nonTemplateHasGap) {
152043
+ differences.push({ position: i2, type: "deletion", bases });
152044
+ } else if (!templateIsGap) {
152045
+ const uniqueBases = /* @__PURE__ */ new Set([templateBase, ...alignedBases]);
152046
+ if (uniqueBases.size > 1) {
152047
+ differences.push({ position: i2, type: "mismatch", bases });
152048
+ }
152049
+ }
152050
+ }
152051
+ return differences;
152052
+ }
152053
+ __name(findAlignmentDifferences, "findAlignmentDifferences");
152054
+ function scrollToAlignmentSelection() {
152055
+ const el = document.querySelector(".veCaret");
152056
+ if (el) {
152057
+ el.scrollIntoView({ inline: "center", block: "nearest" });
152058
+ }
152059
+ }
152060
+ __name(scrollToAlignmentSelection, "scrollToAlignmentSelection");
152061
+ function updateCaretPosition({ start: start2, end: end2 }) {
152062
+ if (window.updateAlignmentSelection) {
152063
+ window.updateAlignmentSelection({ start: start2, end: end2 });
152064
+ }
152065
+ }
152066
+ __name(updateCaretPosition, "updateCaretPosition");
152067
+ const FILTER_OPTIONS = [
152068
+ { value: "all", label: "All" },
152069
+ { value: "mismatch", label: "Mismatches" },
152070
+ { value: "insertion", label: "Insertions" },
152071
+ { value: "deletion", label: "Deletions" },
152072
+ { value: "gap", label: "Gaps" }
152073
+ ];
152074
+ function FindMismatches(props) {
152075
+ var _a2;
152076
+ const { alignmentJson, id: id2, onFilterChange } = props;
152077
+ const alignedSeqs = reactExports.useMemo(
152078
+ () => alignmentJson.map((t2) => {
152079
+ var _a3;
152080
+ return ((_a3 = t2.alignmentData) == null ? void 0 : _a3.sequence) || "";
152081
+ }),
152082
+ [alignmentJson]
152083
+ );
152084
+ const [activeFilter, setActiveFilter] = React.useState("all");
152085
+ const allDifferences = reactExports.useMemo(
152086
+ () => groupConsecutiveDifferences(findAlignmentDifferences(alignedSeqs)),
152087
+ [alignedSeqs]
152088
+ );
152089
+ const countsByType = reactExports.useMemo(() => {
152090
+ const counts = { all: 0, mismatch: 0, insertion: 0, deletion: 0, gap: 0 };
152091
+ allDifferences.forEach((d2) => {
152092
+ counts[d2.type] = (counts[d2.type] || 0) + 1;
152093
+ counts.all++;
152094
+ });
152095
+ return counts;
152096
+ }, [allDifferences]);
152097
+ const differences = reactExports.useMemo(() => {
152098
+ const filtered = activeFilter === "all" ? allDifferences : allDifferences.filter((d2) => d2.type === activeFilter);
152099
+ return [{ position: -1, start: -1, end: -1, bases: [""] }, ...filtered];
152100
+ }, [allDifferences, activeFilter]);
152101
+ const currentCaretPosition = useSelector(
152102
+ (state2) => {
152103
+ var _a3;
152104
+ return (_a3 = state2.VectorEditor.__allEditorsOptions.alignments[id2]) == null ? void 0 : _a3.caretPosition;
152105
+ }
152106
+ );
152107
+ const [currentIdx, setCurrentIdx] = React.useState(0);
152108
+ const currentDiff = differences[currentIdx];
152109
+ const disablePrev = currentIdx <= 1;
152110
+ const disableNext = currentIdx >= differences.length - 1;
152111
+ reactExports.useEffect(() => {
152112
+ setCurrentIdx(0);
152113
+ }, [activeFilter]);
152114
+ reactExports.useEffect(() => {
152115
+ onFilterChange == null ? void 0 : onFilterChange({ activeFilter });
152116
+ }, [activeFilter, onFilterChange]);
152117
+ reactExports.useEffect(() => {
152118
+ if (currentCaretPosition !== -1) {
152119
+ const diffIdx = differences.findIndex(
152120
+ (d2, i2) => i2 > 0 && currentCaretPosition >= d2.start && currentCaretPosition <= d2.end + 1
152121
+ );
152122
+ if (diffIdx !== -1 && diffIdx !== currentIdx) {
152123
+ setCurrentIdx(diffIdx);
152124
+ }
152125
+ }
152126
+ }, [currentCaretPosition, differences, currentIdx]);
152127
+ const updateView = /* @__PURE__ */ __name((diff) => {
152128
+ const idx = differences.indexOf(diff);
152129
+ const { start: start2, end: end2 } = diff;
152130
+ setCurrentIdx(idx);
152131
+ updateCaretPosition({ start: start2, end: end2 });
152132
+ setTimeout(() => {
152133
+ scrollToAlignmentSelection();
152134
+ }, 0);
152135
+ }, "updateView");
152136
+ const prevDifference = /* @__PURE__ */ __name(() => {
152137
+ var _a3, _b2;
152138
+ const pivot = currentCaretPosition >= 0 ? currentCaretPosition : (_b2 = (_a3 = differences[currentIdx]) == null ? void 0 : _a3.start) != null ? _b2 : 0;
152139
+ const prev = [...differences].reverse().find((d2) => d2.start >= 0 && d2.start < pivot);
152140
+ if (prev) updateView(prev);
152141
+ }, "prevDifference");
152142
+ const nextDifference = /* @__PURE__ */ __name(() => {
152143
+ var _a3, _b2;
152144
+ const pivot = currentCaretPosition >= 0 ? currentCaretPosition : (_b2 = (_a3 = differences[currentIdx]) == null ? void 0 : _a3.start) != null ? _b2 : -1;
152145
+ const next = differences.find((d2) => d2.start > pivot && d2.start >= 0);
152146
+ if (next) updateView(next);
152147
+ }, "nextDifference");
152148
+ const noDifferences = differences.length <= 1;
152149
+ const activeOption = FILTER_OPTIONS.find((o2) => o2.value === activeFilter);
152150
+ const activeLabel = (_a2 = activeOption == null ? void 0 : activeOption.label) != null ? _a2 : "Differences";
152151
+ const filterMenu = /* @__PURE__ */ React.createElement(Menu, null, FILTER_OPTIONS.map(({ value, label }) => {
152152
+ var _a3;
152153
+ const count2 = (_a3 = countsByType[value]) != null ? _a3 : 0;
152154
+ const isActive2 = activeFilter === value;
152155
+ return /* @__PURE__ */ React.createElement(
152156
+ MenuItem,
152157
+ {
152158
+ key: value,
152159
+ active: isActive2,
152160
+ onClick: /* @__PURE__ */ __name(() => setActiveFilter(value), "onClick"),
152161
+ text: /* @__PURE__ */ React.createElement("span", { className: "veDiffMenuItem-inner" }, label, /* @__PURE__ */ React.createElement(Tag, { round: true, minimal: true, style: { marginLeft: 6 } }, count2))
152162
+ }
152163
+ );
152164
+ }));
152165
+ return /* @__PURE__ */ React.createElement("div", { className: "veDiffNavigator" }, /* @__PURE__ */ React.createElement(
152166
+ Popover,
152167
+ {
152168
+ minimal: true,
152169
+ position: Position.BOTTOM_LEFT,
152170
+ content: filterMenu,
152171
+ target: /* @__PURE__ */ React.createElement(
152172
+ Button,
152173
+ {
152174
+ minimal: true,
152175
+ "data-tip": "Filter Difference Type",
152176
+ small: true,
152177
+ rightIcon: "caret-down",
152178
+ className: "veDiffFilter-trigger"
152179
+ },
152180
+ activeLabel
152181
+ )
152182
+ }
152183
+ ), noDifferences ? /* @__PURE__ */ React.createElement("span", { className: "veDiffNav-empty" }, "no", " ", activeFilter === "all" ? "differences" : activeLabel.toLowerCase()) : /* @__PURE__ */ React.createElement("div", { className: "veDiffNav" }, /* @__PURE__ */ React.createElement(
152184
+ Button,
152185
+ {
152186
+ minimal: true,
152187
+ small: true,
152188
+ "data-tip": "Previous Difference",
152189
+ icon: "arrow-left",
152190
+ intent: Intent.PRIMARY,
152191
+ onClick: prevDifference,
152192
+ disabled: disablePrev
152193
+ }
152194
+ ), /* @__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(
152195
+ Button,
152196
+ {
152197
+ minimal: true,
152198
+ small: true,
152199
+ "data-tip": "Next Difference",
152200
+ icon: "arrow-right",
152201
+ intent: Intent.PRIMARY,
152202
+ onClick: nextDifference,
152203
+ disabled: disableNext
152204
+ }
152205
+ )));
152206
+ }
152207
+ __name(FindMismatches, "FindMismatches");
152208
+ function getGapMap(sequence2) {
152209
+ const gapMap = [0];
152210
+ sequence2.split("").forEach((char) => {
152211
+ if (char === "-") {
152212
+ gapMap[Math.max(0, gapMap.length - 1)] = (gapMap[Math.max(0, gapMap.length - 1)] || 0) + 1;
152213
+ } else {
152214
+ gapMap.push(gapMap[gapMap.length - 1] || 0);
152215
+ }
152216
+ });
152217
+ return gapMap;
152218
+ }
152219
+ __name(getGapMap, "getGapMap");
152220
+ const MATCH_COLOR = "gold";
152221
+ const CURRENT_MATCH_COLOR = "green";
152222
+ const MISMATCH_COLOR = "red";
152223
+ const ANNOTATION_TYPES = ["features", "parts", "primers"];
152224
+ const initialSearchState = {
152225
+ searchText: "",
152226
+ matches: [],
152227
+ currentMatchIndex: 0,
152228
+ searched: false,
152229
+ featureMatches: [],
152230
+ dnaOrAA: "DNA",
152231
+ ambiguousOrLiteral: "LITERAL",
152232
+ mismatchesAllowed: 0
152233
+ };
152234
+ function searchReducer(state2, action2) {
152235
+ switch (action2.type) {
152236
+ case "SET_SEARCH_TEXT":
152237
+ return __spreadProps(__spreadValues({}, state2), { searchText: action2.payload });
152238
+ case "SET_MATCHES":
152239
+ return __spreadProps(__spreadValues({}, state2), {
152240
+ matches: action2.payload.matches,
152241
+ currentMatchIndex: action2.payload.currentMatchIndex
152242
+ });
152243
+ case "SET_CURRENT_MATCH_INDEX":
152244
+ return __spreadProps(__spreadValues({}, state2), { currentMatchIndex: action2.payload });
152245
+ case "SET_SEARCHED":
152246
+ return __spreadProps(__spreadValues({}, state2), { searched: action2.payload });
152247
+ case "SEARCH_COMPLETE":
152248
+ return __spreadProps(__spreadValues({}, state2), {
152249
+ matches: action2.payload.matches,
152250
+ currentMatchIndex: action2.payload.currentMatchIndex,
152251
+ searched: action2.payload.searched
152252
+ });
152253
+ case "SET_FEATURE_MATCHES":
152254
+ return __spreadProps(__spreadValues({}, state2), { featureMatches: action2.payload });
152255
+ case "SET_DNA_OR_AA":
152256
+ return __spreadProps(__spreadValues({}, state2), { dnaOrAA: action2.payload });
152257
+ case "SET_AMBIGUOUS_OR_LITERAL":
152258
+ return __spreadProps(__spreadValues({}, state2), { ambiguousOrLiteral: action2.payload });
152259
+ case "SET_MISMATCHES_ALLOWED":
152260
+ return __spreadProps(__spreadValues({}, state2), { mismatchesAllowed: Math.max(0, action2.payload) });
152261
+ case "RESET":
152262
+ return __spreadValues({}, initialSearchState);
152263
+ default:
152264
+ return state2;
152265
+ }
152266
+ }
152267
+ __name(searchReducer, "searchReducer");
152268
+ function AlignmentSearchBar(props) {
152269
+ const { alignmentTracks = [], setSearchMatchLayers } = props;
152270
+ const [searchState, dispatch] = reactExports.useReducer(searchReducer, initialSearchState);
152271
+ const {
152272
+ searchText,
152273
+ matches,
152274
+ currentMatchIndex,
152275
+ searched,
152276
+ featureMatches,
152277
+ dnaOrAA,
152278
+ ambiguousOrLiteral,
152279
+ mismatchesAllowed
152280
+ } = searchState;
152281
+ const debouncedSearch = reactExports.useRef(
152282
+ debounce((text2, search2, featureSearch) => {
152283
+ search2(text2);
152284
+ featureSearch(text2);
152285
+ }, 50)
152286
+ ).current;
152287
+ reactExports.useEffect(() => {
152288
+ return () => {
152289
+ debouncedSearch.cancel();
152290
+ };
152291
+ }, [debouncedSearch]);
152292
+ const [highlightAll, setHighlightAll] = reactExports.useState(false);
152293
+ const [isExpanded, setIsExpanded] = reactExports.useState(false);
152294
+ const [isOpen2, setIsOpen] = reactExports.useState(false);
152295
+ const [isPopoverOpen, setIsPopoverOpen] = reactExports.useState(false);
152296
+ const handleToggleExpanded = reactExports.useCallback(() => {
152297
+ setIsExpanded((prev) => {
152298
+ const next = !prev;
152299
+ if (!next) setIsPopoverOpen(true);
152300
+ return next;
152301
+ });
152302
+ }, [setIsPopoverOpen]);
152303
+ reactExports.useEffect(() => {
152304
+ dispatch({ type: "RESET" });
152305
+ if (setSearchMatchLayers) setSearchMatchLayers([]);
152306
+ }, [setSearchMatchLayers]);
152307
+ const buildMatchLayers = reactExports.useCallback(
152308
+ (allMatches, activeIndex) => {
152309
+ if (!setSearchMatchLayers) return;
152310
+ if (!allMatches.length) {
152311
+ setSearchMatchLayers([]);
152312
+ return;
152313
+ }
152314
+ const makeMismatchLayers = /* @__PURE__ */ __name((match) => (match.mismatchAlignmentPositions || []).map((pos) => ({
152315
+ start: pos,
152316
+ end: pos,
152317
+ color: MISMATCH_COLOR,
152318
+ className: "veSearchMismatch",
152319
+ ignoreGaps: true,
152320
+ hideCarets: true
152321
+ })), "makeMismatchLayers");
152322
+ const layers = highlightAll ? allMatches.flatMap((match, i2) => [
152323
+ {
152324
+ start: match.alignmentStart,
152325
+ end: match.alignmentEnd,
152326
+ color: i2 === activeIndex ? CURRENT_MATCH_COLOR : MATCH_COLOR,
152327
+ className: i2 === activeIndex ? "veSearchLayerActive" : "veSearchLayer",
152328
+ ignoreGaps: true
152329
+ },
152330
+ ...makeMismatchLayers(match)
152331
+ ]) : [
152332
+ {
152333
+ start: allMatches[activeIndex].alignmentStart,
152334
+ end: allMatches[activeIndex].alignmentEnd,
152335
+ color: CURRENT_MATCH_COLOR,
152336
+ className: "veSearchLayerActive",
152337
+ ignoreGaps: true
152338
+ },
152339
+ ...makeMismatchLayers(allMatches[activeIndex])
152340
+ ];
152341
+ setSearchMatchLayers(layers);
152342
+ },
152343
+ [setSearchMatchLayers, highlightAll]
152344
+ );
152345
+ const navigateTo = reactExports.useCallback(
152346
+ (allMatches, index2) => {
152347
+ const match = allMatches[index2];
152348
+ if (!match) return;
152349
+ updateCaretPosition({
152350
+ start: match.alignmentStart,
152351
+ end: match.alignmentEnd
152352
+ });
152353
+ setTimeout(() => {
152354
+ scrollToAlignmentSelection();
152355
+ }, 0);
152356
+ buildMatchLayers(allMatches, index2);
152357
+ },
152358
+ [buildMatchLayers]
152359
+ );
152360
+ const runSearch = reactExports.useCallback(
152361
+ (text2) => {
152362
+ const query = text2.trim();
152363
+ if (!query) {
152364
+ dispatch({
152365
+ type: "SEARCH_COMPLETE",
152366
+ payload: { matches: [], currentMatchIndex: 0, searched: false }
152367
+ });
152368
+ if (setSearchMatchLayers) setSearchMatchLayers([]);
152369
+ return;
152370
+ }
152371
+ const allMatches = [];
152372
+ alignmentTracks.slice(0, 1).forEach((track, trackIndex) => {
152373
+ var _a2, _b2;
152374
+ const rawSeq = ((_a2 = track.sequenceData) == null ? void 0 : _a2.sequence) || "";
152375
+ const alignedSeq = ((_b2 = track.alignmentData) == null ? void 0 : _b2.sequence) || "";
152376
+ const gapMap = getGapMap(alignedSeq);
152377
+ const gapOffset = /* @__PURE__ */ __name((n2) => {
152378
+ var _a3, _b3;
152379
+ return (_b3 = (_a3 = gapMap[n2]) != null ? _a3 : gapMap[gapMap.length - 1]) != null ? _b3 : 0;
152380
+ }, "gapOffset");
152381
+ let seqMatches = [];
152382
+ if (dnaOrAA === "DNA" && ambiguousOrLiteral === "LITERAL" && mismatchesAllowed > 0) {
152383
+ const approxMatches = findApproxMatches(
152384
+ query.toLowerCase(),
152385
+ rawSeq.toLowerCase(),
152386
+ mismatchesAllowed,
152387
+ false
152388
+ );
152389
+ seqMatches = approxMatches.map((m2) => ({
152390
+ start: m2.index,
152391
+ end: m2.index + m2.match.length - 1,
152392
+ mismatchPositions: m2.mismatchPositions
152393
+ }));
152394
+ } else {
152395
+ seqMatches = findSequenceMatches(rawSeq, query, {
152396
+ isCircular: false,
152397
+ isAmbiguous: ambiguousOrLiteral === "AMBIGUOUS",
152398
+ isProteinSearch: dnaOrAA !== "DNA",
152399
+ searchReverseStrand: dnaOrAA === "DNA"
152400
+ });
152401
+ }
152402
+ const hitsToProcess = query.length < 2 ? seqMatches.slice(0, 1) : seqMatches;
152403
+ hitsToProcess.forEach(({ start: start2, end: end2, mismatchPositions }) => {
152404
+ const alignmentStart = start2 + gapOffset(start2);
152405
+ const alignmentEnd = end2 + gapOffset(end2);
152406
+ const mismatchAlignmentPositions = (mismatchPositions || []).map(
152407
+ (p2) => {
152408
+ const absPos = start2 + p2;
152409
+ return absPos + gapOffset(absPos);
152410
+ }
152411
+ );
152412
+ allMatches.push({
152413
+ trackIndex,
152414
+ alignmentStart,
152415
+ alignmentEnd,
152416
+ mismatchAlignmentPositions
152417
+ });
152418
+ });
152419
+ });
152420
+ const results = query.length < 2 ? allMatches.slice(0, 1) : allMatches;
152421
+ dispatch({
152422
+ type: "SEARCH_COMPLETE",
152423
+ payload: { matches: results, currentMatchIndex: 0, searched: true }
152424
+ });
152425
+ if (results.length) {
152426
+ navigateTo(results, 0);
152427
+ } else {
152428
+ if (setSearchMatchLayers) setSearchMatchLayers([]);
152429
+ }
152430
+ },
152431
+ [
152432
+ alignmentTracks,
152433
+ navigateTo,
152434
+ dnaOrAA,
152435
+ ambiguousOrLiteral,
152436
+ mismatchesAllowed,
152437
+ setSearchMatchLayers
152438
+ ]
152439
+ );
152440
+ const runFeatureSearch = reactExports.useCallback(
152441
+ (text2) => {
152442
+ const query = text2.trim().toLowerCase();
152443
+ if (!query) {
152444
+ dispatch({ type: "SET_FEATURE_MATCHES", payload: [] });
152445
+ return;
152446
+ }
152447
+ const allMatches = [];
152448
+ alignmentTracks.slice(0, 1).forEach((track, trackIndex) => {
152449
+ const { sequenceData: sequenceData2, alignmentData } = track;
152450
+ const alignedSeq = (alignmentData == null ? void 0 : alignmentData.sequence) || "";
152451
+ const gapMap = getGapMap(alignedSeq);
152452
+ const gapOffset = /* @__PURE__ */ __name((n2) => {
152453
+ var _a2, _b2;
152454
+ return (_b2 = (_a2 = gapMap[n2]) != null ? _a2 : gapMap[gapMap.length - 1]) != null ? _b2 : 0;
152455
+ }, "gapOffset");
152456
+ const trackName = (alignmentData == null ? void 0 : alignmentData.name) || (sequenceData2 == null ? void 0 : sequenceData2.name) || (sequenceData2 == null ? void 0 : sequenceData2.id) || "";
152457
+ ANNOTATION_TYPES.forEach((type2) => {
152458
+ const anns = sequenceData2 == null ? void 0 : sequenceData2[type2];
152459
+ if (!anns) return;
152460
+ const annsArray = Array.isArray(anns) ? anns : Object.values(anns);
152461
+ annsArray.forEach((ann) => {
152462
+ if (!ann.name) return;
152463
+ if (ann.name.toLowerCase().includes(query)) {
152464
+ const alignmentStart = ann.start + gapOffset(ann.start);
152465
+ const alignmentEnd = ann.end + gapOffset(ann.end);
152466
+ allMatches.push({
152467
+ trackIndex,
152468
+ trackName,
152469
+ type: type2,
152470
+ annotation: ann,
152471
+ alignmentStart,
152472
+ alignmentEnd
152473
+ });
152474
+ }
152475
+ });
152476
+ });
152477
+ });
152478
+ dispatch({ type: "SET_FEATURE_MATCHES", payload: allMatches });
152479
+ },
152480
+ [alignmentTracks]
152481
+ );
152482
+ const goToPrev = reactExports.useCallback(() => {
152483
+ if (!matches.length) return;
152484
+ const newIndex = currentMatchIndex === 0 ? matches.length - 1 : currentMatchIndex - 1;
152485
+ dispatch({ type: "SET_CURRENT_MATCH_INDEX", payload: newIndex });
152486
+ navigateTo(matches, newIndex);
152487
+ }, [matches, currentMatchIndex, navigateTo]);
152488
+ const goToNext = reactExports.useCallback(() => {
152489
+ if (!matches.length) return;
152490
+ const newIndex = currentMatchIndex === matches.length - 1 ? 0 : currentMatchIndex + 1;
152491
+ dispatch({ type: "SET_CURRENT_MATCH_INDEX", payload: newIndex });
152492
+ navigateTo(matches, newIndex);
152493
+ }, [matches, currentMatchIndex, navigateTo]);
152494
+ const handleKeyDown = reactExports.useCallback(
152495
+ (e2) => {
152496
+ if (e2.key === "Escape") {
152497
+ setIsOpen(false);
152498
+ }
152499
+ if (e2.key === "Enter") {
152500
+ if (e2.shiftKey) {
152501
+ goToPrev();
152502
+ } else {
152503
+ goToNext();
152504
+ }
152505
+ e2.preventDefault();
152506
+ e2.stopPropagation();
152507
+ }
152508
+ },
152509
+ [goToPrev, goToNext]
152510
+ );
152511
+ reactExports.useEffect(() => {
152512
+ if (!searched || !searchText.trim()) return;
152513
+ runSearch(searchText);
152514
+ runFeatureSearch(searchText);
152515
+ }, [
152516
+ dnaOrAA,
152517
+ ambiguousOrLiteral,
152518
+ mismatchesAllowed,
152519
+ runSearch,
152520
+ runFeatureSearch,
152521
+ searched,
152522
+ searchText
152523
+ ]);
152524
+ reactExports.useEffect(() => {
152525
+ if (searchText.trim().length < 1) setHighlightAll(false);
152526
+ }, [searchText]);
152527
+ const prevHighlightAll = reactExports.useRef(highlightAll);
152528
+ reactExports.useEffect(() => {
152529
+ if (prevHighlightAll.current !== highlightAll) {
152530
+ prevHighlightAll.current = highlightAll;
152531
+ if (matches.length) buildMatchLayers(matches, currentMatchIndex);
152532
+ }
152533
+ }, [highlightAll, matches, currentMatchIndex, buildMatchLayers]);
152534
+ const hasMatches = matches.length > 0;
152535
+ const handleChange = reactExports.useCallback(
152536
+ (e2) => {
152537
+ const value = e2.target.value;
152538
+ dispatch({ type: "SET_SEARCH_TEXT", payload: value });
152539
+ debouncedSearch(value, runSearch, runFeatureSearch);
152540
+ },
152541
+ [debouncedSearch, runSearch, runFeatureSearch]
152542
+ );
152543
+ const handleFeatureClick = reactExports.useCallback((featureMatch) => {
152544
+ updateCaretPosition({
152545
+ start: featureMatch.alignmentStart,
152546
+ end: featureMatch.alignmentEnd
152547
+ });
152548
+ setTimeout(() => {
152549
+ scrollToAlignmentSelection();
152550
+ }, 0);
152551
+ }, []);
152552
+ const matchCounter = /* @__PURE__ */ React.createElement(
152553
+ "span",
152554
+ {
152555
+ style: {
152556
+ marginRight: 3,
152557
+ color: "lightgrey",
152558
+ fontSize: "0.9em",
152559
+ whiteSpace: "nowrap"
152560
+ }
152561
+ },
152562
+ hasMatches ? currentMatchIndex + 1 : 0,
152563
+ "/",
152564
+ matches.length
152565
+ );
152566
+ const inlineNavEl = /* @__PURE__ */ React.createElement("span", { style: { display: "flex", alignItems: "center" } }, !isExpanded && /* @__PURE__ */ React.createElement(
152567
+ Popover,
152568
+ {
152569
+ autoFocus: false,
152570
+ enforceFocus: false,
152571
+ isOpen: isPopoverOpen,
152572
+ onInteraction: setIsPopoverOpen,
152573
+ position: Position.TOP,
152574
+ content: /* @__PURE__ */ React.createElement(
152575
+ "div",
152576
+ {
152577
+ className: "ve-find-options-popover",
152578
+ style: {
152579
+ display: "flex",
152580
+ flexDirection: "column",
152581
+ paddingLeft: 20,
152582
+ paddingBottom: 10,
152583
+ paddingTop: 10,
152584
+ paddingRight: 20,
152585
+ gap: 6
152586
+ }
152587
+ },
152588
+ /* @__PURE__ */ React.createElement(
152589
+ FindOptionsPanel,
152590
+ {
152591
+ dnaOrAA,
152592
+ ambiguousOrLiteral,
152593
+ mismatchesAllowed,
152594
+ searchText,
152595
+ matches,
152596
+ dispatch,
152597
+ highlightAll,
152598
+ setHighlightAll,
152599
+ isExpanded,
152600
+ onToggleExpanded: handleToggleExpanded
152601
+ }
152602
+ )
152603
+ ),
152604
+ target: /* @__PURE__ */ React.createElement(Button, { minimal: true, icon: "wrench", "data-tip": "Options" })
152605
+ }
152606
+ ), matchCounter, /* @__PURE__ */ React.createElement(
152607
+ Button,
152608
+ {
152609
+ minimal: true,
152610
+ small: true,
152611
+ icon: "caret-left",
152612
+ "data-tip": "Previous",
152613
+ disabled: !hasMatches,
152614
+ onClick: goToPrev
152615
+ }
152616
+ ), /* @__PURE__ */ React.createElement(
152617
+ Button,
152618
+ {
152619
+ minimal: true,
152620
+ small: true,
152621
+ icon: "caret-right",
152622
+ "data-tip": "Next",
152623
+ disabled: !hasMatches,
152624
+ onClick: goToNext
152625
+ }
152626
+ ), /* @__PURE__ */ React.createElement(
152627
+ Button,
152628
+ {
152629
+ minimal: true,
152630
+ small: true,
152631
+ "data-tip": "Close (Esc)",
152632
+ icon: "small-cross",
152633
+ onClick: /* @__PURE__ */ __name(() => setIsOpen(false), "onClick")
152634
+ }
152635
+ ));
152636
+ const expandedNavEl = /* @__PURE__ */ React.createElement("span", { style: { display: "flex", alignItems: "center" } }, matchCounter, /* @__PURE__ */ React.createElement(
152637
+ Button,
152638
+ {
152639
+ minimal: true,
152640
+ small: true,
152641
+ icon: "caret-up",
152642
+ disabled: !hasMatches,
152643
+ onClick: goToPrev
152644
+ }
152645
+ ), /* @__PURE__ */ React.createElement(
152646
+ Button,
152647
+ {
152648
+ minimal: true,
152649
+ small: true,
152650
+ icon: "caret-down",
152651
+ disabled: !hasMatches,
152652
+ onClick: goToNext
152653
+ }
152654
+ ));
152655
+ if (!isOpen2) {
152656
+ return /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement(
152657
+ Button,
152658
+ {
152659
+ minimal: true,
152660
+ small: true,
152661
+ intent: "primary",
152662
+ icon: "search",
152663
+ rightIcon: "caret-right",
152664
+ "data-tip": "Search",
152665
+ onClick: /* @__PURE__ */ __name(() => setIsOpen(true), "onClick")
152666
+ }
152667
+ ));
152668
+ }
152669
+ const annotationPopoverOpen = searched && featureMatches.length > 0;
152670
+ const inputEl = /* @__PURE__ */ React.createElement(
152671
+ InputGroup,
152672
+ {
152673
+ className: "tg-find-tool-input alignment-search-bar",
152674
+ leftIcon: "search",
152675
+ placeholder: "Search...",
152676
+ autoFocus: true,
152677
+ value: searchText,
152678
+ onChange: handleChange,
152679
+ onKeyDown: handleKeyDown,
152680
+ rightElement: inlineNavEl
152681
+ }
152682
+ );
152683
+ return /* @__PURE__ */ React.createElement("div", { style: { position: "relative" } }, !isExpanded && /* @__PURE__ */ React.createElement(
152684
+ Popover,
152685
+ {
152686
+ autoFocus: false,
152687
+ enforceFocus: false,
152688
+ modifiers: {
152689
+ arrow: false
152690
+ },
152691
+ position: Position.BOTTOM,
152692
+ isOpen: annotationPopoverOpen,
152693
+ content: /* @__PURE__ */ React.createElement(
152694
+ AnnotationResultsComp,
152695
+ {
152696
+ featureMatches,
152697
+ onClickMatch: handleFeatureClick
152698
+ }
152699
+ ),
152700
+ target: inputEl
152701
+ }
152702
+ ), isExpanded && /* @__PURE__ */ React.createElement(
152703
+ "div",
152704
+ {
152705
+ style: {
152706
+ position: "absolute",
152707
+ top: 0,
152708
+ left: 0,
152709
+ padding: 10,
152710
+ paddingBottom: 25,
152711
+ display: "flex",
152712
+ alignItems: "flex-start",
152713
+ gap: 10,
152714
+ zIndex: 5e4,
152715
+ background: "white",
152716
+ boxShadow: "0 2px 8px rgba(0,0,0,0.2)",
152717
+ borderRadius: 3
152718
+ }
152719
+ },
152720
+ /* @__PURE__ */ React.createElement("div", { style: { display: "flex", flexDirection: "column", gap: 6 } }, /* @__PURE__ */ React.createElement(
152721
+ TextArea,
152722
+ {
152723
+ autoFocus: true,
152724
+ placeholder: "Search sequences and annotations...",
152725
+ value: searchText,
152726
+ onChange: handleChange,
152727
+ onKeyDown: handleKeyDown,
152728
+ style: { resize: "vertical", width: 350, height: 190 }
152729
+ }
152730
+ ), annotationPopoverOpen && /* @__PURE__ */ React.createElement(
152731
+ AnnotationResultsComp,
152732
+ {
152733
+ featureMatches,
152734
+ onClickMatch: handleFeatureClick
152735
+ }
152736
+ )),
152737
+ /* @__PURE__ */ React.createElement("div", { style: { display: "flex", flexDirection: "column", gap: 5 } }, expandedNavEl, /* @__PURE__ */ React.createElement(
152738
+ FindOptionsPanel,
152739
+ {
152740
+ dnaOrAA,
152741
+ ambiguousOrLiteral,
152742
+ mismatchesAllowed,
152743
+ searchText,
152744
+ matches,
152745
+ dispatch,
152746
+ highlightAll,
152747
+ setHighlightAll,
152748
+ isExpanded,
152749
+ onToggleExpanded: handleToggleExpanded
152750
+ }
152751
+ )),
152752
+ /* @__PURE__ */ React.createElement(
152753
+ Button,
152754
+ {
152755
+ minimal: true,
152756
+ style: { position: "absolute", bottom: 0, right: 0 },
152757
+ onClick: /* @__PURE__ */ __name(() => setIsOpen(false), "onClick"),
152758
+ icon: "cross"
152759
+ }
152760
+ )
152761
+ ));
152762
+ }
152763
+ __name(AlignmentSearchBar, "AlignmentSearchBar");
152764
+ function AnnotationResultsComp({ featureMatches, onClickMatch }) {
152765
+ const byType = {};
152766
+ ANNOTATION_TYPES.forEach((type2) => {
152767
+ byType[type2] = [];
152768
+ });
152769
+ featureMatches.forEach((match) => {
152770
+ if (byType[match.type]) {
152771
+ byType[match.type].push(match);
152772
+ }
152773
+ });
152774
+ const featureColorMap = getFeatureToColorMap({ includeHidden: true });
152775
+ return /* @__PURE__ */ React.createElement("div", { className: "veAnnotationFindMatches" }, ANNOTATION_TYPES.map((type2) => {
152776
+ const anns = byType[type2];
152777
+ if (!anns.length) return null;
152778
+ const showing = anns.slice(0, 10);
152779
+ 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, i2) => {
152780
+ const { annotation } = match;
152781
+ const annotationColor = type2 === "parts" ? "#ac68cc" : annotation.color || featureColorMap[annotation.type];
152782
+ return /* @__PURE__ */ React.createElement(
152783
+ "div",
152784
+ {
152785
+ key: i2,
152786
+ onClick: /* @__PURE__ */ __name(() => onClickMatch(match), "onClick"),
152787
+ className: "veAnnotationFoundResult"
152788
+ },
152789
+ /* @__PURE__ */ React.createElement("div", { style: { display: "flex", alignItems: "center" } }, /* @__PURE__ */ React.createElement(
152790
+ "div",
152791
+ {
152792
+ style: {
152793
+ background: annotationColor,
152794
+ height: 15,
152795
+ width: 15,
152796
+ marginRight: 3
152797
+ }
152798
+ }
152799
+ ), annotation.name),
152800
+ /* @__PURE__ */ React.createElement("div", { className: "veAnnotationFoundResultRange" }, annotation.start + 1, "-", annotation.end + 1)
152801
+ );
152802
+ })));
152803
+ }));
152804
+ }
152805
+ __name(AnnotationResultsComp, "AnnotationResultsComp");
152806
+ function FindOptionsPanel({
152807
+ dnaOrAA,
152808
+ ambiguousOrLiteral,
152809
+ mismatchesAllowed,
152810
+ searchText,
152811
+ matches,
152812
+ dispatch,
152813
+ highlightAll,
152814
+ setHighlightAll,
152815
+ isExpanded,
152816
+ onToggleExpanded
152817
+ }) {
152818
+ return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(
152819
+ TgHTMLSelect,
152820
+ {
152821
+ options: [
152822
+ { label: "DNA", value: "DNA" },
152823
+ { label: "Amino Acids", value: "AA" }
152824
+ ],
152825
+ value: dnaOrAA,
152826
+ onChange: /* @__PURE__ */ __name((e2) => dispatch({ type: "SET_DNA_OR_AA", payload: e2.target.value }), "onChange")
152827
+ }
152828
+ ), /* @__PURE__ */ React.createElement("div", { style: { display: "flex" } }, /* @__PURE__ */ React.createElement(
152829
+ TgHTMLSelect,
152830
+ {
152831
+ options: [
152832
+ { label: "Literal", value: "LITERAL" },
152833
+ { label: "Ambiguous", value: "AMBIGUOUS" }
152834
+ ],
152835
+ value: ambiguousOrLiteral,
152836
+ onChange: /* @__PURE__ */ __name((e2) => dispatch({
152837
+ type: "SET_AMBIGUOUS_OR_LITERAL",
152838
+ payload: e2.target.value
152839
+ }), "onChange")
152840
+ }
152841
+ ), /* @__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(
152842
+ "div",
152843
+ {
152844
+ style: {
152845
+ marginTop: "8px",
152846
+ display: "flex",
152847
+ flexDirection: "row",
152848
+ gap: "3px",
152849
+ alignItems: "center"
152850
+ }
152851
+ },
152852
+ /* @__PURE__ */ React.createElement("label", null, "Mismatches Allowed:"),
152853
+ /* @__PURE__ */ React.createElement(
152854
+ NumericInput,
152855
+ {
152856
+ min: 0,
152857
+ max: 10,
152858
+ className: "tg-mismatches-allowed-input",
152859
+ style: { width: "60px" },
152860
+ value: mismatchesAllowed,
152861
+ disabled: dnaOrAA !== "DNA" || ambiguousOrLiteral !== "LITERAL",
152862
+ onValueChange: /* @__PURE__ */ __name((value) => dispatch({
152863
+ type: "SET_MISMATCHES_ALLOWED",
152864
+ payload: Number.parseInt(value, 10) || 0
152865
+ }), "onValueChange")
152866
+ }
152867
+ ),
152868
+ /* @__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."))
152869
+ ), /* @__PURE__ */ React.createElement(
152870
+ Switch,
152871
+ {
152872
+ checked: highlightAll,
152873
+ onChange: /* @__PURE__ */ __name(() => setHighlightAll((v2) => !v2), "onChange"),
152874
+ disabled: searchText.trim().length < 2 || matches.length > MAX_MATCHES_DISPLAYED
152875
+ },
152876
+ /* @__PURE__ */ React.createElement(
152877
+ Tooltip,
152878
+ {
152879
+ disabled: matches.length <= MAX_MATCHES_DISPLAYED,
152880
+ content: `Disabled because there are >${MAX_MATCHES_DISPLAYED} matches`
152881
+ },
152882
+ "Highlight All"
152883
+ )
152884
+ ), /* @__PURE__ */ React.createElement(Switch, { checked: isExpanded, onChange: onToggleExpanded }, "Expanded"));
152885
+ }
152886
+ __name(FindOptionsPanel, "FindOptionsPanel");
151955
152887
  const AlignmentVisibilityTool = pure(/* @__PURE__ */ __name(function AlignmentVisibilityTool2(props) {
151956
152888
  return /* @__PURE__ */ React.createElement(
151957
152889
  Popover,
@@ -151959,17 +152891,18 @@ Part of ${annotation.translationType} Translation from BPs ${annotation.start +
151959
152891
  minimal: true,
151960
152892
  position: "bottom",
151961
152893
  content: /* @__PURE__ */ React.createElement(VisibilityOptions$2, __spreadValues({}, props)),
151962
- target: /* @__PURE__ */ React.createElement(Tooltip, { content: "Visibility Options" }, /* @__PURE__ */ React.createElement(
152894
+ target: /* @__PURE__ */ React.createElement(
151963
152895
  Button,
151964
152896
  {
151965
152897
  className: "tg-alignment-visibility-toggle",
151966
152898
  small: true,
152899
+ "data-tip": "Visibility Options",
151967
152900
  rightIcon: "caret-down",
151968
152901
  intent: Intent.PRIMARY,
151969
152902
  minimal: true,
151970
152903
  icon: "eye-open"
151971
152904
  }
151972
- ))
152905
+ )
151973
152906
  }
151974
152907
  );
151975
152908
  }, "AlignmentVisibilityTool"));
@@ -165135,7 +166068,6 @@ Part of ${annotation.translationType} Translation from BPs ${annotation.start +
165135
166068
  const PropertySidePanel = /* @__PURE__ */ __name(({ properties: properties2, setProperties, style: style2 }) => {
165136
166069
  const sidebarRef = React.useRef(null);
165137
166070
  const [mismatchesCount, setMismatchesCount] = React.useState(0);
165138
- const [mismatchesInRange, setMismatchesInRange] = React.useState(0);
165139
166071
  const { track, isOpen: isOpen2, selection, isPairwise } = properties2;
165140
166072
  const getSequenceInRegion = reactExports.useCallback(() => {
165141
166073
  var _a2, _b2;
@@ -165154,6 +166086,30 @@ Part of ${annotation.translationType} Translation from BPs ${annotation.start +
165154
166086
  if (!Array.isArray(tr)) return [];
165155
166087
  return isPairwise ? tr.filter((m2) => (m2 == null ? void 0 : m2.color) === "red") : tr;
165156
166088
  }, [track, mismatchKey, isPairwise]);
166089
+ const mismatchSchema = reactExports.useMemo(
166090
+ () => ({
166091
+ fields: [
166092
+ {
166093
+ path: "start",
166094
+ type: "number",
166095
+ displayName: "Start",
166096
+ render: /* @__PURE__ */ __name((val2) => val2 + 1, "render")
166097
+ },
166098
+ {
166099
+ path: "end",
166100
+ type: "number",
166101
+ displayName: "End",
166102
+ render: /* @__PURE__ */ __name((val2) => val2 + 1, "render")
166103
+ }
166104
+ ]
166105
+ }),
166106
+ []
166107
+ );
166108
+ const mismatchEntities = reactExports.useMemo(() => {
166109
+ return (trackMismatches || []).map((m2, i2) => __spreadProps(__spreadValues({}, m2), {
166110
+ id: i2.toString()
166111
+ }));
166112
+ }, [trackMismatches]);
165157
166113
  reactExports.useEffect(() => {
165158
166114
  if (!isOpen2 || sidebarRef.current === null || !track) {
165159
166115
  return;
@@ -165171,21 +166127,6 @@ Part of ${annotation.translationType} Translation from BPs ${annotation.start +
165171
166127
  }
165172
166128
  });
165173
166129
  setMismatchesCount(mismatchCount);
165174
- setMismatchesInRange(mismatchCount);
165175
- if (selection && selection.start > -1 && selection.end > -1) {
165176
- let count2 = 0;
165177
- trackMismatches == null ? void 0 : trackMismatches.forEach((tm) => {
165178
- if (tm === null || tm.start === null || tm.end === null) {
165179
- return;
165180
- }
165181
- const overlapStart = Math.max(tm.start, selection.start);
165182
- const overlapEnd = Math.min(tm.end, selection.end);
165183
- if (overlapEnd >= overlapStart) {
165184
- count2 += overlapEnd - overlapStart + 1;
165185
- }
165186
- });
165187
- setMismatchesInRange(count2);
165188
- }
165189
166130
  }, [isOpen2, track, selection, trackMismatches]);
165190
166131
  const aminoFreq = reactExports.useMemo(() => {
165191
166132
  var _a2, _b2;
@@ -165221,7 +166162,7 @@ Part of ${annotation.translationType} Translation from BPs ${annotation.start +
165221
166162
  width: "100%"
165222
166163
  }
165223
166164
  }
165224
- ), /* @__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 : size2, title: "Length" }), /* @__PURE__ */ React.createElement(
166165
+ ), /* @__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 : size2, title: "Length" }), /* @__PURE__ */ React.createElement(
165225
166166
  RowItem,
165226
166167
  {
165227
166168
  item: molecularWeight == null ? void 0 : molecularWeight.toFixed(2),
@@ -165235,18 +166176,45 @@ Part of ${annotation.translationType} Translation from BPs ${annotation.start +
165235
166176
  title: "Extinction Coefficient"
165236
166177
  }
165237
166178
  )), /* @__PURE__ */ React.createElement(
165238
- RowItem,
165239
- {
165240
- item: `${mismatchesInRange}/${mismatchesCount}`,
165241
- title: "Mismatches"
165242
- }
165243
- ), /* @__PURE__ */ React.createElement(
165244
166179
  RowItem,
165245
166180
  {
165246
166181
  item: selection && selection.start > -1 ? /* @__PURE__ */ React.createElement("span", null, selection.start + 1, " - ", selection.end + 1) : /* @__PURE__ */ React.createElement("span", null, "1 - ", isProtein2 ? proteinSize : size2),
165247
166182
  title: "Region"
165248
166183
  }
165249
- )), /* @__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) => {
166184
+ ), /* @__PURE__ */ React.createElement(HeaderItem, { title: `Mismatches (${mismatchesCount})` }), trackMismatches && trackMismatches.length > 0 && /* @__PURE__ */ React.createElement(
166185
+ "div",
166186
+ {
166187
+ style: {
166188
+ margin: "0px 10px"
166189
+ }
166190
+ },
166191
+ /* @__PURE__ */ React.createElement(
166192
+ WrappedDT,
166193
+ {
166194
+ formName: "mismatchesTable",
166195
+ isSimple: true,
166196
+ noHeader: true,
166197
+ noFooter: true,
166198
+ withSearch: false,
166199
+ noPadding: true,
166200
+ compact: true,
166201
+ maxHeight: 150,
166202
+ entities: mismatchEntities,
166203
+ schema: mismatchSchema,
166204
+ onRowClick: /* @__PURE__ */ __name((e2, row) => {
166205
+ updateCaretPosition({ start: row.start, end: row.end });
166206
+ setTimeout(() => {
166207
+ scrollToAlignmentSelection();
166208
+ }, 0);
166209
+ }, "onRowClick")
166210
+ }
166211
+ )
166212
+ )), /* @__PURE__ */ React.createElement(
166213
+ HeaderItem,
166214
+ {
166215
+ title: `${isProtein2 ? "Amino Acid" : "Base Pair"} Frequencies`
166216
+ }
166217
+ ), /* @__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) => {
165250
166218
  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), "%"));
165251
166219
  })));
165252
166220
  } else {
@@ -165303,11 +166271,29 @@ Part of ${annotation.translationType} Translation from BPs ${annotation.start +
165303
166271
  );
165304
166272
  }, "PropertySidePanel");
165305
166273
  function RowItem({ item, title, units }) {
165306
- if (!item) return;
166274
+ if (item == null) {
166275
+ return null;
166276
+ }
165307
166277
  const propertyClass = title.split(" ").join("-").toLowerCase();
165308
- 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 : ""));
166278
+ 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 : ""));
165309
166279
  }
165310
166280
  __name(RowItem, "RowItem");
166281
+ const HeaderItem = /* @__PURE__ */ __name(({ title }) => {
166282
+ return /* @__PURE__ */ React.createElement(
166283
+ "h5",
166284
+ {
166285
+ style: {
166286
+ margin: 0,
166287
+ fontSize: 15,
166288
+ fontWeight: "bold",
166289
+ textAlign: "center",
166290
+ padding: "5px 0",
166291
+ borderBottom: "1px solid #f1f1f1"
166292
+ }
166293
+ },
166294
+ title
166295
+ );
166296
+ }, "HeaderItem");
165311
166297
  function calculatePairwiseIdentity(seq1, seq2, excludeGaps = true) {
165312
166298
  if (seq1.length !== seq2.length) {
165313
166299
  throw new Error("Sequences must be aligned (same length)");
@@ -165703,6 +166689,11 @@ Part of ${annotation.translationType} Translation from BPs ${annotation.start +
165703
166689
  const [tempTrimBefore, setTempTrimBefore] = reactExports.useState({});
165704
166690
  const [tempTrimAfter, setTempTrimAfter] = reactExports.useState({});
165705
166691
  const [tempTrimmingCaret, setTempTrimmingCaret] = reactExports.useState({});
166692
+ const [searchMatchLayers, setSearchMatchLayers] = React.useState([]);
166693
+ const [activeFilterType, setActiveFilterType] = reactExports.useState("all");
166694
+ const handleFilterChange = reactExports.useCallback(({ activeFilter }) => {
166695
+ setActiveFilterType(activeFilter);
166696
+ }, []);
165706
166697
  const bindOutsideChangeHelper = reactExports.useRef({});
165707
166698
  const alignmentHolder = reactExports.useRef(null);
165708
166699
  const alignmentHolderTop = reactExports.useRef(null);
@@ -166427,7 +167418,12 @@ ${seqDataToCopy}\r
166427
167418
  alignmentData,
166428
167419
  chromatogramData
166429
167420
  }) : linearViewOptions)), {
166430
- additionalSelectionLayers,
167421
+ additionalSelectionLayers: [
167422
+ ...i2 !== 0 ? (additionalSelectionLayers || []).filter(
167423
+ (layer) => activeFilterType === "all" ? layer.differenceType !== "gap" : layer.differenceType === activeFilterType
167424
+ ) : additionalSelectionLayers || [],
167425
+ ...searchMatchLayers || []
167426
+ ],
166431
167427
  dimensions: {
166432
167428
  width: linearViewWidth
166433
167429
  },
@@ -166955,7 +167951,7 @@ ${seqDataToCopy}\r
166955
167951
  display: "flex",
166956
167952
  minHeight: "32px",
166957
167953
  width: "100%",
166958
- flexWrap: "nowrap",
167954
+ flexWrap: "wrap",
166959
167955
  flexDirection: "row",
166960
167956
  flex: "0 0 auto"
166961
167957
  },
@@ -167076,6 +168072,22 @@ ${seqDataToCopy}\r
167076
168072
  currentPairwiseAlignmentIndex
167077
168073
  }, alignmentVisibilityToolOptions)
167078
168074
  ),
168075
+ /* @__PURE__ */ React.createElement(
168076
+ AlignmentSearchBar,
168077
+ {
168078
+ alignmentTracks,
168079
+ id: id2,
168080
+ setSearchMatchLayers
168081
+ }
168082
+ ),
168083
+ /* @__PURE__ */ React.createElement(
168084
+ FindMismatches,
168085
+ {
168086
+ alignmentJson: alignmentTracks,
168087
+ id: id2,
168088
+ onFilterChange: handleFilterChange
168089
+ }
168090
+ ),
167079
168091
  additionalTopEl,
167080
168092
  saveMessage && /* @__PURE__ */ React.createElement(
167081
168093
  "div",
@@ -167176,6 +168188,7 @@ ${seqDataToCopy}\r
167176
168188
  }
167177
168189
  )),
167178
168190
  alignmentTracks,
168191
+ activeFilterType,
167179
168192
  dimensions: {
167180
168193
  width: Math.max(width, 10) || 10
167181
168194
  },
@@ -172365,108 +173378,6 @@ ${seqDataToCopy}\r
172365
173378
  ]
172366
173379
  };
172367
173380
  const DigestTool$1 = withEditorInteractions(DigestTool);
172368
- const _Mismatches = class _Mismatches extends React.Component {
172369
- constructor() {
172370
- super(...arguments);
172371
- __publicField(this, "getGapMap", /* @__PURE__ */ __name((sequence2) => {
172372
- const gapMap = [0];
172373
- sequence2.split("").forEach((char) => {
172374
- if (char === "-") {
172375
- gapMap[Math.max(0, gapMap.length - 1)] = (gapMap[Math.max(0, gapMap.length - 1)] || 0) + 1;
172376
- } else {
172377
- gapMap.push(gapMap[gapMap.length - 1] || 0);
172378
- }
172379
- });
172380
- return gapMap;
172381
- }, "getGapMap"));
172382
- __publicField(this, "getMismatchList", /* @__PURE__ */ __name((alignmentData, mismatches) => {
172383
- const mismatchList = [];
172384
- let getGaps = /* @__PURE__ */ __name(() => ({
172385
- gapsBefore: 0,
172386
- gapsInside: 0
172387
- }), "getGaps");
172388
- const gapMap = this.getGapMap(alignmentData.sequence);
172389
- getGaps = /* @__PURE__ */ __name((rangeOrCaretPosition) => {
172390
- if (typeof rangeOrCaretPosition !== "object") {
172391
- return {
172392
- gapsBefore: gapMap[Math.min(rangeOrCaretPosition, gapMap.length - 1)]
172393
- };
172394
- }
172395
- const { start: start2, end: end2 } = rangeOrCaretPosition;
172396
- const toReturn = {
172397
- gapsBefore: gapMap[start2],
172398
- gapsInside: gapMap[Math.min(end2, gapMap.length - 1)] - gapMap[Math.min(start2, gapMap.length - 1)]
172399
- };
172400
- return toReturn;
172401
- }, "getGaps");
172402
- const gapsBeforeSequence = getGaps(0).gapsBefore;
172403
- for (let mismatchI = 0; mismatchI < mismatches.length; mismatchI++) {
172404
- const mismatchEnd = mismatches[mismatchI].end;
172405
- const mismatchStart = mismatches[mismatchI].start;
172406
- const mismatchDifference = mismatchEnd - mismatchStart;
172407
- if (mismatchDifference === 0) {
172408
- mismatchList.push({
172409
- mismatches: mismatchStart + 1 - gapsBeforeSequence,
172410
- start: mismatchStart - gapsBeforeSequence,
172411
- end: mismatchStart - gapsBeforeSequence
172412
- });
172413
- } else {
172414
- for (let innerI = 0; innerI <= mismatchDifference; innerI++) {
172415
- mismatchList.push({
172416
- mismatches: mismatchStart + innerI + 1 - gapsBeforeSequence,
172417
- start: mismatchStart + innerI - gapsBeforeSequence,
172418
- end: mismatchStart + innerI - gapsBeforeSequence
172419
- });
172420
- }
172421
- }
172422
- }
172423
- return mismatchList;
172424
- }, "getMismatchList"));
172425
- }
172426
- UNSAFE_componentWillMount() {
172427
- const { alignmentData, mismatches } = this.props;
172428
- const mismatchList = this.getMismatchList(alignmentData, mismatches);
172429
- const schema2 = {
172430
- fields: [{ path: "mismatches", type: "number" }]
172431
- };
172432
- this.setState({ mismatchList, schema: schema2 });
172433
- }
172434
- render() {
172435
- const { mismatchList, schema: schema2 } = this.state;
172436
- let tableOfMismatches;
172437
- if (mismatchList.length === 0) {
172438
- tableOfMismatches = null;
172439
- } else {
172440
- tableOfMismatches = /* @__PURE__ */ React.createElement(
172441
- WrappedDT,
172442
- {
172443
- maxHeight: 168,
172444
- formName: "mismatchesTable",
172445
- isSimple: true,
172446
- compact: true,
172447
- noRouter: true,
172448
- schema: schema2,
172449
- entities: mismatchList
172450
- }
172451
- );
172452
- }
172453
- return /* @__PURE__ */ React.createElement("div", { style: { maxHeight: 180.8, overflowY: "scroll" } }, /* @__PURE__ */ React.createElement(
172454
- "div",
172455
- {
172456
- style: {
172457
- // margin: 10,
172458
- display: "flex",
172459
- flexDirection: "column",
172460
- alignItems: "center"
172461
- }
172462
- },
172463
- /* @__PURE__ */ React.createElement("div", { style: { width: 100, margin: 4 } }, tableOfMismatches)
172464
- ));
172465
- }
172466
- };
172467
- __name(_Mismatches, "Mismatches");
172468
- let Mismatches = _Mismatches;
172469
- const Mismatches$1 = withSelectedEntities("mismatchesTable")(Mismatches);
172470
173381
  function PCRTool(props) {
172471
173382
  const {
172472
173383
  sequenceData: sequenceData2,
@@ -172638,7 +173549,7 @@ ${seqDataToCopy}\r
172638
173549
  comp: PropertiesDialog$1,
172639
173550
  panelSpecificProps: ["PropertiesProps"]
172640
173551
  },
172641
- mismatches: Mismatches$1
173552
+ mismatches: FindMismatches
172642
173553
  };
172643
173554
  const reorder = /* @__PURE__ */ __name((list2, startIndex, endIndex) => {
172644
173555
  const result = Array.from(list2);
@@ -175438,18 +176349,6 @@ ${seqDataToCopy}\r
175438
176349
  window.createVectorEditor = createVectorEditor;
175439
176350
  window.createAlignmentView = createAlignmentView;
175440
176351
  window.createVersionHistoryView = createVersionHistoryView;
175441
- function getGapMap(sequence2) {
175442
- const gapMap = [0];
175443
- sequence2.split("").forEach((char) => {
175444
- if (char === "-") {
175445
- gapMap[Math.max(0, gapMap.length - 1)] = (gapMap[Math.max(0, gapMap.length - 1)] || 0) + 1;
175446
- } else {
175447
- gapMap.push(gapMap[gapMap.length - 1] || 0);
175448
- }
175449
- });
175450
- return gapMap;
175451
- }
175452
- __name(getGapMap, "getGapMap");
175453
176352
  exports2.getGaps = () => ({
175454
176353
  gapsBefore: 0,
175455
176354
  gapsInside: 0