@teselagen/ove 0.8.3 → 0.8.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/AlignmentView/LabileSitesLayer.d.ts +13 -0
  2. package/AlignmentView/PairwiseAlignmentView.d.ts +1 -9
  3. package/BarPlot/index.d.ts +33 -0
  4. package/LinearView/SequenceName.d.ts +2 -1
  5. package/PropertySidePanel/calculateAminoAcidFrequency.d.ts +46 -0
  6. package/PropertySidePanel/index.d.ts +6 -0
  7. package/RowItem/Caret/index.d.ts +2 -1
  8. package/StatusBar/index.d.ts +2 -1
  9. package/aaprops.svg +2287 -0
  10. package/constants/dnaToColor.d.ts +122 -4
  11. package/index.cjs.js +4183 -2813
  12. package/index.es.js +2135 -765
  13. package/index.umd.js +3714 -2344
  14. package/ove.css +92 -6
  15. package/package.json +2 -2
  16. package/src/AlignmentView/AlignmentVisibilityTool.js +141 -37
  17. package/src/AlignmentView/LabileSitesLayer.js +33 -0
  18. package/src/AlignmentView/Minimap.js +5 -3
  19. package/src/AlignmentView/PairwiseAlignmentView.js +55 -61
  20. package/src/AlignmentView/index.js +476 -257
  21. package/src/AlignmentView/style.css +27 -0
  22. package/src/BarPlot/index.js +156 -0
  23. package/src/CircularView/Caret.js +8 -2
  24. package/src/CircularView/SelectionLayer.js +4 -2
  25. package/src/CircularView/index.js +5 -1
  26. package/src/Editor/darkmode.css +7 -0
  27. package/src/Editor/index.js +3 -0
  28. package/src/Editor/userDefinedHandlersAndOpts.js +2 -1
  29. package/src/FindBar/index.js +2 -3
  30. package/src/LinearView/SequenceName.js +8 -2
  31. package/src/LinearView/index.js +21 -0
  32. package/src/PropertySidePanel/calculateAminoAcidFrequency.js +77 -0
  33. package/src/PropertySidePanel/index.js +236 -0
  34. package/src/PropertySidePanel/style.css +39 -0
  35. package/src/RowItem/Caret/index.js +8 -2
  36. package/src/RowItem/Labels.js +1 -1
  37. package/src/RowItem/SelectionLayer/index.js +5 -1
  38. package/src/RowItem/Sequence.js +99 -5
  39. package/src/RowItem/Translations/Translation.js +3 -2
  40. package/src/RowItem/Translations/index.js +2 -0
  41. package/src/RowItem/index.js +74 -8
  42. package/src/RowItem/style.css +3 -4
  43. package/src/StatusBar/index.js +11 -4
  44. package/src/constants/dnaToColor.js +151 -0
  45. package/src/helperComponents/PinchHelper/PinchHelper.js +5 -1
  46. package/src/helperComponents/SelectDialog.js +5 -2
  47. package/src/style.css +2 -2
  48. package/src/utils/editorUtils.js +5 -3
  49. package/src/utils/getAlignedAminoAcidSequenceProps.js +379 -0
  50. package/src/withEditorInteractions/createSequenceInputPopup.js +15 -5
  51. package/src/withEditorInteractions/index.js +3 -1
  52. package/utils/editorUtils.d.ts +2 -1
  53. package/utils/getAlignedAminoAcidSequenceProps.d.ts +49 -0
package/ove.css CHANGED
@@ -8819,11 +8819,13 @@ span.bp3-popover-target{
8819
8819
  /*# sourceMappingURL=blueprint-icons.css.map */:root {
8820
8820
  --base1: #ffffff;
8821
8821
  --base2: #cdcdcd;
8822
+ --reversed: #293742;
8822
8823
  }
8823
8824
 
8824
8825
  body.bp3-dark {
8825
8826
  --base1: #393a3a;
8826
8827
  --base2: #293742;
8828
+ --reversed: #cdcdcd;
8827
8829
  }
8828
8830
 
8829
8831
  .bp3-dark .tg-card {
@@ -10066,6 +10068,19 @@ body:not(.drag-active)
10066
10068
  display: flex;
10067
10069
  flex-direction: column;
10068
10070
  }
10071
+ .th-dragging {
10072
+ z-index: 999 !important;
10073
+ cursor: grabbing !important;
10074
+ opacity: 0.8;
10075
+ background-color: #f5f8fa;
10076
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
10077
+ }
10078
+
10079
+ .bp3-dark .th-dragging {
10080
+ background-color: #30404d;
10081
+ box-shadow: 0 2px 8px rgba(250, 245, 245, 0.15);
10082
+ outline: 1px solid #48aff0;
10083
+ }
10069
10084
  .dna-loader {
10070
10085
  display: inline-block;
10071
10086
  position: relative;
@@ -10884,10 +10899,9 @@ path.partWithSelectedTag {
10884
10899
  user-select: none;
10885
10900
  }
10886
10901
  */
10887
-
10888
- /* .rowViewTextContainer {
10889
- user-select: none;
10890
- } */
10902
+ .alignmentTrackName {
10903
+ user-select: none !important;
10904
+ }
10891
10905
 
10892
10906
  .veRowViewPrimaryProteinSequenceContainer {
10893
10907
  /* needs a z-index less than the selection layer so you can right click the selection above a sequence https://github.com/TeselaGen/openVectorEditor/issues/625 */
@@ -11175,6 +11189,12 @@ path.partWithSelectedTag {
11175
11189
  .alignmentViewTrackContainer {
11176
11190
  position: relative;
11177
11191
  }
11192
+ .alignmentViewTrackContainer.isTrackSelected {
11193
+ background-color: #c0e2f5;
11194
+ }
11195
+ .bp3-dark .alignmentViewTrackContainer.isTrackSelected {
11196
+ background-color: #2277a9;
11197
+ }
11178
11198
  .alignmentViewTrackContainer,
11179
11199
  .alignmentViewTrackContainer .veVectorInteractionWrapper,
11180
11200
  .alignmentViewTrackContainer .veVectorInteractionWrapper > div {
@@ -11255,6 +11275,11 @@ path.partWithSelectedTag {
11255
11275
  .minimapLane.lane-hovered .miniBluePath {
11256
11276
  fill: rgb(169, 169, 245) !important;
11257
11277
  }
11278
+ .minimapLane.isTrackSelected .miniBluePath {
11279
+ fill: rgb(137, 168, 255) !important;
11280
+ stroke: rgb(0, 0, 0);
11281
+ stroke-width: 0.5px;
11282
+ }
11258
11283
 
11259
11284
  .veAlignmentName {
11260
11285
  padding-top: 1px;
@@ -11269,6 +11294,22 @@ path.partWithSelectedTag {
11269
11294
  text-overflow: ellipsis;
11270
11295
  white-space: nowrap;
11271
11296
  }
11297
+
11298
+ .veLabileSites {
11299
+ position: absolute;
11300
+ top: 0;
11301
+ height: 100%;
11302
+ z-index: 10;
11303
+ }
11304
+
11305
+ .veAlignmentViewLabileSiteLine {
11306
+ position: absolute;
11307
+ height: 100%;
11308
+ width: 4px;
11309
+ background: #1585c5;
11310
+ opacity: 0.5;
11311
+ top: 0;
11312
+ }
11272
11313
  .simple-dialog .dialog-buttons {
11273
11314
  display: flex;
11274
11315
  flex-direction: row;
@@ -11527,6 +11568,45 @@ path.partWithSelectedTag {
11527
11568
  justify-content: space-between;
11528
11569
  align-items: baseline;
11529
11570
  }
11571
+ .ove-sidebar-container {
11572
+ display: flex;
11573
+ flex-direction: column;
11574
+ box-shadow: 0 0px 1px var(--reversed);
11575
+ overflow-y: scroll;
11576
+ }
11577
+
11578
+ .sidebar-table {
11579
+ display: grid;
11580
+ /* Define 3 columns with equal width */
11581
+ grid-template-columns: repeat(3, 1fr);
11582
+ gap: 1px; /* Optional: adds a small gap between cells */
11583
+ border: 1px solid #f1f1f1;
11584
+ margin: 10px;
11585
+ border-radius: 4px;
11586
+ }
11587
+
11588
+ .sidebar-row {
11589
+ display: contents; /* Makes children participate in the grid layout of the parent */
11590
+ }
11591
+
11592
+ .sidebar-cell {
11593
+ padding: 5px;
11594
+ border: 1px solid #f1f1f1;
11595
+ text-align: center;
11596
+ /* Flexbox can be used within cells for content alignment if needed */
11597
+ display: flex;
11598
+ align-items: center;
11599
+ justify-content: center;
11600
+ }
11601
+
11602
+ h5 {
11603
+ margin: 0;
11604
+ font-size: 15px;
11605
+ font-weight: bold;
11606
+ text-align: center;
11607
+ padding: 5px 0;
11608
+ border-bottom: 1px solid #f1f1f1;
11609
+ }
11530
11610
  .ta_link {
11531
11611
  cursor: pointer;
11532
11612
  color: blue;
@@ -11788,6 +11868,12 @@ path.partWithSelectedTag {
11788
11868
  .bp3-dark [data-tick-mark] {
11789
11869
  fill: #f5f8fa !important;
11790
11870
  }
11871
+ .bp3-dark .rowViewTextContainer *::selection {
11872
+ fill: #f5f8fa !important;
11873
+ }
11874
+ .bp3-dark .veRowViewAxis *::selection {
11875
+ fill: #f5f8fa !important;
11876
+ }
11791
11877
  /* .bp3-dark .veRowViewAxis text {
11792
11878
  stroke: #f5f8fa !important;
11793
11879
  } */
@@ -12073,8 +12159,8 @@ tspan.vePart {
12073
12159
  font-family: Menlo, Monaco, monospace;
12074
12160
  font-size: 10px;
12075
12161
  }
12076
- .rowViewTextContainer text {
12077
- user-select: none;
12162
+ .rowViewTextContainer text::selection {
12163
+ background: transparent;
12078
12164
  }
12079
12165
  .translationLayer text,
12080
12166
  .tg-prime-direction,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@teselagen/ove",
3
- "version": "0.8.3",
3
+ "version": "0.8.4",
4
4
  "main": "./src/index.js",
5
5
  "type": "module",
6
6
  "exports": {
@@ -19,7 +19,7 @@
19
19
  "@teselagen/range-utils": "0.3.13",
20
20
  "@teselagen/react-list": "0.8.18",
21
21
  "@teselagen/sequence-utils": "0.3.32",
22
- "@teselagen/ui": "0.10.5",
22
+ "@teselagen/ui": "0.10.7",
23
23
  "@use-gesture/react": "10.3.0",
24
24
  "biomsa": "^0.2.4",
25
25
  "classnames": "^2.3.2",
@@ -9,7 +9,10 @@ import {
9
9
  } from "@blueprintjs/core";
10
10
  import React from "react";
11
11
  import { map, startCase } from "lodash-es";
12
- import { pureNoFunc } from "@teselagen/ui";
12
+ import { pureNoFunc, wrapDialog } from "@teselagen/ui";
13
+ import { compose } from "redux";
14
+ import { showDialog } from "../GlobalDialogUtils";
15
+ // import { fullSequenceTranslationMenu } from "../MenuBar/viewSubmenu";
13
16
 
14
17
  export default pureNoFunc(function AlignmentVisibilityTool(props) {
15
18
  return (
@@ -48,51 +51,137 @@ function VisibilityOptions({
48
51
  } else {
49
52
  annotationCountToUse = annotationsWithCounts[0];
50
53
  }
54
+
55
+ const subMenuElements = ["physicalProperties", "plot"];
56
+ const physicalPropertyElements = [
57
+ "hydrophobicity",
58
+ "polar",
59
+ "negative",
60
+ "positive",
61
+ "charged",
62
+ "aliphatic",
63
+ "aromatic"
64
+ ];
65
+ const plotElements = ["conservation", "properties"];
66
+
51
67
  return (
52
68
  <Menu
53
69
  style={{ padding: 10 }}
54
70
  className="alignmentAnnotationVisibilityToolInner"
55
71
  >
56
72
  {map(togglableAlignmentAnnotationSettings, (visible, annotationName) => {
57
- return (
58
- <MenuItem
59
- icon={visible ? "tick" : ""}
60
- onClick={e => {
61
- e.stopPropagation();
62
- if (annotationName === "axis") {
63
- return alignmentAnnotationVisibilityToggle({
64
- axisNumbers: !visible,
65
- axis: !visible
66
- });
73
+ if (
74
+ !physicalPropertyElements.includes(annotationName) &&
75
+ !plotElements.includes(annotationName)
76
+ ) {
77
+ return (
78
+ <MenuItem
79
+ icon={
80
+ visible && !subMenuElements.includes(annotationName)
81
+ ? "tick"
82
+ : ""
67
83
  }
68
- if (annotationName === "cdsFeatureTranslations" && !visible) {
69
- return alignmentAnnotationVisibilityToggle({
70
- cdsFeatureTranslations: !visible,
71
- translations: !visible
84
+ onClick={e => {
85
+ e.stopPropagation();
86
+ if (annotationName === "axis") {
87
+ return alignmentAnnotationVisibilityToggle({
88
+ axisNumbers: !visible,
89
+ axis: !visible
90
+ });
91
+ }
92
+ if (annotationName === "cdsFeatureTranslations" && !visible) {
93
+ return alignmentAnnotationVisibilityToggle({
94
+ cdsFeatureTranslations: !visible,
95
+ translations: !visible
96
+ });
97
+ }
98
+
99
+ alignmentAnnotationVisibilityToggle({
100
+ [annotationName]: !visible
72
101
  });
102
+ }}
103
+ text={
104
+ <>
105
+ {startCase(annotationName)
106
+ .replace("Cds", "CDS")
107
+ .replace("Dna", "DNA")}
108
+ {annotationName in annotationCountToUse ? (
109
+ <Tag round style={{ marginLeft: 7 }}>
110
+ {annotationCountToUse[annotationName]}
111
+ </Tag>
112
+ ) : (
113
+ ""
114
+ )}
115
+ </>
73
116
  }
74
-
75
- alignmentAnnotationVisibilityToggle({
76
- [annotationName]: !visible
77
- });
78
- }}
79
- text={
80
- <>
81
- {startCase(annotationName)
82
- .replace("Cds", "CDS")
83
- .replace("Dna", "DNA")}
84
- {annotationName in annotationCountToUse ? (
85
- <Tag round style={{ marginLeft: 7 }}>
86
- {annotationCountToUse[annotationName]}
87
- </Tag>
88
- ) : (
89
- ""
90
- )}
91
- </>
92
- }
93
- key={annotationName}
94
- ></MenuItem>
95
- );
117
+ key={annotationName}
118
+ >
119
+ {annotationName === "physicalProperties"
120
+ ? map(
121
+ togglableAlignmentAnnotationSettings,
122
+ (_visible, _annotationName) => {
123
+ if (physicalPropertyElements.includes(_annotationName)) {
124
+ return (
125
+ <MenuItem
126
+ icon={_visible ? "tick" : ""}
127
+ onClick={e => {
128
+ e.stopPropagation();
129
+ alignmentAnnotationVisibilityToggle({
130
+ [_annotationName]: !_visible
131
+ });
132
+ }}
133
+ text={<>{startCase(_annotationName)}</>}
134
+ key={_annotationName}
135
+ />
136
+ );
137
+ }
138
+ }
139
+ ).filter(Boolean)
140
+ : annotationName === "plot"
141
+ ? map(
142
+ togglableAlignmentAnnotationSettings,
143
+ (_visible, _annotationName) => {
144
+ if (plotElements.includes(_annotationName)) {
145
+ return (
146
+ <div style={{ position: "relative" }}>
147
+ <MenuItem
148
+ className={`plot-${_annotationName}`}
149
+ icon={_visible ? "tick" : ""}
150
+ onClick={e => {
151
+ e.stopPropagation();
152
+ alignmentAnnotationVisibilityToggle({
153
+ [_annotationName]: !_visible
154
+ });
155
+ }}
156
+ text={<>{startCase(_annotationName)}</>}
157
+ key={_annotationName}
158
+ />
159
+ {_annotationName === "properties" ? (
160
+ <Button
161
+ icon="info-sign"
162
+ style={{
163
+ position: "absolute",
164
+ top: 3,
165
+ right: 0
166
+ }}
167
+ onClick={() => {
168
+ showDialog({
169
+ ModalComponent: PropertyDialog
170
+ });
171
+ }}
172
+ minimal
173
+ small
174
+ />
175
+ ) : null}
176
+ </div>
177
+ );
178
+ }
179
+ }
180
+ ).filter(Boolean)
181
+ : null}
182
+ </MenuItem>
183
+ );
184
+ }
96
185
  })}
97
186
  {/* <MenuItem icon="" text={fullSequenceTranslationMenu.text}>
98
187
  {fullSequenceTranslationMenu.submenu.map(({ text, cmd }) => {
@@ -102,3 +191,18 @@ function VisibilityOptions({
102
191
  </Menu>
103
192
  );
104
193
  }
194
+
195
+ const PropertyDialog = compose(
196
+ wrapDialog({
197
+ title: "Amino Acid Properties",
198
+ style: {
199
+ width: 600
200
+ }
201
+ })
202
+ )(function () {
203
+ return (
204
+ <div style={{ width: "100%", padding: 10 }}>
205
+ <img src="/aaprops.svg" width={580} alt="Amino Acid Properties" />
206
+ </div>
207
+ );
208
+ });
@@ -0,0 +1,33 @@
1
+ import React from "react";
2
+
3
+ /**
4
+ * Draws vertical lines at specified labile site position.
5
+ *
6
+ * @param {Object} props
7
+ * @param {number} leftMargin - Width of the name column.
8
+ * @param {number} charWidth - Width of each character in the alignment.
9
+ * @param {number} sequenceLength - Number of columns in the alignment.
10
+ * @param {number} numTracks - Number of alignment tracks (rows).
11
+ * @param {number[]} positionsToMark - Array of 0-based column indices to mark.
12
+ * @param {number} rowHeight - Height of each alignment row (default: 24).
13
+ */
14
+ const LabileSitesLayer = ({ leftMargin, charWidth, positionsToMark = [] }) => {
15
+ return (
16
+ <div className="veLabileSites">
17
+ {positionsToMark?.map((pos, i) => {
18
+ const x = leftMargin + (pos - 1.2) * charWidth + charWidth / 2;
19
+ return (
20
+ <div
21
+ className="veAlignmentViewLabileSiteLine"
22
+ key={i}
23
+ style={{
24
+ left: x
25
+ }}
26
+ />
27
+ );
28
+ })}
29
+ </div>
30
+ );
31
+ };
32
+
33
+ export default LabileSitesLayer;
@@ -22,7 +22,8 @@ export default class Minimap extends React.Component {
22
22
  "numBpsShownInLinearView",
23
23
  "scrollAlignmentView",
24
24
  "laneHeight",
25
- "laneSpacing"
25
+ "laneSpacing",
26
+ "isTrackSelected"
26
27
  ].some(key => props[key] !== newProps[key])
27
28
  )
28
29
  return true;
@@ -183,7 +184,8 @@ export default class Minimap extends React.Component {
183
184
  alignmentTracks = [],
184
185
  dimensions: { width = 200 },
185
186
  laneHeight,
186
- laneSpacing = 1
187
+ laneSpacing = 1,
188
+ isTrackSelected = []
187
189
  } = this.props;
188
190
  const charWidth = this.getCharWidth();
189
191
 
@@ -233,7 +235,7 @@ export default class Minimap extends React.Component {
233
235
  return (
234
236
  <div
235
237
  key={i + "-lane"}
236
- className="minimapLane"
238
+ className={`minimapLane ${isTrackSelected[i] ? "isTrackSelected" : ""}`}
237
239
  data-lane-index={i}
238
240
  style={{ height: laneHeight, maxHeight: laneHeight }}
239
241
  >
@@ -1,68 +1,62 @@
1
- import React from "react";
1
+ import React, { useState } from "react";
2
2
  import { getPairwiseOverviewLinearViewOptions } from "./getPairwiseOverviewLinearViewOptions";
3
3
  import { AlignmentView } from "./index";
4
4
 
5
5
  //this view is shown if we detect pairwise alignments
6
- export class PairwiseAlignmentView extends React.Component {
7
- state = {
8
- currentPairwiseAlignmentIndex: undefined
9
- };
10
- render() {
11
- const { pairwiseAlignments, pairwiseOverviewAlignmentTracks } = this.props;
12
- const { currentPairwiseAlignmentIndex } = this.state;
13
- if (currentPairwiseAlignmentIndex > -1) {
14
- //we can render the AlignmentView directly
15
- //get the alignmentTracks based on currentPairwiseAlignmentIndex
16
- const alignmentTracks = pairwiseAlignments[currentPairwiseAlignmentIndex];
6
+ export function PairwiseAlignmentView(props) {
7
+ const [currentPairwiseAlignmentIndex, setCurrentPairwiseAlignmentIndex] =
8
+ useState(undefined);
9
+ const { pairwiseAlignments, pairwiseOverviewAlignmentTracks } = props;
17
10
 
18
- const templateLength = alignmentTracks[0].alignmentData.sequence.length;
19
- return (
20
- <AlignmentView
21
- {...{
22
- ...this.props,
23
- sequenceData: {
24
- //pass fake seq data in so editor interactions work
25
- sequence: Array.from(templateLength)
26
- .map(() => "a")
27
- .join("")
28
- },
29
- allowTrackRearrange: false,
30
- alignmentTracks,
31
- hasTemplate: true,
32
- isPairwise: true,
33
- currentPairwiseAlignmentIndex,
34
- handleBackButtonClicked: () => {
35
- this.setState({
36
- currentPairwiseAlignmentIndex: undefined
37
- });
38
- }
39
- }}
40
- />
41
- );
42
- } else {
43
- //we haven't yet selected an alignment to view
44
- // render the AlignmentView zoomed out for each track in pairwiseOverviewAlignmentTracks
45
- // when the view eye icon is hit (maybe next to the name?)
46
- return (
47
- <AlignmentView
48
- {...{
49
- ...this.props,
50
- alignmentTracks: pairwiseOverviewAlignmentTracks,
51
- allowTrackRearrange: false,
52
- allowTrimming: false,
53
- hasTemplate: true,
54
- isPairwise: true,
55
- isInPairwiseOverviewView: true,
56
- isFullyZoomedOut: true,
57
- noClickDragHandlers: true,
58
- linearViewOptions: getPairwiseOverviewLinearViewOptions,
59
- handleSelectTrack: trackIndex => {
60
- //set currentPairwiseAlignmentIndex
61
- this.setState({ currentPairwiseAlignmentIndex: trackIndex - 1 });
62
- }
63
- }}
64
- />
65
- );
66
- }
11
+ if (currentPairwiseAlignmentIndex > -1) {
12
+ //we can render the AlignmentView directly
13
+ //get the alignmentTracks based on currentPairwiseAlignmentIndex
14
+ const alignmentTracks = pairwiseAlignments[currentPairwiseAlignmentIndex];
15
+
16
+ const templateLength = alignmentTracks[0].alignmentData.sequence.length;
17
+ return (
18
+ <AlignmentView
19
+ {...{
20
+ ...props,
21
+ sequenceData: {
22
+ //pass fake seq data in so editor interactions work
23
+ sequence: Array.from(templateLength)
24
+ .map(() => "a")
25
+ .join("")
26
+ },
27
+ allowTrackRearrange: false,
28
+ alignmentTracks,
29
+ hasTemplate: true,
30
+ isPairwise: true,
31
+ currentPairwiseAlignmentIndex,
32
+ handleBackButtonClicked: () => {
33
+ setCurrentPairwiseAlignmentIndex(undefined);
34
+ }
35
+ }}
36
+ />
37
+ );
38
+ } else {
39
+ //we haven't yet selected an alignment to view
40
+ // render the AlignmentView zoomed out for each track in pairwiseOverviewAlignmentTracks
41
+ // when the view eye icon is hit (maybe next to the name?)
42
+ return (
43
+ <AlignmentView
44
+ {...{
45
+ ...props,
46
+ alignmentTracks: pairwiseOverviewAlignmentTracks,
47
+ allowTrackRearrange: false,
48
+ allowTrimming: false,
49
+ hasTemplate: true,
50
+ isPairwise: true,
51
+ isInPairwiseOverviewView: true,
52
+ isFullyZoomedOut: true,
53
+ noClickDragHandlers: true,
54
+ linearViewOptions: getPairwiseOverviewLinearViewOptions,
55
+ handleSelectTrack: trackIndex => {
56
+ setCurrentPairwiseAlignmentIndex(trackIndex - 1);
57
+ }
58
+ }}
59
+ />
60
+ );
67
61
  }
68
62
  }