@teselagen/ove 0.0.14 → 0.0.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (362) hide show
  1. package/index.umd.js +164777 -135861
  2. package/package.json +78 -2
  3. package/src/AlignmentTool/index.js +16 -0
  4. package/src/AlignmentView/AlignmentVisibilityTool.js +105 -0
  5. package/src/AlignmentView/EditTrackNameDialog.js +34 -0
  6. package/src/AlignmentView/HorizontalPanelDragHandle.js +35 -0
  7. package/src/AlignmentView/Minimap.js +520 -0
  8. package/src/AlignmentView/Mismatches.js +134 -0
  9. package/src/AlignmentView/PairwiseAlignmentView.js +68 -0
  10. package/src/AlignmentView/PerformantSelectionLayer.js +32 -0
  11. package/src/AlignmentView/coerceInitialValue.js +7 -0
  12. package/src/AlignmentView/getGapMap.js +12 -0
  13. package/src/AlignmentView/getGaps.js +27 -0
  14. package/src/AlignmentView/getPairwiseOverviewLinearViewOptions.js +38 -0
  15. package/src/AlignmentView/getTrackFromEvent.js +25 -0
  16. package/src/AlignmentView/index.js +2058 -0
  17. package/src/AlignmentView/isTargetWithinEl.js +6 -0
  18. package/src/AlignmentView/style.css +100 -0
  19. package/src/AlignmentView/updateTrackHelper.js +58 -0
  20. package/src/AutoAnnotate.js +500 -0
  21. package/src/AutoAnnotateBpMatchingDialog.js +208 -0
  22. package/src/CircularView/Axis.js +40 -0
  23. package/src/CircularView/AxisNumbers.js +35 -0
  24. package/src/CircularView/Caret.js +63 -0
  25. package/src/CircularView/CircularDnaSequence.js +73 -0
  26. package/src/CircularView/CircularZoomMinimap.js +16 -0
  27. package/src/CircularView/Cutsite.js +18 -0
  28. package/src/CircularView/Cutsites.js +113 -0
  29. package/src/CircularView/DeletionLayer.js +28 -0
  30. package/src/CircularView/Feature.js +83 -0
  31. package/src/CircularView/Labels/index.js +536 -0
  32. package/src/CircularView/Labels/relaxLabelAngles.js +157 -0
  33. package/src/CircularView/Labels/relaxLabels_DEPRECATED.js +105 -0
  34. package/src/CircularView/Labels/style.css +55 -0
  35. package/src/CircularView/Orf.js +25 -0
  36. package/src/CircularView/Part.js +34 -0
  37. package/src/CircularView/PositionAnnotationOnCircle.js +26 -0
  38. package/src/CircularView/Primer.js +41 -0
  39. package/src/CircularView/RotateCircularViewSlider.js +82 -0
  40. package/src/CircularView/SelectionLayer.js +132 -0
  41. package/src/CircularView/VeTopRightContainer.js +12 -0
  42. package/src/CircularView/ZoomCircularViewSlider.js +62 -0
  43. package/src/CircularView/drawAnnotations.js +433 -0
  44. package/src/CircularView/drawDirectedPiePiece.js +142 -0
  45. package/src/CircularView/getAngleForPositionMidpoint.js +3 -0
  46. package/src/CircularView/getInternalLabel.js +40 -0
  47. package/src/CircularView/getRangeAnglesSpecial.js +12 -0
  48. package/src/CircularView/getYOffset.js +15 -0
  49. package/src/CircularView/index.d.ts +20 -0
  50. package/src/CircularView/index.js +930 -0
  51. package/src/CircularView/normalizeAngle.js +3 -0
  52. package/src/CircularView/normalizeAngleRange.js +9 -0
  53. package/src/CircularView/positionCutsites.js +6 -0
  54. package/src/CircularView/shouldFlipText.js +4 -0
  55. package/src/CircularView/style.css +47 -0
  56. package/src/CircularView/utils/polarToSpecialCartesian.js +7 -0
  57. package/src/CreateAnnotationsPage.js +96 -0
  58. package/src/CreateCustomEnzyme/index.js +337 -0
  59. package/src/CreateCustomEnzyme/style.css +100 -0
  60. package/src/CutsiteFilter/AdditionalCutsiteInfoDialog.js +599 -0
  61. package/src/CutsiteFilter/index.js +408 -0
  62. package/src/CutsiteFilter/style.css +23 -0
  63. package/src/CutsiteFilter/withRestrictionEnzymes.js +15 -0
  64. package/src/DigestTool/AddLaddersDialog.js +82 -0
  65. package/src/DigestTool/DigestTool.js +223 -0
  66. package/src/DigestTool/Ladder.css +20 -0
  67. package/src/DigestTool/Ladder.js +303 -0
  68. package/src/DigestTool/createFragmentLines.js +120 -0
  69. package/src/DigestTool/ladderDefaults.js +26 -0
  70. package/src/DigestTool/ruler.css +89 -0
  71. package/src/Editor/CommandHotkeyHandler.js +44 -0
  72. package/src/Editor/DropHandler.css +21 -0
  73. package/src/Editor/DropHandler.js +64 -0
  74. package/src/Editor/FillWindow.js +46 -0
  75. package/src/Editor/darkmode.css +98 -0
  76. package/src/Editor/index.js +1005 -0
  77. package/src/Editor/style.css +235 -0
  78. package/src/Editor/userDefinedHandlersAndOpts.js +56 -0
  79. package/src/EnzymeViewer/index.js +81 -0
  80. package/src/EnzymeViewer/style.css +6 -0
  81. package/src/FindBar/index.js +411 -0
  82. package/src/FindBar/style.css +46 -0
  83. package/src/GlobalDialog.js +66 -0
  84. package/src/GlobalDialogUtils.js +85 -0
  85. package/src/LinearView/SequenceName.js +15 -0
  86. package/src/LinearView/ZoomLinearView.js +47 -0
  87. package/src/LinearView/index.js +374 -0
  88. package/src/LinearView/style.css +12 -0
  89. package/src/ManageEnzymes/index.js +326 -0
  90. package/src/ManageEnzymes/style.css +100 -0
  91. package/src/MenuBar/defaultConfig.js +149 -0
  92. package/src/MenuBar/index.js +98 -0
  93. package/src/MenuBar/viewSubmenu.js +479 -0
  94. package/src/PCRTool/PCRTool.js +173 -0
  95. package/src/Reflex/Browser.js +107 -0
  96. package/src/Reflex/ReflexContainer.js +802 -0
  97. package/src/Reflex/ReflexElement.js +160 -0
  98. package/src/Reflex/ReflexEvents.js +77 -0
  99. package/src/Reflex/ReflexSplitter.js +205 -0
  100. package/src/Reflex/index.js +5 -0
  101. package/src/Reflex/reflex-styles.css +128 -0
  102. package/src/Reflex/reflex-styles.css.map +9 -0
  103. package/src/RowItem/AnnotationContainerHolder.js +20 -0
  104. package/src/RowItem/AnnotationPositioner.js +27 -0
  105. package/src/RowItem/Axis.js +149 -0
  106. package/src/RowItem/Caret/index.js +64 -0
  107. package/src/RowItem/Caret/style.css +8 -0
  108. package/src/RowItem/Chromatograms/Chromatogram.js +289 -0
  109. package/src/RowItem/CutsiteSelectionLayers.js +47 -0
  110. package/src/RowItem/Cutsites.js +271 -0
  111. package/src/RowItem/DeletionLayers/index.js +113 -0
  112. package/src/RowItem/DeletionLayers/style.css +5 -0
  113. package/src/RowItem/Labels.js +327 -0
  114. package/src/RowItem/Orf.js +109 -0
  115. package/src/RowItem/Orfs.js +35 -0
  116. package/src/RowItem/ReplacementLayers/style.css +5 -0
  117. package/src/RowItem/SelectionLayer/index.js +184 -0
  118. package/src/RowItem/SelectionLayer/style.css +21 -0
  119. package/src/RowItem/Sequence.js +269 -0
  120. package/src/RowItem/StackedAnnotations/PointedAnnotation.js +347 -0
  121. package/src/RowItem/StackedAnnotations/getStructuredBases.js +97 -0
  122. package/src/RowItem/StackedAnnotations/index.js +182 -0
  123. package/src/RowItem/StackedAnnotations/primerBases.js +218 -0
  124. package/src/RowItem/StackedAnnotations/style.css +14 -0
  125. package/src/RowItem/Translations/AASliver.js +190 -0
  126. package/src/RowItem/Translations/Translation.js +162 -0
  127. package/src/RowItem/Translations/index.js +54 -0
  128. package/src/RowItem/Translations/style.css +3 -0
  129. package/src/RowItem/constants.js +3 -0
  130. package/src/RowItem/getCutsiteLabelHeights.js +56 -0
  131. package/src/RowItem/getXStartAndWidthFromNonCircularRange.js +12 -0
  132. package/src/RowItem/getXStartAndWidthOfRangeWrtRow.js +27 -0
  133. package/src/RowItem/getXStartAndWidthOfRowAnnotation.js +19 -0
  134. package/src/RowItem/index.js +647 -0
  135. package/src/RowItem/partOverhangs.js +6 -0
  136. package/src/RowItem/style.css +103 -0
  137. package/src/RowItem/utils.js +32 -0
  138. package/src/RowView/estimateRowHeight.js +184 -0
  139. package/src/RowView/index.d.ts +10 -0
  140. package/src/RowView/index.js +554 -0
  141. package/src/RowView/style.css +12 -0
  142. package/src/SimpleCircularOrLinearView.js +379 -0
  143. package/src/SimpleOligoPreview.js +39 -0
  144. package/src/StatusBar/MeltingTemp.js +81 -0
  145. package/src/StatusBar/index.js +275 -0
  146. package/src/StatusBar/style.css +38 -0
  147. package/src/ToolBar/ToolbarItem.js +194 -0
  148. package/src/ToolBar/alignmentTool.js +503 -0
  149. package/src/ToolBar/array_move.js +10 -0
  150. package/src/ToolBar/cutsiteTool.js +88 -0
  151. package/src/ToolBar/downloadTool.js +38 -0
  152. package/src/ToolBar/editTool.js +26 -0
  153. package/src/ToolBar/featureTool.js +34 -0
  154. package/src/ToolBar/findTool.js +2 -0
  155. package/src/ToolBar/importTool.js +27 -0
  156. package/src/ToolBar/index.js +231 -0
  157. package/src/ToolBar/inlineFindTool.js +38 -0
  158. package/src/ToolBar/oligoTool.js +30 -0
  159. package/src/ToolBar/orfTool.js +141 -0
  160. package/src/ToolBar/partTool.js +47 -0
  161. package/src/ToolBar/printTool.js +31 -0
  162. package/src/ToolBar/redoTool.js +30 -0
  163. package/src/ToolBar/saveTool.js +48 -0
  164. package/src/ToolBar/style.css +138 -0
  165. package/src/ToolBar/undoTool.js +30 -0
  166. package/src/ToolBar/veToolbarIcons/find.png +0 -0
  167. package/src/ToolBar/veToolbarIcons/fullscreen.png +0 -0
  168. package/src/ToolBar/veToolbarIcons/linear.png +0 -0
  169. package/src/ToolBar/veToolbarIcons/pie.png +0 -0
  170. package/src/ToolBar/veToolbarIcons/print.png +0 -0
  171. package/src/ToolBar/veToolbarIcons/save.png +0 -0
  172. package/src/ToolBar/veToolbarIcons/show_cut_sites.png +0 -0
  173. package/src/ToolBar/veToolbarIcons/show_features.png +0 -0
  174. package/src/ToolBar/veToolbarIcons/show_orfs.png +0 -0
  175. package/src/ToolBar/veToolbarIcons/show_primers.png +0 -0
  176. package/src/ToolBar/veToolbarIcons/toggle_views.svg +1 -0
  177. package/src/ToolBar/versionHistoryTool.js +20 -0
  178. package/src/ToolBar/visibilityTool.js +39 -0
  179. package/src/VersionHistoryView/index.js +215 -0
  180. package/src/addAlignment.js +6 -0
  181. package/src/commands/getOveHotkeyDefs.js +12 -0
  182. package/src/commands/index.js +1585 -0
  183. package/src/constants/constants.js +2 -0
  184. package/src/constants/dnaToColor.js +17 -0
  185. package/src/constants/draggableClassnames.js +5 -0
  186. package/src/constants/findToolConstants.js +1 -0
  187. package/src/constants/orfFrameToColorMap.js +10 -0
  188. package/src/constants/rowviewContants.js +3 -0
  189. package/src/constants/specialCutsiteFilterOptions.js +22 -0
  190. package/src/constants.js +1 -0
  191. package/src/createVectorEditor/index.js +138 -0
  192. package/src/createVectorEditor/makeStore.js +34 -0
  193. package/src/fileUtils.js +103 -0
  194. package/src/helperComponents/AddOrEditAnnotationDialog/index.js +711 -0
  195. package/src/helperComponents/AddOrEditAnnotationDialog/style.css +11 -0
  196. package/src/helperComponents/AddOrEditFeatureDialog/index.js +58 -0
  197. package/src/helperComponents/AddOrEditPartDialog/index.js +101 -0
  198. package/src/helperComponents/AddOrEditPrimerDialog/EditCaretPosition.js +234 -0
  199. package/src/helperComponents/AddOrEditPrimerDialog/index.js +329 -0
  200. package/src/helperComponents/AddOrEditPrimerDialog/style.css +41 -0
  201. package/src/helperComponents/EnzymesDialog/index.js +904 -0
  202. package/src/helperComponents/EnzymesDialog/style.css +21 -0
  203. package/src/helperComponents/GoToDialog.js +21 -0
  204. package/src/helperComponents/MergeFeaturesDialog/index.js +253 -0
  205. package/src/helperComponents/MergeFeaturesDialog/style.css +3 -0
  206. package/src/helperComponents/MultipleSeqsDetectedOnImportDialog.js +74 -0
  207. package/src/helperComponents/PinchHelper/PinchHelper.js +24 -0
  208. package/src/helperComponents/PrintDialog/index.js +396 -0
  209. package/src/helperComponents/PrintDialog/style.css +4 -0
  210. package/src/helperComponents/PropertiesDialog/ColorPicker.js +30 -0
  211. package/src/helperComponents/PropertiesDialog/CutsiteProperties.js +185 -0
  212. package/src/helperComponents/PropertiesDialog/FeatureProperties.js +6 -0
  213. package/src/helperComponents/PropertiesDialog/GenbankView.js +74 -0
  214. package/src/helperComponents/PropertiesDialog/GeneralProperties.js +140 -0
  215. package/src/helperComponents/PropertiesDialog/GenericAnnotationProperties.js +406 -0
  216. package/src/helperComponents/PropertiesDialog/OrfProperties.js +117 -0
  217. package/src/helperComponents/PropertiesDialog/PartProperties.js +9 -0
  218. package/src/helperComponents/PropertiesDialog/PrimerProperties.js +19 -0
  219. package/src/helperComponents/PropertiesDialog/SingleEnzymeCutsiteInfo.js +131 -0
  220. package/src/helperComponents/PropertiesDialog/TranslationProperties.js +149 -0
  221. package/src/helperComponents/PropertiesDialog/index.js +166 -0
  222. package/src/helperComponents/PropertiesDialog/style.css +68 -0
  223. package/src/helperComponents/PropertiesDialog/typeField.js +24 -0
  224. package/src/helperComponents/PropertiesDialog/utils.js +37 -0
  225. package/src/helperComponents/RemoveDuplicates/index.js +194 -0
  226. package/src/helperComponents/RenameSequenceDialog.js +7 -0
  227. package/src/helperComponents/SelectDialog.js +150 -0
  228. package/src/helperComponents/UncontrolledSliderWithPlusMinusBtns.css +5 -0
  229. package/src/helperComponents/UncontrolledSliderWithPlusMinusBtns.js +134 -0
  230. package/src/helperComponents/VeWarning/index.js +22 -0
  231. package/src/helperComponents/VeWarning/style.css +10 -0
  232. package/src/helperComponents/createSimpleDialog.js +89 -0
  233. package/src/helperComponents/partTagSearch.js +72 -0
  234. package/src/helperComponents/simpleDialog.css +13 -0
  235. package/src/helperComponents/withHover.js +90 -0
  236. package/src/index.js +60 -0
  237. package/src/redux/alignments.js +373 -0
  238. package/src/redux/annotationLabelVisibility.js +53 -0
  239. package/src/redux/annotationVisibility.js +196 -0
  240. package/src/redux/annotationsToSupport.js +104 -0
  241. package/src/redux/caretPosition.js +27 -0
  242. package/src/redux/charWidth.js +22 -0
  243. package/src/redux/copyOptions.js +34 -0
  244. package/src/redux/createYourOwnEnzyme.js +39 -0
  245. package/src/redux/deletionLayers.js +36 -0
  246. package/src/redux/digestTool.js +34 -0
  247. package/src/redux/featureLengthsToHide.js +27 -0
  248. package/src/redux/findTool.js +79 -0
  249. package/src/redux/frameTranslations.js +52 -0
  250. package/src/redux/hoveredAnnotation.js +24 -0
  251. package/src/redux/index.js +196 -0
  252. package/src/redux/labelLineIntensity.js +25 -0
  253. package/src/redux/labelSize.js +23 -0
  254. package/src/redux/lastSavedId.js +20 -0
  255. package/src/redux/middleware.js +112 -0
  256. package/src/redux/minimumOrfSize.js +24 -0
  257. package/src/redux/modalActions.js +3 -0
  258. package/src/redux/panelsShown.js +273 -0
  259. package/src/redux/partLengthsToHide.js +23 -0
  260. package/src/redux/primerLengthsToHide.js +27 -0
  261. package/src/redux/propertiesTool.js +40 -0
  262. package/src/redux/readOnly.js +28 -0
  263. package/src/redux/replacementLayers.js +36 -0
  264. package/src/redux/restrictionEnzymes.js +52 -0
  265. package/src/redux/selectedAnnotations.js +89 -0
  266. package/src/redux/selectedPartTags.js +21 -0
  267. package/src/redux/selectionLayer.js +46 -0
  268. package/src/redux/sequenceData/circular.js +19 -0
  269. package/src/redux/sequenceData/description.js +21 -0
  270. package/src/redux/sequenceData/features.js +19 -0
  271. package/src/redux/sequenceData/index.js +81 -0
  272. package/src/redux/sequenceData/lineageLines.js +11 -0
  273. package/src/redux/sequenceData/materiallyAvailable.js +19 -0
  274. package/src/redux/sequenceData/name.js +19 -0
  275. package/src/redux/sequenceData/parts.js +19 -0
  276. package/src/redux/sequenceData/primers.js +19 -0
  277. package/src/redux/sequenceData/sequence.js +12 -0
  278. package/src/redux/sequenceData/sharedActionCreators.js +0 -0
  279. package/src/redux/sequenceData/translations.js +20 -0
  280. package/src/redux/sequenceData/upsertDeleteActionGenerator.js +31 -0
  281. package/src/redux/sequenceDataHistory.js +43 -0
  282. package/src/redux/showGCContent.js +23 -0
  283. package/src/redux/toolBar.js +25 -0
  284. package/src/redux/uppercaseSequenceMapFont.js +25 -0
  285. package/src/redux/useAdditionalOrfStartCodons.js +24 -0
  286. package/src/redux/utils/addDashesForMatchStartAndEndForTracks/index.js +71 -0
  287. package/src/redux/utils/addMetaToActionCreators.js +12 -0
  288. package/src/redux/utils/createMergedDefaultStateReducer.js +30 -0
  289. package/src/redux/utils/createMetaAction.js +12 -0
  290. package/src/redux/versionHistory.js +27 -0
  291. package/src/selectors/annotationLabelVisibility.js +2 -0
  292. package/src/selectors/annotationSearchSelector.js +24 -0
  293. package/src/selectors/cdsFeaturesSelector.js +9 -0
  294. package/src/selectors/circularSelector.js +4 -0
  295. package/src/selectors/cutsiteLabelColorSelector.js +6 -0
  296. package/src/selectors/cutsitesByRangeSelector.js +5 -0
  297. package/src/selectors/cutsitesSelector.js +61 -0
  298. package/src/selectors/editorSelector.js +2 -0
  299. package/src/selectors/featuresSelector.js +8 -0
  300. package/src/selectors/filteredCutsitesSelector.js +137 -0
  301. package/src/selectors/filteredFeaturesSelector.js +32 -0
  302. package/src/selectors/filteredPartsSelector.js +57 -0
  303. package/src/selectors/filteredPrimersSelector.js +27 -0
  304. package/src/selectors/filteredRestrictionEnzymesSelector.js +1 -0
  305. package/src/selectors/getAdditionalEnzymesSelector.js +46 -0
  306. package/src/selectors/index.js +41 -0
  307. package/src/selectors/isEnzymeFilterAndSelector.js +1 -0
  308. package/src/selectors/minimumOrfSizeSelector.js +2 -0
  309. package/src/selectors/orfsSelector.js +15 -0
  310. package/src/selectors/partsSelector.js +8 -0
  311. package/src/selectors/primersSelector.js +8 -0
  312. package/src/selectors/restrictionEnzymesSelector.js +34 -0
  313. package/src/selectors/searchLayersSelector.js +71 -0
  314. package/src/selectors/selectedAnnotationsSelector.js +1 -0
  315. package/src/selectors/selectedCutsitesSelector.js +21 -0
  316. package/src/selectors/sequenceDataSelector.js +2 -0
  317. package/src/selectors/sequenceLengthSelector.js +5 -0
  318. package/src/selectors/sequenceSelector.js +4 -0
  319. package/src/selectors/tagsToBoldSelector.js +2 -0
  320. package/src/selectors/translationSearchMatchesSelector.js +14 -0
  321. package/src/selectors/translationsRawSelector.js +8 -0
  322. package/src/selectors/translationsSelector.js +137 -0
  323. package/src/style.css +82 -0
  324. package/src/updateEditor.js +198 -0
  325. package/src/utils/PassThrough.js +3 -0
  326. package/src/utils/addWrappedAddons.js +20 -0
  327. package/src/utils/annotationTypes.js +37 -0
  328. package/src/utils/arrayUtils.js +19 -0
  329. package/src/utils/calculateTickMarkPositionsForGivenRange.js +47 -0
  330. package/src/utils/cleanSequenceData_DEPRECATED/arrayToObjWithIds.js +17 -0
  331. package/src/utils/combineReducersDontIgnoreKeys.js +12 -0
  332. package/src/utils/commandUtils.js +18 -0
  333. package/src/utils/editorUtils.js +223 -0
  334. package/src/utils/getAnnotationClassnames.js +12 -0
  335. package/src/utils/getAnnotationNameAndStartStopString.js +61 -0
  336. package/src/utils/getVisibleStartEnd.js +7 -0
  337. package/src/utils/massageTickSpacing.js +19 -0
  338. package/src/utils/onlyUpdateForKeysDeep.js +31 -0
  339. package/src/utils/prepareRowData.js +64 -0
  340. package/src/utils/proteinUtils.js +3 -0
  341. package/src/utils/pureNoFunc.js +18 -0
  342. package/src/utils/selectionLayer.js +25 -0
  343. package/src/utils/shouldRerender.js +27 -0
  344. package/src/utils/showFileDialog.js +26 -0
  345. package/src/utils/updateLabelsForInViewFeatures.js +55 -0
  346. package/src/utils/updateLabelsForInViewFeaturesCircView.js +41 -0
  347. package/src/utils/useAAColorType.js +8 -0
  348. package/src/utils/useAnnotationLimits.js +42 -0
  349. package/src/utils/useChromatogramPrefs.js +31 -0
  350. package/src/utils/useLadders.js +6 -0
  351. package/src/utils/useMeltingTemp.js +7 -0
  352. package/src/utils/useTmType.js +10 -0
  353. package/src/withEditorInteractions/Keyboard.js +86 -0
  354. package/src/withEditorInteractions/clickAndDragUtils.js +576 -0
  355. package/src/withEditorInteractions/createSequenceInputPopup.js +296 -0
  356. package/src/withEditorInteractions/createSequenceInputPopupStyle.css +85 -0
  357. package/src/withEditorInteractions/getBpsPerRow.js +19 -0
  358. package/src/withEditorInteractions/index.js +1252 -0
  359. package/src/withEditorInteractions/isElementInViewport.js +29 -0
  360. package/src/withEditorInteractions/moveCaret.js +58 -0
  361. package/src/withEditorProps/index.js +1010 -0
  362. package/index.mjs +0 -193228
@@ -0,0 +1,2058 @@
1
+ import { HorizontalPanelDragHandle } from "./HorizontalPanelDragHandle";
2
+ import {
3
+ DragDropContext,
4
+ Droppable,
5
+ Draggable as DndDraggable
6
+ } from "@hello-pangea/dnd";
7
+ import Clipboard from "clipboard";
8
+ import React from "react";
9
+ import { connect } from "react-redux";
10
+ import {
11
+ Button,
12
+ Intent,
13
+ Popover,
14
+ Menu,
15
+ MenuItem,
16
+ Tooltip,
17
+ Icon,
18
+ Spinner,
19
+ AnchorButton,
20
+ EditableText
21
+ } from "@blueprintjs/core";
22
+ import {
23
+ InfoHelper,
24
+ Loading,
25
+ showContextMenu,
26
+ withStore
27
+ } from "@teselagen/ui";
28
+ import { store } from "@risingstack/react-easy-state";
29
+ import {
30
+ throttle,
31
+ // cloneDeep,
32
+ map,
33
+ some,
34
+ forEach,
35
+ isFunction,
36
+ unset,
37
+ omit
38
+ } from "lodash";
39
+ import { getSequenceDataBetweenRange } from "@teselagen/sequence-utils";
40
+ import ReactList from "@teselagen/react-list";
41
+ import ReactDOM from "react-dom";
42
+
43
+ import { NonReduxEnhancedLinearView } from "../LinearView";
44
+ import Minimap, { getTrimmedRangesToDisplay } from "./Minimap";
45
+ import { compose, branch, renderComponent } from "recompose";
46
+ import AlignmentVisibilityTool from "./AlignmentVisibilityTool";
47
+ import * as alignmentActions from "../redux/alignments";
48
+ import estimateRowHeight from "../RowView/estimateRowHeight";
49
+ import prepareRowData from "../utils/prepareRowData";
50
+ import withEditorProps from "../withEditorProps";
51
+
52
+ import "./style.css";
53
+ import {
54
+ editorDragged,
55
+ editorClicked,
56
+ editorDragStarted,
57
+ updateSelectionOrCaret,
58
+ editorDragStopped
59
+ } from "../withEditorInteractions/clickAndDragUtils";
60
+ import { ResizeSensor } from "@blueprintjs/core";
61
+ import ReactDraggable from "react-draggable";
62
+ import draggableClassnames from "../constants/draggableClassnames";
63
+ import Caret from "../RowItem/Caret";
64
+ import { debounce } from "lodash";
65
+ import { view } from "@risingstack/react-easy-state";
66
+ import { noop } from "lodash";
67
+ import { massageTickSpacing } from "../utils/massageTickSpacing";
68
+ import { getClientX, getClientY } from "../utils/editorUtils";
69
+
70
+ import UncontrolledSliderWithPlusMinusBtns from "../helperComponents/UncontrolledSliderWithPlusMinusBtns";
71
+ import { updateLabelsForInViewFeatures } from "../utils/updateLabelsForInViewFeatures";
72
+
73
+ import PinchHelper from "../helperComponents/PinchHelper/PinchHelper";
74
+ import { showDialog } from "../GlobalDialogUtils";
75
+ import { GlobalDialog } from "../GlobalDialog";
76
+ import { array_move } from "../ToolBar/array_move";
77
+ import classNames from "classnames";
78
+ import { getTrackFromEvent } from "./getTrackFromEvent";
79
+ import { PerformantSelectionLayer } from "./PerformantSelectionLayer";
80
+ import { PairwiseAlignmentView } from "./PairwiseAlignmentView";
81
+ import { updateTrackHelper } from "./updateTrackHelper";
82
+ // import { getGaps } from "./getGaps";
83
+ import { isTargetWithinEl } from "./isTargetWithinEl";
84
+ import { EditTrackNameDialog } from "./EditTrackNameDialog";
85
+ import { coerceInitialValue } from "./coerceInitialValue";
86
+
87
+ let charWidthInLinearViewDefault = 12;
88
+ try {
89
+ const newVal = JSON.parse(
90
+ window.localStorage.getItem("charWidthInLinearViewDefault")
91
+ );
92
+ if (newVal) charWidthInLinearViewDefault = newVal;
93
+ } catch (e) {
94
+ console.error(
95
+ "error setting charWidthInLinearViewDefault from local storage:",
96
+ e
97
+ );
98
+ }
99
+
100
+ export class AlignmentView extends React.Component {
101
+ bindOutsideChangeHelper = {};
102
+ constructor(props) {
103
+ super(props);
104
+ window.scrollAlignmentToPercent = this.scrollAlignmentToPercent;
105
+ if (window.Cypress)
106
+ window.Cypress.scrollAlignmentToPercent = this.scrollAlignmentToPercent;
107
+ this.onShortcutCopy = document.addEventListener(
108
+ "keydown",
109
+ this.handleAlignmentCopy
110
+ );
111
+ }
112
+ getMaxLength = () => {
113
+ const { alignmentTracks } = this.props;
114
+ const { sequenceData = { sequence: "" }, alignmentData } =
115
+ alignmentTracks[0];
116
+ const data = alignmentData || sequenceData;
117
+ return data.noSequence ? data.size : data.sequence.length;
118
+ };
119
+
120
+ getNearestCursorPositionToMouseEvent(rowData, event, callback) {
121
+ this.charWidth = this.getCharWidthInLinearView();
122
+ //loop through all the rendered rows to see if the click event lands in one of them
123
+ let nearestCaretPos = 0;
124
+ const rowDomNode = this.veTracksAndAlignmentHolder;
125
+ const boundingRowRect = rowDomNode.getBoundingClientRect();
126
+ const maxEnd = this.getMaxLength();
127
+ if (
128
+ getClientX(event) - boundingRowRect.left - this.state.nameDivWidth <
129
+ 0
130
+ ) {
131
+ nearestCaretPos = 0;
132
+ } else {
133
+ const clickXPositionRelativeToRowContainer =
134
+ getClientX(event) - boundingRowRect.left - this.state.nameDivWidth;
135
+ const numberOfBPsInFromRowStart = Math.floor(
136
+ (clickXPositionRelativeToRowContainer + this.charWidth / 2) /
137
+ this.charWidth
138
+ );
139
+ nearestCaretPos = numberOfBPsInFromRowStart + 0;
140
+ if (nearestCaretPos > maxEnd + 1) {
141
+ nearestCaretPos = maxEnd + 1;
142
+ }
143
+ }
144
+ if (this.props.sequenceData && this.props.sequenceData.isProtein) {
145
+ nearestCaretPos = Math.round(nearestCaretPos / 3) * 3;
146
+ }
147
+ if (this.props.sequenceLength === 0) nearestCaretPos = 0;
148
+ const callbackVals = {
149
+ updateSelectionOrCaret: this.updateSelectionOrCaret,
150
+ nearestCaretPos,
151
+ sequenceLength: this.getSequenceLength(),
152
+ caretPosition: this.easyStore.caretPosition,
153
+ selectionLayer: this.easyStore.selectionLayer,
154
+ easyStore: this.easyStore,
155
+ caretPositionUpdate: this.caretPositionUpdate,
156
+ selectionLayerUpdate: this.selectionLayerUpdate,
157
+ event,
158
+ doNotWrapOrigin: true,
159
+ shiftHeld: event.shiftKey,
160
+ // caretGrabbed: event.target.className === "cursor",
161
+ selectionStartGrabbed: event.target.classList.contains(
162
+ draggableClassnames.selectionStart
163
+ ),
164
+ selectionEndGrabbed: event.target.classList.contains(
165
+ draggableClassnames.selectionEnd
166
+ )
167
+ };
168
+ callback(callbackVals);
169
+ }
170
+
171
+ componentWillUnmount() {
172
+ if (window.Cypress) {
173
+ delete window.scrollAlignmentToPercent;
174
+ delete window.Cypress.scrollAlignmentToPercent;
175
+ delete window.updateAlignmentSelection;
176
+ delete window.Cypress.updateAlignmentSelection;
177
+ }
178
+ const { removeAlignmentFromRedux, id } = this.props;
179
+ removeAlignmentFromRedux({ id });
180
+ this.onShortcutCopy &&
181
+ document.removeEventListener("keydown", this.handleAlignmentCopy);
182
+ }
183
+ handleAlignmentCopy = (event) => {
184
+ if (
185
+ event.key === "c" &&
186
+ !event.shiftKey &&
187
+ (event.metaKey === true || event.ctrlKey === true)
188
+ ) {
189
+ const input = document.createElement("textarea");
190
+ document.body.appendChild(input);
191
+ const seqDataToCopy = this.getAllAlignmentsFastaText();
192
+ input.value = seqDataToCopy;
193
+ input.select();
194
+ const copySuccess = document.execCommand("copy");
195
+ if (!copySuccess) {
196
+ window.toastr.error("Selection Not Copied");
197
+ } else {
198
+ window.toastr.success("Selection Copied");
199
+ }
200
+ document.body.removeChild(input);
201
+ event.preventDefault();
202
+ }
203
+ };
204
+ getAllAlignmentsFastaText = () => {
205
+ const selectionLayer =
206
+ this.props.store.getState().VectorEditor.__allEditorsOptions.alignments[
207
+ this.props.id
208
+ ].selectionLayer || {};
209
+ const { alignmentTracks } = this.props;
210
+ const seqDataOfAllTracksToCopy = [];
211
+ alignmentTracks.forEach((track) => {
212
+ const seqDataToCopy = getSequenceDataBetweenRange(
213
+ track.alignmentData,
214
+ selectionLayer
215
+ ).sequence;
216
+ seqDataOfAllTracksToCopy.push(
217
+ `>${track.alignmentData.name}\r\n${seqDataToCopy}\r\n`
218
+ );
219
+ });
220
+ return seqDataOfAllTracksToCopy.join("");
221
+ };
222
+ state = {
223
+ alignmentName: this.props.alignmentName,
224
+ isTrackDragging: false,
225
+ charWidthInLinearView: charWidthInLinearViewDefault,
226
+ scrollAlignmentView: false,
227
+ width: 0,
228
+ nameDivWidth: 140
229
+ };
230
+ easyStore = store({
231
+ selectionLayer: { start: -1, end: -1 },
232
+ caretPosition: -1,
233
+ percentScrolled: 0,
234
+ viewportWidth: 400,
235
+ verticalVisibleRange: { start: 0, end: 0 }
236
+ });
237
+
238
+ getMinCharWidth = (noNameDiv) => {
239
+ const toReturn = Math.min(
240
+ Math.max(
241
+ this.state.width - (noNameDiv ? 0 : this.state.nameDivWidth) - 5,
242
+ 1
243
+ ) / this.getSequenceLength(),
244
+ 10
245
+ );
246
+ if (isNaN(toReturn)) return 10;
247
+ return toReturn;
248
+ };
249
+
250
+ getSequenceLength = () => {
251
+ const { alignmentTracks: [template] = [] } = this.props;
252
+ return template.alignmentData.sequence.length || 1;
253
+ };
254
+ async componentDidUpdate(prevProps) {
255
+ if (
256
+ prevProps.scrollPercentageToJumpTo !==
257
+ this.props.scrollPercentageToJumpTo &&
258
+ this.props.scrollPercentageToJumpTo !== undefined
259
+ ) {
260
+ this.scrollAlignmentToPercent(this.props.scrollPercentageToJumpTo);
261
+ }
262
+ //autosave if necessary!
263
+ if (
264
+ this.props.shouldAutosave &&
265
+ prevProps &&
266
+ prevProps.stateTrackingId &&
267
+ this.props.stateTrackingId !== prevProps.stateTrackingId
268
+ ) {
269
+ this.setState({ saveMessage: "Alignment Saving.." });
270
+ this.setState({ saveMessageLoading: true });
271
+
272
+ let cleanedTracks;
273
+ if (this.props.pairwiseAlignments) {
274
+ cleanedTracks = this.props.pairwiseAlignments.map(cleanTracks);
275
+ } else {
276
+ cleanedTracks = cleanTracks(this.props.alignmentTracks);
277
+ }
278
+
279
+ await this.props.handleAlignmentSave(cleanedTracks, this.props);
280
+ this.setState({ saveMessage: "Alignment Saved" });
281
+ this.setState({ saveMessageLoading: false });
282
+ setTimeout(() => {
283
+ this.setState({ saveMessage: undefined });
284
+ this.setState({ saveMessageLoading: false });
285
+ }, 5000);
286
+ }
287
+ }
288
+ componentDidMount() {
289
+ const updateAlignmentSelection = (newRangeOrCaret) => {
290
+ this.updateSelectionOrCaret(false, newRangeOrCaret, {
291
+ forceReduxUpdate: true
292
+ });
293
+ };
294
+ window.updateAlignmentSelection = updateAlignmentSelection;
295
+ if (window.Cypress)
296
+ window.Cypress.updateAlignmentSelection = updateAlignmentSelection;
297
+ setTimeout(() => {
298
+ updateLabelsForInViewFeatures({ rectElement: ".alignmentHolder" });
299
+ }, 0);
300
+ setTimeout(() => {
301
+ this.setVerticalScrollRange();
302
+ }, 500);
303
+ }
304
+
305
+ annotationClicked = ({
306
+ event,
307
+ annotation,
308
+ gapsBefore = 0,
309
+ gapsInside = 0
310
+ }) => {
311
+ event.preventDefault && event.preventDefault();
312
+ event.stopPropagation && event.stopPropagation();
313
+ this.updateSelectionOrCaret(event.shiftKey, {
314
+ ...annotation,
315
+ start: annotation.start + gapsBefore,
316
+ end: annotation.end + gapsBefore + gapsInside
317
+ });
318
+ };
319
+
320
+ updateSelectionOrCaret = (
321
+ shiftHeld,
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();
392
+ return toReturn || 0;
393
+ };
394
+ setVerticalScrollRange = throttle(() => {
395
+ if (
396
+ this &&
397
+ this.InfiniteScroller &&
398
+ this.InfiniteScroller.getFractionalVisibleRange &&
399
+ this.easyStore
400
+ ) {
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
+ setTimeout(() => {
423
+ this.setVerticalScrollRange();
424
+ this.oldAlignmentHolderScrollTop = this.alignmentHolder.scrollTop;
425
+ }, 100);
426
+ }
427
+ if (this.blockScroll) {
428
+ //we have to block the scroll sometimes when adjusting the minimap so things aren't too jumpy
429
+ return;
430
+ }
431
+
432
+ const scrollPercentage =
433
+ this.alignmentHolder.scrollLeft /
434
+ (this.alignmentHolder.scrollWidth - this.alignmentHolder.clientWidth);
435
+ this.easyStore.percentScrolled = scrollPercentage || 0;
436
+ if (!this.isZooming) {
437
+ this.easyStore.percentScrolledPreZoom = this.easyStore.percentScrolled;
438
+ }
439
+ if (this.alignmentHolderTop) {
440
+ this.alignmentHolderTop.scrollLeft = this.alignmentHolder.scrollLeft;
441
+ }
442
+ updateLabelsForInViewFeatures({ rectElement: ".alignmentHolder" });
443
+ };
444
+ handleTopScroll = () => {
445
+ this.alignmentHolder.scrollLeft = this.alignmentHolderTop.scrollLeft;
446
+ };
447
+ /**
448
+ * Responsible for handling resizing the highlighted region of the minimap
449
+ * @param {*} newSliderSize
450
+ * @param {*} newPercent
451
+ */
452
+ onMinimapSizeAdjust = (newSliderSize, newPercent) => {
453
+ const percentageOfSpace = newSliderSize / this.state.width;
454
+ const seqLength = this.getSequenceLength();
455
+ const numBpsInView = seqLength * percentageOfSpace;
456
+ const newCharWidth =
457
+ (this.state.width - this.state.nameDivWidth) / numBpsInView;
458
+ this.blockScroll = true;
459
+ this.setCharWidthInLinearView({ charWidthInLinearView: newCharWidth });
460
+ setTimeout(() => {
461
+ this.scrollAlignmentToPercent(newPercent);
462
+ this.blockScroll = false;
463
+ updateLabelsForInViewFeatures({ rectElement: ".alignmentHolder" });
464
+ });
465
+ };
466
+
467
+ setCharWidthInLinearView = ({ charWidthInLinearView }) => {
468
+ window.localStorage.setItem(
469
+ "charWidthInLinearViewDefault",
470
+ charWidthInLinearView
471
+ );
472
+ this.setState({ charWidthInLinearView });
473
+ charWidthInLinearViewDefault = JSON.parse(
474
+ window.localStorage.getItem("charWidthInLinearViewDefault")
475
+ );
476
+ };
477
+
478
+ scrollToCaret = () => {
479
+ let el = window.document.querySelector(".veCaret:not(.zoomSelection)"); //adding .veRowViewCaret breaks this for some reason
480
+ if (!el) {
481
+ el = window.document.querySelector(".veCaret"); //adding .veRowViewCaret breaks this for some reason
482
+ }
483
+ if (!el) {
484
+ return;
485
+ }
486
+ el.scrollIntoView({ inline: "center", block: "nearest" });
487
+ };
488
+
489
+ scrollAlignmentToPercent = (scrollPercentage) => {
490
+ const scrollPercentageToUse = Math.min(Math.max(scrollPercentage, 0), 1);
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);
504
+ };
505
+
506
+ estimateRowHeight = (index, cache) => {
507
+ const { alignmentVisibilityToolOptions, alignmentTracks } = this.props;
508
+ const track = alignmentTracks[index];
509
+ if (!track) return 100;
510
+ const { sequenceData } = track;
511
+ this.rowData = prepareRowData(sequenceData, sequenceData.sequence.length);
512
+ return estimateRowHeight({
513
+ index,
514
+ cache,
515
+ // clearCache: this.clearCache,
516
+ row: this.rowData[index],
517
+ annotationVisibility:
518
+ alignmentVisibilityToolOptions.alignmentAnnotationVisibility,
519
+ annotationLabelVisibility:
520
+ alignmentVisibilityToolOptions.alignmentAnnotationLabelVisibility
521
+ });
522
+ };
523
+
524
+ getMaxLinearViewWidth = () => {
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;
554
+ const isDragDisabled = !allowTrackRearrange || isPairwise;
555
+ let i;
556
+ if (isTemplate) {
557
+ i = _i;
558
+ } else if (hasTemplate) {
559
+ i = _i + 1;
560
+ } else {
561
+ i = _i;
562
+ }
563
+
564
+ const track = alignmentTracks[i];
565
+ if (!track) return null;
566
+ const {
567
+ sequenceData,
568
+ alignmentData,
569
+ isReversed,
570
+ wasTrimmed,
571
+ additionalSelectionLayers,
572
+ chromatogramData
573
+ // mismatches
574
+ } = track;
575
+ const seqLen = this.getMaxLength();
576
+
577
+ const trimmedRangesToDisplay = getTrimmedRangesToDisplay({
578
+ seqLen,
579
+ trimmedRange: alignmentData?.trimmedRange
580
+ });
581
+ const linearViewWidth =
582
+ (alignmentData || sequenceData).sequence.length * charWidthInLinearView;
583
+ const name = sequenceData.name || sequenceData.id;
584
+
585
+ //tnw: commenting this out for now since it can significantly slow down the alignment visualization
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
+ );
695
+
696
+ const { compactNames } =
697
+ alignmentVisibilityToolOptions.alignmentAnnotationVisibility;
698
+ const selectionLayer = [
699
+ this.state[`tempTrimBefore${i}`] || trimmedRangesToDisplay[0],
700
+ this.state[`tempTrimAfter${i}`] || trimmedRangesToDisplay[1]
701
+ ]
702
+ .filter((i) => i)
703
+ .map((i) => ({
704
+ ...i,
705
+ hideCarets: true,
706
+ ignoreGaps: true,
707
+ className: "tg-trimmed-region",
708
+ color: "gray"
709
+ }));
710
+ const innerRenderItem = (provided = {}, snapshot) => (
711
+ <div
712
+ ref={provided?.innerRef}
713
+ {...provided?.draggableProps}
714
+ className={classNames("alignmentViewTrackContainer", {
715
+ isDragDisabled
716
+ })}
717
+ data-alignment-track-index={i}
718
+ style={{
719
+ boxShadow: isTemplate
720
+ ? "red 0px -1px 0px 0px inset, red 0px 1px 0px 0px inset"
721
+ : "0px -1px 0px 0px inset",
722
+ display: "flex",
723
+ ...provided?.draggableProps?.style,
724
+ ...(snapshot?.isDragging && { left: unset })
725
+ }}
726
+ key={i}
727
+ >
728
+ <div
729
+ className="alignmentTrackName"
730
+ style={{
731
+ position: "sticky",
732
+ left: 0,
733
+ zIndex: 10,
734
+ borderBottom: `1px solid ${isTemplate ? "red" : "lightgray"}`,
735
+ borderRight: `1px solid ${isTemplate ? "red" : "lightgray"}`,
736
+ width: this.state.nameDivWidth - 3,
737
+ padding: 2,
738
+ marginRight: 3,
739
+ paddingBottom: 0,
740
+ minWidth: this.state.nameDivWidth - 3,
741
+ overflow: "hidden",
742
+ scrollbarWidth: "none",
743
+ whiteSpace: "nowrap"
744
+ }}
745
+ title={name}
746
+ key={i}
747
+ >
748
+ <div
749
+ {...provided?.dragHandleProps}
750
+ style={{
751
+ ...(compactNames && {
752
+ display: "flex",
753
+ alignItems: "flex-start"
754
+ }),
755
+ cursor:
756
+ !isPairwise && allowTrackRearrange && !isTemplate ? "move" : ""
757
+ }}
758
+ >
759
+ <div
760
+ className="alignmentTrackNameDiv"
761
+ style={{
762
+ background: "#3FA6DA",
763
+ display: "inline-block",
764
+ color: "white",
765
+ borderRadius: 5,
766
+ paddingRight: 5,
767
+ ...(compactNames && {
768
+ marginRight: 5
769
+ })
770
+ }}
771
+ >
772
+ {allowTrackNameEdit && (
773
+ <Button
774
+ onClick={() => {
775
+ showDialog({
776
+ ModalComponent: EditTrackNameDialog,
777
+ props: {
778
+ initialValues: {
779
+ name
780
+ },
781
+ updateName: ({ newName }) => {
782
+ updateTrackHelper({
783
+ currentPairwiseAlignmentIndex,
784
+ pairwiseAlignments,
785
+ upsertAlignmentRun,
786
+ alignmentId,
787
+ alignmentTracks,
788
+ alignmentTrackIndex: i,
789
+ update: { name: newName }
790
+ });
791
+ }
792
+ }
793
+ });
794
+ }}
795
+ small
796
+ data-tip="Edit Track Name"
797
+ className="edit-track-name-btn"
798
+ icon={<Icon size={12} color="lightgrey" icon="edit"></Icon>}
799
+ minimal
800
+ ></Button>
801
+ )}
802
+ {sequenceData.seqLink && (
803
+ <AnchorButton
804
+ href={sequenceData.seqLink}
805
+ data-tip={sequenceData.seqLinkTooltip}
806
+ target="_blank"
807
+ small
808
+ icon={
809
+ <Icon size={12} color="white" icon="document-open"></Icon>
810
+ }
811
+ minimal
812
+ ></AnchorButton>
813
+ )}
814
+ {name}
815
+ </div>
816
+ <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
+ {isReversed && (
823
+ <span
824
+ style={{
825
+ backgroundColor: isReversed ? "#E76A6E" : "#4C90F0",
826
+ padding: 2,
827
+ paddingLeft: 4,
828
+ color: "white",
829
+ marginRight: 2,
830
+ borderRadius: "5px"
831
+ }}
832
+ data-tip={
833
+ isReversed
834
+ ? "The alignment algorithm matched the reverse complement of this input sequence"
835
+ : "The original sequence was NOT reversed complemented by the alignment algorithm"
836
+ }
837
+ >
838
+ {isReversed ? "REV" : "FWD"}{" "}
839
+ </span>
840
+ )}
841
+ {wasTrimmed && (
842
+ <span
843
+ style={{
844
+ backgroundColor: "#13C9BA",
845
+ padding: 2,
846
+ paddingLeft: 4,
847
+ color: "white",
848
+ marginRight: 2,
849
+ borderRadius: "5px"
850
+ }}
851
+ data-tip="This sequence was trimmed and resubmitted for alignment"
852
+ >
853
+ TRIMMED
854
+ </span>
855
+ )}
856
+ {sequenceData.sequence.length} bps
857
+ </div>
858
+ </div>
859
+ <HorizontalPanelDragHandle
860
+ onDrag={({ dx }) => {
861
+ this.setState({
862
+ nameDivWidth: Math.min(
863
+ this.state.nameDivWidth - dx,
864
+ this.state.width - 20
865
+ )
866
+ });
867
+ }}
868
+ />
869
+ </div>
870
+
871
+ {handleSelectTrack && !isTemplate && (
872
+ <div
873
+ onClick={() => {
874
+ handleSelectTrack(i);
875
+ }}
876
+ style={{
877
+ position: "absolute",
878
+ opacity: 0,
879
+ height: "100%",
880
+ left: this.state.nameDivWidth,
881
+ width: linearViewWidth,
882
+ fontWeight: "bolder",
883
+ cursor: "pointer",
884
+ padding: 5,
885
+ textAlign: "center",
886
+ zIndex: 400
887
+ }}
888
+ className="alignmentViewSelectTrackPopover veWhiteBackground"
889
+ >
890
+ Inspect track
891
+ </div>
892
+ )}
893
+ <NonReduxEnhancedLinearView
894
+ {...{
895
+ ...rest,
896
+ caretPosition: this.state[`tempTrimmingCaret${i}`] || -1,
897
+ selectionLayer,
898
+ isInAlignment: true,
899
+ // : { start: -1, end: -1 },
900
+ annotationVisibilityOverrides:
901
+ alignmentVisibilityToolOptions.alignmentAnnotationVisibility,
902
+ linearViewAnnotationLabelVisibilityOverrides:
903
+ alignmentVisibilityToolOptions.alignmentAnnotationLabelVisibility,
904
+ marginWith: 0,
905
+ orfClicked: this.annotationClicked,
906
+ primerClicked: this.annotationClicked,
907
+ translationClicked: this.annotationClicked,
908
+ cutsiteClicked: this.annotationClicked,
909
+ translationDoubleClicked: this.annotationClicked,
910
+ deletionLayerClicked: this.annotationClicked,
911
+ replacementLayerClicked: this.annotationClicked,
912
+ featureClicked: this.annotationClicked,
913
+ partClicked: this.annotationClicked,
914
+ searchLayerClicked: this.annotationClicked,
915
+ editorDragStarted: noop, //override these since we're defining the handlers above
916
+ editorDragStopped: noop, //override these since we're defining the handlers above
917
+ editorDragged: noop, //override these since we're defining the handlers above
918
+ hideName: true,
919
+ sequenceData,
920
+ // sequenceDataWithRefSeqCdsFeatures,
921
+ tickSpacing,
922
+ allowSeqDataOverride: true, //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
923
+ editorName: `${isTemplate ? "template_" : ""}alignmentView${i}`,
924
+ alignmentData,
925
+ chromatogramData,
926
+ height: "100%",
927
+ vectorInteractionWrapperStyle: {
928
+ overflowY: "hidden"
929
+ },
930
+ marginWidth: 0,
931
+ linearViewCharWidth: charWidthInLinearView,
932
+ ignoreGapsOnHighlight: true,
933
+ ...(linearViewOptions &&
934
+ (isFunction(linearViewOptions)
935
+ ? linearViewOptions({
936
+ index: i,
937
+ isTemplate,
938
+ alignmentVisibilityToolOptions,
939
+ sequenceData,
940
+ alignmentData,
941
+ chromatogramData
942
+ })
943
+ : linearViewOptions)),
944
+ additionalSelectionLayers,
945
+ dimensions: {
946
+ width: linearViewWidth
947
+ },
948
+ width: linearViewWidth,
949
+ paddingBottom: 5,
950
+ scrollDataPassed: this.easyStore
951
+ }}
952
+ />
953
+ </div>
954
+ );
955
+ if (isTemplate) return innerRenderItem();
956
+ if (cloneProps)
957
+ return innerRenderItem(cloneProps.provided, cloneProps.snapshot);
958
+ const idToUse = alignmentData.id || sequenceData.id || i + "_index_id";
959
+ return (
960
+ <DndDraggable
961
+ key={idToUse.toString()}
962
+ index={i}
963
+ isDragDisabled={isDragDisabled}
964
+ draggableId={idToUse.toString()}
965
+ >
966
+ {innerRenderItem}
967
+ </DndDraggable>
968
+ );
969
+ };
970
+ handleResize = throttle(([e]) => {
971
+ this.easyStore.viewportWidth =
972
+ e.contentRect.width - this.state.nameDivWidth || 400;
973
+ this.setState({ width: e.contentRect.width });
974
+ }, 200);
975
+
976
+ removeMinimapHighlightForMouseLeave = () => {
977
+ const minimapLaneEl = document.querySelector(`.minimapLane.lane-hovered`);
978
+ if (!minimapLaneEl) return;
979
+ minimapLaneEl.classList.remove("lane-hovered");
980
+ };
981
+ updateMinimapHighlightForMouseMove = (event) => {
982
+ this.latestMouseY = getClientY(event); //we use this variable later
983
+ this.updateMinimapHighlight();
984
+ };
985
+ updateMinimapHighlight = () => {
986
+ const rows = document.querySelectorAll(`.alignmentViewTrackContainer`);
987
+ const rowsLength = document.querySelectorAll(`.minimapLane`).length;
988
+ if (rowsLength <= 4) {
989
+ return; // no need to include this functionality here
990
+ }
991
+ some(rows, (rowDomNode) => {
992
+ const boundingRowRect = rowDomNode.getBoundingClientRect();
993
+ if (
994
+ this.latestMouseY > boundingRowRect.top &&
995
+ this.latestMouseY < boundingRowRect.top + boundingRowRect.height
996
+ ) {
997
+ const prevMinimapLaneEl = document.querySelector(
998
+ `.minimapLane.lane-hovered`
999
+ );
1000
+ if (prevMinimapLaneEl) {
1001
+ prevMinimapLaneEl.classList.remove("lane-hovered");
1002
+ }
1003
+ const i = Number(rowDomNode.getAttribute("data-alignment-track-index"));
1004
+
1005
+ //then the click falls within this row
1006
+ const minimapLaneEl = document.querySelector(
1007
+ `.minimapLane:nth-child(${i + 1})`
1008
+ );
1009
+ if (!minimapLaneEl) return;
1010
+ minimapLaneEl.classList.add("lane-hovered");
1011
+ return true; //break the loop early because we found the row the click event landed in
1012
+ }
1013
+ });
1014
+ };
1015
+ onTrackDragStart = () => {
1016
+ this.setState({ isTrackDragging: true });
1017
+ };
1018
+ onTrackDragEnd = ({ destination, source }) => {
1019
+ this.setState({ isTrackDragging: false });
1020
+ if (!destination) {
1021
+ return;
1022
+ }
1023
+ const { upsertAlignmentRun, alignmentId, alignmentTracks } = this.props;
1024
+ upsertAlignmentRun({
1025
+ id: alignmentId,
1026
+ alignmentTracks: array_move(
1027
+ alignmentTracks,
1028
+ source.index,
1029
+ destination.index
1030
+ )
1031
+ });
1032
+ };
1033
+
1034
+ render() {
1035
+ const charWidthInLinearView = this.getCharWidthInLinearView();
1036
+ const {
1037
+ alignmentTracks = [],
1038
+ height,
1039
+ minimapLaneHeight,
1040
+ minimapLaneSpacing,
1041
+ isInPairwiseOverviewView,
1042
+ isPairwise,
1043
+ currentPairwiseAlignmentIndex,
1044
+ hasTemplate,
1045
+ noVisibilityOptions,
1046
+ updateAlignmentSortOrder,
1047
+ alignmentSortOrder,
1048
+ handleBackButtonClicked,
1049
+ noClickDragHandlers,
1050
+ upsertAlignmentRun,
1051
+ alignmentId,
1052
+ allowTrimming,
1053
+ additionalSelectionLayerRightClickedOptions,
1054
+ selectionLayerRightClicked,
1055
+ additionalTopEl,
1056
+ handleAlignmentRename,
1057
+ alignmentVisibilityToolOptions
1058
+ } = this.props;
1059
+ const sequenceLength = this.getMaxLength();
1060
+ if (
1061
+ !alignmentTracks ||
1062
+ !alignmentTracks[0] ||
1063
+ !alignmentTracks[0].alignmentData
1064
+ ) {
1065
+ console.error("corrupted data!", this.props);
1066
+ return "corrupted data!";
1067
+ }
1068
+
1069
+ const getTrackVis = (alignmentTracks, isTemplate, allTracks) => {
1070
+ const rowData = {};
1071
+ const innerTrackVis = (drop_provided, drop_snapshot) => {
1072
+ return (
1073
+ <div
1074
+ className="alignmentTracks "
1075
+ style={{
1076
+ overflowY: "auto",
1077
+ display: "flex",
1078
+ zIndex: 10
1079
+ }}
1080
+ >
1081
+ <div
1082
+ style={{
1083
+ overflowX: "auto",
1084
+ width: this.state.width
1085
+ }}
1086
+ ref={(ref) => {
1087
+ this[isTemplate ? "alignmentHolderTop" : "alignmentHolder"] =
1088
+ ref;
1089
+ }}
1090
+ onContextMenu={(e) => {
1091
+ if (
1092
+ !allowTrimming ||
1093
+ isTargetWithinEl(e, ".alignmentTrackName")
1094
+ ) {
1095
+ return;
1096
+ }
1097
+
1098
+ this.getTrackTrimmingOptions({
1099
+ e,
1100
+ allTracks,
1101
+ upsertAlignmentRun,
1102
+ alignmentId,
1103
+ currentPairwiseAlignmentIndex
1104
+ });
1105
+ }}
1106
+ onMouseLeave={this.removeMinimapHighlightForMouseLeave}
1107
+ onMouseMove={this.updateMinimapHighlightForMouseMove}
1108
+ dataname="scrollGroup"
1109
+ className="alignmentHolder"
1110
+ onScroll={isTemplate ? this.handleTopScroll : this.handleScroll}
1111
+ >
1112
+ <ReactDraggable
1113
+ disabled={this.state.isTrackDragging}
1114
+ bounds={{ top: 0, left: 0, right: 0, bottom: 0 }}
1115
+ onDrag={
1116
+ noClickDragHandlers
1117
+ ? noop
1118
+ : (event) => {
1119
+ if (this.state.isTrackDragging) return;
1120
+ this.getNearestCursorPositionToMouseEvent(
1121
+ rowData,
1122
+ event,
1123
+ editorDragged
1124
+ );
1125
+ }
1126
+ }
1127
+ onStart={
1128
+ noClickDragHandlers
1129
+ ? noop
1130
+ : (event) => {
1131
+ if (isTargetWithinEl(event, ".alignmentTrackName")) {
1132
+ return this.setState({ isTrackDragging: true });
1133
+ }
1134
+ if (this.state.isTrackDragging) return;
1135
+ this.getNearestCursorPositionToMouseEvent(
1136
+ rowData,
1137
+ event,
1138
+ editorDragStarted
1139
+ );
1140
+ }
1141
+ }
1142
+ onStop={
1143
+ noClickDragHandlers
1144
+ ? noop
1145
+ : (...args) => {
1146
+ setTimeout(() => {
1147
+ this.setState({ isTrackDragging: false });
1148
+ }, 0);
1149
+ editorDragStopped(...args);
1150
+ }
1151
+ }
1152
+ >
1153
+ <div
1154
+ ref={(ref) => (this.veTracksAndAlignmentHolder = ref)}
1155
+ className={classNames("veTracksAndAlignmentHolder", {
1156
+ isTrackDragging: this.state.isTrackDragging
1157
+ })}
1158
+ // onContextMenu={
1159
+ //tnrtodo add copy single track/all tracks logic here
1160
+ // (event) => {
1161
+ // this.getNearestCursorPositionToMouseEvent(
1162
+ // rowData,
1163
+ // event,
1164
+ // () => {
1165
+ // }
1166
+ // );
1167
+ // }
1168
+ // }
1169
+ onClick={
1170
+ noClickDragHandlers
1171
+ ? noop
1172
+ : (event) => {
1173
+ if (this.state.isTrackDragging) return;
1174
+ if (isTargetWithinEl(event, ".alignmentTrackName")) {
1175
+ return;
1176
+ }
1177
+ this.getNearestCursorPositionToMouseEvent(
1178
+ rowData,
1179
+ event,
1180
+ editorClicked
1181
+ );
1182
+ }
1183
+ }
1184
+ >
1185
+ <PerformantSelectionLayer
1186
+ leftMargin={this.state.nameDivWidth}
1187
+ className="veAlignmentSelectionLayer"
1188
+ isDraggable
1189
+ selectionLayerRightClicked={
1190
+ selectionLayerRightClicked
1191
+ ? (...args) => {
1192
+ selectionLayerRightClicked(...args, this.props);
1193
+ }
1194
+ : (...args) => {
1195
+ const { event } = args[0];
1196
+ const track = getTrackFromEvent(event, allTracks);
1197
+
1198
+ const alignmentData = track.alignmentData;
1199
+ const { name } = alignmentData;
1200
+ showContextMenu(
1201
+ [
1202
+ ...(additionalSelectionLayerRightClickedOptions
1203
+ ? additionalSelectionLayerRightClickedOptions(
1204
+ ...args,
1205
+ this.props
1206
+ )
1207
+ : []),
1208
+ {
1209
+ text: "Copy Selection of All Alignments as Fasta",
1210
+ className:
1211
+ "copyAllAlignmentsFastaClipboardHelper",
1212
+ hotkey: "cmd+c",
1213
+ willUnmount: () => {
1214
+ this
1215
+ .copyAllAlignmentsFastaClipboardHelper &&
1216
+ this.copyAllAlignmentsFastaClipboardHelper.destroy();
1217
+ },
1218
+ didMount: () => {
1219
+ this.copyAllAlignmentsFastaClipboardHelper =
1220
+ new Clipboard(
1221
+ `.copyAllAlignmentsFastaClipboardHelper`,
1222
+ {
1223
+ action: "copyAllAlignmentsFasta",
1224
+ text: () => {
1225
+ return this.getAllAlignmentsFastaText();
1226
+ }
1227
+ }
1228
+ );
1229
+ },
1230
+ onClick: () => {
1231
+ window.toastr.success("Selection Copied");
1232
+ }
1233
+ },
1234
+ {
1235
+ text: `Copy Selection of ${name} as Fasta`,
1236
+ className:
1237
+ "copySpecificAlignmentFastaClipboardHelper",
1238
+ willUnmount: () => {
1239
+ this
1240
+ .copySpecificAlignmentFastaClipboardHelper &&
1241
+ this.copySpecificAlignmentFastaClipboardHelper.destroy();
1242
+ },
1243
+ didMount: () => {
1244
+ this.copySpecificAlignmentFastaClipboardHelper =
1245
+ new Clipboard(
1246
+ `.copySpecificAlignmentFastaClipboardHelper`,
1247
+ {
1248
+ action: "copySpecificAlignmentFasta",
1249
+ text: () => {
1250
+ const { selectionLayer } =
1251
+ this.props.store.getState()
1252
+ .VectorEditor
1253
+ .__allEditorsOptions.alignments[
1254
+ this.props.id
1255
+ ] || {};
1256
+ const seqDataToCopy =
1257
+ getSequenceDataBetweenRange(
1258
+ alignmentData,
1259
+ selectionLayer
1260
+ ).sequence;
1261
+ const seqDataToCopyAsFasta = `>${name}\r\n${seqDataToCopy}\r\n`;
1262
+ return seqDataToCopyAsFasta;
1263
+ }
1264
+ }
1265
+ );
1266
+ },
1267
+ onClick: () => {
1268
+ window.toastr.success(
1269
+ "Selection Copied As Fasta"
1270
+ );
1271
+ }
1272
+ },
1273
+ {
1274
+ text: `Copy Selection of ${name}`,
1275
+ className:
1276
+ "copySpecificAlignmentAsPlainClipboardHelper",
1277
+ willUnmount: () => {
1278
+ this
1279
+ .copySpecificAlignmentAsPlainClipboardHelper &&
1280
+ this.copySpecificAlignmentAsPlainClipboardHelper.destroy();
1281
+ },
1282
+ didMount: () => {
1283
+ this.copySpecificAlignmentAsPlainClipboardHelper =
1284
+ new Clipboard(
1285
+ `.copySpecificAlignmentAsPlainClipboardHelper`,
1286
+ {
1287
+ action: "copySpecificAlignmentFasta",
1288
+ text: () => {
1289
+ const { selectionLayer } =
1290
+ this.props.store.getState()
1291
+ .VectorEditor
1292
+ .__allEditorsOptions.alignments[
1293
+ this.props.id
1294
+ ] || {};
1295
+ const seqDataToCopy =
1296
+ getSequenceDataBetweenRange(
1297
+ alignmentData,
1298
+ selectionLayer
1299
+ ).sequence;
1300
+ return seqDataToCopy;
1301
+ }
1302
+ }
1303
+ );
1304
+ },
1305
+ onClick: () => {
1306
+ window.toastr.success("Selection Copied");
1307
+ }
1308
+ }
1309
+ ],
1310
+ undefined,
1311
+ event
1312
+ );
1313
+ }
1314
+ }
1315
+ easyStore={this.easyStore}
1316
+ sequenceLength={sequenceLength}
1317
+ charWidth={this.getCharWidthInLinearView()}
1318
+ row={{ start: 0, end: sequenceLength - 1 }}
1319
+ ></PerformantSelectionLayer>
1320
+ <PerformantCaret
1321
+ leftMargin={this.state.nameDivWidth}
1322
+ className="veAlignmentSelectionLayer"
1323
+ isDraggable
1324
+ sequenceLength={sequenceLength}
1325
+ charWidth={this.getCharWidthInLinearView()}
1326
+ row={{ start: 0, end: sequenceLength - 1 }}
1327
+ easyStore={this.easyStore}
1328
+ />
1329
+ {isTemplate ? (
1330
+ this.renderItem(0, 0, isTemplate)
1331
+ ) : (
1332
+ <ReactList
1333
+ ref={(c) => {
1334
+ this.InfiniteScroller = c;
1335
+ const domNode = ReactDOM.findDOMNode(c);
1336
+ if (domNode instanceof HTMLElement) {
1337
+ drop_provided.innerRef(domNode);
1338
+ }
1339
+ }}
1340
+ type="variable"
1341
+ itemSizeEstimator={this.estimateRowHeight}
1342
+ itemRenderer={this.renderItem}
1343
+ length={
1344
+ alignmentTracks.length +
1345
+ (drop_snapshot.isUsingPlaceholder ? 1 : 0)
1346
+ }
1347
+ />
1348
+ )}
1349
+ </div>
1350
+ </ReactDraggable>
1351
+ </div>
1352
+ </div>
1353
+ );
1354
+ };
1355
+ if (isTemplate) return innerTrackVis();
1356
+ else
1357
+ return (
1358
+ <Droppable
1359
+ mode="virtual"
1360
+ renderClone={(provided, snapshot, { source: { index } }) => {
1361
+ return this.renderItem(index, index, false, {
1362
+ provided,
1363
+ snapshot
1364
+ });
1365
+ }}
1366
+ direction="vertical"
1367
+ droppableId={"droppable" + isTemplate ? "_no_drop" : ""}
1368
+ >
1369
+ {innerTrackVis}
1370
+ </Droppable>
1371
+ );
1372
+ };
1373
+
1374
+ const [firstTrack, ...otherTracks] = alignmentTracks;
1375
+ const totalWidthOfMinimap = this.state.width;
1376
+ const totalWidthInAlignmentView = 14 * this.getSequenceLength();
1377
+ const minSliderSize = Math.min(
1378
+ totalWidthOfMinimap * (totalWidthOfMinimap / totalWidthInAlignmentView),
1379
+ totalWidthOfMinimap
1380
+ );
1381
+ const viewportHeight = Math.max(
1382
+ document.documentElement.clientHeight,
1383
+ window.innerHeight || 0
1384
+ );
1385
+
1386
+ /**
1387
+ * Parameters to be passed to our Pinch Handler component
1388
+ * OnPinch is the method to be executed when the pinch gesture is registered
1389
+ * Pinch Handler for minimap
1390
+ */
1391
+ const pinchHandler = {
1392
+ onPinch: ({ delta: [d] }) => {
1393
+ this.bindOutsideChangeHelper.triggerChange(({ 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
+ updateLabelsForInViewFeatures();
1410
+ }
1411
+ };
1412
+
1413
+ return (
1414
+ <PinchHelper {...pinchHandler}>
1415
+ <ResizeSensor onResize={this.handleResize}>
1416
+ <div
1417
+ style={{
1418
+ height: height || (isPairwise ? "auto" : viewportHeight * 0.88),
1419
+ display: "flex",
1420
+ flexDirection: "column",
1421
+ justifyContent: "space-between",
1422
+ position: "relative",
1423
+ overflowY: "auto",
1424
+ ...this.props.style
1425
+ // borderTop: "1px solid black"
1426
+ }}
1427
+ className="alignmentView"
1428
+ >
1429
+ <DragDropContext
1430
+ onDragStart={this.onTrackDragStart}
1431
+ onDragEnd={this.onTrackDragEnd}
1432
+ >
1433
+ <div
1434
+ style={{
1435
+ display: "flex",
1436
+ flexDirection: "column",
1437
+ position: "relative",
1438
+ overflowY: "auto"
1439
+ }}
1440
+ className="alignmentView-top-container"
1441
+ >
1442
+ <div
1443
+ style={{
1444
+ paddingTop: "3px",
1445
+ paddingBottom: "5px",
1446
+ borderBottom: "1px solid",
1447
+ display: "flex",
1448
+ minHeight: "32px",
1449
+ width: "100%",
1450
+ flexWrap: "nowrap",
1451
+ flexDirection: "row",
1452
+ flex: "0 0 auto"
1453
+ }}
1454
+ className="ve-alignment-top-bar"
1455
+ >
1456
+ {handleBackButtonClicked && (
1457
+ <Tooltip content="Back to Pairwise Alignment Overview">
1458
+ <Button
1459
+ icon="arrow-left"
1460
+ onClick={() => {
1461
+ // this.setState({
1462
+ // charWidthInLinearView: charWidthInLinearViewDefault
1463
+ // });
1464
+ handleBackButtonClicked();
1465
+ this.caretPositionUpdate(-1);
1466
+ }}
1467
+ small
1468
+ intent={Intent.PRIMARY}
1469
+ minimal
1470
+ style={{ marginRight: 10 }}
1471
+ className="alignmentViewBackButton"
1472
+ />
1473
+ </Tooltip>
1474
+ )}
1475
+
1476
+ <div style={{ display: "flex" }}>
1477
+ <EditableText
1478
+ disabled={!handleAlignmentRename}
1479
+ onChange={(v) => {
1480
+ this.setState({
1481
+ alignmentName: v
1482
+ });
1483
+ }}
1484
+ maxLength={399} //stop the name from being tooo long
1485
+ value={this.state.alignmentName}
1486
+ onConfirm={async (v) => {
1487
+ if (!v) {
1488
+ this.setState({
1489
+ alignmentName: this.props.alignmentName
1490
+ });
1491
+ return;
1492
+ }
1493
+ if (v === this.props.alignmentName) {
1494
+ return; //already saved this name
1495
+ }
1496
+ this.setState({ saveMessage: "Alignment Renaming.." });
1497
+ this.setState({ saveMessageLoading: true });
1498
+ await handleAlignmentRename(v, this.props);
1499
+ this.setState({ saveMessage: "Rename Successful" });
1500
+ this.setState({ saveMessageLoading: false });
1501
+ setTimeout(() => {
1502
+ this.setState({ saveMessage: undefined });
1503
+ this.setState({ saveMessageLoading: false });
1504
+ }, 5000);
1505
+ }}
1506
+ selectAllOnFocus={true}
1507
+ className="veAlignmentName"
1508
+ ></EditableText>
1509
+ &nbsp;&nbsp;&nbsp;
1510
+ <div
1511
+ className="veAlignmentType"
1512
+ style={{
1513
+ paddingTop: "3px",
1514
+ fontSize: "14px",
1515
+ color: "grey",
1516
+ maxWidth: "300px",
1517
+ overflow: "hidden",
1518
+ textOverflow: "ellipsis",
1519
+ whiteSpace: "nowrap"
1520
+ }}
1521
+ title={
1522
+ this.props.alignmentType || "Unknown Alignment Type"
1523
+ }
1524
+ >
1525
+ {this.props.alignmentType || "Unknown Alignment Type"}
1526
+ </div>
1527
+ </div>
1528
+
1529
+ {this.props.unmappedSeqs && (
1530
+ <InfoHelper
1531
+ size={20}
1532
+ content={
1533
+ <div>
1534
+ This alignment had sequences that did not map to the
1535
+ template sequence:
1536
+ {this.props.unmappedSeqs.map(
1537
+ ({ sequenceData }, i) => (
1538
+ <div key={i}>{sequenceData.name}</div>
1539
+ )
1540
+ )}
1541
+ </div>
1542
+ }
1543
+ intent="warning"
1544
+ icon="warning-sign"
1545
+ ></InfoHelper>
1546
+ )}
1547
+ {!isInPairwiseOverviewView && (
1548
+ <UncontrolledSliderWithPlusMinusBtns
1549
+ noWraparound
1550
+ bindOutsideChangeHelper={this.bindOutsideChangeHelper}
1551
+ onClick={() => {
1552
+ setTimeout(this.scrollToCaret, 0);
1553
+ }}
1554
+ minCharWidth={this.getMinCharWidth()}
1555
+ onChange={async (zoomLvl) => {
1556
+ this.isZooming = true;
1557
+ setTimeout(() => {
1558
+ this.isZooming = false;
1559
+ }, 10);
1560
+ // zoomLvl is in the range of 0 to 10
1561
+ const minCharWidth = this.getMinCharWidth();
1562
+ const scaleFactor = Math.pow(12 / minCharWidth, 1 / 10);
1563
+ const newCharWidth =
1564
+ minCharWidth * Math.pow(scaleFactor, zoomLvl);
1565
+ await this.setCharWidthInLinearView({
1566
+ charWidthInLinearView: newCharWidth
1567
+ });
1568
+ await this.scrollToCaret();
1569
+ await updateLabelsForInViewFeatures({
1570
+ rectElement: ".alignmentHolder"
1571
+ });
1572
+ }}
1573
+ coerceInitialValue={coerceInitialValue}
1574
+ title="Adjust Zoom Level"
1575
+ style={{ paddingTop: "4px", width: 100 }}
1576
+ className="veZoomAlignmentSlider ove-slider"
1577
+ labelRenderer={false}
1578
+ initialValue={charWidthInLinearView}
1579
+ stepSize={0.05} //was 0.01
1580
+ max={10}
1581
+ min={0}
1582
+ clickStepSize={0.5}
1583
+ />
1584
+ )}
1585
+ {!noVisibilityOptions && !isInPairwiseOverviewView && (
1586
+ <AlignmentVisibilityTool
1587
+ currentPairwiseAlignmentIndex={
1588
+ currentPairwiseAlignmentIndex
1589
+ }
1590
+ {...alignmentVisibilityToolOptions}
1591
+ />
1592
+ )}
1593
+ {updateAlignmentSortOrder && !isInPairwiseOverviewView && (
1594
+ <Popover
1595
+ minimal
1596
+ content={
1597
+ <Menu>
1598
+ <MenuItem
1599
+ active={true || alignmentSortOrder}
1600
+ onClick={() => {
1601
+ updateAlignmentSortOrder("Position");
1602
+ }}
1603
+ text="Position"
1604
+ />
1605
+ <MenuItem
1606
+ active={false || alignmentSortOrder}
1607
+ onClick={() => {
1608
+ updateAlignmentSortOrder("Alphabetical");
1609
+ }}
1610
+ text="Alphabetical"
1611
+ />
1612
+ </Menu>
1613
+ }
1614
+ target={
1615
+ <Button
1616
+ small
1617
+ text="Sort Order"
1618
+ rightIcon="caret-down"
1619
+ icon="sort"
1620
+ />
1621
+ }
1622
+ />
1623
+ )}
1624
+ {additionalTopEl}
1625
+ {this.state.saveMessage && (
1626
+ <div
1627
+ className="ove-menu-toast"
1628
+ style={{
1629
+ display: "flex",
1630
+ alignItems: "center",
1631
+ marginLeft: "auto",
1632
+ marginRight: 10
1633
+ }}
1634
+ >
1635
+ {this.state.saveMessageLoading ? (
1636
+ <div>
1637
+ <Spinner size={15}></Spinner>
1638
+ </div>
1639
+ ) : (
1640
+ <Icon icon="tick-circle" intent="success"></Icon>
1641
+ )}{" "}
1642
+ &nbsp;
1643
+ {this.state.saveMessage}
1644
+ </div>
1645
+ )}
1646
+ </div>
1647
+ {hasTemplate ? (
1648
+ <React.Fragment>
1649
+ <div className="alignmentTrackFixedToTop">
1650
+ {getTrackVis([firstTrack], true, alignmentTracks)}
1651
+ </div>
1652
+ {getTrackVis(otherTracks, false, alignmentTracks)}
1653
+ </React.Fragment>
1654
+ ) : (
1655
+ getTrackVis(alignmentTracks, false, alignmentTracks)
1656
+ )}
1657
+ </div>
1658
+ </DragDropContext>
1659
+ {!isInPairwiseOverviewView && (
1660
+ <div
1661
+ className="alignmentViewBottomBar"
1662
+ style={{
1663
+ // flexGrow: 1,
1664
+ // minHeight: "-webkit-min-content", //https://stackoverflow.com/questions/28029736/how-to-prevent-a-flex-item-from-shrinking-smaller-than-its-content
1665
+ maxHeight: 210,
1666
+ marginTop: 4,
1667
+ paddingTop: 4,
1668
+ borderTop: "1px solid lightgrey",
1669
+ display: "flex"
1670
+ }}
1671
+ >
1672
+ <Minimap
1673
+ {...{
1674
+ selectionLayerComp: (
1675
+ <React.Fragment>
1676
+ <PerformantSelectionLayer
1677
+ is
1678
+ hideCarets
1679
+ className="veAlignmentSelectionLayer veMinimapSelectionLayer"
1680
+ easyStore={this.easyStore}
1681
+ sequenceLength={sequenceLength}
1682
+ charWidth={this.getMinCharWidth(true)}
1683
+ row={{ start: 0, end: sequenceLength - 1 }}
1684
+ ></PerformantSelectionLayer>
1685
+ <PerformantCaret
1686
+ style={{
1687
+ opacity: 0.2
1688
+ }}
1689
+ className="veAlignmentSelectionLayer veMinimapSelectionLayer"
1690
+ sequenceLength={sequenceLength}
1691
+ charWidth={this.getMinCharWidth(true)}
1692
+ row={{ start: 0, end: sequenceLength - 1 }}
1693
+ easyStore={this.easyStore}
1694
+ />
1695
+ </React.Fragment>
1696
+ ),
1697
+ alignmentTracks,
1698
+ dimensions: {
1699
+ width: Math.max(this.state.width, 10) || 10
1700
+ },
1701
+ nameDivOffsetPercent: 0,
1702
+ // this.state.nameDivWidth / this.getMaxLinearViewWidth(),
1703
+ scrollYToTrack: this.scrollYToTrack,
1704
+ onSizeAdjust: this.onMinimapSizeAdjust,
1705
+ minSliderSize,
1706
+ laneHeight:
1707
+ minimapLaneHeight ||
1708
+ (alignmentTracks.length > 5 ? 10 : 17),
1709
+ laneSpacing:
1710
+ minimapLaneSpacing ||
1711
+ (alignmentTracks.length > 5 ? 2 : 1),
1712
+ easyStore: this.easyStore,
1713
+ numBpsShownInLinearView: this.getNumBpsShownInLinearView(),
1714
+ scrollAlignmentView: this.state.scrollAlignmentView
1715
+ }}
1716
+ onMinimapScrollX={this.scrollAlignmentToPercent}
1717
+ />
1718
+ </div>
1719
+ )}
1720
+ <GlobalDialog
1721
+ // {...pickedUserDefinedHandlersAndOpts}
1722
+ // dialogOverrides={pick(this.props, [
1723
+ // "AddOrEditFeatureDialogOverride",
1724
+ // "AddOrEditPartDialogOverride",
1725
+ // "AddOrEditPrimerDialogOverride"
1726
+ // ])}
1727
+ />
1728
+ </div>
1729
+ </ResizeSensor>
1730
+ </PinchHelper>
1731
+ );
1732
+ }
1733
+
1734
+ getTrackTrimmingOptions({
1735
+ e,
1736
+ allTracks,
1737
+ upsertAlignmentRun,
1738
+ currentPairwiseAlignmentIndex,
1739
+ alignmentId
1740
+ }) {
1741
+ const track = getTrackFromEvent(e, allTracks);
1742
+
1743
+ this.getNearestCursorPositionToMouseEvent(
1744
+ this.rowData,
1745
+ e,
1746
+ ({ nearestCaretPos }) => {
1747
+ this.setState({
1748
+ [`tempTrimmingCaret${track.index}`]: nearestCaretPos
1749
+ });
1750
+ const afterDisabled =
1751
+ nearestCaretPos <= track.alignmentData.trimmedRange?.start;
1752
+ const beforeDisabled =
1753
+ nearestCaretPos > track.alignmentData.trimmedRange?.end;
1754
+ showContextMenu(
1755
+ [
1756
+ {
1757
+ divider: (
1758
+ <div
1759
+ style={{
1760
+ color: "#137cbd",
1761
+ fontSize: 13,
1762
+ fontWeight: "bold"
1763
+ }}
1764
+ >{`Trim ${
1765
+ track.sequenceData.name || track.sequenceData.id
1766
+ }...`}</div>
1767
+ )
1768
+ },
1769
+ {
1770
+ text: "Ignore Before",
1771
+ disabled: beforeDisabled,
1772
+ icon: "drawer-left-filled",
1773
+ onMouseOver: () =>
1774
+ !beforeDisabled &&
1775
+ this.setState({
1776
+ [`tempTrimBefore${track.index}`]: {
1777
+ start: 0,
1778
+ end: nearestCaretPos - 1
1779
+ }
1780
+ }),
1781
+ onMouseLeave: () =>
1782
+ this.setState({
1783
+ [`tempTrimBefore${track.index}`]: undefined
1784
+ }),
1785
+ onClick: () => {
1786
+ updateTrackHelper({
1787
+ currentPairwiseAlignmentIndex,
1788
+ upsertAlignmentRun,
1789
+ alignmentId,
1790
+ alignmentTracks: allTracks,
1791
+ alignmentTrackIndex: track.index,
1792
+ hasBeenTrimmed: true,
1793
+ update: {
1794
+ trimmedRange: {
1795
+ start: nearestCaretPos,
1796
+ end:
1797
+ track.alignmentData.trimmedRange?.end ||
1798
+ this.getMaxLength() - 1
1799
+ }
1800
+ }
1801
+ });
1802
+ }
1803
+ },
1804
+ {
1805
+ text: "Ignore After",
1806
+ disabled: afterDisabled,
1807
+ icon: "drawer-right-filled",
1808
+ onMouseOver: () =>
1809
+ !afterDisabled &&
1810
+ this.setState({
1811
+ [`tempTrimAfter${track.index}`]: {
1812
+ start: nearestCaretPos,
1813
+ end: this.getMaxLength() - 1
1814
+ }
1815
+ }),
1816
+ onMouseLeave: () =>
1817
+ this.setState({
1818
+ [`tempTrimAfter${track.index}`]: undefined
1819
+ }),
1820
+ onClick: () => {
1821
+ updateTrackHelper({
1822
+ currentPairwiseAlignmentIndex,
1823
+ upsertAlignmentRun,
1824
+ alignmentId,
1825
+ alignmentTracks: allTracks,
1826
+ alignmentTrackIndex: track.index,
1827
+ hasBeenTrimmed: true,
1828
+ update: {
1829
+ trimmedRange: {
1830
+ start: track.alignmentData.trimmedRange?.start || 0,
1831
+ end: nearestCaretPos - 1
1832
+ }
1833
+ }
1834
+ });
1835
+ }
1836
+ },
1837
+ {
1838
+ divider: ""
1839
+ },
1840
+ {
1841
+ text: "Clear Trim(s)",
1842
+ disabled: !(track?.alignmentData?.trimmedRange?.start > -1),
1843
+ icon: "trash",
1844
+ onClick: () => {
1845
+ updateTrackHelper({
1846
+ currentPairwiseAlignmentIndex,
1847
+ upsertAlignmentRun,
1848
+ alignmentId,
1849
+ alignmentTracks: allTracks,
1850
+ alignmentTrackIndex: track.index,
1851
+ hasBeenTrimmed: false,
1852
+ update: {
1853
+ trimmedRange: undefined
1854
+ }
1855
+ });
1856
+ }
1857
+ }
1858
+ ],
1859
+ undefined,
1860
+ e,
1861
+ () => {
1862
+ this.setState({
1863
+ [`tempTrimmingCaret${track.index}`]: undefined
1864
+ });
1865
+ }
1866
+ );
1867
+ }
1868
+ );
1869
+ e.preventDefault();
1870
+ e.stopPropagation();
1871
+ }
1872
+ }
1873
+
1874
+ export default compose(
1875
+ withStore,
1876
+ withEditorProps,
1877
+ connect(
1878
+ (state, ownProps) => {
1879
+ // const {id}
1880
+ const {
1881
+ VectorEditor: {
1882
+ __allEditorsOptions: { alignments }
1883
+ }
1884
+ } = state;
1885
+ const { id: alignmentId, updateAlignmentViewVisibility } = ownProps;
1886
+ const alignment = { ...alignments[alignmentId], id: alignmentId };
1887
+ const {
1888
+ stateTrackingId,
1889
+ unmappedSeqs,
1890
+ alignmentTracks,
1891
+ pairwiseAlignments,
1892
+ alignmentType,
1893
+ scrollPercentageToJumpTo,
1894
+ pairwiseOverviewAlignmentTracks,
1895
+ loading,
1896
+ alignmentAnnotationVisibility,
1897
+ alignmentAnnotationLabelVisibility,
1898
+ caretPosition = -1,
1899
+ selectionLayer = { start: -1, end: -1 }
1900
+ } = alignment || {};
1901
+ if (loading) {
1902
+ return {
1903
+ loading: true
1904
+ };
1905
+ }
1906
+ if (!alignmentTracks && !pairwiseAlignments)
1907
+ return {
1908
+ noTracks: true
1909
+ };
1910
+ const templateLength = (
1911
+ pairwiseAlignments ? pairwiseAlignments[0][0] : alignmentTracks[0]
1912
+ ).alignmentData.sequence.length;
1913
+
1914
+ const alignmentAnnotationsToToggle = [
1915
+ "features",
1916
+ "parts",
1917
+ "sequence",
1918
+ "reverseSequence",
1919
+ "axis",
1920
+ "translations",
1921
+ "cdsFeatureTranslations",
1922
+ "chromatogram",
1923
+ "dnaColors",
1924
+ "compactNames"
1925
+ ];
1926
+ const togglableAlignmentAnnotationSettings = {};
1927
+ map(alignmentAnnotationsToToggle, (annotation) => {
1928
+ if (annotation in alignmentAnnotationVisibility) {
1929
+ togglableAlignmentAnnotationSettings[annotation] =
1930
+ alignmentAnnotationVisibility[annotation];
1931
+ }
1932
+ });
1933
+
1934
+ const annotationsWithCounts = [];
1935
+ if (alignmentTracks) {
1936
+ let totalNumOfFeatures = 0;
1937
+ let totalNumOfParts = 0;
1938
+ alignmentTracks.forEach((seq) => {
1939
+ if (seq.sequenceData.features) {
1940
+ totalNumOfFeatures += seq.sequenceData.features.length;
1941
+ }
1942
+ if (seq.sequenceData.parts) {
1943
+ totalNumOfParts += seq.sequenceData.parts.length;
1944
+ }
1945
+ });
1946
+ annotationsWithCounts.push({
1947
+ features: totalNumOfFeatures,
1948
+ parts: totalNumOfParts
1949
+ });
1950
+ } else if (pairwiseAlignments) {
1951
+ pairwiseAlignments.forEach((pairwise) => {
1952
+ let totalNumOfFeatures = 0;
1953
+ let totalNumOfParts = 0;
1954
+ pairwise.forEach((seq) => {
1955
+ if (seq.sequenceData.features) {
1956
+ totalNumOfFeatures += seq.sequenceData.features.length;
1957
+ }
1958
+ if (seq.sequenceData.parts) {
1959
+ totalNumOfParts += seq.sequenceData.parts.length;
1960
+ }
1961
+ });
1962
+ annotationsWithCounts.push({
1963
+ features: totalNumOfFeatures,
1964
+ parts: totalNumOfParts
1965
+ });
1966
+ });
1967
+ }
1968
+
1969
+ return {
1970
+ isAlignment: true,
1971
+ selectionLayer,
1972
+ unmappedSeqs,
1973
+ caretPosition,
1974
+ alignmentId,
1975
+ stateTrackingId,
1976
+ sequenceData: {
1977
+ //pass fake seq data in so editor interactions work
1978
+ sequence: Array.from(templateLength)
1979
+ .map(() => "a")
1980
+ .join("")
1981
+ },
1982
+ pairwiseAlignments,
1983
+ alignmentType,
1984
+ alignmentTracks,
1985
+ scrollPercentageToJumpTo,
1986
+ pairwiseOverviewAlignmentTracks,
1987
+ //manipulate the props coming in so we can pass a single clean prop to the visibility options tool
1988
+ alignmentVisibilityToolOptions: {
1989
+ alignmentAnnotationVisibility,
1990
+ alignmentAnnotationLabelVisibility,
1991
+ alignmentAnnotationVisibilityToggle: (updates) => {
1992
+ setTimeout(() => {
1993
+ updateLabelsForInViewFeatures({
1994
+ rectElement: ".alignmentHolder"
1995
+ });
1996
+ }, 0);
1997
+
1998
+ updateAlignmentViewVisibility({
1999
+ ...alignment,
2000
+ alignmentAnnotationVisibility: {
2001
+ ...alignment.alignmentAnnotationVisibility,
2002
+ ...updates
2003
+ }
2004
+ });
2005
+ },
2006
+ alignmentAnnotationLabelVisibilityToggle: (name) => {
2007
+ updateAlignmentViewVisibility({
2008
+ ...alignment,
2009
+ alignmentAnnotationLabelVisibility: {
2010
+ ...alignment.alignmentAnnotationLabelVisibility,
2011
+ [name]: !alignment.alignmentAnnotationLabelVisibility[name]
2012
+ }
2013
+ });
2014
+ },
2015
+ togglableAlignmentAnnotationSettings,
2016
+ annotationsWithCounts
2017
+ }
2018
+ };
2019
+ },
2020
+ {
2021
+ ...alignmentActions
2022
+ }
2023
+ ),
2024
+ branch(
2025
+ ({ loading }) => loading,
2026
+ renderComponent(() => {
2027
+ return <Loading bounce />;
2028
+ })
2029
+ ),
2030
+ branch(
2031
+ ({ noTracks }) => noTracks,
2032
+ renderComponent(() => {
2033
+ return (
2034
+ <div style={{ minHeight: 30, minWidth: 150 }}>"No Tracks Found"</div>
2035
+ );
2036
+ })
2037
+ ),
2038
+ branch(
2039
+ ({ pairwiseAlignments }) => pairwiseAlignments,
2040
+ renderComponent((props) => {
2041
+ return <PairwiseAlignmentView {...props} />;
2042
+ })
2043
+ )
2044
+ )(AlignmentView);
2045
+
2046
+ const PerformantCaret = view(({ easyStore, ...rest }) => {
2047
+ return <Caret caretPosition={easyStore.caretPosition} {...rest} />;
2048
+ });
2049
+
2050
+ function cleanTracks(alignmentTracks) {
2051
+ return alignmentTracks.map((t) => {
2052
+ return omit(t, [
2053
+ "matchHighlightRanges",
2054
+ "additionalSelectionLayers",
2055
+ "mismatches"
2056
+ ]);
2057
+ });
2058
+ }