@teselagen/ove 0.0.13 → 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 +164751 -135808
  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 -193201
@@ -0,0 +1,1585 @@
1
+ import React from "react";
2
+ import { Tag, Classes, NumericInput, Slider } from "@blueprintjs/core";
3
+ import {
4
+ convertRangeTo0Based,
5
+ getSequenceWithinRange
6
+ } from "@teselagen/range-utils";
7
+ import classnames from "classnames";
8
+ import pluralize from "pluralize";
9
+ import { showConfirmationDialog } from "@teselagen/ui";
10
+ import {
11
+ adjustBpsToReplaceOrInsert,
12
+ annotationTypes,
13
+ getSequenceDataBetweenRange
14
+ } from "@teselagen/sequence-utils";
15
+ import { oveCommandFactory } from "../utils/commandUtils";
16
+ import {
17
+ upperFirst,
18
+ map,
19
+ forEach,
20
+ startCase,
21
+ get,
22
+ filter,
23
+ camelCase,
24
+ reduce,
25
+ some,
26
+ sortBy,
27
+ noop
28
+ } from "lodash";
29
+ import showFileDialog from "../utils/showFileDialog";
30
+ import { defaultCopyOptions } from "../redux/copyOptions";
31
+ import { divideBy3 } from "../utils/proteinUtils";
32
+ import packageJson from "../../package.json";
33
+ import { PartTagSearch } from "../helperComponents/partTagSearch";
34
+ import {
35
+ showAddOrEditAnnotationDialog,
36
+ showDialog
37
+ } from "../GlobalDialogUtils";
38
+ import { partsSubmenu } from "../MenuBar/viewSubmenu";
39
+
40
+ const isProtein = (props) => props.sequenceData && props.sequenceData.isProtein;
41
+ const isOligo = (props) => props.sequenceData && props.sequenceData.isOligo;
42
+ const isRna = (props) => props.sequenceData && props.sequenceData.isRna;
43
+ const partsPrimersFeatures = ["Parts", "Features", "Primers"];
44
+ const getNewTranslationHandler = (isReverse) => ({
45
+ handler: (props, state, ctxInfo) => {
46
+ const annotation =
47
+ get(ctxInfo, "context.annotation") || props.selectionLayer;
48
+ if (!(annotation.start > -1)) {
49
+ return window.toastr.warning("No region found to translate");
50
+ }
51
+ props.upsertTranslation({
52
+ start: annotation.start,
53
+ end: annotation.end,
54
+ forward: !isReverse
55
+ });
56
+ props.annotationVisibilityShow("translations");
57
+ },
58
+ isHidden: (props) =>
59
+ isProtein(props) ||
60
+ !props.annotationsToSupport ||
61
+ // props.readOnly ||
62
+ !props.annotationsToSupport.translations,
63
+ isDisabled: (props) =>
64
+ /* (props.readOnly && readOnlyDisabledTooltip) || */ props.sequenceLength ===
65
+ 0 || noSelection(props)
66
+ });
67
+
68
+ const fileCommandDefs = {
69
+ newSequence: {
70
+ isHidden: (props) => !props.onNew,
71
+ handler: (props, ...rest) => props.onNew(props, ...rest)
72
+ },
73
+
74
+ renameSequence: {
75
+ isHidden: (props) => props.readOnly,
76
+ isDisabled: (props) => props.readOnly && readOnlyDisabledTooltip,
77
+ handler: (props) => {
78
+ showDialog({
79
+ dialogType: "RenameSequenceDialog",
80
+ props: {
81
+ initialValues: { newName: props.sequenceData.name },
82
+ onSubmit: (values) => {
83
+ props.sequenceNameUpdate(values.newName);
84
+ props.onRename && props.onRename(values.newName, props);
85
+ }
86
+ }
87
+ });
88
+ }
89
+ },
90
+
91
+ saveSequence: {
92
+ name: "Save",
93
+ isDisabled: (props) =>
94
+ props.alwaysAllowSave
95
+ ? false
96
+ : (props.readOnly && readOnlyDisabledTooltip) ||
97
+ !props.sequenceData ||
98
+ props.sequenceData.stateTrackingId === "initialLoadId" ||
99
+ props.sequenceData.stateTrackingId === props.lastSavedId,
100
+ isHidden: (props) => props.readOnly || !props.handleSave,
101
+ handler: (props) => props.handleSave(),
102
+ hotkey: "mod+s"
103
+ },
104
+ saveSequenceAs: {
105
+ name: "Save As",
106
+ // isDisabled: props =>
107
+ // (props.readOnly && readOnlyDisabledTooltip) ||
108
+ // !props.sequenceData ||
109
+ // (props.sequenceData.stateTrackingId === "initialLoadId" ||
110
+ // props.sequenceData.stateTrackingId === props.lastSavedId),
111
+ isHidden: (props) => !props.onSaveAs,
112
+ handler: (props) => props.handleSave({ isSaveAs: true }),
113
+ hotkey: "mod+shift+s"
114
+ },
115
+ toolsCmd: {
116
+ handler: noop,
117
+ isHidden: isProtein
118
+ },
119
+
120
+ deleteSequence: {
121
+ isDisabled: (props) =>
122
+ (props.readOnly && readOnlyDisabledTooltip) || !props.onDelete,
123
+ isHidden: (props) => !props.onDelete,
124
+ handler: (props) => props.onDelete(props.sequenceData)
125
+ },
126
+
127
+ duplicateSequence: {
128
+ isDisabled: (props) => !props.onDuplicate,
129
+ isHidden: (props) => !props.onDuplicate,
130
+ handler: (props, ...rest) =>
131
+ props.onDuplicate(props.sequenceData, props, ...rest),
132
+ hotkey: "alt+shift+d"
133
+ },
134
+
135
+ toggleReadOnlyMode: {
136
+ toggle: [],
137
+ isDisabled: (props) => props.disableSetReadOnly || !props.onSave,
138
+ isHidden: (props) => !props.toggleReadOnlyMode,
139
+ isActive: (props) => props.readOnly,
140
+ handler: (props) => props.toggleReadOnlyMode()
141
+ },
142
+
143
+ importSequence: {
144
+ isHidden: (props) => props.hideSingleImport,
145
+ isDisabled: (props) => props.readOnly,
146
+ handler: (props) => {
147
+ showFileDialog({
148
+ multiple: false,
149
+ onSelect: (files) => {
150
+ props.importSequenceFromFile(files[0]);
151
+ }
152
+ });
153
+ }
154
+ },
155
+ filterPartsByTagCmd: {
156
+ isHidden: (props) => !props.allPartTags,
157
+ name: "Search Parts By Tag",
158
+ component: (props) => () => {
159
+ return (
160
+ // eslint-disable-next-line jsx-a11y/anchor-is-valid
161
+ <div
162
+ className="bp3-menu-item"
163
+ style={{
164
+ padding: 3,
165
+ paddingLeft: 30
166
+ }}
167
+ data-test="filter-parts-by-tag"
168
+ >
169
+ <PartTagSearch dontAutoOpen {...props}></PartTagSearch>
170
+ </div>
171
+ );
172
+ },
173
+ handler: noop
174
+ },
175
+ filterFeatureLengthsCmd: getFilterByLengthCmd("feature"),
176
+ filterPartLengthsCmd: getFilterByLengthCmd("part"),
177
+ filterPrimerLengthsCmd: getFilterByLengthCmd("primer"),
178
+ featureTypesCmd: {
179
+ name: (props) => {
180
+ const total = Object.keys(
181
+ reduce(
182
+ props.sequenceData.features,
183
+ (acc, feat) => {
184
+ acc[feat.type] = true;
185
+ return acc;
186
+ },
187
+ {}
188
+ )
189
+ ).length;
190
+ const toHideCount = Object.keys(
191
+ props.annotationVisibility.featureTypesToHide
192
+ ).length;
193
+ return (
194
+ <span>
195
+ Filter By Type &nbsp;
196
+ <Tag className="tg-smallTag" round style={{ marginLeft: 4 }}>
197
+ {total - toHideCount}/{total}
198
+ </Tag>
199
+ </span>
200
+ );
201
+ },
202
+ submenu: (props) => {
203
+ const types = {};
204
+ forEach(props.sequenceData.features, (feat) => {
205
+ if (!feat.type) return;
206
+ const checked =
207
+ !props.annotationVisibility.featureTypesToHide[feat.type];
208
+ if (types[feat.type]) {
209
+ types[feat.type].count++;
210
+ } else {
211
+ types[feat.type] = {
212
+ count: 1,
213
+ shouldDismissPopover: false,
214
+ onClick: () =>
215
+ checked
216
+ ? props.hideFeatureTypes([feat.type])
217
+ : props.showFeatureTypes([feat.type]),
218
+ checked
219
+ };
220
+ }
221
+ types[feat.type].text = (
222
+ <span>
223
+ {feat.type} &nbsp;
224
+ <Tag className="tg-smallTag" round style={{ marginLeft: 4 }}>
225
+ {types[feat.type].count}
226
+ </Tag>
227
+ </span>
228
+ );
229
+ });
230
+ const typeMenu = map(types);
231
+ return [
232
+ {
233
+ text: "Uncheck All",
234
+ onClick: () => props.hideFeatureTypes(Object.keys(types)),
235
+ shouldDismissPopover: false
236
+ },
237
+ {
238
+ text: "Check All",
239
+ onClick: () => props.resetFeatureTypesToHide(),
240
+ shouldDismissPopover: false
241
+ },
242
+ "--",
243
+ ...(typeMenu.length
244
+ ? typeMenu
245
+ : [
246
+ {
247
+ text: "No Features To Filter",
248
+ disabled: true
249
+ }
250
+ ])
251
+ ];
252
+ }
253
+ // isDisabled:
254
+ },
255
+ featureFilterIndividualCmd: getFilterIndividualCmd("feature"),
256
+ partFilterIndividualCmd: getFilterIndividualCmd("part"),
257
+ primerFilterIndividualCmd: getFilterIndividualCmd("primer"),
258
+ exportSequenceAsGenbank: {
259
+ name: (props) =>
260
+ isProtein(props) ? "Download GenPept File" : "Download Genbank File",
261
+ handler: (props) =>
262
+ props.exportSequenceToFile(isProtein(props) ? "genpept" : "genbank")
263
+ },
264
+ exportSequenceAsFasta: {
265
+ name: "Download FASTA File",
266
+ handler: (props) => props.exportSequenceToFile("fasta")
267
+ },
268
+ exportSequenceAsTeselagenJson: {
269
+ name: "Download Teselagen JSON File",
270
+ handler: (props) => props.exportSequenceToFile("teselagenJson")
271
+ },
272
+
273
+ viewProperties: {
274
+ handler: (props) => props.propertiesViewOpen()
275
+ },
276
+ viewRevisionHistory: {
277
+ handler: (props) => props.toggleViewVersionHistory(),
278
+ isHidden: (props) => !props.getVersionList || !props.getSequenceAtVersion
279
+ },
280
+
281
+ print: {
282
+ hotkeyProps: { preventDefault: true },
283
+ handler: (props) =>
284
+ showDialog({
285
+ dialogType: "PrintDialog",
286
+ props
287
+ }),
288
+ hotkey: "mod+p"
289
+ },
290
+ ...partsPrimersFeatures.reduce((acc, type) => {
291
+ //showRemoveDuplicatesDialogFeatures showRemoveDuplicatesDialogParts showRemoveDuplicatesDialogPrimers
292
+ acc[`showRemoveDuplicatesDialog${type}`] = {
293
+ name: `Remove Duplicate ${startCase(type)}`,
294
+ isDisabled: (props) => props.readOnly,
295
+ handler: (props) =>
296
+ showDialog({
297
+ dialogType: "RemoveDuplicates",
298
+ props: {
299
+ type: camelCase(type),
300
+ editorName: props.editorName,
301
+ dialogProps: {
302
+ title: `Remove Duplicate ${type}`
303
+ }
304
+ }
305
+ })
306
+ };
307
+ return acc;
308
+ }, {}),
309
+ autoAnnotateHolder: {
310
+ isHidden: (props) =>
311
+ !some(partsPrimersFeatures, (type) => props[`autoAnnotate${type}`])
312
+ },
313
+ onConfigureFeatureTypesClick: {
314
+ name: "Configure Feature Types",
315
+ handler: (p) => p.onConfigureFeatureTypesClick(),
316
+ isHidden: (props) => !props.onConfigureFeatureTypesClick
317
+ },
318
+ ...partsPrimersFeatures.reduce((acc, type) => {
319
+ const handlerName = `autoAnnotate${type}`;
320
+ acc[handlerName] = {
321
+ name: `Auto Annotate ${type}`,
322
+ isDisabled: (props) => props.readOnly,
323
+ isHidden: (props) => !props[handlerName],
324
+ handler: async (props) => {
325
+ if (props[handlerName]) {
326
+ const lowerType = type.toLowerCase();
327
+ const toAdd = await props[handlerName](props);
328
+ props.updateSequenceData({
329
+ ...props.sequenceData,
330
+ [lowerType]: { ...props.sequenceData[lowerType], ...toAdd }
331
+ });
332
+ } else {
333
+ console.warn(`we shouldn't be here..`);
334
+ }
335
+ }
336
+ };
337
+ return acc;
338
+ }, {})
339
+ };
340
+ //copy options
341
+ const toggleCopyOptionCommandDefs = {};
342
+ Object.keys(defaultCopyOptions).forEach((type) => {
343
+ const cmdId = `toggleCopy${upperFirst(type)}`;
344
+ toggleCopyOptionCommandDefs[cmdId] = {
345
+ name: `Include ${startCase(type)}`,
346
+ handler: (props) => props.toggleCopyOption(type),
347
+ isActive: (props) => props.copyOptions && props.copyOptions[type]
348
+ };
349
+ });
350
+
351
+ const readOnlyDisabledTooltip =
352
+ "Sorry this function is not allowed in Read-Only Mode";
353
+ const noSelection = ({ selectionLayer = {} }) =>
354
+ !(selectionLayer.start > -1 && selectionLayer.end > -1) &&
355
+ "Selection Required";
356
+
357
+ const triggerClipboardCommand = (type) => {
358
+ const wrapper = document.querySelector(".veVectorInteractionWrapper");
359
+ if (!wrapper) {
360
+ return window.toastr.info(`Cannot trigger a ${type} in the current view`);
361
+ }
362
+ const hiddenInput = wrapper.querySelector("input.clipboard");
363
+ hiddenInput.focus();
364
+ const worked = document.execCommand(type);
365
+ wrapper.focus();
366
+ if (!worked) {
367
+ const keys = { paste: "Cmd/Ctrl+V", cut: "Cmd/Ctrl+X", copy: "Cmd/Ctrl+C" };
368
+ if (keys[type]) {
369
+ // TODO maybe improve msg with HTML
370
+ window.toastr.info(`Press ${keys[type]} to ${type}`);
371
+ } else {
372
+ console.warn(
373
+ `The ${type} command did not work. document.execCommand(${type}) didn't work`
374
+ );
375
+ }
376
+ }
377
+ };
378
+
379
+ const editCommandDefs = {
380
+ changeCaseCmd: {
381
+ isHidden: isProtein,
382
+ handler: noop
383
+ },
384
+ changeCircularityCmd: {
385
+ isHidden: (p) => p.readOnly || isProtein(p) || isOligo(p) || isRna(p),
386
+ handler: noop
387
+ },
388
+ cut: {
389
+ isDisabled: (props) =>
390
+ (props.readOnly && readOnlyDisabledTooltip) || props.sequenceLength === 0,
391
+ isHidden: (props) => props.readOnly,
392
+ handler: () => {
393
+ triggerClipboardCommand("cut");
394
+ },
395
+ hotkey: "mod+x"
396
+ },
397
+ createNewFromSubsequence: {
398
+ name: "New Sequence From Selected Range",
399
+ isDisabled: (props) =>
400
+ props.sequenceLength === 0 || props.selectionLayer.start === -1,
401
+ isHidden: (props) => !props.onCreateNewFromSubsequence,
402
+ handler: (props) => {
403
+ props.onCreateNewFromSubsequence(
404
+ getSequenceDataBetweenRange(props.sequenceData, props.selectionLayer),
405
+ props
406
+ );
407
+ }
408
+ // hotkey: "mod+x"
409
+ },
410
+
411
+ copy: {
412
+ isDisabled: (props) => props.sequenceLength === 0,
413
+
414
+ handler: () => triggerClipboardCommand("copy"),
415
+ hotkey: "mod+c"
416
+ },
417
+
418
+ paste: {
419
+ isDisabled: (props) => props.readOnly && readOnlyDisabledTooltip,
420
+ isHidden: (props) => props.readOnly,
421
+
422
+ handler: () => triggerClipboardCommand("paste"),
423
+ hotkey: "mod+v"
424
+ },
425
+
426
+ undo: {
427
+ isHidden: (props) => props.readOnly,
428
+
429
+ isDisabled: (props) =>
430
+ props.readOnly ||
431
+ !(
432
+ props.sequenceDataHistory &&
433
+ props.sequenceDataHistory.past &&
434
+ props.sequenceDataHistory.past.length
435
+ ),
436
+ handler: (props) => props.undo(),
437
+ hotkey: "mod+z"
438
+ },
439
+
440
+ redo: {
441
+ isHidden: (props) => props.readOnly,
442
+
443
+ isDisabled: (props) =>
444
+ props.readOnly ||
445
+ !(
446
+ props.sequenceDataHistory &&
447
+ props.sequenceDataHistory.future &&
448
+ props.sequenceDataHistory.future.length
449
+ ),
450
+ handler: (props) => props.redo(),
451
+ hotkey: "mod+shift+z"
452
+ },
453
+ find: {
454
+ isDisabled: (props) => props.sequenceLength === 0,
455
+ name: "Find...",
456
+ handler: (props) => {
457
+ if (props.findTool.isOpen) {
458
+ const inputEl =
459
+ document.querySelector("textarea.tg-find-tool-input") ||
460
+ document.querySelector(".tg-find-tool-input input");
461
+ if (!inputEl) return;
462
+ inputEl.focus && inputEl.focus();
463
+ inputEl.select && inputEl.select();
464
+ } else {
465
+ props.toggleFindTool();
466
+ }
467
+ },
468
+ hotkey: "mod+f",
469
+ hotkeyProps: { preventDefault: true }
470
+ },
471
+ about: {
472
+ isDisabled: (props) => props.sequenceLength === 0,
473
+ name: "About",
474
+ handler: () =>
475
+ showConfirmationDialog({
476
+ text: (
477
+ <div>
478
+ <h5>Open Vector Editor Version: {packageJson.version}</h5>
479
+ This editor is made by Teselagen.
480
+ <br />
481
+ <br />
482
+ Issues can be logged here:{" "}
483
+ <a href="https://github.com/TeselaGen/openVectorEditor/issues">
484
+ Open Vector Editor
485
+ </a>
486
+ </div>
487
+ ),
488
+ confirmButtonText: "Back",
489
+ cancelButtonText: null,
490
+ canEscapeKeyCancel: true //this is false by default
491
+ })
492
+ },
493
+ versionNumber: {
494
+ name: "OVE Version: " + packageJson.version,
495
+ handler: () => {
496
+ const win = window.open(
497
+ "https://github.com/TeselaGen/openVectorEditor/commits/master",
498
+ "_blank"
499
+ );
500
+ win.focus();
501
+ }
502
+ },
503
+
504
+ goTo: {
505
+ isDisabled: (props) => props.sequenceLength === 0,
506
+ name: "Go To...",
507
+ handler: (props) => {
508
+ showDialog({
509
+ dialogType: "GoToDialog",
510
+ props: {
511
+ extraProps: {
512
+ sequencePosition: {
513
+ min: 0,
514
+ max: divideBy3(props.sequenceLength, isProtein(props))
515
+ }
516
+ },
517
+ initialValues: {
518
+ sequencePosition: divideBy3(
519
+ props.caretPosition >= 0 ? props.caretPosition : 0,
520
+ isProtein(props)
521
+ )
522
+ },
523
+ onSubmit: (values) =>
524
+ props.caretPositionUpdate(
525
+ values.sequencePosition * (isProtein(props) ? 3 : 1)
526
+ )
527
+ }
528
+ });
529
+ },
530
+ hotkey: "mod+g",
531
+ hotkeyProps: { preventDefault: true }
532
+ },
533
+
534
+ select: {
535
+ isDisabled: (props) => props.sequenceLength === 0,
536
+ name: "Select...",
537
+ handler: (props) => {
538
+ let { start, end } = props.selectionLayer;
539
+ if (!(start > -1)) {
540
+ start = props.caretPosition;
541
+ end = props.caretPosition;
542
+ }
543
+ showDialog({
544
+ dialogType: "SelectDialog",
545
+ props: {
546
+ extraProps: {
547
+ circular: props.sequenceData && props.sequenceData.circular,
548
+ from: {
549
+ min: 1,
550
+ max: divideBy3(props.sequenceLength || 1, isProtein(props))
551
+ },
552
+ to: {
553
+ min: 1,
554
+ max: divideBy3(props.sequenceLength || 1, isProtein(props))
555
+ }
556
+ },
557
+ selectionLayerUpdate: props.selectionLayerUpdate,
558
+ caretPositionUpdate: props.caretPositionUpdate,
559
+ initialCaretPosition: props.caretPosition,
560
+ initialValues: {
561
+ from: Math.max(
562
+ 1,
563
+ 1 + divideBy3(start >= 0 ? start : 0, isProtein(props))
564
+ ),
565
+ to: Math.max(1, 1 + divideBy3(end >= 0 ? end : 0, isProtein(props)))
566
+ },
567
+ isProtein: isProtein(props),
568
+ sequenceLength: divideBy3(
569
+ props.sequenceLength || 1,
570
+ isProtein(props)
571
+ ),
572
+ onSubmit: (values) => {
573
+ const newRange = convertRangeTo0Based({
574
+ start: isProtein(props) ? values.from * 3 : values.from,
575
+ end: isProtein(props) ? values.to * 3 : values.to
576
+ });
577
+
578
+ return props.selectionLayerUpdate({
579
+ start: isProtein(props) ? newRange.start - 2 : newRange.start,
580
+ end: newRange.end
581
+ });
582
+ }
583
+ }
584
+ });
585
+ }
586
+ },
587
+ selectAll: {
588
+ handler: (props, obj) => {
589
+ const { event, viaHotkey } = obj || {};
590
+ if (viaHotkey) {
591
+ event.stopPropagation();
592
+ event.preventDefault();
593
+ }
594
+ props.selectAll();
595
+ },
596
+ isDisabled: (props) => props.sequenceLength === 0,
597
+ hotkey: "mod+a"
598
+ //tnr: we can't pass the following because it will block inputs
599
+ // hotkeyProps: { preventDefault: true, stopPropagation: true }
600
+ },
601
+
602
+ selectInverse: {
603
+ isDisabled: (props) => noSelection(props),
604
+ handler: (props) => props.handleInverse(),
605
+ hotkey: "mod+i"
606
+ },
607
+
608
+ complementSelection: {
609
+ isHidden: (props) => props.readOnly || isProtein(props),
610
+
611
+ isDisabled: (props) =>
612
+ (props.readOnly && readOnlyDisabledTooltip) || noSelection(props),
613
+ handler: (props) => props.handleComplementSelection()
614
+ },
615
+
616
+ complementEntireSequence: {
617
+ isHidden: (props) => props.readOnly || isProtein(props),
618
+
619
+ isDisabled: (props) =>
620
+ (props.readOnly && readOnlyDisabledTooltip) || props.sequenceLength === 0,
621
+
622
+ handler: (props) => props.handleComplementSequence()
623
+ },
624
+ sequenceCase: {
625
+ isHidden: isProtein
626
+ },
627
+ toggleCircular: {
628
+ name: "Circular",
629
+ isActive: (props) => props.sequenceData.circular,
630
+ handler: (props) => props.updateCircular(true)
631
+ },
632
+ toggleLinear: {
633
+ name: "Linear",
634
+ isActive: (props) => !props.sequenceData.circular,
635
+ handler: (props) => props.updateCircular(false)
636
+ },
637
+ ...[
638
+ { hotkey: "option + =", type: "flipCaseSequence" },
639
+ { hotkey: "option + plus", type: "upperCaseSequence" },
640
+ { hotkey: "option + -", type: "lowerCaseSequence" },
641
+ { /* hotkey: "option+-", */ type: "upperCaseSelection" },
642
+ { /* hotkey: "option+-", */ type: "lowerCaseSelection" }
643
+ ].reduce((acc, { type, hotkey }) => {
644
+ const isSelection = type.includes("Selection");
645
+
646
+ acc[type] = {
647
+ isHidden: isProtein,
648
+ isDisabled: (props) => {
649
+ if (props.readOnly) {
650
+ return "The sequence is read only. Try changing 'View > Sequence > Case'";
651
+ }
652
+ if (isSelection && !(props.selectionLayer.start > -1)) {
653
+ return "No Selection to Replace";
654
+ }
655
+ },
656
+ name: startCase(type),
657
+ hotkey,
658
+ handler: (props) => {
659
+ const { sequence } = props.sequenceData;
660
+ const { selectionLayer } = props;
661
+ let toastFired;
662
+ if (props.uppercaseSequenceMapFont !== "noPreference") {
663
+ toastFired = true;
664
+ props.updateSequenceCase("noPreference");
665
+ window.toastr.success(
666
+ `Sequence Case Edited Successfully. To avoid confusion we set: 'View > Sequence Case' to 'No Preference'`,
667
+ {
668
+ timeout: 10000
669
+ }
670
+ );
671
+ }
672
+ const func = type.includes("lower") ? "toLowerCase" : "toUpperCase";
673
+ let newSeq;
674
+ const orginalSeq = isSelection
675
+ ? getSequenceWithinRange(selectionLayer, sequence)
676
+ : sequence;
677
+ if (type.includes("flip")) {
678
+ newSeq = invertString(orginalSeq);
679
+ } else {
680
+ newSeq = orginalSeq[func]();
681
+ }
682
+ if (newSeq !== orginalSeq) {
683
+ !toastFired &&
684
+ window.toastr.success(`Sequence Case Edited Successfully`);
685
+ //don't trigger a mutation unless something has actually changed
686
+ props.updateSequenceData({
687
+ ...props.sequenceData,
688
+ sequence: isSelection
689
+ ? adjustBpsToReplaceOrInsert(
690
+ sequence,
691
+ newSeq,
692
+ selectionLayer,
693
+ false
694
+ )
695
+ : newSeq
696
+ });
697
+ }
698
+ }
699
+ };
700
+ return acc;
701
+ }, {}),
702
+
703
+ toggleShowGCContent: {
704
+ isActive: (props) => props.showGCContent,
705
+ handler: (props) => {
706
+ props.toggleShowGCContent(!props.showGCContent);
707
+ }
708
+ },
709
+
710
+ toggleSequenceMapFontUpper: {
711
+ isActive: (props) => props.uppercaseSequenceMapFont === "uppercase",
712
+ handler: (props) => {
713
+ props.updateSequenceCase("uppercase");
714
+ window.toastr.success(`Sequence Case View Changed`);
715
+ },
716
+ hotkey: "ctrl+option+plus"
717
+ },
718
+ toggleSequenceMapFontRaw: {
719
+ isActive: (props) => props.uppercaseSequenceMapFont === "noPreference",
720
+ handler: (props) => {
721
+ props.updateSequenceCase("noPreference");
722
+ window.toastr.success(`Sequence Case View Changed`);
723
+ },
724
+ hotkey: "ctrl+option+="
725
+ },
726
+ toggleSequenceMapFontLower: {
727
+ isActive: (props) => props.uppercaseSequenceMapFont === "lowercase",
728
+ handler: (props) => {
729
+ props.updateSequenceCase("lowercase");
730
+ window.toastr.success(`Sequence Case View Changed`);
731
+ },
732
+ hotkey: "ctrl+option+-"
733
+ },
734
+ setRowViewSequenceSpacing: {
735
+ handler: noop,
736
+ name: (props) => {
737
+ return (
738
+ <div data-test="setRowViewSequenceSpacing">
739
+ Spacing (in Sequence Map)
740
+ <div style={{ paddingLeft: 11, paddingRight: 11, paddingTop: 3 }}>
741
+ <Slider
742
+ stepSize={1}
743
+ onChange={(v) => {
744
+ props.updateSequenceSpacing(v);
745
+ }}
746
+ value={Number(props.charWidth)}
747
+ max={16}
748
+ min={8}
749
+ labelStepSize={1}
750
+ ></Slider>
751
+ </div>
752
+ </div>
753
+ );
754
+ }
755
+ },
756
+ createMenuHolder: {
757
+ name: "Create",
758
+ isHidden: (props) => isProtein(props) && props.readOnly,
759
+ handler: noop,
760
+ submenu: (props) => {
761
+ return [
762
+ "newFeature",
763
+ "newPart",
764
+ "newTranslation",
765
+ "newReverseTranslation",
766
+ "newPrimer",
767
+ "createNewFromSubsequence",
768
+ ...(props.getAdditionalCreateOpts
769
+ ? props.getAdditionalCreateOpts(props) || []
770
+ : [])
771
+ ];
772
+ }
773
+ },
774
+ // toggleSequenceMapFontNoPreference: {
775
+ // isActive: props =>
776
+ // !props.uppercaseSequenceMapFont ||
777
+ // props.uppercaseSequenceMapFont === "noPreference",
778
+ // handler: props => {
779
+ // props.updateSequenceCase("noPreference");
780
+ // }
781
+ // },
782
+ reverseComplementSelection: {
783
+ isDisabled: (props) =>
784
+ (props.readOnly && readOnlyDisabledTooltip) || noSelection(props),
785
+ isHidden: (props) => props.readOnly || isProtein(props),
786
+
787
+ handler: (props) => props.handleReverseComplementSelection(),
788
+ hotkey: "mod+e"
789
+ },
790
+
791
+ reverseComplementEntireSequence: {
792
+ isHidden: (props) => props.readOnly || isProtein(props),
793
+
794
+ isDisabled: (props) =>
795
+ (props.readOnly && readOnlyDisabledTooltip) || props.sequenceLength === 0,
796
+ handler: (props) => props.handleReverseComplementSequence()
797
+ },
798
+ fullSequenceTranslations: {
799
+ isHidden: isProtein,
800
+ handler: noop
801
+ },
802
+ sequenceAA_allFrames: {
803
+ isActive: (props) =>
804
+ props.frameTranslations["1"] &&
805
+ props.frameTranslations["2"] &&
806
+ props.frameTranslations["3"],
807
+ handler: (props) => {
808
+ if (
809
+ props.frameTranslations["1"] &&
810
+ props.frameTranslations["2"] &&
811
+ props.frameTranslations["3"]
812
+ ) {
813
+ props.frameTranslationToggleOff("1");
814
+ props.frameTranslationToggleOff("2");
815
+ props.frameTranslationToggleOff("3");
816
+ } else {
817
+ props.annotationVisibilityShow("translations");
818
+ props.frameTranslationToggleOn("1");
819
+ props.frameTranslationToggleOn("2");
820
+ props.frameTranslationToggleOn("3");
821
+ }
822
+ }
823
+ },
824
+ sequenceAAReverse_allFrames: {
825
+ isHidden: isProtein,
826
+
827
+ isActive: (props) =>
828
+ props.frameTranslations["-1"] &&
829
+ props.frameTranslations["-2"] &&
830
+ props.frameTranslations["-3"],
831
+ handler: (props) => {
832
+ if (
833
+ props.frameTranslations["-1"] &&
834
+ props.frameTranslations["-2"] &&
835
+ props.frameTranslations["-3"]
836
+ ) {
837
+ props.frameTranslationToggleOff("-1");
838
+ props.frameTranslationToggleOff("-2");
839
+ props.frameTranslationToggleOff("-3");
840
+ } else {
841
+ props.annotationVisibilityShow("translations");
842
+ props.frameTranslationToggleOn("-1");
843
+ props.frameTranslationToggleOn("-2");
844
+ props.frameTranslationToggleOn("-3");
845
+ }
846
+ }
847
+ },
848
+ sequenceAA_frame1: {
849
+ isActive: (props) => props.frameTranslations["1"],
850
+ handler: (props) => {
851
+ if (!props.frameTranslations["1"]) {
852
+ props.annotationVisibilityShow("translations");
853
+ }
854
+ props.frameTranslationToggle("1");
855
+ }
856
+ },
857
+ sequenceAA_frame2: {
858
+ isActive: (props) => props.frameTranslations["2"],
859
+ handler: (props) => {
860
+ if (!props.frameTranslations["2"]) {
861
+ props.annotationVisibilityShow("translations");
862
+ }
863
+ props.frameTranslationToggle("2");
864
+ }
865
+ },
866
+ sequenceAA_frame3: {
867
+ isActive: (props) => props.frameTranslations["3"],
868
+ handler: (props) => {
869
+ if (!props.frameTranslations["3"]) {
870
+ props.annotationVisibilityShow("translations");
871
+ }
872
+ props.frameTranslationToggle("3");
873
+ }
874
+ },
875
+ sequenceAAReverse_frame1: {
876
+ isActive: (props) => props.frameTranslations["-1"],
877
+ handler: (props) => {
878
+ if (!props.frameTranslations["-1"]) {
879
+ props.annotationVisibilityShow("translations");
880
+ }
881
+ props.frameTranslationToggle("-1");
882
+ }
883
+ },
884
+ sequenceAAReverse_frame2: {
885
+ isActive: (props) => props.frameTranslations["-2"],
886
+ handler: (props) => {
887
+ if (!props.frameTranslations["-2"]) {
888
+ props.annotationVisibilityShow("translations");
889
+ }
890
+ props.frameTranslationToggle("-2");
891
+ }
892
+ },
893
+
894
+ sequenceAAReverse_frame3: {
895
+ isActive: (props) => props.frameTranslations["-3"],
896
+ handler: (props) => {
897
+ if (!props.frameTranslations["-3"]) {
898
+ props.annotationVisibilityShow("translations");
899
+ }
900
+ props.frameTranslationToggle("-3");
901
+ }
902
+ },
903
+ newTranslation: getNewTranslationHandler(),
904
+ newReverseTranslation: getNewTranslationHandler(true),
905
+
906
+ newFeature: {
907
+ handler: (props /* state, ctxInfo */) => {
908
+ props.handleNewFeature();
909
+ },
910
+ isHidden: (props) =>
911
+ props.readOnly ||
912
+ !props.annotationsToSupport ||
913
+ !props.annotationsToSupport.features,
914
+ isDisabled: (props) =>
915
+ (props.readOnly && readOnlyDisabledTooltip) || props.sequenceLength === 0,
916
+ hotkey: "mod+k",
917
+ hotkeyProps: { preventDefault: true }
918
+ },
919
+ useGtgAndCtgAsStartCodons: {
920
+ isHidden: isProtein,
921
+
922
+ name: "Use GTG And CTG As Start Codons",
923
+ isActive: (props) => props.useAdditionalOrfStartCodons,
924
+ handler: (props) => props.useAdditionalOrfStartCodonsToggle()
925
+ },
926
+ minOrfSizeCmd: {
927
+ name: (props) => {
928
+ return (
929
+ <div data-test="min-orf-size" style={{ display: "flex" }}>
930
+ Minimum ORF Size:
931
+ <input
932
+ type="number"
933
+ className={classnames(Classes.INPUT, "minOrfSizeInput")}
934
+ onChange={function (event) {
935
+ let minimumOrfSize = parseInt(event.target.value, 10);
936
+ if (!minimumOrfSize) {
937
+ minimumOrfSize = 0;
938
+ }
939
+ if (!(minimumOrfSize > -1)) minimumOrfSize = -minimumOrfSize;
940
+ props.annotationVisibilityShow("orfs");
941
+ props.minimumOrfSizeUpdate(minimumOrfSize);
942
+ }}
943
+ value={props.minimumOrfSize}
944
+ />
945
+ </div>
946
+ );
947
+ },
948
+ handler: noop
949
+ },
950
+ hotkeyDialog: {
951
+ name: "View Editor Hotkeys",
952
+ handler: (props) => props.openHotkeyDialog()
953
+ },
954
+
955
+ newPart: {
956
+ handler: (props) => props.handleNewPart(),
957
+ isHidden: (props) =>
958
+ props.readOnly ||
959
+ !props.annotationsToSupport ||
960
+ !props.annotationsToSupport.parts,
961
+
962
+ isDisabled: (props) =>
963
+ (props.readOnly && readOnlyDisabledTooltip) || props.sequenceLength === 0,
964
+ hotkey: "mod+l",
965
+ hotkeyProps: { preventDefault: true }
966
+ },
967
+ newPrimer: {
968
+ handler: (props) => props.handleNewPrimer(),
969
+ isHidden: (props) =>
970
+ props.readOnly ||
971
+ !props.annotationsToSupport ||
972
+ !props.annotationsToSupport.primers,
973
+ isDisabled: (props) =>
974
+ (props.readOnly && readOnlyDisabledTooltip) || props.sequenceLength === 0
975
+ },
976
+
977
+ rotateToCaretPosition: {
978
+ isHidden: (props) => props.readOnly || isProtein(props),
979
+ isDisabled: (props) =>
980
+ (props.readOnly && readOnlyDisabledTooltip) ||
981
+ (props.caretPosition === -1 && "You must first place cursor") ||
982
+ (!props.sequenceData.circular && "Disabled for Linear Sequences") ||
983
+ props.sequenceLength === 0,
984
+ handler: (props) => props.handleRotateToCaretPosition(),
985
+ hotkey: "mod+b"
986
+ },
987
+ ...toggleCopyOptionCommandDefs
988
+ };
989
+
990
+ const cirularityCommandDefs = {
991
+ circular: {
992
+ isHidden: (props) => props.readOnly || isProtein(props),
993
+
994
+ isDisabled: (props) => props.readOnly && readOnlyDisabledTooltip,
995
+ handler: (props) => props.updateCircular(true),
996
+ isActive: (props) => props && props.sequenceData.circular
997
+ },
998
+ linear: {
999
+ isHidden: (props) => props.readOnly,
1000
+
1001
+ isDisabled: (props) => props.readOnly && readOnlyDisabledTooltip,
1002
+ handler: (props) => props.updateCircular(false),
1003
+ isActive: (props) => props && !props.sequenceData.circular
1004
+ }
1005
+ };
1006
+
1007
+ const nicheAnnotations = [
1008
+ {
1009
+ type: "warnings",
1010
+ isHidden: (p) => {
1011
+ return !map(p.sequenceData["warnings"]).length;
1012
+ }
1013
+ },
1014
+ {
1015
+ type: "assemblyPieces",
1016
+ isHidden: (p) => {
1017
+ return !map(p.sequenceData["assemblyPieces"]).length;
1018
+ }
1019
+ },
1020
+ {
1021
+ type: "lineageAnnotations",
1022
+ isHidden: (p) => {
1023
+ return !map(p.sequenceData["lineageAnnotations"]).length;
1024
+ }
1025
+ }
1026
+ ];
1027
+ const labelToggleCommandDefs = {};
1028
+ ["feature", "part", "cutsite", "primer", ...nicheAnnotations].forEach(
1029
+ (_type) => {
1030
+ let rest = {};
1031
+ let type = _type;
1032
+ if (_type.type) {
1033
+ type = _type.type.slice(0, -1);
1034
+ rest = _type;
1035
+ }
1036
+ const cmdId = `toggle${upperFirst(type)}Labels`;
1037
+ const plural = type + "s";
1038
+ labelToggleCommandDefs[cmdId] = {
1039
+ toggle: ["show", "hide"],
1040
+ handler: (props) => props.annotationLabelVisibilityToggle(plural),
1041
+ isHidden: (props) => {
1042
+ return (
1043
+ props && props.typesToOmit && props.typesToOmit[plural] === false
1044
+ );
1045
+ },
1046
+ ...rest,
1047
+ isActive: (props) => {
1048
+ return props && props.annotationLabelVisibility[plural];
1049
+ }
1050
+ };
1051
+ }
1052
+ );
1053
+
1054
+ const editAnnotationCommandDefs = ["feature", "part", "primer"].reduce(
1055
+ (acc, key) => {
1056
+ acc[`edit${upperFirst(key)}`] = {
1057
+ name: (props) =>
1058
+ props.readOnly
1059
+ ? `View ${upperFirst(key)} Details`
1060
+ : `Edit ${upperFirst(key)}`,
1061
+ handler: (props, state, ctxInfo) => {
1062
+ const annotation = get(ctxInfo, "context.annotation");
1063
+ showAddOrEditAnnotationDialog({ annotation, type: key });
1064
+ }
1065
+ // isHidden: (props) => props.readOnly
1066
+ };
1067
+ return acc;
1068
+ },
1069
+ {}
1070
+ );
1071
+
1072
+ const deleteAnnotationCommandDefs = [
1073
+ "feature",
1074
+ "part",
1075
+ "primer",
1076
+ "translation"
1077
+ ].reduce((acc, key) => {
1078
+ acc[`delete${upperFirst(key)}`] = {
1079
+ name: `Delete ${upperFirst(key)}`,
1080
+ handler: (props, state, ctxInfo) => {
1081
+ const annotation = get(ctxInfo, "context.annotation");
1082
+ props[`delete${upperFirst(key)}`](annotation);
1083
+ },
1084
+ isHidden: (props) => props.readOnly
1085
+ };
1086
+ return acc;
1087
+ }, {});
1088
+
1089
+ const viewPropertiesCommandDefs = [
1090
+ "general",
1091
+ "genbank",
1092
+ "features",
1093
+ "parts",
1094
+ "orfs",
1095
+ "cutsites",
1096
+ "primers",
1097
+ "translations"
1098
+ ].reduce((acc, key) => {
1099
+ const singularKey = pluralize.singular(key);
1100
+ const upperKey = upperFirst(singularKey);
1101
+ const name = (() => {
1102
+ if (singularKey === "cutsite") {
1103
+ return "View Cut Site Properties";
1104
+ }
1105
+ if (singularKey === "orf") {
1106
+ return "View ORF Properties";
1107
+ }
1108
+ return `View ${upperFirst(singularKey)} Properties`;
1109
+ })();
1110
+
1111
+ acc[`view${upperKey}Properties`] = {
1112
+ name,
1113
+ handler: (props, state, ctxInfo) => {
1114
+ const annotation = get(ctxInfo, "context.annotation");
1115
+ props.propertiesViewOpen();
1116
+ //we need to clear the properties tab first in case the same item has already been selected
1117
+ props.propertiesViewTabUpdate(key, undefined);
1118
+ setTimeout(() => {
1119
+ //then shortly after we can update it with the correct annotation
1120
+ props.propertiesViewTabUpdate(key, annotation);
1121
+ }, 0);
1122
+ }
1123
+ };
1124
+ return acc;
1125
+ }, {});
1126
+
1127
+ const annotationToggleCommandDefs = {};
1128
+ [
1129
+ "features",
1130
+ "parts",
1131
+
1132
+ ...nicheAnnotations,
1133
+ { type: "cutsites", isHidden: isProtein },
1134
+ "axis",
1135
+ { type: "orfs", text: "ORFs", isHidden: isProtein },
1136
+ { type: "primers", isHidden: isProtein },
1137
+ { type: "chromatogram", isHidden: (p) => !p.sequenceData.chromatogramData },
1138
+ "translations",
1139
+
1140
+ {
1141
+ type: "orfTranslations",
1142
+ text: "ORF Translations",
1143
+ isHidden: isProtein,
1144
+ isDisabled: (props) => {
1145
+ return (
1146
+ (!props.annotationVisibility.orfs &&
1147
+ "ORFs must be visible to view their translations") ||
1148
+ (!props.annotationVisibility.translations &&
1149
+ "Translations must be visible to view ORF translations")
1150
+ );
1151
+ }
1152
+ },
1153
+ {
1154
+ type: "cdsFeatureTranslations",
1155
+ text: "CDS Feature Translations",
1156
+ isHidden: isProtein,
1157
+ isDisabled: (props) => {
1158
+ return (
1159
+ (!props.annotationVisibility.features &&
1160
+ "Features must be visible to view their translations") ||
1161
+ (!props.annotationVisibility.translations &&
1162
+ "Translations must be visible to view CDS feature translations")
1163
+ );
1164
+ }
1165
+ },
1166
+ // {
1167
+ // type: "aminoAcidNumbers",
1168
+ // isHidden: (p, c) =>
1169
+ // (c.isDnaMenu && p.isProtein) || (!c.isDnaMenu && !p.isProtein)
1170
+ // },
1171
+ { type: "aminoAcidNumbers" },
1172
+ "axisNumbers",
1173
+ {
1174
+ type: "sequence",
1175
+ name: "DNA Sequence",
1176
+ noCount: true,
1177
+ isHidden: (props) => !isProtein(props)
1178
+ },
1179
+ {
1180
+ type: "reverseSequence",
1181
+ name: (props) =>
1182
+ isProtein(props) ? "DNA Reverse Sequence" : "Reverse Sequence"
1183
+ },
1184
+ {
1185
+ type: "fivePrimeThreePrimeHints",
1186
+ name: () => "5' 3' Hints"
1187
+ },
1188
+ {
1189
+ type: "dnaColors",
1190
+ name: () => "DNA Colors",
1191
+ isDisabled: (props) =>
1192
+ !props.annotationVisibility.sequence &&
1193
+ !props.annotationVisibility.reverseSequence &&
1194
+ "The DNA sequence must be visible in order to color it"
1195
+ }
1196
+ ].forEach((typeOrObj) => {
1197
+ let type = typeOrObj;
1198
+ let obj = {};
1199
+ if (typeOrObj.type) {
1200
+ type = typeOrObj.type;
1201
+ obj = typeOrObj;
1202
+ }
1203
+ const cmdId = `toggle${upperFirst(type)}`;
1204
+ annotationToggleCommandDefs[cmdId] = {
1205
+ toggle: ["show", "hide"],
1206
+ name: (props) => {
1207
+ const sequenceData = props.sequenceData || {};
1208
+ let count;
1209
+ let hasCount = false;
1210
+ const annotations = props[type] || sequenceData[type];
1211
+ if (annotations && !obj.noCount) {
1212
+ hasCount = true;
1213
+ count = annotations.length || Object.keys(annotations).length || 0;
1214
+ }
1215
+ if (type === "cdsFeatureTranslations") {
1216
+ hasCount = true;
1217
+ count = filter(
1218
+ props.features || sequenceData.features || [],
1219
+ ({ type }) => type === "CDS"
1220
+ ).length;
1221
+ }
1222
+ if (type === "orfTranslations") {
1223
+ hasCount = true;
1224
+ count = filter(
1225
+ props.orfs || sequenceData.orfs || [],
1226
+ ({ isOrf }) => isOrf
1227
+ ).length;
1228
+ }
1229
+ return (
1230
+ <span>
1231
+ {obj.text || startCase(type === "cutsites" ? "Cut Sites" : type)}
1232
+ &nbsp;
1233
+ {hasCount && (
1234
+ <Tag className="tg-smallTag" round style={{ marginLeft: 4 }}>
1235
+ {count}
1236
+ </Tag>
1237
+ )}
1238
+ </span>
1239
+ );
1240
+ },
1241
+ handler: (props) => props.annotationVisibilityToggle(type),
1242
+ isActive: (props) => {
1243
+ return (
1244
+ props && props.annotationVisibility && props.annotationVisibility[type]
1245
+ );
1246
+ },
1247
+ ...obj, //spread this here to override the above props if necessary
1248
+ isHidden: (props) => {
1249
+ return (
1250
+ (props && props.typesToOmit && props.typesToOmit[type] === false) ||
1251
+ (obj.isHidden && obj.isHidden(props))
1252
+ );
1253
+ }
1254
+ };
1255
+ });
1256
+
1257
+ const additionalAnnotationCommandsDefs = {
1258
+ limitsMenu: {
1259
+ isHidden: (props) => props.maxAnnotationsToDisplay
1260
+ },
1261
+ showAll: {
1262
+ handler: (props) => {
1263
+ annotationTypes.forEach((type) => {
1264
+ if (props.isProtein) {
1265
+ if (type === "translations" || type === "cutsites")
1266
+ return props.annotationVisibilityHide(type);
1267
+ }
1268
+ props.annotationVisibilityShow(type);
1269
+ });
1270
+ }
1271
+ },
1272
+ hideAll: {
1273
+ handler: (props) => {
1274
+ annotationTypes.forEach((type) => {
1275
+ props.annotationVisibilityHide(type);
1276
+ });
1277
+ }
1278
+ },
1279
+ showAllLabels: {
1280
+ handler: (props) => {
1281
+ annotationTypes.forEach((type) => {
1282
+ // if (props.isProtein) {
1283
+ // if (type === "translations" || type === "cutsites")
1284
+ // return props.annotationVisibilityHide(type);
1285
+ // }
1286
+ props.annotationLabelVisibilityShow(type);
1287
+ });
1288
+ }
1289
+ },
1290
+ hideAllLabels: {
1291
+ handler: (props) => {
1292
+ annotationTypes.forEach((type) => {
1293
+ props.annotationLabelVisibilityHide(type);
1294
+ });
1295
+ }
1296
+ },
1297
+ toggleAminoAcidNumbers_dna: {
1298
+ ...annotationToggleCommandDefs.toggleAminoAcidNumbers,
1299
+ isHidden: (props) => isProtein(props)
1300
+ },
1301
+ toggleAminoAcidNumbers_protein: {
1302
+ ...annotationToggleCommandDefs.toggleAminoAcidNumbers,
1303
+ isHidden: (props) => isProtein(props)
1304
+ }
1305
+ };
1306
+
1307
+ const toolCommandDefs = {
1308
+ simulateDigestion: {
1309
+ handler: (props) => props.createNewDigest(),
1310
+ hotkey: "mod+shift+d",
1311
+ hotkeyProps: { preventDefault: true },
1312
+ isHidden: (props) => isProtein(props)
1313
+ },
1314
+ simulatePCR: {
1315
+ handler: (props) => props.createNewPCR(),
1316
+ hotkey: "mod+shift+p",
1317
+ hotkeyProps: { preventDefault: true },
1318
+ isHidden: (props) => isProtein(props)
1319
+ },
1320
+ // TODO: enzyme manager (?)
1321
+ restrictionEnzymesManager: {
1322
+ name: "Manage Enzymes",
1323
+ handler: (props) => {
1324
+ if (props.enzymeManageOverride) {
1325
+ props.enzymeManageOverride(props);
1326
+ } else {
1327
+ props.createYourOwnEnzymeReset();
1328
+ showDialog({
1329
+ dialogType: "EnzymesDialog",
1330
+ props: {
1331
+ inputSequenceToTestAgainst: props.sequenceData
1332
+ ? props.sequenceData.sequence
1333
+ : ""
1334
+ }
1335
+ });
1336
+ }
1337
+ },
1338
+ isHidden: (props) => isProtein(props)
1339
+ },
1340
+ openFilterCutsites: {
1341
+ name: "Filter Cut Sites",
1342
+ handler: (props) => {
1343
+ props.openToolbarItemUpdate("cutsiteTool");
1344
+ },
1345
+ isHidden: (props) => isProtein(props)
1346
+ },
1347
+ openCreateCustomEnzyme: {
1348
+ name: "Create Custom Enzyme",
1349
+ handler: () => {
1350
+ showDialog({
1351
+ dialogType: "CreateCustomEnzyme"
1352
+ });
1353
+ },
1354
+ isHidden: (props) => props.overrideManageEnzymes
1355
+ }
1356
+ };
1357
+
1358
+ const labelIntensities = {
1359
+ Low: 0.1,
1360
+ Medium: 0.4,
1361
+ High: 0.9
1362
+ };
1363
+ const labelSizes = {
1364
+ "33%": 3,
1365
+ "50%": 4,
1366
+ "75%": 6,
1367
+ "100%": 8,
1368
+ "125%": 10,
1369
+ "150%": 12,
1370
+ "200%": 16
1371
+ };
1372
+ const labelCommandDefs = {
1373
+ adjustLabelLineIntensity: {
1374
+ name: "Label Line Intensity",
1375
+ submenu: (props) =>
1376
+ map(Object.keys(labelIntensities), (key) => ({
1377
+ text: key,
1378
+ checked: props.labelLineIntensity === labelIntensities[key],
1379
+ onClick: () => props.changeLabelLineIntensity(labelIntensities[key])
1380
+ }))
1381
+ },
1382
+ adjustLabelSize: {
1383
+ name: "Circular Label Size",
1384
+ submenu: (props) =>
1385
+ map(Object.keys(labelSizes), (key) => ({
1386
+ text: key,
1387
+ checked: props.labelSize === labelSizes[key],
1388
+ onClick: () => props.changeLabelSize(labelSizes[key])
1389
+ }))
1390
+ }
1391
+ };
1392
+
1393
+ export const commandDefs = {
1394
+ showChromQualScoresMenu: {
1395
+ isHidden: (props) =>
1396
+ !props.sequenceData.chromatogramData ||
1397
+ !props.sequenceData.chromatogramData.baseTraces
1398
+ },
1399
+ togglePartsWithSubmenu: {
1400
+ ...annotationToggleCommandDefs.toggleParts,
1401
+ submenu: partsSubmenu
1402
+ },
1403
+ ...additionalAnnotationCommandsDefs,
1404
+ ...fileCommandDefs,
1405
+ ...cirularityCommandDefs,
1406
+ ...annotationToggleCommandDefs,
1407
+ ...viewPropertiesCommandDefs,
1408
+ ...editAnnotationCommandDefs,
1409
+ ...deleteAnnotationCommandDefs,
1410
+ ...labelToggleCommandDefs,
1411
+ ...editCommandDefs,
1412
+ ...toolCommandDefs,
1413
+ ...labelCommandDefs
1414
+ };
1415
+
1416
+ export default (instance) => oveCommandFactory(instance, commandDefs);
1417
+
1418
+ const invertString = function (str) {
1419
+ let s = "";
1420
+ let i = 0;
1421
+ while (i < str.length) {
1422
+ let n = str.charAt(i);
1423
+ if (n === n.toUpperCase()) {
1424
+ // *Call* toLowerCase
1425
+ n = n.toLowerCase();
1426
+ } else {
1427
+ // *Call* toUpperCase
1428
+ n = n.toUpperCase();
1429
+ }
1430
+
1431
+ i += 1;
1432
+ s += n;
1433
+ }
1434
+ return s;
1435
+ };
1436
+
1437
+ function getFilterByLengthCmd(type) {
1438
+ return {
1439
+ name: (props) => {
1440
+ return (
1441
+ <div data-test={`filter-${type}-length`}>
1442
+ Filter By Length
1443
+ <div onClick={(e) => e.stopPropagation()}>
1444
+ <NumericInput
1445
+ onValueChange={function (valueAsNumber) {
1446
+ const minimumFilterLength = parseInt(valueAsNumber, 10);
1447
+ if (!(minimumFilterLength > -1)) return;
1448
+ if (minimumFilterLength > props.sequenceLength) return;
1449
+ props[`update${startCase(type)}LengthsToHide`]({
1450
+ enabled: true,
1451
+ min: minimumFilterLength
1452
+ });
1453
+ }}
1454
+ value={props[`${type}LengthsToHide`].min}
1455
+ min={0}
1456
+ // max={props[`${type}LengthsToHide`].max} //tnr: I think it is better to not bound the max
1457
+ fill={true}
1458
+ clampValueOnBlur={true}
1459
+ data-test={`min-${type}-length`}
1460
+ />
1461
+ <NumericInput
1462
+ onValueChange={function (valueAsNumber) {
1463
+ const maximumFilterLength = parseInt(valueAsNumber, 10);
1464
+ if (!(maximumFilterLength > -1)) return;
1465
+ // if (maximumFilterLength > props.sequenceLength) return; //tnr: I think it is better not to bound the max
1466
+ props[`update${startCase(type)}LengthsToHide`]({
1467
+ enabled: true,
1468
+ max: maximumFilterLength
1469
+ });
1470
+ }}
1471
+ value={props[`${type}LengthsToHide`].max}
1472
+ min={0}
1473
+ // max={props.sequenceLength} //tnr: I think it is better to not bound the max
1474
+ fill={true}
1475
+ clampValueOnBlur={true}
1476
+ data-test={`max-${type}-length`}
1477
+ />
1478
+ </div>
1479
+ </div>
1480
+ );
1481
+ },
1482
+ isActive: (props) => props[`${type}LengthsToHide`].enabled,
1483
+ handler: (props) => {
1484
+ props[`toggle${startCase(type)}LengthsToHide`]();
1485
+ }
1486
+ };
1487
+ }
1488
+
1489
+ function getFilterIndividualCmd(type) {
1490
+ const pluralType = pluralize(type);
1491
+ const upperType = startCase(type);
1492
+ return {
1493
+ isHidden: (props) => {
1494
+ const total = Object.keys(
1495
+ reduce(
1496
+ props.sequenceData[pluralType],
1497
+ (acc, feat) => {
1498
+ acc[feat.id] = true;
1499
+ return acc;
1500
+ },
1501
+ {}
1502
+ )
1503
+ ).length;
1504
+ return total > 500;
1505
+ },
1506
+ name: (props) => {
1507
+ const total = Object.keys(
1508
+ reduce(
1509
+ props.sequenceData[pluralType],
1510
+ (acc, feat) => {
1511
+ acc[feat.id] = true;
1512
+ return acc;
1513
+ },
1514
+ {}
1515
+ )
1516
+ ).length;
1517
+ const toHideCount = Object.keys(
1518
+ props.annotationVisibility[`${type}IndividualToHide`]
1519
+ ).length;
1520
+ return (
1521
+ <span>
1522
+ Filter Individually &nbsp;
1523
+ <Tag className="tg-smallTag" round style={{ marginLeft: 4 }}>
1524
+ {total - toHideCount}/{total}
1525
+ </Tag>
1526
+ </span>
1527
+ );
1528
+ },
1529
+ submenu: (props) => {
1530
+ const individualAnns = {};
1531
+ forEach(
1532
+ sortBy(props.sequenceData[pluralType], ({ start }) => start + 1),
1533
+ (ann) => {
1534
+ if (!ann.id) return;
1535
+ const checked =
1536
+ !props.annotationVisibility[`${type}IndividualToHide`][ann.id];
1537
+ if (individualAnns[ann.id]) {
1538
+ console.error(`ann.id:`, ann.id);
1539
+ console.error(`we should not be here!`);
1540
+ } else {
1541
+ individualAnns[ann.id] = {
1542
+ shouldDismissPopover: false,
1543
+ onClick: () =>
1544
+ checked
1545
+ ? props[`hide${upperType}Individual`]([ann.id])
1546
+ : props[`show${upperType}Individual`]([ann.id]),
1547
+ checked
1548
+ };
1549
+ }
1550
+ individualAnns[ann.id].text = (
1551
+ <span style={{ display: "flex", justifyContent: "space-between" }}>
1552
+ {ann.name} &nbsp;{" "}
1553
+ <span style={{ fontSize: 10 }}>
1554
+ ({ann.start + 1}-{ann.end + 1})
1555
+ </span>
1556
+ </span>
1557
+ );
1558
+ }
1559
+ );
1560
+ const menu = map(individualAnns);
1561
+ return [
1562
+ {
1563
+ text: "Uncheck All",
1564
+ onClick: () =>
1565
+ props[`hide${upperType}Individual`](Object.keys(individualAnns)),
1566
+ shouldDismissPopover: false
1567
+ },
1568
+ {
1569
+ text: "Check All",
1570
+ onClick: () => props[`reset${upperType}IndividualToHide`](),
1571
+ shouldDismissPopover: false
1572
+ },
1573
+ "--",
1574
+ ...(menu.length
1575
+ ? menu
1576
+ : [
1577
+ {
1578
+ text: `No ${upperType}s To Filter`,
1579
+ disabled: true
1580
+ }
1581
+ ])
1582
+ ];
1583
+ }
1584
+ };
1585
+ }