@teselagen/ove 0.7.7 → 0.7.8
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/AlignmentView/index.d.ts +1 -101
- package/RowItem/Caret/index.d.ts +14 -1
- package/index.cjs.js +1329 -1329
- package/index.es.js +1329 -1329
- package/index.umd.js +1327 -1318
- package/package.json +1 -1
- package/src/AlignmentView/index.js +1151 -1273
- package/src/RowItem/Caret/index.js +14 -16
|
@@ -4,7 +4,13 @@ import {
|
|
|
4
4
|
Droppable,
|
|
5
5
|
Draggable as DndDraggable
|
|
6
6
|
} from "@hello-pangea/dnd";
|
|
7
|
-
import React
|
|
7
|
+
import React, {
|
|
8
|
+
useCallback,
|
|
9
|
+
useEffect,
|
|
10
|
+
useMemo,
|
|
11
|
+
useRef,
|
|
12
|
+
useState
|
|
13
|
+
} from "react";
|
|
8
14
|
import { connect } from "react-redux";
|
|
9
15
|
import {
|
|
10
16
|
Button,
|
|
@@ -19,17 +25,8 @@ import {
|
|
|
19
25
|
EditableText
|
|
20
26
|
} from "@blueprintjs/core";
|
|
21
27
|
import { InfoHelper, Loading, showContextMenu, withStore } from "@teselagen/ui";
|
|
22
|
-
import { store } from "@risingstack/react-easy-state";
|
|
23
|
-
import {
|
|
24
|
-
throttle,
|
|
25
|
-
// cloneDeep,
|
|
26
|
-
map,
|
|
27
|
-
some,
|
|
28
|
-
forEach,
|
|
29
|
-
isFunction,
|
|
30
|
-
unset,
|
|
31
|
-
omit
|
|
32
|
-
} from "lodash-es";
|
|
28
|
+
import { store as _store } from "@risingstack/react-easy-state";
|
|
29
|
+
import { throttle, map, some, isFunction, unset, omit } from "lodash-es";
|
|
33
30
|
import { getSequenceDataBetweenRange } from "@teselagen/sequence-utils";
|
|
34
31
|
import ReactList from "@teselagen/react-list";
|
|
35
32
|
import ReactDOM from "react-dom";
|
|
@@ -39,7 +36,7 @@ import Minimap, { getTrimmedRangesToDisplay } from "./Minimap";
|
|
|
39
36
|
import { compose, branch, renderComponent } from "recompose";
|
|
40
37
|
import AlignmentVisibilityTool from "./AlignmentVisibilityTool";
|
|
41
38
|
import * as alignmentActions from "../redux/alignments";
|
|
42
|
-
import
|
|
39
|
+
import _estimateRowHeight from "../RowView/estimateRowHeight";
|
|
43
40
|
import prepareRowData from "../utils/prepareRowData";
|
|
44
41
|
import withEditorProps from "../withEditorProps";
|
|
45
42
|
|
|
@@ -48,7 +45,7 @@ import {
|
|
|
48
45
|
editorDragged,
|
|
49
46
|
editorClicked,
|
|
50
47
|
editorDragStarted,
|
|
51
|
-
updateSelectionOrCaret,
|
|
48
|
+
updateSelectionOrCaret as _updateSelectionOrCaret,
|
|
52
49
|
editorDragStopped
|
|
53
50
|
} from "../withEditorInteractions/clickAndDragUtils";
|
|
54
51
|
import { ResizeSensor } from "@blueprintjs/core";
|
|
@@ -91,119 +88,95 @@ try {
|
|
|
91
88
|
);
|
|
92
89
|
}
|
|
93
90
|
|
|
94
|
-
export
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
sequenceLength: this.getSequenceLength(),
|
|
147
|
-
caretPosition: this.easyStore.caretPosition,
|
|
148
|
-
selectionLayer: this.easyStore.selectionLayer,
|
|
149
|
-
easyStore: this.easyStore,
|
|
150
|
-
caretPositionUpdate: this.caretPositionUpdate,
|
|
151
|
-
selectionLayerUpdate: this.selectionLayerUpdate,
|
|
152
|
-
event,
|
|
153
|
-
doNotWrapOrigin: true,
|
|
154
|
-
shiftHeld: event.shiftKey,
|
|
155
|
-
// caretGrabbed: event.target.className === "cursor",
|
|
156
|
-
selectionStartGrabbed: event.target.classList.contains(
|
|
157
|
-
draggableClassnames.selectionStart
|
|
158
|
-
),
|
|
159
|
-
selectionEndGrabbed: event.target.classList.contains(
|
|
160
|
-
draggableClassnames.selectionEnd
|
|
161
|
-
)
|
|
162
|
-
};
|
|
163
|
-
callback(callbackVals);
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
componentWillUnmount() {
|
|
167
|
-
if (window.Cypress) {
|
|
168
|
-
delete window.scrollAlignmentToPercent;
|
|
169
|
-
delete window.Cypress.scrollAlignmentToPercent;
|
|
170
|
-
delete window.updateAlignmentSelection;
|
|
171
|
-
delete window.Cypress.updateAlignmentSelection;
|
|
172
|
-
}
|
|
173
|
-
// const { removeAlignmentFromRedux, id } = this.props;
|
|
174
|
-
// removeAlignmentFromRedux({ id });
|
|
175
|
-
this.onShortcutCopy &&
|
|
176
|
-
document.removeEventListener("keydown", this.handleAlignmentCopy);
|
|
177
|
-
}
|
|
91
|
+
export const AlignmentView = props => {
|
|
92
|
+
const {
|
|
93
|
+
alignmentType,
|
|
94
|
+
alignmentRunUpdate,
|
|
95
|
+
alignmentName: _alignmentName,
|
|
96
|
+
caretPosition,
|
|
97
|
+
handleAlignmentSave,
|
|
98
|
+
id,
|
|
99
|
+
isFullyZoomedOut,
|
|
100
|
+
scrollPercentageToJumpTo,
|
|
101
|
+
selectionLayer,
|
|
102
|
+
sequenceData,
|
|
103
|
+
sequenceLength,
|
|
104
|
+
shouldAutosave,
|
|
105
|
+
store,
|
|
106
|
+
height,
|
|
107
|
+
minimapLaneHeight,
|
|
108
|
+
minimapLaneSpacing,
|
|
109
|
+
isInPairwiseOverviewView,
|
|
110
|
+
noVisibilityOptions,
|
|
111
|
+
updateAlignmentSortOrder,
|
|
112
|
+
alignmentSortOrder,
|
|
113
|
+
handleBackButtonClicked,
|
|
114
|
+
allowTrimming,
|
|
115
|
+
additionalSelectionLayerRightClickedOptions,
|
|
116
|
+
selectionLayerRightClicked,
|
|
117
|
+
additionalTopEl,
|
|
118
|
+
additionalTopLeftEl,
|
|
119
|
+
handleAlignmentRename,
|
|
120
|
+
stateTrackingId,
|
|
121
|
+
style,
|
|
122
|
+
unmappedSeqs
|
|
123
|
+
} = props;
|
|
124
|
+
|
|
125
|
+
const {
|
|
126
|
+
alignmentId,
|
|
127
|
+
alignmentTrackIndex,
|
|
128
|
+
alignmentTracks = [],
|
|
129
|
+
alignmentVisibilityToolOptions,
|
|
130
|
+
allowTrackNameEdit,
|
|
131
|
+
allowTrackRearrange,
|
|
132
|
+
currentPairwiseAlignmentIndex,
|
|
133
|
+
handleSelectTrack,
|
|
134
|
+
hasTemplate,
|
|
135
|
+
isPairwise,
|
|
136
|
+
linearViewOptions,
|
|
137
|
+
noClickDragHandlers,
|
|
138
|
+
pairwiseAlignments,
|
|
139
|
+
pairwiseOverviewAlignmentTracks,
|
|
140
|
+
upsertAlignmentRun,
|
|
141
|
+
...rest
|
|
142
|
+
} = props;
|
|
178
143
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
};
|
|
144
|
+
const [width, setWidth] = useState(0);
|
|
145
|
+
const [nameDivWidth, setNameDivWidth] = useState(140);
|
|
146
|
+
const [charWidthInLinearView, _setCharWidthInLinearView] = useState(
|
|
147
|
+
charWidthInLinearViewDefault
|
|
148
|
+
);
|
|
149
|
+
const [alignmentName, setAlignmentName] = useState(_alignmentName);
|
|
150
|
+
const [isTrackDragging, setIsTrackDragging] = useState(false);
|
|
151
|
+
const [saveMessage, setSaveMessage] = useState();
|
|
152
|
+
const [saveMessageLoading, setSaveMessageLoading] = useState(false);
|
|
153
|
+
const [tempTrimBefore, setTempTrimBefore] = useState({});
|
|
154
|
+
const [tempTrimAfter, setTempTrimAfter] = useState({});
|
|
155
|
+
const [tempTrimmingCaret, setTempTrimmingCaret] = useState({});
|
|
156
|
+
const bindOutsideChangeHelper = useRef({});
|
|
157
|
+
const alignmentHolder = useRef(null);
|
|
158
|
+
const alignmentHolderTop = useRef(null);
|
|
159
|
+
const veTracksAndAlignmentHolder = useRef(null);
|
|
160
|
+
const InfiniteScroller = useRef(null);
|
|
161
|
+
const oldAlignmentHolderScrollTop = useRef(0);
|
|
162
|
+
const blockScroll = useRef(false);
|
|
163
|
+
const isZooming = useRef(false);
|
|
164
|
+
const rowData = useRef({});
|
|
165
|
+
const latestMouseY = useRef();
|
|
166
|
+
const easyStore = useRef(
|
|
167
|
+
_store({
|
|
168
|
+
selectionLayer: { start: -1, end: -1 },
|
|
169
|
+
caretPosition: -1,
|
|
170
|
+
percentScrolled: 0,
|
|
171
|
+
viewportWidth: 400,
|
|
172
|
+
verticalVisibleRange: { start: 0, end: 0 }
|
|
173
|
+
})
|
|
174
|
+
);
|
|
200
175
|
|
|
201
|
-
getAllAlignmentsFastaText = () => {
|
|
176
|
+
const getAllAlignmentsFastaText = useCallback(() => {
|
|
202
177
|
const selectionLayer =
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
].selectionLayer || {};
|
|
206
|
-
const { alignmentTracks } = this.props;
|
|
178
|
+
store.getState().VectorEditor.__allEditorsOptions.alignments[id]
|
|
179
|
+
.selectionLayer || {};
|
|
207
180
|
const seqDataOfAllTracksToCopy = [];
|
|
208
181
|
alignmentTracks.forEach(track => {
|
|
209
182
|
const seqDataToCopy = getSequenceDataBetweenRange(
|
|
@@ -215,94 +188,307 @@ export class AlignmentView extends React.Component {
|
|
|
215
188
|
);
|
|
216
189
|
});
|
|
217
190
|
return seqDataOfAllTracksToCopy.join("");
|
|
218
|
-
};
|
|
191
|
+
}, [alignmentTracks, id, store]);
|
|
219
192
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
193
|
+
useEffect(() => {
|
|
194
|
+
const handleAlignmentCopy = event => {
|
|
195
|
+
if (
|
|
196
|
+
event.key === "c" &&
|
|
197
|
+
!event.shiftKey &&
|
|
198
|
+
(event.metaKey === true || event.ctrlKey === true)
|
|
199
|
+
) {
|
|
200
|
+
const input = document.createElement("textarea");
|
|
201
|
+
document.body.appendChild(input);
|
|
202
|
+
const seqDataToCopy = getAllAlignmentsFastaText();
|
|
203
|
+
input.value = seqDataToCopy;
|
|
204
|
+
input.select();
|
|
205
|
+
const copySuccess = document.execCommand("copy");
|
|
206
|
+
if (!copySuccess) {
|
|
207
|
+
window.toastr.error("Selection Not Copied");
|
|
208
|
+
} else {
|
|
209
|
+
window.toastr.success("Selection Copied");
|
|
210
|
+
}
|
|
211
|
+
document.body.removeChild(input);
|
|
212
|
+
event.preventDefault();
|
|
213
|
+
}
|
|
214
|
+
};
|
|
228
215
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
verticalVisibleRange: { start: 0, end: 0 }
|
|
235
|
-
});
|
|
216
|
+
document.addEventListener("keydown", handleAlignmentCopy);
|
|
217
|
+
return () => {
|
|
218
|
+
document.removeEventListener("keydown", handleAlignmentCopy);
|
|
219
|
+
};
|
|
220
|
+
}, [getAllAlignmentsFastaText]);
|
|
236
221
|
|
|
237
|
-
|
|
238
|
-
const
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
)
|
|
245
|
-
|
|
246
|
-
|
|
222
|
+
const scrollAlignmentToPercent = scrollPercentage => {
|
|
223
|
+
const scrollPercentageToUse = Math.min(Math.max(scrollPercentage, 0), 1);
|
|
224
|
+
easyStore.current.percentScrolled = scrollPercentageToUse;
|
|
225
|
+
alignmentHolder.current.scrollLeft =
|
|
226
|
+
scrollPercentageToUse *
|
|
227
|
+
(alignmentHolder.current.scrollWidth -
|
|
228
|
+
alignmentHolder.current.clientWidth);
|
|
229
|
+
if (alignmentHolderTop.current) {
|
|
230
|
+
alignmentHolderTop.current.scrollLeft =
|
|
231
|
+
scrollPercentageToUse *
|
|
232
|
+
(alignmentHolderTop.current.scrollWidth -
|
|
233
|
+
alignmentHolderTop.current.clientWidth);
|
|
234
|
+
}
|
|
247
235
|
};
|
|
248
236
|
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
237
|
+
useEffect(() => {
|
|
238
|
+
window.scrollAlignmentToPercent = scrollAlignmentToPercent;
|
|
239
|
+
if (window.Cypress)
|
|
240
|
+
window.Cypress.scrollAlignmentToPercent = scrollAlignmentToPercent;
|
|
241
|
+
|
|
242
|
+
return () => {
|
|
243
|
+
delete window.scrollAlignmentToPercent;
|
|
244
|
+
if (window.Cypress) delete window.Cypress.scrollAlignmentToPercent;
|
|
245
|
+
};
|
|
246
|
+
}, []);
|
|
247
|
+
|
|
248
|
+
const maxLength = useMemo(() => {
|
|
249
|
+
const { sequenceData = { sequence: "" }, alignmentData } =
|
|
250
|
+
alignmentTracks[0];
|
|
251
|
+
const data = alignmentData || sequenceData;
|
|
252
|
+
return data.noSequence ? data.size : data.sequence.length;
|
|
253
|
+
}, [alignmentTracks]);
|
|
254
|
+
|
|
255
|
+
const getSequenceLength = useCallback(() => {
|
|
256
|
+
return alignmentTracks?.[0]?.alignmentData?.sequence?.length || 1;
|
|
257
|
+
}, [alignmentTracks]);
|
|
258
|
+
|
|
259
|
+
const getMinCharWidth = useCallback(
|
|
260
|
+
noNameDiv => {
|
|
261
|
+
const toReturn = Math.min(
|
|
262
|
+
Math.max(width - (noNameDiv ? 0 : nameDivWidth) - 5, 1) /
|
|
263
|
+
getSequenceLength(),
|
|
264
|
+
10
|
|
265
|
+
);
|
|
266
|
+
if (isNaN(toReturn)) return 10;
|
|
267
|
+
return toReturn;
|
|
268
|
+
},
|
|
269
|
+
[getSequenceLength, nameDivWidth, width]
|
|
270
|
+
);
|
|
271
|
+
|
|
272
|
+
const charWidth = useMemo(() => {
|
|
273
|
+
if (isFullyZoomedOut) {
|
|
274
|
+
return getMinCharWidth();
|
|
275
|
+
} else {
|
|
276
|
+
return Math.max(getMinCharWidth(), charWidthInLinearView);
|
|
260
277
|
}
|
|
261
|
-
|
|
262
|
-
if (
|
|
263
|
-
this.props.shouldAutosave &&
|
|
264
|
-
prevProps &&
|
|
265
|
-
prevProps.stateTrackingId &&
|
|
266
|
-
this.props.stateTrackingId !== prevProps.stateTrackingId
|
|
267
|
-
) {
|
|
268
|
-
this.setState({ saveMessage: "Alignment Saving.." });
|
|
269
|
-
this.setState({ saveMessageLoading: true });
|
|
278
|
+
}, [charWidthInLinearView, getMinCharWidth, isFullyZoomedOut]);
|
|
270
279
|
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
280
|
+
const debouncedAlignmentRunUpdate = debounce(alignmentRunUpdate, 1000);
|
|
281
|
+
|
|
282
|
+
const caretPositionUpdate = useCallback(
|
|
283
|
+
position => {
|
|
284
|
+
if ((caretPosition || -1) === position) {
|
|
285
|
+
return;
|
|
276
286
|
}
|
|
287
|
+
easyStore.current.caretPosition = position;
|
|
288
|
+
easyStore.current.selectionLayer = { start: -1, end: -1 };
|
|
289
|
+
debouncedAlignmentRunUpdate({
|
|
290
|
+
alignmentId,
|
|
291
|
+
selectionLayer: { start: -1, end: -1 },
|
|
292
|
+
caretPosition: position
|
|
293
|
+
});
|
|
294
|
+
},
|
|
295
|
+
[alignmentId, caretPosition, debouncedAlignmentRunUpdate]
|
|
296
|
+
);
|
|
277
297
|
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
this.setState({ saveMessageLoading: false });
|
|
284
|
-
}, 5000);
|
|
285
|
-
}
|
|
286
|
-
}
|
|
298
|
+
const selectionLayerUpdate = useCallback(
|
|
299
|
+
(newSelection, { forceReduxUpdate } = {}) => {
|
|
300
|
+
const usableSelectionLayer = selectionLayer || { start: -1, end: -1 };
|
|
301
|
+
if (!newSelection) return;
|
|
302
|
+
const { start, end } = newSelection;
|
|
287
303
|
|
|
288
|
-
|
|
304
|
+
if (
|
|
305
|
+
usableSelectionLayer.start === start &&
|
|
306
|
+
usableSelectionLayer.end === end
|
|
307
|
+
) {
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
easyStore.current.caretPosition = -1;
|
|
311
|
+
easyStore.current.selectionLayer = newSelection;
|
|
312
|
+
|
|
313
|
+
(forceReduxUpdate ? alignmentRunUpdate : debouncedAlignmentRunUpdate)({
|
|
314
|
+
alignmentId,
|
|
315
|
+
selectionLayer: newSelection,
|
|
316
|
+
caretPosition: -1
|
|
317
|
+
});
|
|
318
|
+
},
|
|
319
|
+
[
|
|
320
|
+
alignmentId,
|
|
321
|
+
alignmentRunUpdate,
|
|
322
|
+
debouncedAlignmentRunUpdate,
|
|
323
|
+
selectionLayer
|
|
324
|
+
]
|
|
325
|
+
);
|
|
326
|
+
|
|
327
|
+
const updateSelectionOrCaret = useCallback(
|
|
328
|
+
(shiftHeld, newRangeOrCaret, { forceReduxUpdate } = {}) => {
|
|
329
|
+
const forceReduxSelectionLayerUpdate = newSelection => {
|
|
330
|
+
selectionLayerUpdate(newSelection, { forceReduxUpdate: true });
|
|
331
|
+
};
|
|
332
|
+
|
|
333
|
+
const sequenceLength = getSequenceLength();
|
|
334
|
+
|
|
335
|
+
_updateSelectionOrCaret({
|
|
336
|
+
doNotWrapOrigin: true,
|
|
337
|
+
shiftHeld,
|
|
338
|
+
sequenceLength,
|
|
339
|
+
newRangeOrCaret,
|
|
340
|
+
caretPosition: easyStore.current.caretPosition,
|
|
341
|
+
selectionLayer: easyStore.current.selectionLayer,
|
|
342
|
+
selectionLayerUpdate: forceReduxUpdate
|
|
343
|
+
? forceReduxSelectionLayerUpdate
|
|
344
|
+
: selectionLayerUpdate,
|
|
345
|
+
caretPositionUpdate
|
|
346
|
+
});
|
|
347
|
+
},
|
|
348
|
+
[caretPositionUpdate, getSequenceLength, selectionLayerUpdate]
|
|
349
|
+
);
|
|
350
|
+
|
|
351
|
+
const getNearestCursorPositionToMouseEvent = useCallback(
|
|
352
|
+
(_, event, callback) => {
|
|
353
|
+
//loop through all the rendered rows to see if the click event lands in one of them
|
|
354
|
+
let nearestCaretPos = 0;
|
|
355
|
+
const rowDomNode = veTracksAndAlignmentHolder.current;
|
|
356
|
+
const boundingRowRect = rowDomNode.getBoundingClientRect();
|
|
357
|
+
if (getClientX(event) - boundingRowRect.left - nameDivWidth < 0) {
|
|
358
|
+
nearestCaretPos = 0;
|
|
359
|
+
} else {
|
|
360
|
+
const clickXPositionRelativeToRowContainer =
|
|
361
|
+
getClientX(event) - boundingRowRect.left - nameDivWidth;
|
|
362
|
+
const numberOfBPsInFromRowStart = Math.floor(
|
|
363
|
+
(clickXPositionRelativeToRowContainer + charWidth / 2) / charWidth
|
|
364
|
+
);
|
|
365
|
+
nearestCaretPos = numberOfBPsInFromRowStart + 0;
|
|
366
|
+
if (nearestCaretPos > maxLength + 1) {
|
|
367
|
+
nearestCaretPos = maxLength + 1;
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
if (sequenceData?.isProtein) {
|
|
371
|
+
nearestCaretPos = Math.round(nearestCaretPos / 3) * 3;
|
|
372
|
+
}
|
|
373
|
+
if (sequenceLength === 0) nearestCaretPos = 0;
|
|
374
|
+
const callbackVals = {
|
|
375
|
+
updateSelectionOrCaret,
|
|
376
|
+
nearestCaretPos,
|
|
377
|
+
sequenceLength: getSequenceLength(),
|
|
378
|
+
caretPosition: easyStore.current.caretPosition,
|
|
379
|
+
selectionLayer: easyStore.current.selectionLayer,
|
|
380
|
+
easyStore: easyStore.current,
|
|
381
|
+
caretPositionUpdate,
|
|
382
|
+
selectionLayerUpdate,
|
|
383
|
+
event,
|
|
384
|
+
doNotWrapOrigin: true,
|
|
385
|
+
shiftHeld: event.shiftKey,
|
|
386
|
+
// caretGrabbed: event.target.className === "cursor",
|
|
387
|
+
selectionStartGrabbed: event.target.classList.contains(
|
|
388
|
+
draggableClassnames.selectionStart
|
|
389
|
+
),
|
|
390
|
+
selectionEndGrabbed: event.target.classList.contains(
|
|
391
|
+
draggableClassnames.selectionEnd
|
|
392
|
+
)
|
|
393
|
+
};
|
|
394
|
+
callback(callbackVals);
|
|
395
|
+
},
|
|
396
|
+
[
|
|
397
|
+
caretPositionUpdate,
|
|
398
|
+
charWidth,
|
|
399
|
+
getSequenceLength,
|
|
400
|
+
maxLength,
|
|
401
|
+
nameDivWidth,
|
|
402
|
+
selectionLayerUpdate,
|
|
403
|
+
sequenceData?.isProtein,
|
|
404
|
+
sequenceLength,
|
|
405
|
+
updateSelectionOrCaret
|
|
406
|
+
]
|
|
407
|
+
);
|
|
408
|
+
|
|
409
|
+
useEffect(() => {
|
|
289
410
|
const updateAlignmentSelection = newRangeOrCaret => {
|
|
290
|
-
|
|
411
|
+
updateSelectionOrCaret(false, newRangeOrCaret, {
|
|
291
412
|
forceReduxUpdate: true
|
|
292
413
|
});
|
|
293
414
|
};
|
|
294
415
|
window.updateAlignmentSelection = updateAlignmentSelection;
|
|
295
416
|
if (window.Cypress)
|
|
296
417
|
window.Cypress.updateAlignmentSelection = updateAlignmentSelection;
|
|
418
|
+
return () => {
|
|
419
|
+
delete window.updateAlignmentSelection;
|
|
420
|
+
if (window.Cypress) {
|
|
421
|
+
delete window.Cypress.updateAlignmentSelection;
|
|
422
|
+
}
|
|
423
|
+
};
|
|
424
|
+
}, [updateSelectionOrCaret]);
|
|
425
|
+
|
|
426
|
+
const setVerticalScrollRange = throttle(() => {
|
|
427
|
+
if (
|
|
428
|
+
InfiniteScroller.current &&
|
|
429
|
+
InfiniteScroller.current.getFractionalVisibleRange &&
|
|
430
|
+
easyStore.current
|
|
431
|
+
) {
|
|
432
|
+
let [start, end] = InfiniteScroller.current.getFractionalVisibleRange();
|
|
433
|
+
if (hasTemplate) {
|
|
434
|
+
end = end + 1;
|
|
435
|
+
}
|
|
436
|
+
if (
|
|
437
|
+
easyStore.current.verticalVisibleRange.start !== start ||
|
|
438
|
+
easyStore.current.verticalVisibleRange.end !== end
|
|
439
|
+
)
|
|
440
|
+
easyStore.current.verticalVisibleRange = { start, end };
|
|
441
|
+
}
|
|
442
|
+
}, 100);
|
|
443
|
+
|
|
444
|
+
useEffect(() => {
|
|
297
445
|
setTimeout(() => {
|
|
298
446
|
updateLabelsForInViewFeatures({ rectElement: ".alignmentHolder" });
|
|
299
447
|
}, 0);
|
|
300
448
|
setTimeout(() => {
|
|
301
|
-
|
|
449
|
+
setVerticalScrollRange();
|
|
302
450
|
}, 500);
|
|
303
|
-
}
|
|
451
|
+
});
|
|
452
|
+
|
|
453
|
+
useEffect(() => {
|
|
454
|
+
if (scrollPercentageToJumpTo !== undefined) {
|
|
455
|
+
scrollAlignmentToPercent(scrollPercentageToJumpTo);
|
|
456
|
+
}
|
|
457
|
+
}, [scrollPercentageToJumpTo]);
|
|
458
|
+
|
|
459
|
+
useEffect(() => {
|
|
460
|
+
const saveAlignment = async () => {
|
|
461
|
+
//autosave if necessary!
|
|
462
|
+
if (shouldAutosave && stateTrackingId) {
|
|
463
|
+
setSaveMessage("Alignment Saving..");
|
|
464
|
+
setSaveMessageLoading(true);
|
|
465
|
+
|
|
466
|
+
let cleanedTracks;
|
|
467
|
+
if (pairwiseAlignments) {
|
|
468
|
+
cleanedTracks = pairwiseAlignments.map(cleanTracks);
|
|
469
|
+
} else {
|
|
470
|
+
cleanedTracks = cleanTracks(alignmentTracks);
|
|
471
|
+
}
|
|
304
472
|
|
|
305
|
-
|
|
473
|
+
await handleAlignmentSave(cleanedTracks);
|
|
474
|
+
setSaveMessage("Alignment Saved");
|
|
475
|
+
setSaveMessageLoading(false);
|
|
476
|
+
setTimeout(() => {
|
|
477
|
+
setSaveMessage(undefined);
|
|
478
|
+
setSaveMessageLoading(false);
|
|
479
|
+
}, 5000);
|
|
480
|
+
}
|
|
481
|
+
};
|
|
482
|
+
saveAlignment();
|
|
483
|
+
}, [
|
|
484
|
+
alignmentTracks,
|
|
485
|
+
handleAlignmentSave,
|
|
486
|
+
pairwiseAlignments,
|
|
487
|
+
shouldAutosave,
|
|
488
|
+
stateTrackingId
|
|
489
|
+
]);
|
|
490
|
+
|
|
491
|
+
const annotationClicked = ({
|
|
306
492
|
event,
|
|
307
493
|
annotation,
|
|
308
494
|
gapsBefore = 0,
|
|
@@ -310,172 +496,83 @@ export class AlignmentView extends React.Component {
|
|
|
310
496
|
}) => {
|
|
311
497
|
event.preventDefault && event.preventDefault();
|
|
312
498
|
event.stopPropagation && event.stopPropagation();
|
|
313
|
-
|
|
499
|
+
updateSelectionOrCaret(event.shiftKey, {
|
|
314
500
|
...annotation,
|
|
315
501
|
start: annotation.start + gapsBefore,
|
|
316
502
|
end: annotation.end + gapsBefore + gapsInside
|
|
317
503
|
});
|
|
318
504
|
};
|
|
319
505
|
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
newRangeOrCaret,
|
|
323
|
-
{ forceReduxUpdate } = {}
|
|
324
|
-
) => {
|
|
325
|
-
const sequenceLength = this.getSequenceLength();
|
|
326
|
-
|
|
327
|
-
updateSelectionOrCaret({
|
|
328
|
-
doNotWrapOrigin: true,
|
|
329
|
-
shiftHeld,
|
|
330
|
-
sequenceLength,
|
|
331
|
-
newRangeOrCaret,
|
|
332
|
-
caretPosition: this.easyStore.caretPosition,
|
|
333
|
-
selectionLayer: this.easyStore.selectionLayer,
|
|
334
|
-
selectionLayerUpdate: forceReduxUpdate
|
|
335
|
-
? this.forceReduxSelectionLayerUpdate
|
|
336
|
-
: this.selectionLayerUpdate,
|
|
337
|
-
caretPositionUpdate: this.caretPositionUpdate
|
|
338
|
-
});
|
|
339
|
-
};
|
|
340
|
-
|
|
341
|
-
caretPositionUpdate = position => {
|
|
342
|
-
const { caretPosition = -1, alignmentId } = this.props;
|
|
343
|
-
if (caretPosition === position) {
|
|
344
|
-
return;
|
|
345
|
-
}
|
|
346
|
-
this.easyStore.caretPosition = position;
|
|
347
|
-
this.easyStore.selectionLayer = { start: -1, end: -1 };
|
|
348
|
-
this.debouncedAlignmentRunUpdate({
|
|
349
|
-
alignmentId,
|
|
350
|
-
selectionLayer: { start: -1, end: -1 },
|
|
351
|
-
caretPosition: position
|
|
352
|
-
});
|
|
353
|
-
};
|
|
354
|
-
|
|
355
|
-
debouncedAlignmentRunUpdate = debounce(this.props.alignmentRunUpdate, 1000);
|
|
356
|
-
|
|
357
|
-
forceReduxSelectionLayerUpdate = newSelection => {
|
|
358
|
-
this.selectionLayerUpdate(newSelection, { forceReduxUpdate: true });
|
|
359
|
-
};
|
|
360
|
-
|
|
361
|
-
selectionLayerUpdate = (newSelection, { forceReduxUpdate } = {}) => {
|
|
362
|
-
const { selectionLayer = { start: -1, end: -1 }, alignmentId } = this.props;
|
|
363
|
-
if (!newSelection) return;
|
|
364
|
-
const { start, end } = newSelection;
|
|
365
|
-
|
|
366
|
-
if (selectionLayer.start === start && selectionLayer.end === end) {
|
|
367
|
-
return;
|
|
368
|
-
}
|
|
369
|
-
this.easyStore.caretPosition = -1;
|
|
370
|
-
this.easyStore.selectionLayer = newSelection;
|
|
371
|
-
|
|
372
|
-
(forceReduxUpdate
|
|
373
|
-
? this.props.alignmentRunUpdate
|
|
374
|
-
: this.debouncedAlignmentRunUpdate)({
|
|
375
|
-
alignmentId,
|
|
376
|
-
selectionLayer: newSelection,
|
|
377
|
-
caretPosition: -1
|
|
378
|
-
});
|
|
379
|
-
};
|
|
380
|
-
|
|
381
|
-
getCharWidthInLinearView = () => {
|
|
382
|
-
if (this.props.isFullyZoomedOut) {
|
|
383
|
-
return this.getMinCharWidth();
|
|
384
|
-
} else {
|
|
385
|
-
return Math.max(this.getMinCharWidth(), this.state.charWidthInLinearView);
|
|
386
|
-
}
|
|
387
|
-
};
|
|
388
|
-
getNumBpsShownInLinearView = () => {
|
|
389
|
-
const toReturn =
|
|
390
|
-
(this.state.width - this.state.nameDivWidth) /
|
|
391
|
-
this.getCharWidthInLinearView();
|
|
506
|
+
const getNumBpsShownInLinearView = () => {
|
|
507
|
+
const toReturn = (width - nameDivWidth) / charWidth;
|
|
392
508
|
return toReturn || 0;
|
|
393
509
|
};
|
|
394
|
-
|
|
510
|
+
|
|
511
|
+
const handleScroll = () => {
|
|
395
512
|
if (
|
|
396
|
-
|
|
397
|
-
this.InfiniteScroller &&
|
|
398
|
-
this.InfiniteScroller.getFractionalVisibleRange &&
|
|
399
|
-
this.easyStore
|
|
513
|
+
alignmentHolder.current.scrollTop !== oldAlignmentHolderScrollTop.current
|
|
400
514
|
) {
|
|
401
|
-
let [start, end] = this.InfiniteScroller.getFractionalVisibleRange();
|
|
402
|
-
if (this.props.hasTemplate) {
|
|
403
|
-
end = end + 1;
|
|
404
|
-
}
|
|
405
|
-
if (
|
|
406
|
-
this.easyStore.verticalVisibleRange.start !== start ||
|
|
407
|
-
this.easyStore.verticalVisibleRange.end !== end
|
|
408
|
-
)
|
|
409
|
-
this.easyStore.verticalVisibleRange = { start, end };
|
|
410
|
-
}
|
|
411
|
-
}, 100);
|
|
412
|
-
handleScroll = () => {
|
|
413
|
-
// tnr: maybe add this in at some point
|
|
414
|
-
// this.updateMinimapHighlightForScroll(
|
|
415
|
-
// this.oldMinimapScrollTracker || this.alignmentHolder.scrollTop,
|
|
416
|
-
// this.alignmentHolder.scrollTop
|
|
417
|
-
// );
|
|
418
|
-
// this.oldMinimapScrollTracker = this.alignmentHolder.scrollTop;
|
|
419
|
-
// if (this.alignmentHolder.scrollTop !== this.oldMinimapScrollTracker) {
|
|
420
|
-
// }
|
|
421
|
-
if (this.alignmentHolder.scrollTop !== this.oldAlignmentHolderScrollTop) {
|
|
422
515
|
setTimeout(() => {
|
|
423
|
-
|
|
424
|
-
|
|
516
|
+
setVerticalScrollRange();
|
|
517
|
+
oldAlignmentHolderScrollTop.current = alignmentHolder.current.scrollTop;
|
|
425
518
|
}, 100);
|
|
426
519
|
}
|
|
427
|
-
if (
|
|
520
|
+
if (blockScroll.current) {
|
|
428
521
|
//we have to block the scroll sometimes when adjusting the minimap so things aren't too jumpy
|
|
429
522
|
return;
|
|
430
523
|
}
|
|
431
524
|
|
|
432
525
|
const scrollPercentage =
|
|
433
|
-
|
|
434
|
-
(
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
526
|
+
alignmentHolder.current.scrollLeft /
|
|
527
|
+
(alignmentHolder.current.scrollWidth -
|
|
528
|
+
alignmentHolder.current.clientWidth);
|
|
529
|
+
easyStore.current.percentScrolled = scrollPercentage || 0;
|
|
530
|
+
if (!isZooming.current) {
|
|
531
|
+
easyStore.current.percentScrolledPreZoom =
|
|
532
|
+
easyStore.current.percentScrolled;
|
|
438
533
|
}
|
|
439
|
-
if (
|
|
440
|
-
|
|
534
|
+
if (alignmentHolderTop.current) {
|
|
535
|
+
alignmentHolderTop.current.scrollLeft =
|
|
536
|
+
alignmentHolder.current.scrollLeft;
|
|
441
537
|
}
|
|
442
538
|
updateLabelsForInViewFeatures({ rectElement: ".alignmentHolder" });
|
|
443
539
|
};
|
|
444
|
-
|
|
445
|
-
|
|
540
|
+
|
|
541
|
+
const handleTopScroll = () => {
|
|
542
|
+
alignmentHolder.current.scrollLeft = alignmentHolderTop.current.scrollLeft;
|
|
446
543
|
};
|
|
544
|
+
|
|
447
545
|
/**
|
|
448
546
|
* Responsible for handling resizing the highlighted region of the minimap
|
|
449
547
|
* @param {*} newSliderSize
|
|
450
548
|
* @param {*} newPercent
|
|
451
549
|
*/
|
|
452
|
-
onMinimapSizeAdjust = (newSliderSize, newPercent) => {
|
|
453
|
-
const percentageOfSpace = newSliderSize /
|
|
454
|
-
const seqLength =
|
|
550
|
+
const onMinimapSizeAdjust = (newSliderSize, newPercent) => {
|
|
551
|
+
const percentageOfSpace = newSliderSize / width;
|
|
552
|
+
const seqLength = getSequenceLength();
|
|
455
553
|
const numBpsInView = seqLength * percentageOfSpace;
|
|
456
|
-
const newCharWidth =
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
this.setCharWidthInLinearView({ charWidthInLinearView: newCharWidth });
|
|
554
|
+
const newCharWidth = (width - nameDivWidth) / numBpsInView;
|
|
555
|
+
blockScroll.current = true;
|
|
556
|
+
setCharWidthInLinearView({ charWidthInLinearView: newCharWidth });
|
|
460
557
|
setTimeout(() => {
|
|
461
|
-
|
|
462
|
-
|
|
558
|
+
scrollAlignmentToPercent(newPercent);
|
|
559
|
+
blockScroll.current = false;
|
|
463
560
|
updateLabelsForInViewFeatures({ rectElement: ".alignmentHolder" });
|
|
464
561
|
});
|
|
465
562
|
};
|
|
466
563
|
|
|
467
|
-
setCharWidthInLinearView = ({ charWidthInLinearView }) => {
|
|
564
|
+
const setCharWidthInLinearView = ({ charWidthInLinearView }) => {
|
|
468
565
|
window.localStorage.setItem(
|
|
469
566
|
"charWidthInLinearViewDefault",
|
|
470
567
|
charWidthInLinearView
|
|
471
568
|
);
|
|
472
|
-
|
|
569
|
+
_setCharWidthInLinearView(charWidthInLinearView);
|
|
473
570
|
charWidthInLinearViewDefault = JSON.parse(
|
|
474
571
|
window.localStorage.getItem("charWidthInLinearViewDefault")
|
|
475
572
|
);
|
|
476
573
|
};
|
|
477
574
|
|
|
478
|
-
scrollToCaret = () => {
|
|
575
|
+
const scrollToCaret = () => {
|
|
479
576
|
let el = window.document.querySelector(".veCaret:not(.zoomSelection)"); //adding .veRowViewCaret breaks this for some reason
|
|
480
577
|
if (!el) {
|
|
481
578
|
el = window.document.querySelector(".veCaret"); //adding .veRowViewCaret breaks this for some reason
|
|
@@ -486,34 +583,22 @@ export class AlignmentView extends React.Component {
|
|
|
486
583
|
el.scrollIntoView({ inline: "center", block: "nearest" });
|
|
487
584
|
};
|
|
488
585
|
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
this.easyStore.percentScrolled = scrollPercentageToUse;
|
|
492
|
-
this.alignmentHolder.scrollLeft =
|
|
493
|
-
scrollPercentageToUse *
|
|
494
|
-
(this.alignmentHolder.scrollWidth - this.alignmentHolder.clientWidth);
|
|
495
|
-
if (this.alignmentHolderTop) {
|
|
496
|
-
this.alignmentHolderTop.scrollLeft =
|
|
497
|
-
scrollPercentageToUse *
|
|
498
|
-
(this.alignmentHolderTop.scrollWidth -
|
|
499
|
-
this.alignmentHolderTop.clientWidth);
|
|
500
|
-
}
|
|
501
|
-
};
|
|
502
|
-
scrollYToTrack = trackIndex => {
|
|
503
|
-
this.InfiniteScroller.scrollTo(trackIndex);
|
|
586
|
+
const scrollYToTrack = trackIndex => {
|
|
587
|
+
InfiniteScroller.current.scrollTo(trackIndex);
|
|
504
588
|
};
|
|
505
589
|
|
|
506
|
-
estimateRowHeight = (index, cache) => {
|
|
507
|
-
const { alignmentVisibilityToolOptions, alignmentTracks } = this.props;
|
|
590
|
+
const estimateRowHeight = (index, cache) => {
|
|
508
591
|
const track = alignmentTracks[index];
|
|
509
592
|
if (!track) return 100;
|
|
510
593
|
const { sequenceData } = track;
|
|
511
|
-
|
|
512
|
-
|
|
594
|
+
rowData.current = prepareRowData(
|
|
595
|
+
sequenceData,
|
|
596
|
+
sequenceData.sequence.length
|
|
597
|
+
);
|
|
598
|
+
return _estimateRowHeight({
|
|
513
599
|
index,
|
|
514
600
|
cache,
|
|
515
|
-
|
|
516
|
-
row: this.rowData[index],
|
|
601
|
+
row: rowData.current[index],
|
|
517
602
|
annotationVisibility:
|
|
518
603
|
alignmentVisibilityToolOptions.alignmentAnnotationVisibility,
|
|
519
604
|
annotationLabelVisibility:
|
|
@@ -521,36 +606,7 @@ export class AlignmentView extends React.Component {
|
|
|
521
606
|
});
|
|
522
607
|
};
|
|
523
608
|
|
|
524
|
-
|
|
525
|
-
let maxWidth = 0;
|
|
526
|
-
const charWidthInLinearView = this.getCharWidthInLinearView();
|
|
527
|
-
forEach(this.props.alignmentTracks, t => {
|
|
528
|
-
const w = (t.alignmentData || t.sequenceData).sequence.length;
|
|
529
|
-
if (w > maxWidth) maxWidth = w;
|
|
530
|
-
});
|
|
531
|
-
return maxWidth * charWidthInLinearView;
|
|
532
|
-
};
|
|
533
|
-
|
|
534
|
-
renderItem = (_i, key, isTemplate, cloneProps) => {
|
|
535
|
-
const charWidthInLinearView = this.getCharWidthInLinearView();
|
|
536
|
-
const {
|
|
537
|
-
alignmentTrackIndex,
|
|
538
|
-
pairwiseAlignments,
|
|
539
|
-
currentPairwiseAlignmentIndex,
|
|
540
|
-
pairwiseOverviewAlignmentTracks,
|
|
541
|
-
alignmentTracks = [],
|
|
542
|
-
upsertAlignmentRun,
|
|
543
|
-
alignmentId,
|
|
544
|
-
noClickDragHandlers,
|
|
545
|
-
handleSelectTrack,
|
|
546
|
-
allowTrackRearrange,
|
|
547
|
-
isPairwise,
|
|
548
|
-
linearViewOptions,
|
|
549
|
-
alignmentVisibilityToolOptions,
|
|
550
|
-
hasTemplate,
|
|
551
|
-
allowTrackNameEdit,
|
|
552
|
-
...rest
|
|
553
|
-
} = this.props;
|
|
609
|
+
const renderItem = (_i, key, isTemplate, cloneProps) => {
|
|
554
610
|
const isDragDisabled = !allowTrackRearrange || isPairwise;
|
|
555
611
|
let i;
|
|
556
612
|
if (isTemplate) {
|
|
@@ -561,7 +617,7 @@ export class AlignmentView extends React.Component {
|
|
|
561
617
|
i = _i;
|
|
562
618
|
}
|
|
563
619
|
|
|
564
|
-
const track = alignmentTracks[i];
|
|
620
|
+
const track = alignmentTracks?.[i];
|
|
565
621
|
if (!track) return null;
|
|
566
622
|
const {
|
|
567
623
|
sequenceData,
|
|
@@ -572,132 +628,23 @@ export class AlignmentView extends React.Component {
|
|
|
572
628
|
chromatogramData
|
|
573
629
|
// mismatches
|
|
574
630
|
} = track;
|
|
575
|
-
const seqLen =
|
|
631
|
+
const seqLen = maxLength;
|
|
576
632
|
|
|
577
633
|
const trimmedRangesToDisplay = getTrimmedRangesToDisplay({
|
|
578
634
|
seqLen,
|
|
579
635
|
trimmedRange: alignmentData?.trimmedRange
|
|
580
636
|
});
|
|
581
637
|
const linearViewWidth =
|
|
582
|
-
(alignmentData || sequenceData).sequence.length *
|
|
638
|
+
(alignmentData || sequenceData).sequence.length * charWidth;
|
|
583
639
|
const name = sequenceData.name || sequenceData.id;
|
|
584
640
|
|
|
585
|
-
|
|
586
|
-
/**
|
|
587
|
-
* for alignment of sanger seq reads to a ref seq, have translations show up at the bp pos of ref seq's CDS features across all seq reads
|
|
588
|
-
**/
|
|
589
|
-
// let sequenceDataWithRefSeqCdsFeatures;
|
|
590
|
-
// if (this.props.alignmentType === "SANGER SEQUENCING") {
|
|
591
|
-
// if (i !== 0) {
|
|
592
|
-
// sequenceDataWithRefSeqCdsFeatures = cloneDeep(sequenceData);
|
|
593
|
-
// const refSeqCdsFeaturesBpPos = [];
|
|
594
|
-
// alignmentTracks[0].sequenceData.features.forEach((feature) => {
|
|
595
|
-
// if (feature.type === "CDS") {
|
|
596
|
-
// const editedFeature = cloneDeep(feature);
|
|
597
|
-
// // in seq reads, ref seq's CDS feature translations need to show up at the bp pos of alignment, not the original bp pos
|
|
598
|
-
// // actual position in the track
|
|
599
|
-
// const absoluteFeatureStart =
|
|
600
|
-
// getGaps(feature.start, alignmentTracks[0].alignmentData.sequence)
|
|
601
|
-
// .gapsBefore + feature.start;
|
|
602
|
-
// const gapsBeforeSeqRead = getGaps(
|
|
603
|
-
// 0,
|
|
604
|
-
// alignmentData.sequence
|
|
605
|
-
// ).gapsBefore;
|
|
606
|
-
// const bpsFromSeqReadStartToFeatureStartIncludingGaps =
|
|
607
|
-
// absoluteFeatureStart - gapsBeforeSeqRead;
|
|
608
|
-
// const absoluteFeatureEnd =
|
|
609
|
-
// getGaps(feature.end, alignmentTracks[0].alignmentData.sequence)
|
|
610
|
-
// .gapsBefore + feature.end;
|
|
611
|
-
// // const gapsBeforeFeatureInSeqRead = getGaps(feature.start - gapsBeforeSeqRead, alignmentData.sequence).gapsBefore
|
|
612
|
-
// const gapsAfterSeqRead =
|
|
613
|
-
// alignmentData.sequence.length -
|
|
614
|
-
// alignmentData.sequence.replace(/-+$/g, "").length;
|
|
615
|
-
// const seqReadLengthWithoutGapsBeforeAfter =
|
|
616
|
-
// alignmentData.sequence.length -
|
|
617
|
-
// gapsBeforeSeqRead -
|
|
618
|
-
// gapsAfterSeqRead;
|
|
619
|
-
// const absoluteSeqReadStart = gapsBeforeSeqRead;
|
|
620
|
-
// const absoluteSeqReadEnd =
|
|
621
|
-
// absoluteSeqReadStart + seqReadLengthWithoutGapsBeforeAfter;
|
|
622
|
-
// let featureStartInSeqRead;
|
|
623
|
-
// if (absoluteFeatureEnd < absoluteSeqReadStart) {
|
|
624
|
-
// // if the feature ends before the seq read starts, do nothing
|
|
625
|
-
// } else if (absoluteFeatureStart > absoluteSeqReadEnd) {
|
|
626
|
-
// // if the feature starts after the seq read ends, do nothing
|
|
627
|
-
// } else if (
|
|
628
|
-
// absoluteFeatureStart < absoluteSeqReadStart &&
|
|
629
|
-
// absoluteFeatureEnd > absoluteSeqReadStart
|
|
630
|
-
// ) {
|
|
631
|
-
// // if the feature starts before the seq read starts but doesn't end before the seq read starts
|
|
632
|
-
// const arrayOfCodonStartPos = [];
|
|
633
|
-
// for (
|
|
634
|
-
// let i = absoluteFeatureStart;
|
|
635
|
-
// i < absoluteSeqReadStart + 6;
|
|
636
|
-
// i += 3
|
|
637
|
-
// ) {
|
|
638
|
-
// arrayOfCodonStartPos.push(i);
|
|
639
|
-
// }
|
|
640
|
-
// // want to start translation at the codon start pos closest to seq read start
|
|
641
|
-
// const absoluteTranslationStartInFrame =
|
|
642
|
-
// arrayOfCodonStartPos.reduce((prev, curr) =>
|
|
643
|
-
// Math.abs(curr - absoluteSeqReadStart) <
|
|
644
|
-
// Math.abs(prev - absoluteSeqReadStart) &&
|
|
645
|
-
// curr >= absoluteSeqReadStart
|
|
646
|
-
// ? curr
|
|
647
|
-
// : prev
|
|
648
|
-
// );
|
|
649
|
-
// const seqReadTranslationStartInFrame =
|
|
650
|
-
// absoluteTranslationStartInFrame - gapsBeforeSeqRead;
|
|
651
|
-
// editedFeature.start = seqReadTranslationStartInFrame;
|
|
652
|
-
// const shortenedFeatureLength =
|
|
653
|
-
// Math.abs(absoluteFeatureEnd - absoluteFeatureStart) -
|
|
654
|
-
// (absoluteTranslationStartInFrame - absoluteFeatureStart);
|
|
655
|
-
// editedFeature.end = editedFeature.start + shortenedFeatureLength;
|
|
656
|
-
// refSeqCdsFeaturesBpPos.push(editedFeature);
|
|
657
|
-
// } else {
|
|
658
|
-
// // if the feature is fully contained within the seq read start/end
|
|
659
|
-
// const seqReadStartToFeatureStartIncludingGaps =
|
|
660
|
-
// alignmentData.sequence
|
|
661
|
-
// .replace(/^-+/g, "")
|
|
662
|
-
// .replace(/-+$/g, "")
|
|
663
|
-
// .slice(0, bpsFromSeqReadStartToFeatureStartIncludingGaps);
|
|
664
|
-
// const arrayOfGaps = seqReadStartToFeatureStartIncludingGaps.match(
|
|
665
|
-
// new RegExp("-", "g")
|
|
666
|
-
// );
|
|
667
|
-
// let numOfGapsFromSeqReadStartToFeatureStart = 0;
|
|
668
|
-
// if (arrayOfGaps !== null) {
|
|
669
|
-
// numOfGapsFromSeqReadStartToFeatureStart = arrayOfGaps.length;
|
|
670
|
-
// }
|
|
671
|
-
// featureStartInSeqRead =
|
|
672
|
-
// bpsFromSeqReadStartToFeatureStartIncludingGaps -
|
|
673
|
-
// numOfGapsFromSeqReadStartToFeatureStart;
|
|
674
|
-
// editedFeature.start = featureStartInSeqRead;
|
|
675
|
-
// const featureLength = Math.abs(feature.end - feature.start);
|
|
676
|
-
// editedFeature.end = editedFeature.start + featureLength;
|
|
677
|
-
// refSeqCdsFeaturesBpPos.push(editedFeature);
|
|
678
|
-
// }
|
|
679
|
-
// }
|
|
680
|
-
// });
|
|
681
|
-
// // add ref seq's CDS features to seq reads (not the actual sequenceData) to generate translations at those bp pos
|
|
682
|
-
// if (refSeqCdsFeaturesBpPos.length !== 0) {
|
|
683
|
-
// sequenceDataWithRefSeqCdsFeatures.features.push(
|
|
684
|
-
// ...refSeqCdsFeaturesBpPos
|
|
685
|
-
// );
|
|
686
|
-
// // use returned aligned sequence rather than original sequence because after bowtie2, may be reverse complement or have soft-clipped ends
|
|
687
|
-
// sequenceDataWithRefSeqCdsFeatures.sequence =
|
|
688
|
-
// alignmentData.sequence.replace(/-/g, "");
|
|
689
|
-
// }
|
|
690
|
-
// }
|
|
691
|
-
// }
|
|
692
|
-
const tickSpacing = massageTickSpacing(
|
|
693
|
-
Math.ceil(120 / charWidthInLinearView)
|
|
694
|
-
);
|
|
641
|
+
const tickSpacing = massageTickSpacing(Math.ceil(120 / charWidth));
|
|
695
642
|
|
|
696
643
|
const { compactNames } =
|
|
697
644
|
alignmentVisibilityToolOptions.alignmentAnnotationVisibility;
|
|
698
645
|
const selectionLayer = [
|
|
699
|
-
|
|
700
|
-
|
|
646
|
+
tempTrimBefore[i] || trimmedRangesToDisplay[0],
|
|
647
|
+
tempTrimAfter[i] || trimmedRangesToDisplay[1]
|
|
701
648
|
]
|
|
702
649
|
.filter(i => i)
|
|
703
650
|
.map(i => ({
|
|
@@ -733,11 +680,11 @@ export class AlignmentView extends React.Component {
|
|
|
733
680
|
zIndex: 10,
|
|
734
681
|
borderBottom: `1px solid ${isTemplate ? "red" : "lightgray"}`,
|
|
735
682
|
borderRight: `1px solid ${isTemplate ? "red" : "lightgray"}`,
|
|
736
|
-
width:
|
|
683
|
+
width: nameDivWidth - 3,
|
|
737
684
|
padding: 2,
|
|
738
685
|
marginRight: 3,
|
|
739
686
|
paddingBottom: 0,
|
|
740
|
-
minWidth:
|
|
687
|
+
minWidth: nameDivWidth - 3,
|
|
741
688
|
overflow: "hidden",
|
|
742
689
|
scrollbarWidth: "none",
|
|
743
690
|
whiteSpace: "nowrap"
|
|
@@ -784,7 +731,7 @@ export class AlignmentView extends React.Component {
|
|
|
784
731
|
pairwiseAlignments,
|
|
785
732
|
upsertAlignmentRun,
|
|
786
733
|
alignmentId,
|
|
787
|
-
alignmentTracks,
|
|
734
|
+
alignmentTracks: alignmentTracks || [],
|
|
788
735
|
alignmentTrackIndex: i,
|
|
789
736
|
update: { name: newName }
|
|
790
737
|
});
|
|
@@ -795,9 +742,9 @@ export class AlignmentView extends React.Component {
|
|
|
795
742
|
small
|
|
796
743
|
data-tip="Edit Track Name"
|
|
797
744
|
className="edit-track-name-btn"
|
|
798
|
-
icon={<Icon size={12} color="lightgrey" icon="edit"
|
|
745
|
+
icon={<Icon size={12} color="lightgrey" icon="edit" />}
|
|
799
746
|
minimal
|
|
800
|
-
|
|
747
|
+
/>
|
|
801
748
|
)}
|
|
802
749
|
{sequenceData.seqLink && (
|
|
803
750
|
<AnchorButton
|
|
@@ -805,20 +752,13 @@ export class AlignmentView extends React.Component {
|
|
|
805
752
|
data-tip={sequenceData.seqLinkTooltip}
|
|
806
753
|
target="_blank"
|
|
807
754
|
small
|
|
808
|
-
icon={
|
|
809
|
-
<Icon size={12} color="white" icon="document-open"></Icon>
|
|
810
|
-
}
|
|
755
|
+
icon={<Icon size={12} color="white" icon="document-open" />}
|
|
811
756
|
minimal
|
|
812
|
-
|
|
757
|
+
/>
|
|
813
758
|
)}
|
|
814
759
|
{name}
|
|
815
760
|
</div>
|
|
816
761
|
<div style={{ fontSize: 10, marginTop: 2, marginBottom: 2 }}>
|
|
817
|
-
{/* <Icon //tnr: add this once we support forward/reverse for each track
|
|
818
|
-
color="darkgrey"
|
|
819
|
-
style={{ marginRight: 10 }}
|
|
820
|
-
icon="arrow-right"
|
|
821
|
-
></Icon> */}
|
|
822
762
|
{isReversed && (
|
|
823
763
|
<span
|
|
824
764
|
style={{
|
|
@@ -858,12 +798,7 @@ export class AlignmentView extends React.Component {
|
|
|
858
798
|
</div>
|
|
859
799
|
<HorizontalPanelDragHandle
|
|
860
800
|
onDrag={({ dx }) => {
|
|
861
|
-
|
|
862
|
-
nameDivWidth: Math.min(
|
|
863
|
-
this.state.nameDivWidth - dx,
|
|
864
|
-
this.state.width - 20
|
|
865
|
-
)
|
|
866
|
-
});
|
|
801
|
+
setNameDivWidth(Math.min(nameDivWidth - dx, width - 20));
|
|
867
802
|
}}
|
|
868
803
|
/>
|
|
869
804
|
</div>
|
|
@@ -877,7 +812,7 @@ export class AlignmentView extends React.Component {
|
|
|
877
812
|
position: "absolute",
|
|
878
813
|
opacity: 0,
|
|
879
814
|
height: "100%",
|
|
880
|
-
left:
|
|
815
|
+
left: nameDivWidth,
|
|
881
816
|
width: linearViewWidth,
|
|
882
817
|
fontWeight: "bolder",
|
|
883
818
|
cursor: "pointer",
|
|
@@ -891,65 +826,63 @@ export class AlignmentView extends React.Component {
|
|
|
891
826
|
</div>
|
|
892
827
|
)}
|
|
893
828
|
<NonReduxEnhancedLinearView
|
|
894
|
-
{...
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
(
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
width: linearViewWidth
|
|
948
|
-
},
|
|
949
|
-
width: linearViewWidth,
|
|
950
|
-
paddingBottom: 5,
|
|
951
|
-
scrollDataPassed: this.easyStore
|
|
829
|
+
{...rest}
|
|
830
|
+
caretPosition={tempTrimmingCaret[i] || -1}
|
|
831
|
+
selectionLayer={selectionLayer}
|
|
832
|
+
isInAlignment
|
|
833
|
+
annotationVisibilityOverrides={
|
|
834
|
+
alignmentVisibilityToolOptions.alignmentAnnotationVisibility
|
|
835
|
+
}
|
|
836
|
+
linearViewAnnotationLabelVisibilityOverrides={
|
|
837
|
+
alignmentVisibilityToolOptions.alignmentAnnotationLabelVisibility
|
|
838
|
+
}
|
|
839
|
+
marginWith={0}
|
|
840
|
+
orfClicked={annotationClicked}
|
|
841
|
+
primerClicked={annotationClicked}
|
|
842
|
+
translationClicked={annotationClicked}
|
|
843
|
+
cutsiteClicked={annotationClicked}
|
|
844
|
+
translationDoubleClicked={annotationClicked}
|
|
845
|
+
deletionLayerClicked={annotationClicked}
|
|
846
|
+
replacementLayerClicked={annotationClicked}
|
|
847
|
+
featureClicked={annotationClicked}
|
|
848
|
+
partClicked={annotationClicked}
|
|
849
|
+
searchLayerClicked={annotationClicked}
|
|
850
|
+
editorDragStarted={noop} //override these since we're defining the handlers above
|
|
851
|
+
editorDragStopped={noop} //override these since we're defining the handlers above
|
|
852
|
+
editorDragged={noop} //override these since we're defining the handlers above
|
|
853
|
+
hideName
|
|
854
|
+
sequenceData={sequenceData}
|
|
855
|
+
tickSpacing={tickSpacing}
|
|
856
|
+
allowSeqDataOverride //override the sequence data stored in redux so we can track the caret position/selection layer in redux but not have to update the redux editor
|
|
857
|
+
editorName={`${isTemplate ? "template_" : ""}alignmentView${i}`}
|
|
858
|
+
alignmentData={alignmentData}
|
|
859
|
+
chromatogramData={chromatogramData}
|
|
860
|
+
height={"100%"}
|
|
861
|
+
vectorInteractionWrapperStyle={{
|
|
862
|
+
overflowY: "hidden"
|
|
863
|
+
}}
|
|
864
|
+
withZoomLinearView={false}
|
|
865
|
+
marginWidth={0}
|
|
866
|
+
linearViewCharWidth={charWidth}
|
|
867
|
+
ignoreGapsOnHighlight
|
|
868
|
+
{...(linearViewOptions &&
|
|
869
|
+
(isFunction(linearViewOptions)
|
|
870
|
+
? linearViewOptions({
|
|
871
|
+
index: i,
|
|
872
|
+
isTemplate,
|
|
873
|
+
alignmentVisibilityToolOptions,
|
|
874
|
+
sequenceData,
|
|
875
|
+
alignmentData,
|
|
876
|
+
chromatogramData
|
|
877
|
+
})
|
|
878
|
+
: linearViewOptions))}
|
|
879
|
+
additionalSelectionLayers={additionalSelectionLayers}
|
|
880
|
+
dimensions={{
|
|
881
|
+
width: linearViewWidth
|
|
952
882
|
}}
|
|
883
|
+
width={linearViewWidth}
|
|
884
|
+
paddingBottom={5}
|
|
885
|
+
scrollDataPassed={easyStore.current}
|
|
953
886
|
/>
|
|
954
887
|
</div>
|
|
955
888
|
);
|
|
@@ -968,22 +901,24 @@ export class AlignmentView extends React.Component {
|
|
|
968
901
|
</DndDraggable>
|
|
969
902
|
);
|
|
970
903
|
};
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
904
|
+
|
|
905
|
+
const handleResize = throttle(([e]) => {
|
|
906
|
+
easyStore.current.viewportWidth = e.contentRect.width - nameDivWidth || 400;
|
|
907
|
+
setWidth(e.contentRect.width);
|
|
975
908
|
}, 200);
|
|
976
909
|
|
|
977
|
-
removeMinimapHighlightForMouseLeave = () => {
|
|
910
|
+
const removeMinimapHighlightForMouseLeave = () => {
|
|
978
911
|
const minimapLaneEl = document.querySelector(`.minimapLane.lane-hovered`);
|
|
979
912
|
if (!minimapLaneEl) return;
|
|
980
913
|
minimapLaneEl.classList.remove("lane-hovered");
|
|
981
914
|
};
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
915
|
+
|
|
916
|
+
const updateMinimapHighlightForMouseMove = event => {
|
|
917
|
+
latestMouseY.current = getClientY(event); //we use this variable later
|
|
918
|
+
updateMinimapHighlight();
|
|
985
919
|
};
|
|
986
|
-
|
|
920
|
+
|
|
921
|
+
const updateMinimapHighlight = () => {
|
|
987
922
|
const rows = document.querySelectorAll(`.alignmentViewTrackContainer`);
|
|
988
923
|
const rowsLength = document.querySelectorAll(`.minimapLane`).length;
|
|
989
924
|
if (rowsLength <= 4) {
|
|
@@ -992,8 +927,8 @@ export class AlignmentView extends React.Component {
|
|
|
992
927
|
some(rows, rowDomNode => {
|
|
993
928
|
const boundingRowRect = rowDomNode.getBoundingClientRect();
|
|
994
929
|
if (
|
|
995
|
-
|
|
996
|
-
|
|
930
|
+
latestMouseY.current > boundingRowRect.top &&
|
|
931
|
+
latestMouseY.current < boundingRowRect.top + boundingRowRect.height
|
|
997
932
|
) {
|
|
998
933
|
const prevMinimapLaneEl = document.querySelector(
|
|
999
934
|
`.minimapLane.lane-hovered`
|
|
@@ -1013,15 +948,16 @@ export class AlignmentView extends React.Component {
|
|
|
1013
948
|
}
|
|
1014
949
|
});
|
|
1015
950
|
};
|
|
1016
|
-
|
|
1017
|
-
|
|
951
|
+
|
|
952
|
+
const onTrackDragStart = () => {
|
|
953
|
+
setIsTrackDragging(true);
|
|
1018
954
|
};
|
|
1019
|
-
|
|
1020
|
-
|
|
955
|
+
|
|
956
|
+
const onTrackDragEnd = ({ destination, source }) => {
|
|
957
|
+
setIsTrackDragging(false);
|
|
1021
958
|
if (!destination) {
|
|
1022
959
|
return;
|
|
1023
960
|
}
|
|
1024
|
-
const { upsertAlignmentRun, alignmentId, alignmentTracks } = this.props;
|
|
1025
961
|
upsertAlignmentRun({
|
|
1026
962
|
id: alignmentId,
|
|
1027
963
|
alignmentTracks: array_move(
|
|
@@ -1032,691 +968,23 @@ export class AlignmentView extends React.Component {
|
|
|
1032
968
|
});
|
|
1033
969
|
};
|
|
1034
970
|
|
|
1035
|
-
|
|
1036
|
-
const charWidthInLinearView = this.getCharWidthInLinearView();
|
|
1037
|
-
const {
|
|
1038
|
-
alignmentTracks = [],
|
|
1039
|
-
height,
|
|
1040
|
-
minimapLaneHeight,
|
|
1041
|
-
minimapLaneSpacing,
|
|
1042
|
-
isInPairwiseOverviewView,
|
|
1043
|
-
isPairwise,
|
|
1044
|
-
currentPairwiseAlignmentIndex,
|
|
1045
|
-
hasTemplate,
|
|
1046
|
-
noVisibilityOptions,
|
|
1047
|
-
updateAlignmentSortOrder,
|
|
1048
|
-
alignmentSortOrder,
|
|
1049
|
-
handleBackButtonClicked,
|
|
1050
|
-
noClickDragHandlers,
|
|
1051
|
-
upsertAlignmentRun,
|
|
1052
|
-
alignmentId,
|
|
1053
|
-
allowTrimming,
|
|
1054
|
-
additionalSelectionLayerRightClickedOptions,
|
|
1055
|
-
selectionLayerRightClicked,
|
|
1056
|
-
additionalTopEl,
|
|
1057
|
-
additionalTopLeftEl,
|
|
1058
|
-
handleAlignmentRename,
|
|
1059
|
-
alignmentVisibilityToolOptions
|
|
1060
|
-
} = this.props;
|
|
1061
|
-
const sequenceLength = this.getMaxLength();
|
|
1062
|
-
if (
|
|
1063
|
-
!alignmentTracks ||
|
|
1064
|
-
!alignmentTracks[0] ||
|
|
1065
|
-
!alignmentTracks[0].alignmentData
|
|
1066
|
-
) {
|
|
1067
|
-
console.error("corrupted data!", this.props);
|
|
1068
|
-
return "corrupted data!";
|
|
1069
|
-
}
|
|
1070
|
-
|
|
1071
|
-
const getTrackVis = (alignmentTracks, isTemplate, allTracks) => {
|
|
1072
|
-
const rowData = {};
|
|
1073
|
-
const innerTrackVis = (drop_provided, drop_snapshot) => {
|
|
1074
|
-
return (
|
|
1075
|
-
<div
|
|
1076
|
-
className="alignmentTracks "
|
|
1077
|
-
style={{
|
|
1078
|
-
overflowY: "auto",
|
|
1079
|
-
display: "flex",
|
|
1080
|
-
zIndex: 10
|
|
1081
|
-
}}
|
|
1082
|
-
>
|
|
1083
|
-
<div
|
|
1084
|
-
style={{
|
|
1085
|
-
overflowX: "auto",
|
|
1086
|
-
width: this.state.width
|
|
1087
|
-
}}
|
|
1088
|
-
ref={ref => {
|
|
1089
|
-
this[isTemplate ? "alignmentHolderTop" : "alignmentHolder"] =
|
|
1090
|
-
ref;
|
|
1091
|
-
}}
|
|
1092
|
-
onContextMenu={e => {
|
|
1093
|
-
if (
|
|
1094
|
-
!allowTrimming ||
|
|
1095
|
-
isTargetWithinEl(e, ".alignmentTrackName")
|
|
1096
|
-
) {
|
|
1097
|
-
return;
|
|
1098
|
-
}
|
|
1099
|
-
|
|
1100
|
-
this.getTrackTrimmingOptions({
|
|
1101
|
-
e,
|
|
1102
|
-
allTracks,
|
|
1103
|
-
upsertAlignmentRun,
|
|
1104
|
-
alignmentId,
|
|
1105
|
-
currentPairwiseAlignmentIndex
|
|
1106
|
-
});
|
|
1107
|
-
}}
|
|
1108
|
-
onMouseLeave={this.removeMinimapHighlightForMouseLeave}
|
|
1109
|
-
onMouseMove={this.updateMinimapHighlightForMouseMove}
|
|
1110
|
-
dataname="scrollGroup"
|
|
1111
|
-
className="alignmentHolder"
|
|
1112
|
-
onScroll={isTemplate ? this.handleTopScroll : this.handleScroll}
|
|
1113
|
-
>
|
|
1114
|
-
<ReactDraggable
|
|
1115
|
-
disabled={this.state.isTrackDragging}
|
|
1116
|
-
bounds={{ top: 0, left: 0, right: 0, bottom: 0 }}
|
|
1117
|
-
onDrag={
|
|
1118
|
-
noClickDragHandlers
|
|
1119
|
-
? noop
|
|
1120
|
-
: event => {
|
|
1121
|
-
if (this.state.isTrackDragging) return;
|
|
1122
|
-
this.getNearestCursorPositionToMouseEvent(
|
|
1123
|
-
rowData,
|
|
1124
|
-
event,
|
|
1125
|
-
editorDragged
|
|
1126
|
-
);
|
|
1127
|
-
}
|
|
1128
|
-
}
|
|
1129
|
-
onStart={
|
|
1130
|
-
noClickDragHandlers
|
|
1131
|
-
? noop
|
|
1132
|
-
: event => {
|
|
1133
|
-
if (isTargetWithinEl(event, ".alignmentTrackName")) {
|
|
1134
|
-
return this.setState({ isTrackDragging: true });
|
|
1135
|
-
}
|
|
1136
|
-
if (this.state.isTrackDragging) return;
|
|
1137
|
-
this.getNearestCursorPositionToMouseEvent(
|
|
1138
|
-
rowData,
|
|
1139
|
-
event,
|
|
1140
|
-
editorDragStarted
|
|
1141
|
-
);
|
|
1142
|
-
}
|
|
1143
|
-
}
|
|
1144
|
-
onStop={
|
|
1145
|
-
noClickDragHandlers
|
|
1146
|
-
? noop
|
|
1147
|
-
: (...args) => {
|
|
1148
|
-
setTimeout(() => {
|
|
1149
|
-
this.setState({ isTrackDragging: false });
|
|
1150
|
-
}, 0);
|
|
1151
|
-
editorDragStopped(...args);
|
|
1152
|
-
}
|
|
1153
|
-
}
|
|
1154
|
-
>
|
|
1155
|
-
<div
|
|
1156
|
-
ref={ref => (this.veTracksAndAlignmentHolder = ref)}
|
|
1157
|
-
className={classNames("veTracksAndAlignmentHolder", {
|
|
1158
|
-
isTrackDragging: this.state.isTrackDragging
|
|
1159
|
-
})}
|
|
1160
|
-
// onContextMenu={
|
|
1161
|
-
//tnrtodo add copy single track/all tracks logic here
|
|
1162
|
-
// (event) => {
|
|
1163
|
-
// this.getNearestCursorPositionToMouseEvent(
|
|
1164
|
-
// rowData,
|
|
1165
|
-
// event,
|
|
1166
|
-
// () => {
|
|
1167
|
-
// }
|
|
1168
|
-
// );
|
|
1169
|
-
// }
|
|
1170
|
-
// }
|
|
1171
|
-
onClick={
|
|
1172
|
-
noClickDragHandlers
|
|
1173
|
-
? noop
|
|
1174
|
-
: event => {
|
|
1175
|
-
if (this.state.isTrackDragging) return;
|
|
1176
|
-
if (isTargetWithinEl(event, ".alignmentTrackName")) {
|
|
1177
|
-
return;
|
|
1178
|
-
}
|
|
1179
|
-
this.getNearestCursorPositionToMouseEvent(
|
|
1180
|
-
rowData,
|
|
1181
|
-
event,
|
|
1182
|
-
editorClicked
|
|
1183
|
-
);
|
|
1184
|
-
}
|
|
1185
|
-
}
|
|
1186
|
-
>
|
|
1187
|
-
<PerformantSelectionLayer
|
|
1188
|
-
leftMargin={this.state.nameDivWidth}
|
|
1189
|
-
className="veAlignmentSelectionLayer"
|
|
1190
|
-
isDraggable
|
|
1191
|
-
selectionLayerRightClicked={
|
|
1192
|
-
selectionLayerRightClicked
|
|
1193
|
-
? (...args) => {
|
|
1194
|
-
selectionLayerRightClicked(...args, this.props);
|
|
1195
|
-
}
|
|
1196
|
-
: (...args) => {
|
|
1197
|
-
const { event } = args[0];
|
|
1198
|
-
const track = getTrackFromEvent(event, allTracks);
|
|
1199
|
-
|
|
1200
|
-
const alignmentData = track.alignmentData;
|
|
1201
|
-
const { name } = alignmentData;
|
|
1202
|
-
|
|
1203
|
-
const copySpecificAlignmentFasta = async () => {
|
|
1204
|
-
const { selectionLayer } =
|
|
1205
|
-
this.props.store.getState().VectorEditor
|
|
1206
|
-
.__allEditorsOptions.alignments[
|
|
1207
|
-
this.props.id
|
|
1208
|
-
] || {};
|
|
1209
|
-
const seqDataToCopy = getSequenceDataBetweenRange(
|
|
1210
|
-
alignmentData,
|
|
1211
|
-
selectionLayer
|
|
1212
|
-
).sequence;
|
|
1213
|
-
const seqDataToCopyAsFasta = `>${name}\r\n${seqDataToCopy}\r\n`;
|
|
1214
|
-
await navigator.clipboard.writeText(
|
|
1215
|
-
seqDataToCopyAsFasta
|
|
1216
|
-
);
|
|
1217
|
-
};
|
|
1218
|
-
|
|
1219
|
-
const copySpecificAlignment = async () => {
|
|
1220
|
-
const { selectionLayer } =
|
|
1221
|
-
this.props.store.getState().VectorEditor
|
|
1222
|
-
.__allEditorsOptions.alignments[
|
|
1223
|
-
this.props.id
|
|
1224
|
-
] || {};
|
|
1225
|
-
const seqDataToCopy = getSequenceDataBetweenRange(
|
|
1226
|
-
alignmentData,
|
|
1227
|
-
selectionLayer
|
|
1228
|
-
).sequence;
|
|
1229
|
-
await navigator.clipboard.writeText(
|
|
1230
|
-
seqDataToCopy
|
|
1231
|
-
);
|
|
1232
|
-
};
|
|
1233
|
-
|
|
1234
|
-
const getAllAlignmentsFastaText = async () => {
|
|
1235
|
-
await navigator.clipboard.writeText(
|
|
1236
|
-
this.getAllAlignmentsFastaText()
|
|
1237
|
-
);
|
|
1238
|
-
};
|
|
1239
|
-
|
|
1240
|
-
showContextMenu(
|
|
1241
|
-
[
|
|
1242
|
-
...(additionalSelectionLayerRightClickedOptions
|
|
1243
|
-
? additionalSelectionLayerRightClickedOptions(
|
|
1244
|
-
...args,
|
|
1245
|
-
this.props
|
|
1246
|
-
)
|
|
1247
|
-
: []),
|
|
1248
|
-
{
|
|
1249
|
-
text: "Copy Selection of All Alignments as Fasta",
|
|
1250
|
-
className:
|
|
1251
|
-
"copyAllAlignmentsFastaClipboardHelper",
|
|
1252
|
-
hotkey: "cmd+c",
|
|
1253
|
-
onClick: () => {
|
|
1254
|
-
getAllAlignmentsFastaText();
|
|
1255
|
-
window.toastr.success("Selection Copied");
|
|
1256
|
-
}
|
|
1257
|
-
},
|
|
1258
|
-
{
|
|
1259
|
-
text: `Copy Selection of ${name} as Fasta`,
|
|
1260
|
-
className:
|
|
1261
|
-
"copySpecificAlignmentFastaClipboardHelper",
|
|
1262
|
-
onClick: () => {
|
|
1263
|
-
copySpecificAlignmentFasta();
|
|
1264
|
-
window.toastr.success(
|
|
1265
|
-
"Selection Copied As Fasta"
|
|
1266
|
-
);
|
|
1267
|
-
}
|
|
1268
|
-
},
|
|
1269
|
-
{
|
|
1270
|
-
text: `Copy Selection of ${name}`,
|
|
1271
|
-
className:
|
|
1272
|
-
"copySpecificAlignmentAsPlainClipboardHelper",
|
|
1273
|
-
onClick: () => {
|
|
1274
|
-
copySpecificAlignment();
|
|
1275
|
-
window.toastr.success("Selection Copied");
|
|
1276
|
-
}
|
|
1277
|
-
}
|
|
1278
|
-
],
|
|
1279
|
-
undefined,
|
|
1280
|
-
event
|
|
1281
|
-
);
|
|
1282
|
-
}
|
|
1283
|
-
}
|
|
1284
|
-
easyStore={this.easyStore}
|
|
1285
|
-
sequenceLength={sequenceLength}
|
|
1286
|
-
charWidth={this.getCharWidthInLinearView()}
|
|
1287
|
-
row={{ start: 0, end: sequenceLength - 1 }}
|
|
1288
|
-
/>
|
|
1289
|
-
<PerformantCaret
|
|
1290
|
-
leftMargin={this.state.nameDivWidth}
|
|
1291
|
-
className="veAlignmentSelectionLayer"
|
|
1292
|
-
isDraggable
|
|
1293
|
-
sequenceLength={sequenceLength}
|
|
1294
|
-
charWidth={this.getCharWidthInLinearView()}
|
|
1295
|
-
row={{ start: 0, end: sequenceLength - 1 }}
|
|
1296
|
-
easyStore={this.easyStore}
|
|
1297
|
-
/>
|
|
1298
|
-
{isTemplate ? (
|
|
1299
|
-
this.renderItem(0, 0, isTemplate)
|
|
1300
|
-
) : (
|
|
1301
|
-
<ReactList
|
|
1302
|
-
ref={c => {
|
|
1303
|
-
this.InfiniteScroller = c;
|
|
1304
|
-
const domNode = ReactDOM.findDOMNode(c);
|
|
1305
|
-
if (domNode instanceof HTMLElement) {
|
|
1306
|
-
drop_provided.innerRef(domNode);
|
|
1307
|
-
}
|
|
1308
|
-
}}
|
|
1309
|
-
type="variable"
|
|
1310
|
-
itemSizeEstimator={this.estimateRowHeight}
|
|
1311
|
-
itemRenderer={this.renderItem}
|
|
1312
|
-
length={
|
|
1313
|
-
alignmentTracks.length +
|
|
1314
|
-
(drop_snapshot.isUsingPlaceholder ? 1 : 0)
|
|
1315
|
-
}
|
|
1316
|
-
/>
|
|
1317
|
-
)}
|
|
1318
|
-
</div>
|
|
1319
|
-
</ReactDraggable>
|
|
1320
|
-
</div>
|
|
1321
|
-
</div>
|
|
1322
|
-
);
|
|
1323
|
-
};
|
|
1324
|
-
if (isTemplate) return innerTrackVis();
|
|
1325
|
-
else
|
|
1326
|
-
return (
|
|
1327
|
-
<Droppable
|
|
1328
|
-
mode="virtual"
|
|
1329
|
-
renderClone={(provided, snapshot, { source: { index } }) => {
|
|
1330
|
-
return this.renderItem(index, index, false, {
|
|
1331
|
-
provided,
|
|
1332
|
-
snapshot
|
|
1333
|
-
});
|
|
1334
|
-
}}
|
|
1335
|
-
direction="vertical"
|
|
1336
|
-
droppableId={"droppable" + isTemplate ? "_no_drop" : ""}
|
|
1337
|
-
>
|
|
1338
|
-
{innerTrackVis}
|
|
1339
|
-
</Droppable>
|
|
1340
|
-
);
|
|
1341
|
-
};
|
|
1342
|
-
|
|
1343
|
-
const [firstTrack, ...otherTracks] = alignmentTracks;
|
|
1344
|
-
const totalWidthOfMinimap = this.state.width;
|
|
1345
|
-
const totalWidthInAlignmentView = 14 * this.getSequenceLength();
|
|
1346
|
-
const minSliderSize = Math.min(
|
|
1347
|
-
totalWidthOfMinimap * (totalWidthOfMinimap / totalWidthInAlignmentView),
|
|
1348
|
-
totalWidthOfMinimap
|
|
1349
|
-
);
|
|
1350
|
-
const viewportHeight = Math.max(
|
|
1351
|
-
document.documentElement.clientHeight,
|
|
1352
|
-
window.innerHeight || 0
|
|
1353
|
-
);
|
|
1354
|
-
|
|
1355
|
-
/**
|
|
1356
|
-
* Parameters to be passed to our Pinch Handler component
|
|
1357
|
-
* OnPinch is the method to be executed when the pinch gesture is registered
|
|
1358
|
-
* Pinch Handler for minimap
|
|
1359
|
-
*/
|
|
1360
|
-
const pinchHandler = {
|
|
1361
|
-
onPinch: ({ delta: [d] }) => {
|
|
1362
|
-
this.bindOutsideChangeHelper.triggerChange(({ value, changeValue }) => {
|
|
1363
|
-
// changeValue(d);
|
|
1364
|
-
if (d > 0) {
|
|
1365
|
-
if (value > 8) {
|
|
1366
|
-
changeValue(value + 0.4);
|
|
1367
|
-
} else {
|
|
1368
|
-
changeValue(value + 0.2);
|
|
1369
|
-
}
|
|
1370
|
-
} else if (d < 0) {
|
|
1371
|
-
if (value > 8) {
|
|
1372
|
-
changeValue(value - 0.4);
|
|
1373
|
-
} else {
|
|
1374
|
-
changeValue(value - 0.2);
|
|
1375
|
-
}
|
|
1376
|
-
}
|
|
1377
|
-
});
|
|
1378
|
-
updateLabelsForInViewFeatures();
|
|
1379
|
-
}
|
|
1380
|
-
};
|
|
1381
|
-
|
|
1382
|
-
return (
|
|
1383
|
-
<PinchHelper {...pinchHandler}>
|
|
1384
|
-
<ResizeSensor onResize={this.handleResize}>
|
|
1385
|
-
<div
|
|
1386
|
-
style={{
|
|
1387
|
-
height: height || (isPairwise ? "auto" : viewportHeight * 0.88),
|
|
1388
|
-
display: "flex",
|
|
1389
|
-
flexDirection: "column",
|
|
1390
|
-
justifyContent: "space-between",
|
|
1391
|
-
position: "relative",
|
|
1392
|
-
overflowY: "auto",
|
|
1393
|
-
...this.props.style
|
|
1394
|
-
// borderTop: "1px solid black"
|
|
1395
|
-
}}
|
|
1396
|
-
className="alignmentView"
|
|
1397
|
-
>
|
|
1398
|
-
<DragDropContext
|
|
1399
|
-
onDragStart={this.onTrackDragStart}
|
|
1400
|
-
onDragEnd={this.onTrackDragEnd}
|
|
1401
|
-
>
|
|
1402
|
-
<div
|
|
1403
|
-
style={{
|
|
1404
|
-
display: "flex",
|
|
1405
|
-
flexDirection: "column",
|
|
1406
|
-
position: "relative",
|
|
1407
|
-
overflowY: "auto"
|
|
1408
|
-
}}
|
|
1409
|
-
className="alignmentView-top-container"
|
|
1410
|
-
>
|
|
1411
|
-
<div
|
|
1412
|
-
style={{
|
|
1413
|
-
paddingTop: "3px",
|
|
1414
|
-
paddingBottom: "5px",
|
|
1415
|
-
borderBottom: "1px solid",
|
|
1416
|
-
display: "flex",
|
|
1417
|
-
minHeight: "32px",
|
|
1418
|
-
width: "100%",
|
|
1419
|
-
flexWrap: "nowrap",
|
|
1420
|
-
flexDirection: "row",
|
|
1421
|
-
flex: "0 0 auto"
|
|
1422
|
-
}}
|
|
1423
|
-
className="ve-alignment-top-bar"
|
|
1424
|
-
>
|
|
1425
|
-
{additionalTopLeftEl}
|
|
1426
|
-
{handleBackButtonClicked && (
|
|
1427
|
-
<Tooltip content="Back to Pairwise Alignment Overview">
|
|
1428
|
-
<Button
|
|
1429
|
-
icon="arrow-left"
|
|
1430
|
-
onClick={() => {
|
|
1431
|
-
// this.setState({
|
|
1432
|
-
// charWidthInLinearView: charWidthInLinearViewDefault
|
|
1433
|
-
// });
|
|
1434
|
-
handleBackButtonClicked();
|
|
1435
|
-
this.caretPositionUpdate(-1);
|
|
1436
|
-
}}
|
|
1437
|
-
small
|
|
1438
|
-
intent={Intent.PRIMARY}
|
|
1439
|
-
minimal
|
|
1440
|
-
style={{ marginRight: 10 }}
|
|
1441
|
-
className="alignmentViewBackButton"
|
|
1442
|
-
/>
|
|
1443
|
-
</Tooltip>
|
|
1444
|
-
)}
|
|
1445
|
-
|
|
1446
|
-
<div style={{ display: "flex" }}>
|
|
1447
|
-
<EditableText
|
|
1448
|
-
disabled={!handleAlignmentRename}
|
|
1449
|
-
onChange={v => {
|
|
1450
|
-
this.setState({
|
|
1451
|
-
alignmentName: v
|
|
1452
|
-
});
|
|
1453
|
-
}}
|
|
1454
|
-
maxLength={399} //stop the name from being tooo long
|
|
1455
|
-
value={this.state.alignmentName}
|
|
1456
|
-
onConfirm={async v => {
|
|
1457
|
-
if (!v) {
|
|
1458
|
-
this.setState({
|
|
1459
|
-
alignmentName: this.props.alignmentName
|
|
1460
|
-
});
|
|
1461
|
-
return;
|
|
1462
|
-
}
|
|
1463
|
-
if (v === this.props.alignmentName) {
|
|
1464
|
-
return; //already saved this name
|
|
1465
|
-
}
|
|
1466
|
-
this.setState({ saveMessage: "Alignment Renaming.." });
|
|
1467
|
-
this.setState({ saveMessageLoading: true });
|
|
1468
|
-
await handleAlignmentRename(v, this.props);
|
|
1469
|
-
this.setState({ saveMessage: "Rename Successful" });
|
|
1470
|
-
this.setState({ saveMessageLoading: false });
|
|
1471
|
-
setTimeout(() => {
|
|
1472
|
-
this.setState({ saveMessage: undefined });
|
|
1473
|
-
this.setState({ saveMessageLoading: false });
|
|
1474
|
-
}, 5000);
|
|
1475
|
-
}}
|
|
1476
|
-
selectAllOnFocus={true}
|
|
1477
|
-
className="veAlignmentName"
|
|
1478
|
-
></EditableText>
|
|
1479
|
-
|
|
1480
|
-
<div
|
|
1481
|
-
className="veAlignmentType"
|
|
1482
|
-
style={{
|
|
1483
|
-
paddingTop: "3px",
|
|
1484
|
-
fontSize: "14px",
|
|
1485
|
-
color: "grey",
|
|
1486
|
-
maxWidth: "300px",
|
|
1487
|
-
overflow: "hidden",
|
|
1488
|
-
textOverflow: "ellipsis",
|
|
1489
|
-
whiteSpace: "nowrap"
|
|
1490
|
-
}}
|
|
1491
|
-
data-title={
|
|
1492
|
-
this.props.alignmentType || "Unknown Alignment Type"
|
|
1493
|
-
}
|
|
1494
|
-
>
|
|
1495
|
-
{this.props.alignmentType || "Unknown Alignment Type"}
|
|
1496
|
-
</div>
|
|
1497
|
-
</div>
|
|
1498
|
-
|
|
1499
|
-
{this.props.unmappedSeqs && (
|
|
1500
|
-
<InfoHelper
|
|
1501
|
-
size={20}
|
|
1502
|
-
content={
|
|
1503
|
-
<div>
|
|
1504
|
-
This alignment had sequences that did not map to the
|
|
1505
|
-
template sequence:
|
|
1506
|
-
{this.props.unmappedSeqs.map(
|
|
1507
|
-
({ sequenceData }, i) => (
|
|
1508
|
-
<div key={i}>{sequenceData.name}</div>
|
|
1509
|
-
)
|
|
1510
|
-
)}
|
|
1511
|
-
</div>
|
|
1512
|
-
}
|
|
1513
|
-
intent="warning"
|
|
1514
|
-
icon="warning-sign"
|
|
1515
|
-
></InfoHelper>
|
|
1516
|
-
)}
|
|
1517
|
-
{!isInPairwiseOverviewView && (
|
|
1518
|
-
<UncontrolledSliderWithPlusMinusBtns
|
|
1519
|
-
noWraparound
|
|
1520
|
-
bindOutsideChangeHelper={this.bindOutsideChangeHelper}
|
|
1521
|
-
onClick={() => {
|
|
1522
|
-
setTimeout(this.scrollToCaret, 0);
|
|
1523
|
-
}}
|
|
1524
|
-
minCharWidth={this.getMinCharWidth()}
|
|
1525
|
-
onChange={async zoomLvl => {
|
|
1526
|
-
this.isZooming = true;
|
|
1527
|
-
setTimeout(() => {
|
|
1528
|
-
this.isZooming = false;
|
|
1529
|
-
}, 10);
|
|
1530
|
-
// zoomLvl is in the range of 0 to 10
|
|
1531
|
-
const minCharWidth = this.getMinCharWidth();
|
|
1532
|
-
const scaleFactor = Math.pow(12 / minCharWidth, 1 / 10);
|
|
1533
|
-
const newCharWidth =
|
|
1534
|
-
minCharWidth * Math.pow(scaleFactor, zoomLvl);
|
|
1535
|
-
await this.setCharWidthInLinearView({
|
|
1536
|
-
charWidthInLinearView: newCharWidth
|
|
1537
|
-
});
|
|
1538
|
-
await this.scrollToCaret();
|
|
1539
|
-
await updateLabelsForInViewFeatures({
|
|
1540
|
-
rectElement: ".alignmentHolder"
|
|
1541
|
-
});
|
|
1542
|
-
}}
|
|
1543
|
-
coerceInitialValue={coerceInitialValue}
|
|
1544
|
-
title="Adjust Zoom Level"
|
|
1545
|
-
style={{ paddingTop: "4px", width: 100 }}
|
|
1546
|
-
className="veZoomAlignmentSlider ove-slider"
|
|
1547
|
-
labelRenderer={false}
|
|
1548
|
-
initialValue={charWidthInLinearView}
|
|
1549
|
-
stepSize={0.05} //was 0.01
|
|
1550
|
-
max={10}
|
|
1551
|
-
min={0}
|
|
1552
|
-
clickStepSize={0.5}
|
|
1553
|
-
/>
|
|
1554
|
-
)}
|
|
1555
|
-
{!noVisibilityOptions && !isInPairwiseOverviewView && (
|
|
1556
|
-
<AlignmentVisibilityTool
|
|
1557
|
-
currentPairwiseAlignmentIndex={
|
|
1558
|
-
currentPairwiseAlignmentIndex
|
|
1559
|
-
}
|
|
1560
|
-
{...alignmentVisibilityToolOptions}
|
|
1561
|
-
/>
|
|
1562
|
-
)}
|
|
1563
|
-
{updateAlignmentSortOrder && !isInPairwiseOverviewView && (
|
|
1564
|
-
<Popover
|
|
1565
|
-
minimal
|
|
1566
|
-
content={
|
|
1567
|
-
<Menu>
|
|
1568
|
-
<MenuItem
|
|
1569
|
-
active={true || alignmentSortOrder}
|
|
1570
|
-
onClick={() => {
|
|
1571
|
-
updateAlignmentSortOrder("Position");
|
|
1572
|
-
}}
|
|
1573
|
-
text="Position"
|
|
1574
|
-
/>
|
|
1575
|
-
<MenuItem
|
|
1576
|
-
active={false || alignmentSortOrder}
|
|
1577
|
-
onClick={() => {
|
|
1578
|
-
updateAlignmentSortOrder("Alphabetical");
|
|
1579
|
-
}}
|
|
1580
|
-
text="Alphabetical"
|
|
1581
|
-
/>
|
|
1582
|
-
</Menu>
|
|
1583
|
-
}
|
|
1584
|
-
target={
|
|
1585
|
-
<Button
|
|
1586
|
-
small
|
|
1587
|
-
text="Sort Order"
|
|
1588
|
-
rightIcon="caret-down"
|
|
1589
|
-
icon="sort"
|
|
1590
|
-
/>
|
|
1591
|
-
}
|
|
1592
|
-
/>
|
|
1593
|
-
)}
|
|
1594
|
-
{additionalTopEl}
|
|
1595
|
-
{this.state.saveMessage && (
|
|
1596
|
-
<div
|
|
1597
|
-
className="ove-menu-toast"
|
|
1598
|
-
style={{
|
|
1599
|
-
display: "flex",
|
|
1600
|
-
alignItems: "center",
|
|
1601
|
-
marginLeft: "auto",
|
|
1602
|
-
marginRight: 10
|
|
1603
|
-
}}
|
|
1604
|
-
>
|
|
1605
|
-
{this.state.saveMessageLoading ? (
|
|
1606
|
-
<div>
|
|
1607
|
-
<Spinner size={15}></Spinner>
|
|
1608
|
-
</div>
|
|
1609
|
-
) : (
|
|
1610
|
-
<Icon icon="tick-circle" intent="success"></Icon>
|
|
1611
|
-
)}{" "}
|
|
1612
|
-
|
|
1613
|
-
{this.state.saveMessage}
|
|
1614
|
-
</div>
|
|
1615
|
-
)}
|
|
1616
|
-
</div>
|
|
1617
|
-
{hasTemplate ? (
|
|
1618
|
-
<React.Fragment>
|
|
1619
|
-
<div className="alignmentTrackFixedToTop">
|
|
1620
|
-
{getTrackVis([firstTrack], true, alignmentTracks)}
|
|
1621
|
-
</div>
|
|
1622
|
-
{getTrackVis(otherTracks, false, alignmentTracks)}
|
|
1623
|
-
</React.Fragment>
|
|
1624
|
-
) : (
|
|
1625
|
-
getTrackVis(alignmentTracks, false, alignmentTracks)
|
|
1626
|
-
)}
|
|
1627
|
-
</div>
|
|
1628
|
-
</DragDropContext>
|
|
1629
|
-
{!isInPairwiseOverviewView && (
|
|
1630
|
-
<div
|
|
1631
|
-
className="alignmentViewBottomBar"
|
|
1632
|
-
style={{
|
|
1633
|
-
// flexGrow: 1,
|
|
1634
|
-
// minHeight: "-webkit-min-content", //https://stackoverflow.com/questions/28029736/how-to-prevent-a-flex-item-from-shrinking-smaller-than-its-content
|
|
1635
|
-
maxHeight: 210,
|
|
1636
|
-
marginTop: 4,
|
|
1637
|
-
paddingTop: 4,
|
|
1638
|
-
borderTop: "1px solid lightgrey",
|
|
1639
|
-
display: "flex"
|
|
1640
|
-
}}
|
|
1641
|
-
>
|
|
1642
|
-
<Minimap
|
|
1643
|
-
{...{
|
|
1644
|
-
selectionLayerComp: (
|
|
1645
|
-
<React.Fragment>
|
|
1646
|
-
<PerformantSelectionLayer
|
|
1647
|
-
is
|
|
1648
|
-
hideCarets
|
|
1649
|
-
className="veAlignmentSelectionLayer veMinimapSelectionLayer"
|
|
1650
|
-
easyStore={this.easyStore}
|
|
1651
|
-
sequenceLength={sequenceLength}
|
|
1652
|
-
charWidth={this.getMinCharWidth(true)}
|
|
1653
|
-
row={{ start: 0, end: sequenceLength - 1 }}
|
|
1654
|
-
/>
|
|
1655
|
-
<PerformantCaret
|
|
1656
|
-
style={{
|
|
1657
|
-
opacity: 0.2
|
|
1658
|
-
}}
|
|
1659
|
-
className="veAlignmentSelectionLayer veMinimapSelectionLayer"
|
|
1660
|
-
sequenceLength={sequenceLength}
|
|
1661
|
-
charWidth={this.getMinCharWidth(true)}
|
|
1662
|
-
row={{ start: 0, end: sequenceLength - 1 }}
|
|
1663
|
-
easyStore={this.easyStore}
|
|
1664
|
-
/>
|
|
1665
|
-
</React.Fragment>
|
|
1666
|
-
),
|
|
1667
|
-
alignmentTracks,
|
|
1668
|
-
dimensions: {
|
|
1669
|
-
width: Math.max(this.state.width, 10) || 10
|
|
1670
|
-
},
|
|
1671
|
-
nameDivOffsetPercent: 0,
|
|
1672
|
-
// this.state.nameDivWidth / this.getMaxLinearViewWidth(),
|
|
1673
|
-
scrollYToTrack: this.scrollYToTrack,
|
|
1674
|
-
onSizeAdjust: this.onMinimapSizeAdjust,
|
|
1675
|
-
minSliderSize,
|
|
1676
|
-
laneHeight:
|
|
1677
|
-
minimapLaneHeight ||
|
|
1678
|
-
(alignmentTracks.length > 5 ? 10 : 17),
|
|
1679
|
-
laneSpacing:
|
|
1680
|
-
minimapLaneSpacing ||
|
|
1681
|
-
(alignmentTracks.length > 5 ? 2 : 1),
|
|
1682
|
-
easyStore: this.easyStore,
|
|
1683
|
-
numBpsShownInLinearView: this.getNumBpsShownInLinearView(),
|
|
1684
|
-
scrollAlignmentView: this.state.scrollAlignmentView
|
|
1685
|
-
}}
|
|
1686
|
-
onMinimapScrollX={this.scrollAlignmentToPercent}
|
|
1687
|
-
/>
|
|
1688
|
-
</div>
|
|
1689
|
-
)}
|
|
1690
|
-
<GlobalDialog
|
|
1691
|
-
// {...pickedUserDefinedHandlersAndOpts}
|
|
1692
|
-
// dialogOverrides={pick(this.props, [
|
|
1693
|
-
// "AddOrEditFeatureDialogOverride",
|
|
1694
|
-
// "AddOrEditPartDialogOverride",
|
|
1695
|
-
// "AddOrEditPrimerDialogOverride"
|
|
1696
|
-
// ])}
|
|
1697
|
-
/>
|
|
1698
|
-
</div>
|
|
1699
|
-
</ResizeSensor>
|
|
1700
|
-
</PinchHelper>
|
|
1701
|
-
);
|
|
1702
|
-
}
|
|
1703
|
-
|
|
1704
|
-
getTrackTrimmingOptions({
|
|
971
|
+
const getTrackTrimmingOptions = ({
|
|
1705
972
|
e,
|
|
1706
973
|
allTracks,
|
|
1707
974
|
upsertAlignmentRun,
|
|
1708
975
|
currentPairwiseAlignmentIndex,
|
|
1709
976
|
alignmentId
|
|
1710
|
-
}) {
|
|
977
|
+
}) => {
|
|
1711
978
|
const track = getTrackFromEvent(e, allTracks);
|
|
1712
979
|
|
|
1713
|
-
|
|
1714
|
-
|
|
980
|
+
getNearestCursorPositionToMouseEvent(
|
|
981
|
+
rowData.current,
|
|
1715
982
|
e,
|
|
1716
983
|
({ nearestCaretPos }) => {
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
984
|
+
setTempTrimmingCaret(prev => ({
|
|
985
|
+
...prev,
|
|
986
|
+
[track.index]: nearestCaretPos
|
|
987
|
+
}));
|
|
1720
988
|
const afterDisabled =
|
|
1721
989
|
nearestCaretPos <= track.alignmentData.trimmedRange?.start;
|
|
1722
990
|
const beforeDisabled =
|
|
@@ -1742,16 +1010,15 @@ export class AlignmentView extends React.Component {
|
|
|
1742
1010
|
icon: "drawer-left-filled",
|
|
1743
1011
|
onMouseOver: () =>
|
|
1744
1012
|
!beforeDisabled &&
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
}
|
|
1750
|
-
}),
|
|
1013
|
+
setTempTrimBefore(prev => ({
|
|
1014
|
+
...prev,
|
|
1015
|
+
[track.index]: { start: 0, end: nearestCaretPos - 1 }
|
|
1016
|
+
})),
|
|
1751
1017
|
onMouseLeave: () =>
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1018
|
+
setTempTrimBefore(prev => ({
|
|
1019
|
+
...prev,
|
|
1020
|
+
[track.index]: undefined
|
|
1021
|
+
})),
|
|
1755
1022
|
onClick: () => {
|
|
1756
1023
|
updateTrackHelper({
|
|
1757
1024
|
currentPairwiseAlignmentIndex,
|
|
@@ -1764,8 +1031,7 @@ export class AlignmentView extends React.Component {
|
|
|
1764
1031
|
trimmedRange: {
|
|
1765
1032
|
start: nearestCaretPos,
|
|
1766
1033
|
end:
|
|
1767
|
-
track.alignmentData.trimmedRange?.end ||
|
|
1768
|
-
this.getMaxLength() - 1
|
|
1034
|
+
track.alignmentData.trimmedRange?.end || maxLength - 1
|
|
1769
1035
|
}
|
|
1770
1036
|
}
|
|
1771
1037
|
});
|
|
@@ -1777,16 +1043,15 @@ export class AlignmentView extends React.Component {
|
|
|
1777
1043
|
icon: "drawer-right-filled",
|
|
1778
1044
|
onMouseOver: () =>
|
|
1779
1045
|
!afterDisabled &&
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
}
|
|
1785
|
-
}),
|
|
1046
|
+
setTempTrimAfter(prev => ({
|
|
1047
|
+
...prev,
|
|
1048
|
+
[track.index]: { start: nearestCaretPos, end: maxLength - 1 }
|
|
1049
|
+
})),
|
|
1786
1050
|
onMouseLeave: () =>
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1051
|
+
setTempTrimAfter(prev => ({
|
|
1052
|
+
...prev,
|
|
1053
|
+
[track.index]: undefined
|
|
1054
|
+
})),
|
|
1790
1055
|
onClick: () => {
|
|
1791
1056
|
updateTrackHelper({
|
|
1792
1057
|
currentPairwiseAlignmentIndex,
|
|
@@ -1829,17 +1094,630 @@ export class AlignmentView extends React.Component {
|
|
|
1829
1094
|
undefined,
|
|
1830
1095
|
e,
|
|
1831
1096
|
() => {
|
|
1832
|
-
|
|
1833
|
-
[`tempTrimmingCaret${track.index}`]: undefined
|
|
1834
|
-
});
|
|
1097
|
+
setTempTrimAfter(prev => ({ ...prev, [track.index]: undefined }));
|
|
1835
1098
|
}
|
|
1836
1099
|
);
|
|
1837
1100
|
}
|
|
1838
1101
|
);
|
|
1839
1102
|
e.preventDefault();
|
|
1840
1103
|
e.stopPropagation();
|
|
1104
|
+
};
|
|
1105
|
+
|
|
1106
|
+
if (
|
|
1107
|
+
!alignmentTracks ||
|
|
1108
|
+
!alignmentTracks[0] ||
|
|
1109
|
+
!alignmentTracks[0].alignmentData
|
|
1110
|
+
) {
|
|
1111
|
+
console.error("corrupted data!", props);
|
|
1112
|
+
return "corrupted data!";
|
|
1841
1113
|
}
|
|
1842
|
-
|
|
1114
|
+
|
|
1115
|
+
const getTrackVis = (alignmentTracks, isTemplate, allTracks) => {
|
|
1116
|
+
const rowData = {};
|
|
1117
|
+
const innerTrackVis = (drop_provided, drop_snapshot) => {
|
|
1118
|
+
return (
|
|
1119
|
+
<div
|
|
1120
|
+
className="alignmentTracks "
|
|
1121
|
+
style={{
|
|
1122
|
+
overflowY: "auto",
|
|
1123
|
+
display: "flex",
|
|
1124
|
+
zIndex: 10
|
|
1125
|
+
}}
|
|
1126
|
+
>
|
|
1127
|
+
<div
|
|
1128
|
+
style={{ overflowX: "auto", width }}
|
|
1129
|
+
ref={ref => {
|
|
1130
|
+
if (isTemplate) {
|
|
1131
|
+
alignmentHolderTop.current = ref;
|
|
1132
|
+
} else {
|
|
1133
|
+
alignmentHolder.current = ref;
|
|
1134
|
+
}
|
|
1135
|
+
}}
|
|
1136
|
+
onContextMenu={e => {
|
|
1137
|
+
if (
|
|
1138
|
+
!allowTrimming ||
|
|
1139
|
+
isTargetWithinEl(e, ".alignmentTrackName")
|
|
1140
|
+
) {
|
|
1141
|
+
return;
|
|
1142
|
+
}
|
|
1143
|
+
|
|
1144
|
+
getTrackTrimmingOptions({
|
|
1145
|
+
e,
|
|
1146
|
+
allTracks,
|
|
1147
|
+
upsertAlignmentRun,
|
|
1148
|
+
alignmentId,
|
|
1149
|
+
currentPairwiseAlignmentIndex
|
|
1150
|
+
});
|
|
1151
|
+
}}
|
|
1152
|
+
onMouseLeave={removeMinimapHighlightForMouseLeave}
|
|
1153
|
+
onMouseMove={updateMinimapHighlightForMouseMove}
|
|
1154
|
+
dataname="scrollGroup"
|
|
1155
|
+
className="alignmentHolder"
|
|
1156
|
+
onScroll={isTemplate ? handleTopScroll : handleScroll}
|
|
1157
|
+
>
|
|
1158
|
+
<ReactDraggable
|
|
1159
|
+
disabled={isTrackDragging}
|
|
1160
|
+
bounds={{ top: 0, left: 0, right: 0, bottom: 0 }}
|
|
1161
|
+
onDrag={
|
|
1162
|
+
noClickDragHandlers
|
|
1163
|
+
? noop
|
|
1164
|
+
: event => {
|
|
1165
|
+
if (isTrackDragging) return;
|
|
1166
|
+
getNearestCursorPositionToMouseEvent(
|
|
1167
|
+
rowData,
|
|
1168
|
+
event,
|
|
1169
|
+
editorDragged
|
|
1170
|
+
);
|
|
1171
|
+
}
|
|
1172
|
+
}
|
|
1173
|
+
onStart={
|
|
1174
|
+
noClickDragHandlers
|
|
1175
|
+
? noop
|
|
1176
|
+
: event => {
|
|
1177
|
+
if (isTargetWithinEl(event, ".alignmentTrackName")) {
|
|
1178
|
+
setIsTrackDragging(true);
|
|
1179
|
+
return;
|
|
1180
|
+
}
|
|
1181
|
+
if (isTrackDragging) return;
|
|
1182
|
+
getNearestCursorPositionToMouseEvent(
|
|
1183
|
+
rowData,
|
|
1184
|
+
event,
|
|
1185
|
+
editorDragStarted
|
|
1186
|
+
);
|
|
1187
|
+
}
|
|
1188
|
+
}
|
|
1189
|
+
onStop={
|
|
1190
|
+
noClickDragHandlers
|
|
1191
|
+
? noop
|
|
1192
|
+
: (...args) => {
|
|
1193
|
+
setTimeout(() => {
|
|
1194
|
+
setIsTrackDragging(false);
|
|
1195
|
+
}, 0);
|
|
1196
|
+
editorDragStopped(...args);
|
|
1197
|
+
}
|
|
1198
|
+
}
|
|
1199
|
+
>
|
|
1200
|
+
<div
|
|
1201
|
+
ref={veTracksAndAlignmentHolder}
|
|
1202
|
+
className={classNames("veTracksAndAlignmentHolder", {
|
|
1203
|
+
isTrackDragging
|
|
1204
|
+
})}
|
|
1205
|
+
onClick={
|
|
1206
|
+
noClickDragHandlers
|
|
1207
|
+
? noop
|
|
1208
|
+
: event => {
|
|
1209
|
+
if (isTrackDragging) return;
|
|
1210
|
+
if (isTargetWithinEl(event, ".alignmentTrackName")) {
|
|
1211
|
+
return;
|
|
1212
|
+
}
|
|
1213
|
+
getNearestCursorPositionToMouseEvent(
|
|
1214
|
+
rowData,
|
|
1215
|
+
event,
|
|
1216
|
+
editorClicked
|
|
1217
|
+
);
|
|
1218
|
+
}
|
|
1219
|
+
}
|
|
1220
|
+
>
|
|
1221
|
+
<PerformantSelectionLayer
|
|
1222
|
+
leftMargin={nameDivWidth}
|
|
1223
|
+
className="veAlignmentSelectionLayer"
|
|
1224
|
+
isDraggable
|
|
1225
|
+
selectionLayerRightClicked={
|
|
1226
|
+
selectionLayerRightClicked
|
|
1227
|
+
? (...args) => {
|
|
1228
|
+
// It's necessary to take the props out of the function
|
|
1229
|
+
// arguments
|
|
1230
|
+
selectionLayerRightClicked(...args, props);
|
|
1231
|
+
}
|
|
1232
|
+
: (...args) => {
|
|
1233
|
+
const { event } = args[0];
|
|
1234
|
+
const track = getTrackFromEvent(event, allTracks);
|
|
1235
|
+
|
|
1236
|
+
const alignmentData = track.alignmentData;
|
|
1237
|
+
const { name } = alignmentData;
|
|
1238
|
+
|
|
1239
|
+
const copySpecificAlignmentFasta = async () => {
|
|
1240
|
+
const { selectionLayer } =
|
|
1241
|
+
store.getState().VectorEditor.__allEditorsOptions
|
|
1242
|
+
.alignments[id] || {};
|
|
1243
|
+
const seqDataToCopy = getSequenceDataBetweenRange(
|
|
1244
|
+
alignmentData,
|
|
1245
|
+
selectionLayer
|
|
1246
|
+
).sequence;
|
|
1247
|
+
const seqDataToCopyAsFasta = `>${name}\r\n${seqDataToCopy}\r\n`;
|
|
1248
|
+
await navigator.clipboard.writeText(
|
|
1249
|
+
seqDataToCopyAsFasta
|
|
1250
|
+
);
|
|
1251
|
+
};
|
|
1252
|
+
|
|
1253
|
+
const copySpecificAlignment = async () => {
|
|
1254
|
+
const { selectionLayer } =
|
|
1255
|
+
store.getState().VectorEditor.__allEditorsOptions
|
|
1256
|
+
.alignments[id] || {};
|
|
1257
|
+
const seqDataToCopy = getSequenceDataBetweenRange(
|
|
1258
|
+
alignmentData,
|
|
1259
|
+
selectionLayer
|
|
1260
|
+
).sequence;
|
|
1261
|
+
await navigator.clipboard.writeText(seqDataToCopy);
|
|
1262
|
+
};
|
|
1263
|
+
|
|
1264
|
+
const getAllAlignmentsFastaText = async () => {
|
|
1265
|
+
await navigator.clipboard.writeText(
|
|
1266
|
+
getAllAlignmentsFastaText()
|
|
1267
|
+
);
|
|
1268
|
+
};
|
|
1269
|
+
|
|
1270
|
+
showContextMenu(
|
|
1271
|
+
[
|
|
1272
|
+
...(additionalSelectionLayerRightClickedOptions
|
|
1273
|
+
? additionalSelectionLayerRightClickedOptions(
|
|
1274
|
+
...args,
|
|
1275
|
+
props
|
|
1276
|
+
)
|
|
1277
|
+
: []),
|
|
1278
|
+
{
|
|
1279
|
+
text: "Copy Selection of All Alignments as Fasta",
|
|
1280
|
+
className:
|
|
1281
|
+
"copyAllAlignmentsFastaClipboardHelper",
|
|
1282
|
+
hotkey: "cmd+c",
|
|
1283
|
+
onClick: () => {
|
|
1284
|
+
getAllAlignmentsFastaText();
|
|
1285
|
+
window.toastr.success("Selection Copied");
|
|
1286
|
+
}
|
|
1287
|
+
},
|
|
1288
|
+
{
|
|
1289
|
+
text: `Copy Selection of ${name} as Fasta`,
|
|
1290
|
+
className:
|
|
1291
|
+
"copySpecificAlignmentFastaClipboardHelper",
|
|
1292
|
+
onClick: () => {
|
|
1293
|
+
copySpecificAlignmentFasta();
|
|
1294
|
+
window.toastr.success(
|
|
1295
|
+
"Selection Copied As Fasta"
|
|
1296
|
+
);
|
|
1297
|
+
}
|
|
1298
|
+
},
|
|
1299
|
+
{
|
|
1300
|
+
text: `Copy Selection of ${name}`,
|
|
1301
|
+
className:
|
|
1302
|
+
"copySpecificAlignmentAsPlainClipboardHelper",
|
|
1303
|
+
onClick: () => {
|
|
1304
|
+
copySpecificAlignment();
|
|
1305
|
+
window.toastr.success("Selection Copied");
|
|
1306
|
+
}
|
|
1307
|
+
}
|
|
1308
|
+
],
|
|
1309
|
+
undefined,
|
|
1310
|
+
event
|
|
1311
|
+
);
|
|
1312
|
+
}
|
|
1313
|
+
}
|
|
1314
|
+
easyStore={easyStore.current}
|
|
1315
|
+
sequenceLength={maxLength}
|
|
1316
|
+
charWidth={charWidth}
|
|
1317
|
+
row={{ start: 0, end: maxLength - 1 }}
|
|
1318
|
+
/>
|
|
1319
|
+
<PerformantCaret
|
|
1320
|
+
leftMargin={nameDivWidth}
|
|
1321
|
+
className="veAlignmentSelectionLayer"
|
|
1322
|
+
isDraggable
|
|
1323
|
+
sequenceLength={maxLength}
|
|
1324
|
+
charWidth={charWidth}
|
|
1325
|
+
row={{ start: 0, end: maxLength - 1 }}
|
|
1326
|
+
easyStore={easyStore.current}
|
|
1327
|
+
/>
|
|
1328
|
+
{isTemplate ? (
|
|
1329
|
+
renderItem(0, 0, isTemplate)
|
|
1330
|
+
) : (
|
|
1331
|
+
<ReactList
|
|
1332
|
+
ref={c => {
|
|
1333
|
+
InfiniteScroller.current = c;
|
|
1334
|
+
const domNode = ReactDOM.findDOMNode(c);
|
|
1335
|
+
if (domNode instanceof HTMLElement) {
|
|
1336
|
+
drop_provided.innerRef(domNode);
|
|
1337
|
+
}
|
|
1338
|
+
}}
|
|
1339
|
+
type="variable"
|
|
1340
|
+
itemSizeEstimator={estimateRowHeight}
|
|
1341
|
+
itemRenderer={renderItem}
|
|
1342
|
+
length={
|
|
1343
|
+
alignmentTracks.length +
|
|
1344
|
+
(drop_snapshot.isUsingPlaceholder ? 1 : 0)
|
|
1345
|
+
}
|
|
1346
|
+
/>
|
|
1347
|
+
)}
|
|
1348
|
+
</div>
|
|
1349
|
+
</ReactDraggable>
|
|
1350
|
+
</div>
|
|
1351
|
+
</div>
|
|
1352
|
+
);
|
|
1353
|
+
};
|
|
1354
|
+
if (isTemplate) return innerTrackVis();
|
|
1355
|
+
else
|
|
1356
|
+
return (
|
|
1357
|
+
<Droppable
|
|
1358
|
+
mode="virtual"
|
|
1359
|
+
renderClone={(provided, snapshot, { source: { index } }) => {
|
|
1360
|
+
return renderItem(index, index, false, {
|
|
1361
|
+
provided,
|
|
1362
|
+
snapshot
|
|
1363
|
+
});
|
|
1364
|
+
}}
|
|
1365
|
+
direction="vertical"
|
|
1366
|
+
droppableId={"droppable" + isTemplate ? "_no_drop" : ""}
|
|
1367
|
+
>
|
|
1368
|
+
{innerTrackVis}
|
|
1369
|
+
</Droppable>
|
|
1370
|
+
);
|
|
1371
|
+
};
|
|
1372
|
+
|
|
1373
|
+
const [firstTrack, ...otherTracks] = alignmentTracks;
|
|
1374
|
+
const totalWidthOfMinimap = width;
|
|
1375
|
+
const totalWidthInAlignmentView = 14 * getSequenceLength();
|
|
1376
|
+
const minSliderSize = Math.min(
|
|
1377
|
+
totalWidthOfMinimap * (totalWidthOfMinimap / totalWidthInAlignmentView),
|
|
1378
|
+
totalWidthOfMinimap
|
|
1379
|
+
);
|
|
1380
|
+
const viewportHeight = Math.max(
|
|
1381
|
+
document.documentElement.clientHeight,
|
|
1382
|
+
window.innerHeight || 0
|
|
1383
|
+
);
|
|
1384
|
+
|
|
1385
|
+
/**
|
|
1386
|
+
* Parameters to be passed to our Pinch Handler component
|
|
1387
|
+
* OnPinch is the method to be executed when the pinch gesture is registered
|
|
1388
|
+
* Pinch Handler for minimap
|
|
1389
|
+
*/
|
|
1390
|
+
const pinchHandler = {
|
|
1391
|
+
onPinch: ({ delta: [d] }) => {
|
|
1392
|
+
bindOutsideChangeHelper.current.triggerChange(
|
|
1393
|
+
({ value, changeValue }) => {
|
|
1394
|
+
// changeValue(d);
|
|
1395
|
+
if (d > 0) {
|
|
1396
|
+
if (value > 8) {
|
|
1397
|
+
changeValue(value + 0.4);
|
|
1398
|
+
} else {
|
|
1399
|
+
changeValue(value + 0.2);
|
|
1400
|
+
}
|
|
1401
|
+
} else if (d < 0) {
|
|
1402
|
+
if (value > 8) {
|
|
1403
|
+
changeValue(value - 0.4);
|
|
1404
|
+
} else {
|
|
1405
|
+
changeValue(value - 0.2);
|
|
1406
|
+
}
|
|
1407
|
+
}
|
|
1408
|
+
}
|
|
1409
|
+
);
|
|
1410
|
+
updateLabelsForInViewFeatures();
|
|
1411
|
+
}
|
|
1412
|
+
};
|
|
1413
|
+
|
|
1414
|
+
return (
|
|
1415
|
+
<PinchHelper {...pinchHandler}>
|
|
1416
|
+
<ResizeSensor onResize={handleResize}>
|
|
1417
|
+
<div
|
|
1418
|
+
style={{
|
|
1419
|
+
height: height || (isPairwise ? "auto" : viewportHeight * 0.88),
|
|
1420
|
+
display: "flex",
|
|
1421
|
+
flexDirection: "column",
|
|
1422
|
+
justifyContent: "space-between",
|
|
1423
|
+
position: "relative",
|
|
1424
|
+
overflowY: "auto",
|
|
1425
|
+
...style
|
|
1426
|
+
// borderTop: "1px solid black"
|
|
1427
|
+
}}
|
|
1428
|
+
className="alignmentView"
|
|
1429
|
+
>
|
|
1430
|
+
<DragDropContext
|
|
1431
|
+
onDragStart={onTrackDragStart}
|
|
1432
|
+
onDragEnd={onTrackDragEnd}
|
|
1433
|
+
>
|
|
1434
|
+
<div
|
|
1435
|
+
style={{
|
|
1436
|
+
display: "flex",
|
|
1437
|
+
flexDirection: "column",
|
|
1438
|
+
position: "relative",
|
|
1439
|
+
overflowY: "auto"
|
|
1440
|
+
}}
|
|
1441
|
+
className="alignmentView-top-container"
|
|
1442
|
+
>
|
|
1443
|
+
<div
|
|
1444
|
+
style={{
|
|
1445
|
+
paddingTop: "3px",
|
|
1446
|
+
paddingBottom: "5px",
|
|
1447
|
+
borderBottom: "1px solid",
|
|
1448
|
+
display: "flex",
|
|
1449
|
+
minHeight: "32px",
|
|
1450
|
+
width: "100%",
|
|
1451
|
+
flexWrap: "nowrap",
|
|
1452
|
+
flexDirection: "row",
|
|
1453
|
+
flex: "0 0 auto"
|
|
1454
|
+
}}
|
|
1455
|
+
className="ve-alignment-top-bar"
|
|
1456
|
+
>
|
|
1457
|
+
{additionalTopLeftEl}
|
|
1458
|
+
{handleBackButtonClicked && (
|
|
1459
|
+
<Tooltip content="Back to Pairwise Alignment Overview">
|
|
1460
|
+
<Button
|
|
1461
|
+
icon="arrow-left"
|
|
1462
|
+
onClick={() => {
|
|
1463
|
+
handleBackButtonClicked();
|
|
1464
|
+
caretPositionUpdate(-1);
|
|
1465
|
+
}}
|
|
1466
|
+
small
|
|
1467
|
+
intent={Intent.PRIMARY}
|
|
1468
|
+
minimal
|
|
1469
|
+
style={{ marginRight: 10 }}
|
|
1470
|
+
className="alignmentViewBackButton"
|
|
1471
|
+
/>
|
|
1472
|
+
</Tooltip>
|
|
1473
|
+
)}
|
|
1474
|
+
|
|
1475
|
+
<div style={{ display: "flex" }}>
|
|
1476
|
+
<EditableText
|
|
1477
|
+
disabled={!handleAlignmentRename}
|
|
1478
|
+
onChange={v => {
|
|
1479
|
+
setAlignmentName(v);
|
|
1480
|
+
}}
|
|
1481
|
+
maxLength={399} //stop the name from being tooo long
|
|
1482
|
+
value={alignmentName}
|
|
1483
|
+
onConfirm={async v => {
|
|
1484
|
+
if (!v) {
|
|
1485
|
+
setAlignmentName(_alignmentName);
|
|
1486
|
+
return;
|
|
1487
|
+
}
|
|
1488
|
+
if (v === _alignmentName) {
|
|
1489
|
+
return; //already saved this name
|
|
1490
|
+
}
|
|
1491
|
+
setSaveMessage("Alignment Renaming..");
|
|
1492
|
+
setSaveMessageLoading(true);
|
|
1493
|
+
await handleAlignmentRename(v, props);
|
|
1494
|
+
setSaveMessage("Rename Successful");
|
|
1495
|
+
setSaveMessageLoading(false);
|
|
1496
|
+
setTimeout(() => {
|
|
1497
|
+
setSaveMessage(undefined);
|
|
1498
|
+
setSaveMessageLoading(false);
|
|
1499
|
+
}, 5000);
|
|
1500
|
+
}}
|
|
1501
|
+
selectAllOnFocus={true}
|
|
1502
|
+
className="veAlignmentName"
|
|
1503
|
+
/>
|
|
1504
|
+
|
|
1505
|
+
<div
|
|
1506
|
+
className="veAlignmentType"
|
|
1507
|
+
style={{
|
|
1508
|
+
paddingTop: "3px",
|
|
1509
|
+
fontSize: "14px",
|
|
1510
|
+
color: "grey",
|
|
1511
|
+
maxWidth: "300px",
|
|
1512
|
+
overflow: "hidden",
|
|
1513
|
+
textOverflow: "ellipsis",
|
|
1514
|
+
whiteSpace: "nowrap"
|
|
1515
|
+
}}
|
|
1516
|
+
data-title={alignmentType || "Unknown Alignment Type"}
|
|
1517
|
+
>
|
|
1518
|
+
{alignmentType || "Unknown Alignment Type"}
|
|
1519
|
+
</div>
|
|
1520
|
+
</div>
|
|
1521
|
+
|
|
1522
|
+
{unmappedSeqs && (
|
|
1523
|
+
<InfoHelper
|
|
1524
|
+
size={20}
|
|
1525
|
+
content={
|
|
1526
|
+
<div>
|
|
1527
|
+
This alignment had sequences that did not map to the
|
|
1528
|
+
template sequence:
|
|
1529
|
+
{unmappedSeqs.map(({ sequenceData }, i) => (
|
|
1530
|
+
<div key={i}>{sequenceData.name}</div>
|
|
1531
|
+
))}
|
|
1532
|
+
</div>
|
|
1533
|
+
}
|
|
1534
|
+
intent="warning"
|
|
1535
|
+
icon="warning-sign"
|
|
1536
|
+
/>
|
|
1537
|
+
)}
|
|
1538
|
+
{!isInPairwiseOverviewView && (
|
|
1539
|
+
<UncontrolledSliderWithPlusMinusBtns
|
|
1540
|
+
noWraparound
|
|
1541
|
+
bindOutsideChangeHelper={bindOutsideChangeHelper.current}
|
|
1542
|
+
onClick={() => {
|
|
1543
|
+
setTimeout(scrollToCaret, 0);
|
|
1544
|
+
}}
|
|
1545
|
+
minCharWidth={getMinCharWidth()}
|
|
1546
|
+
onChange={async zoomLvl => {
|
|
1547
|
+
isZooming.current = true;
|
|
1548
|
+
setTimeout(() => {
|
|
1549
|
+
isZooming.current = false;
|
|
1550
|
+
}, 10);
|
|
1551
|
+
// zoomLvl is in the range of 0 to 10
|
|
1552
|
+
const minCharWidth = getMinCharWidth();
|
|
1553
|
+
const scaleFactor = Math.pow(12 / minCharWidth, 1 / 10);
|
|
1554
|
+
const newCharWidth =
|
|
1555
|
+
minCharWidth * Math.pow(scaleFactor, zoomLvl);
|
|
1556
|
+
await setCharWidthInLinearView({
|
|
1557
|
+
charWidthInLinearView: newCharWidth
|
|
1558
|
+
});
|
|
1559
|
+
await scrollToCaret();
|
|
1560
|
+
await updateLabelsForInViewFeatures({
|
|
1561
|
+
rectElement: ".alignmentHolder"
|
|
1562
|
+
});
|
|
1563
|
+
}}
|
|
1564
|
+
coerceInitialValue={coerceInitialValue}
|
|
1565
|
+
title="Adjust Zoom Level"
|
|
1566
|
+
style={{ paddingTop: "4px", width: 100 }}
|
|
1567
|
+
className="veZoomAlignmentSlider ove-slider"
|
|
1568
|
+
labelRenderer={false}
|
|
1569
|
+
initialValue={charWidth}
|
|
1570
|
+
stepSize={0.05} //was 0.01
|
|
1571
|
+
max={10}
|
|
1572
|
+
min={0}
|
|
1573
|
+
clickStepSize={0.5}
|
|
1574
|
+
/>
|
|
1575
|
+
)}
|
|
1576
|
+
{!noVisibilityOptions && !isInPairwiseOverviewView && (
|
|
1577
|
+
<AlignmentVisibilityTool
|
|
1578
|
+
currentPairwiseAlignmentIndex={
|
|
1579
|
+
currentPairwiseAlignmentIndex
|
|
1580
|
+
}
|
|
1581
|
+
{...alignmentVisibilityToolOptions}
|
|
1582
|
+
/>
|
|
1583
|
+
)}
|
|
1584
|
+
{updateAlignmentSortOrder && !isInPairwiseOverviewView && (
|
|
1585
|
+
<Popover
|
|
1586
|
+
minimal
|
|
1587
|
+
content={
|
|
1588
|
+
<Menu>
|
|
1589
|
+
<MenuItem
|
|
1590
|
+
active={true || alignmentSortOrder}
|
|
1591
|
+
onClick={() => {
|
|
1592
|
+
updateAlignmentSortOrder("Position");
|
|
1593
|
+
}}
|
|
1594
|
+
text="Position"
|
|
1595
|
+
/>
|
|
1596
|
+
<MenuItem
|
|
1597
|
+
active={false || alignmentSortOrder}
|
|
1598
|
+
onClick={() => {
|
|
1599
|
+
updateAlignmentSortOrder("Alphabetical");
|
|
1600
|
+
}}
|
|
1601
|
+
text="Alphabetical"
|
|
1602
|
+
/>
|
|
1603
|
+
</Menu>
|
|
1604
|
+
}
|
|
1605
|
+
target={
|
|
1606
|
+
<Button
|
|
1607
|
+
small
|
|
1608
|
+
text="Sort Order"
|
|
1609
|
+
rightIcon="caret-down"
|
|
1610
|
+
icon="sort"
|
|
1611
|
+
/>
|
|
1612
|
+
}
|
|
1613
|
+
/>
|
|
1614
|
+
)}
|
|
1615
|
+
{additionalTopEl}
|
|
1616
|
+
{saveMessage && (
|
|
1617
|
+
<div
|
|
1618
|
+
className="ove-menu-toast"
|
|
1619
|
+
style={{
|
|
1620
|
+
display: "flex",
|
|
1621
|
+
alignItems: "center",
|
|
1622
|
+
marginLeft: "auto",
|
|
1623
|
+
marginRight: 10
|
|
1624
|
+
}}
|
|
1625
|
+
>
|
|
1626
|
+
{saveMessageLoading ? (
|
|
1627
|
+
<div>
|
|
1628
|
+
<Spinner size={15}></Spinner>
|
|
1629
|
+
</div>
|
|
1630
|
+
) : (
|
|
1631
|
+
<Icon icon="tick-circle" intent="success"></Icon>
|
|
1632
|
+
)}{" "}
|
|
1633
|
+
|
|
1634
|
+
{saveMessage}
|
|
1635
|
+
</div>
|
|
1636
|
+
)}
|
|
1637
|
+
</div>
|
|
1638
|
+
{hasTemplate ? (
|
|
1639
|
+
<>
|
|
1640
|
+
<div className="alignmentTrackFixedToTop">
|
|
1641
|
+
{getTrackVis([firstTrack], true, alignmentTracks)}
|
|
1642
|
+
</div>
|
|
1643
|
+
{getTrackVis(otherTracks, false, alignmentTracks)}
|
|
1644
|
+
</>
|
|
1645
|
+
) : (
|
|
1646
|
+
getTrackVis(alignmentTracks, false, alignmentTracks)
|
|
1647
|
+
)}
|
|
1648
|
+
</div>
|
|
1649
|
+
</DragDropContext>
|
|
1650
|
+
{!isInPairwiseOverviewView && (
|
|
1651
|
+
<div
|
|
1652
|
+
className="alignmentViewBottomBar"
|
|
1653
|
+
style={{
|
|
1654
|
+
// flexGrow: 1,
|
|
1655
|
+
// minHeight: "-webkit-min-content", //https://stackoverflow.com/questions/28029736/how-to-prevent-a-flex-item-from-shrinking-smaller-than-its-content
|
|
1656
|
+
maxHeight: 210,
|
|
1657
|
+
marginTop: 4,
|
|
1658
|
+
paddingTop: 4,
|
|
1659
|
+
borderTop: "1px solid lightgrey",
|
|
1660
|
+
display: "flex"
|
|
1661
|
+
}}
|
|
1662
|
+
>
|
|
1663
|
+
<Minimap
|
|
1664
|
+
selectionLayerComp={
|
|
1665
|
+
<>
|
|
1666
|
+
<PerformantSelectionLayer
|
|
1667
|
+
is
|
|
1668
|
+
hideCarets
|
|
1669
|
+
className="veAlignmentSelectionLayer veMinimapSelectionLayer"
|
|
1670
|
+
easyStore={easyStore.current}
|
|
1671
|
+
sequenceLength={maxLength}
|
|
1672
|
+
charWidth={getMinCharWidth(true)}
|
|
1673
|
+
row={{ start: 0, end: maxLength - 1 }}
|
|
1674
|
+
/>
|
|
1675
|
+
<PerformantCaret
|
|
1676
|
+
style={{
|
|
1677
|
+
opacity: 0.2
|
|
1678
|
+
}}
|
|
1679
|
+
className="veAlignmentSelectionLayer veMinimapSelectionLayer"
|
|
1680
|
+
sequenceLength={maxLength}
|
|
1681
|
+
charWidth={getMinCharWidth(true)}
|
|
1682
|
+
row={{ start: 0, end: maxLength - 1 }}
|
|
1683
|
+
easyStore={easyStore.current}
|
|
1684
|
+
/>
|
|
1685
|
+
</>
|
|
1686
|
+
}
|
|
1687
|
+
alignmentTracks={alignmentTracks}
|
|
1688
|
+
dimensions={{
|
|
1689
|
+
width: Math.max(width, 10) || 10
|
|
1690
|
+
}}
|
|
1691
|
+
nameDivOffsetPercent={0}
|
|
1692
|
+
scrollYToTrack={scrollYToTrack}
|
|
1693
|
+
onSizeAdjust={onMinimapSizeAdjust}
|
|
1694
|
+
minSliderSize={minSliderSize}
|
|
1695
|
+
laneHeight={
|
|
1696
|
+
minimapLaneHeight || (alignmentTracks.length > 5 ? 10 : 17)
|
|
1697
|
+
}
|
|
1698
|
+
laneSpacing={
|
|
1699
|
+
minimapLaneSpacing || (alignmentTracks.length > 5 ? 2 : 1)
|
|
1700
|
+
}
|
|
1701
|
+
easyStore={easyStore.current}
|
|
1702
|
+
numBpsShownInLinearView={getNumBpsShownInLinearView()}
|
|
1703
|
+
scrollAlignmentView={false}
|
|
1704
|
+
onMinimapScrollX={scrollAlignmentToPercent}
|
|
1705
|
+
/>
|
|
1706
|
+
</div>
|
|
1707
|
+
)}
|
|
1708
|
+
<GlobalDialog
|
|
1709
|
+
// {...pickedUserDefinedHandlersAndOpts}
|
|
1710
|
+
// dialogOverrides={pick(this.props, [
|
|
1711
|
+
// "AddOrEditFeatureDialogOverride",
|
|
1712
|
+
// "AddOrEditPartDialogOverride",
|
|
1713
|
+
// "AddOrEditPrimerDialogOverride"
|
|
1714
|
+
// ])}
|
|
1715
|
+
/>
|
|
1716
|
+
</div>
|
|
1717
|
+
</ResizeSensor>
|
|
1718
|
+
</PinchHelper>
|
|
1719
|
+
);
|
|
1720
|
+
};
|
|
1843
1721
|
|
|
1844
1722
|
export default compose(
|
|
1845
1723
|
withStore,
|