jbrowse-plugin-msaview 3.5.0 → 3.7.0

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 (242) hide show
  1. package/README.md +8 -0
  2. package/dist/AddHighlightModel/index.js +3 -3
  3. package/dist/BgzipFastaMsaAdapter/BgzipFastaMsaAdapter.d.ts +8 -2
  4. package/dist/BgzipFastaMsaAdapter/configSchema.d.ts +3 -3
  5. package/dist/BgzipFastaMsaAdapter/configSchema.js +1 -1
  6. package/dist/LaunchMsaView/cleanProteinSequence.test.d.ts +1 -0
  7. package/dist/LaunchMsaView/cleanProteinSequence.test.js +77 -0
  8. package/dist/LaunchMsaView/codingFeature.d.ts +14 -0
  9. package/dist/LaunchMsaView/codingFeature.js +38 -0
  10. package/dist/LaunchMsaView/codingFeature.test.d.ts +1 -0
  11. package/dist/LaunchMsaView/codingFeature.test.js +75 -0
  12. package/dist/LaunchMsaView/components/BlastQuery/BlastAutomaticPanel.d.ts +3 -1
  13. package/dist/LaunchMsaView/components/BlastQuery/BlastAutomaticPanel.js +47 -55
  14. package/dist/LaunchMsaView/components/BlastQuery/BlastManualPanel.d.ts +3 -1
  15. package/dist/LaunchMsaView/components/BlastQuery/BlastManualPanel.js +16 -17
  16. package/dist/LaunchMsaView/components/BlastQuery/BlastPanel.d.ts +7 -3
  17. package/dist/LaunchMsaView/components/BlastQuery/BlastPanel.js +8 -7
  18. package/dist/LaunchMsaView/components/BlastQuery/BlastSettingsDialog.js +2 -2
  19. package/dist/LaunchMsaView/components/BlastQuery/CachedBlastResults.js +14 -13
  20. package/dist/LaunchMsaView/components/BlastQuery/consts.d.ts +2 -0
  21. package/dist/LaunchMsaView/components/BlastQuery/consts.js +28 -4
  22. package/dist/LaunchMsaView/components/HelpButton.d.ts +2 -0
  23. package/dist/LaunchMsaView/components/HelpButton.js +17 -0
  24. package/dist/LaunchMsaView/components/HelpDialog.d.ts +4 -0
  25. package/dist/LaunchMsaView/components/HelpDialog.js +18 -0
  26. package/dist/LaunchMsaView/components/LaunchMsaViewDialog.d.ts +3 -1
  27. package/dist/LaunchMsaView/components/LaunchMsaViewDialog.js +21 -16
  28. package/dist/LaunchMsaView/components/ManualMSALoader/ManualMSALoader.d.ts +3 -1
  29. package/dist/LaunchMsaView/components/ManualMSALoader/ManualMSALoader.js +34 -26
  30. package/dist/LaunchMsaView/components/OrthologQuery/OrthologPanel.d.ts +3 -1
  31. package/dist/LaunchMsaView/components/OrthologQuery/OrthologPanel.js +30 -26
  32. package/dist/LaunchMsaView/components/OrthologQuery/OrthologSourceSelect.d.ts +2 -1
  33. package/dist/LaunchMsaView/components/OrthologQuery/OrthologSourceSelect.js +18 -2
  34. package/dist/LaunchMsaView/components/OrthologQuery/OrthologSourceSelect.test.d.ts +1 -0
  35. package/dist/LaunchMsaView/components/OrthologQuery/OrthologSourceSelect.test.js +13 -0
  36. package/dist/LaunchMsaView/components/PreLoadedMSA/PreLoadedMSADataPanel.d.ts +3 -1
  37. package/dist/LaunchMsaView/components/PreLoadedMSA/PreLoadedMSADataPanel.js +50 -27
  38. package/dist/LaunchMsaView/components/SequenceStatus.d.ts +14 -0
  39. package/dist/LaunchMsaView/components/SequenceStatus.js +15 -0
  40. package/dist/LaunchMsaView/components/SubmitCancelActions.d.ts +6 -2
  41. package/dist/LaunchMsaView/components/SubmitCancelActions.js +28 -13
  42. package/dist/LaunchMsaView/components/SubmitCancelActions.test.js +52 -4
  43. package/dist/LaunchMsaView/components/TabPanel.js +15 -3
  44. package/dist/LaunchMsaView/components/TabPanel.test.d.ts +1 -0
  45. package/dist/LaunchMsaView/components/TabPanel.test.js +36 -0
  46. package/dist/LaunchMsaView/components/TranscriptSelector.d.ts +3 -1
  47. package/dist/LaunchMsaView/components/TranscriptSelector.js +4 -2
  48. package/dist/LaunchMsaView/components/calculateProteinSequence.d.ts +12 -2
  49. package/dist/LaunchMsaView/components/calculateProteinSequence.js +38 -23
  50. package/dist/LaunchMsaView/components/fetchSeq.d.ts +4 -1
  51. package/dist/LaunchMsaView/components/fetchSeq.js +14 -4
  52. package/dist/LaunchMsaView/components/launchConnectedView.d.ts +23 -0
  53. package/dist/LaunchMsaView/components/launchConnectedView.js +39 -0
  54. package/dist/LaunchMsaView/components/launchPlacement.d.ts +8 -0
  55. package/dist/LaunchMsaView/components/launchPlacement.js +25 -0
  56. package/dist/LaunchMsaView/components/useFeatureSequence.d.ts +2 -0
  57. package/dist/LaunchMsaView/components/useFeatureSequence.js +14 -11
  58. package/dist/LaunchMsaView/components/useTranscriptSelection.d.ts +10 -1
  59. package/dist/LaunchMsaView/components/useTranscriptSelection.js +21 -3
  60. package/dist/LaunchMsaView/components/useTranscriptSelection.test.d.ts +1 -0
  61. package/dist/LaunchMsaView/components/useTranscriptSelection.test.js +57 -0
  62. package/dist/LaunchMsaView/detectQueryRow.js +4 -7
  63. package/dist/LaunchMsaView/index.js +28 -9
  64. package/dist/LaunchMsaView/launchTarget.d.ts +36 -8
  65. package/dist/LaunchMsaView/launchTarget.js +23 -13
  66. package/dist/LaunchMsaView/launchTarget.test.js +80 -15
  67. package/dist/LaunchMsaView/useQueryRowName.d.ts +12 -0
  68. package/dist/LaunchMsaView/useQueryRowName.js +11 -0
  69. package/dist/LaunchMsaView/useQueryRowName.test.js +18 -1
  70. package/dist/LaunchMsaView/util.d.ts +6 -0
  71. package/dist/LaunchMsaView/util.js +12 -3
  72. package/dist/LaunchMsaViewExtensionPoint/index.test.js +15 -0
  73. package/dist/MsaViewPanel/afterCreateAutoruns.d.ts +6 -4
  74. package/dist/MsaViewPanel/afterCreateAutoruns.js +38 -14
  75. package/dist/MsaViewPanel/applyRegion.d.ts +23 -0
  76. package/dist/MsaViewPanel/applyRegion.js +19 -0
  77. package/dist/MsaViewPanel/applyRegion.test.d.ts +1 -0
  78. package/dist/MsaViewPanel/applyRegion.test.js +45 -0
  79. package/dist/MsaViewPanel/components/LaunchProgress.d.ts +4 -3
  80. package/dist/MsaViewPanel/components/LaunchProgress.js +10 -7
  81. package/dist/MsaViewPanel/components/MsaViewPanel.js +7 -3
  82. package/dist/MsaViewPanel/components/MsaViewPanel.test.js +24 -0
  83. package/dist/MsaViewPanel/doLaunchBlast.js +2 -1
  84. package/dist/MsaViewPanel/doLaunchBlast.test.js +12 -0
  85. package/dist/MsaViewPanel/doLaunchOrthologs.test.js +1 -1
  86. package/dist/MsaViewPanel/genomeToMSA.js +24 -6
  87. package/dist/MsaViewPanel/genomeToMSA.test.js +218 -44
  88. package/dist/MsaViewPanel/launchAutoruns.test.d.ts +1 -0
  89. package/dist/MsaViewPanel/launchAutoruns.test.js +36 -0
  90. package/dist/MsaViewPanel/model.d.ts +156 -61
  91. package/dist/MsaViewPanel/model.js +69 -3
  92. package/dist/MsaViewPanel/model.test.js +66 -0
  93. package/dist/MsaViewPanel/msaDataStore.d.ts +5 -3
  94. package/dist/MsaViewPanel/msaDataStore.js +3 -12
  95. package/dist/MsaViewPanel/msaDataStore.test.js +8 -1
  96. package/dist/MsaViewPanel/observeProteinHighlights.test.js +4 -4
  97. package/dist/MsaViewPanel/regionSnapshot.test.d.ts +1 -0
  98. package/dist/MsaViewPanel/regionSnapshot.test.js +29 -0
  99. package/dist/MsaViewPanel/resolveConnectedTranscript.d.ts +41 -4
  100. package/dist/MsaViewPanel/resolveConnectedTranscript.js +109 -68
  101. package/dist/MsaViewPanel/resolveConnectedTranscript.test.d.ts +1 -0
  102. package/dist/MsaViewPanel/resolveConnectedTranscript.test.js +149 -0
  103. package/dist/MsaViewPanel/runLaunch.d.ts +8 -4
  104. package/dist/MsaViewPanel/runLaunch.js +15 -4
  105. package/dist/MsaViewPanel/runLaunch.test.js +16 -0
  106. package/dist/MsaViewPanel/storedData.test.js +43 -0
  107. package/dist/MsaViewPanel/syncGenomeHoverToMsaColumn.test.js +15 -7
  108. package/dist/MsaViewPanel/util.d.ts +23 -23
  109. package/dist/MsaViewPanel/util.js +23 -24
  110. package/dist/index.d.ts +11 -11
  111. package/dist/index.js +17 -10
  112. package/dist/jbrowse-plugin-msaview.umd.production.min.js +54 -67
  113. package/dist/jbrowse-plugin-msaview.umd.production.min.js.map +4 -4
  114. package/dist/utils/blastCache.d.ts +26 -2
  115. package/dist/utils/blastCache.js +51 -39
  116. package/dist/utils/blastCache.test.js +46 -19
  117. package/dist/utils/domainCache.d.ts +1 -1
  118. package/dist/utils/domainCache.js +18 -14
  119. package/dist/utils/ebiJobDispatcher.d.ts +11 -0
  120. package/dist/utils/ebiJobDispatcher.js +12 -13
  121. package/dist/utils/ebiJobDispatcher.test.js +17 -3
  122. package/dist/utils/eutils.d.ts +14 -0
  123. package/dist/utils/eutils.js +48 -14
  124. package/dist/utils/eutils.test.d.ts +1 -0
  125. package/dist/utils/eutils.test.js +31 -0
  126. package/dist/utils/idb.d.ts +16 -3
  127. package/dist/utils/idb.js +51 -5
  128. package/dist/utils/idb.test.d.ts +1 -0
  129. package/dist/utils/idb.test.js +78 -0
  130. package/dist/utils/ncbiDomains.js +4 -4
  131. package/dist/utils/ncbiOrthologs.d.ts +1 -1
  132. package/dist/utils/ncbiOrthologs.js +18 -17
  133. package/dist/utils/ncbiOrthologs.test.js +17 -1
  134. package/dist/utils/ncbiTaxonomy.js +14 -15
  135. package/dist/utils/taxonomyNames.js +23 -19
  136. package/dist/utils/taxonomyNames.test.d.ts +1 -0
  137. package/dist/utils/taxonomyNames.test.js +33 -0
  138. package/dist/utils/unirefHomologs.js +2 -2
  139. package/dist/utils/unirefHomologs.test.js +8 -1
  140. package/dist/version.d.ts +1 -1
  141. package/dist/version.js +1 -1
  142. package/package.json +23 -23
  143. package/src/AddHighlightModel/index.tsx +1 -1
  144. package/src/BgzipFastaMsaAdapter/configSchema.ts +1 -1
  145. package/src/LaunchMsaView/cleanProteinSequence.test.ts +91 -0
  146. package/src/LaunchMsaView/codingFeature.test.ts +96 -0
  147. package/src/LaunchMsaView/codingFeature.ts +49 -0
  148. package/src/LaunchMsaView/components/BlastQuery/BlastAutomaticPanel.tsx +60 -56
  149. package/src/LaunchMsaView/components/BlastQuery/BlastManualPanel.tsx +18 -15
  150. package/src/LaunchMsaView/components/BlastQuery/BlastPanel.tsx +24 -11
  151. package/src/LaunchMsaView/components/BlastQuery/BlastSettingsDialog.tsx +4 -4
  152. package/src/LaunchMsaView/components/BlastQuery/CachedBlastResults.tsx +16 -15
  153. package/src/LaunchMsaView/components/BlastQuery/consts.ts +31 -4
  154. package/src/LaunchMsaView/components/HelpButton.tsx +33 -0
  155. package/src/LaunchMsaView/components/HelpDialog.tsx +78 -0
  156. package/src/LaunchMsaView/components/LaunchMsaViewDialog.tsx +54 -34
  157. package/src/LaunchMsaView/components/ManualMSALoader/ManualMSALoader.tsx +46 -23
  158. package/src/LaunchMsaView/components/OrthologQuery/OrthologPanel.tsx +41 -28
  159. package/src/LaunchMsaView/components/OrthologQuery/OrthologSourceSelect.test.ts +16 -0
  160. package/src/LaunchMsaView/components/OrthologQuery/OrthologSourceSelect.tsx +24 -2
  161. package/src/LaunchMsaView/components/PreLoadedMSA/PreLoadedMSADataPanel.tsx +81 -25
  162. package/src/LaunchMsaView/components/SequenceStatus.tsx +29 -0
  163. package/src/LaunchMsaView/components/SubmitCancelActions.test.tsx +88 -4
  164. package/src/LaunchMsaView/components/SubmitCancelActions.tsx +43 -16
  165. package/src/LaunchMsaView/components/TabPanel.test.tsx +51 -0
  166. package/src/LaunchMsaView/components/TabPanel.tsx +16 -4
  167. package/src/LaunchMsaView/components/TranscriptSelector.tsx +6 -1
  168. package/src/LaunchMsaView/components/calculateProteinSequence.ts +51 -22
  169. package/src/LaunchMsaView/components/fetchSeq.ts +25 -5
  170. package/src/LaunchMsaView/components/launchConnectedView.ts +56 -0
  171. package/src/LaunchMsaView/components/launchPlacement.tsx +41 -0
  172. package/src/LaunchMsaView/components/useFeatureSequence.ts +18 -11
  173. package/src/LaunchMsaView/components/useTranscriptSelection.test.tsx +66 -0
  174. package/src/LaunchMsaView/components/useTranscriptSelection.ts +41 -2
  175. package/src/LaunchMsaView/detectQueryRow.ts +5 -7
  176. package/src/LaunchMsaView/index.ts +32 -13
  177. package/src/LaunchMsaView/launchTarget.test.ts +88 -15
  178. package/src/LaunchMsaView/launchTarget.ts +49 -14
  179. package/src/LaunchMsaView/useQueryRowName.test.ts +28 -1
  180. package/src/LaunchMsaView/useQueryRowName.ts +17 -0
  181. package/src/LaunchMsaView/util.ts +15 -5
  182. package/src/LaunchMsaViewExtensionPoint/index.test.ts +16 -0
  183. package/src/LaunchMsaViewExtensionPoint/index.ts +28 -1
  184. package/src/MsaViewPanel/afterCreateAutoruns.ts +46 -14
  185. package/src/MsaViewPanel/applyRegion.test.ts +55 -0
  186. package/src/MsaViewPanel/applyRegion.ts +33 -0
  187. package/src/MsaViewPanel/components/LaunchProgress.tsx +12 -5
  188. package/src/MsaViewPanel/components/MsaViewPanel.test.tsx +26 -0
  189. package/src/MsaViewPanel/components/MsaViewPanel.tsx +7 -5
  190. package/src/MsaViewPanel/doLaunchBlast.test.ts +20 -1
  191. package/src/MsaViewPanel/doLaunchBlast.ts +2 -1
  192. package/src/MsaViewPanel/doLaunchOrthologs.test.ts +1 -1
  193. package/src/MsaViewPanel/genomeToMSA.test.ts +255 -59
  194. package/src/MsaViewPanel/genomeToMSA.ts +30 -5
  195. package/src/MsaViewPanel/launchAutoruns.test.ts +45 -0
  196. package/src/MsaViewPanel/model.test.ts +75 -0
  197. package/src/MsaViewPanel/model.ts +76 -3
  198. package/src/MsaViewPanel/msaDataStore.test.ts +14 -2
  199. package/src/MsaViewPanel/msaDataStore.ts +4 -12
  200. package/src/MsaViewPanel/observeProteinHighlights.test.ts +4 -4
  201. package/src/MsaViewPanel/regionSnapshot.test.ts +34 -0
  202. package/src/MsaViewPanel/resolveConnectedTranscript.test.ts +183 -0
  203. package/src/MsaViewPanel/resolveConnectedTranscript.ts +160 -67
  204. package/src/MsaViewPanel/runLaunch.test.ts +19 -0
  205. package/src/MsaViewPanel/runLaunch.ts +21 -4
  206. package/src/MsaViewPanel/storedData.test.ts +58 -0
  207. package/src/MsaViewPanel/syncGenomeHoverToMsaColumn.test.ts +17 -7
  208. package/src/MsaViewPanel/util.ts +38 -29
  209. package/src/index.ts +19 -10
  210. package/src/utils/blastCache.test.ts +59 -20
  211. package/src/utils/blastCache.ts +62 -39
  212. package/src/utils/domainCache.ts +27 -15
  213. package/src/utils/ebiJobDispatcher.test.ts +18 -3
  214. package/src/utils/ebiJobDispatcher.ts +12 -16
  215. package/src/utils/eutils.test.ts +39 -0
  216. package/src/utils/eutils.ts +64 -14
  217. package/src/utils/idb.test.ts +106 -0
  218. package/src/utils/idb.ts +57 -5
  219. package/src/utils/ncbiDomains.ts +4 -4
  220. package/src/utils/ncbiOrthologs.test.ts +20 -0
  221. package/src/utils/ncbiOrthologs.ts +26 -28
  222. package/src/utils/ncbiTaxonomy.ts +17 -20
  223. package/src/utils/taxonomyNames.test.ts +41 -0
  224. package/src/utils/taxonomyNames.ts +33 -19
  225. package/src/utils/unirefHomologs.test.ts +11 -1
  226. package/src/utils/unirefHomologs.ts +2 -2
  227. package/src/version.ts +1 -1
  228. package/dist/LaunchMsaView/components/BlastQuery/blastLaunchView.d.ts +0 -16
  229. package/dist/LaunchMsaView/components/BlastQuery/blastLaunchView.js +0 -31
  230. package/dist/LaunchMsaView/components/ManualMSALoader/launchView.d.ts +0 -16
  231. package/dist/LaunchMsaView/components/ManualMSALoader/launchView.js +0 -16
  232. package/dist/LaunchMsaView/components/OrthologQuery/orthologLaunchView.d.ts +0 -9
  233. package/dist/LaunchMsaView/components/OrthologQuery/orthologLaunchView.js +0 -15
  234. package/dist/LaunchMsaView/components/PreLoadedMSA/preCalculatedLaunchView.d.ts +0 -11
  235. package/dist/LaunchMsaView/components/PreLoadedMSA/preCalculatedLaunchView.js +0 -21
  236. package/dist/LaunchMsaView/components/geneticCodes.d.ts +0 -15
  237. package/dist/LaunchMsaView/components/geneticCodes.js +0 -227
  238. package/src/LaunchMsaView/components/BlastQuery/blastLaunchView.ts +0 -59
  239. package/src/LaunchMsaView/components/ManualMSALoader/launchView.ts +0 -43
  240. package/src/LaunchMsaView/components/OrthologQuery/orthologLaunchView.ts +0 -31
  241. package/src/LaunchMsaView/components/PreLoadedMSA/preCalculatedLaunchView.ts +0 -38
  242. package/src/LaunchMsaView/components/geneticCodes.ts +0 -298
@@ -1,31 +1,34 @@
1
- import React, { useState } from 'react';
1
+ import React, { useMemo, useState } from 'react';
2
2
  import { LoadingEllipses, SanitizedHTML } from '@jbrowse/core/ui';
3
3
  import { getEnv, getSession } from '@jbrowse/core/util';
4
- import { MenuItem } from '@mui/material';
4
+ import { MenuItem, Typography } from '@mui/material';
5
5
  import { observer } from 'mobx-react';
6
6
  import { makeStyles } from 'tss-react/mui';
7
7
  import TextField2 from '../../../components/TextField2';
8
8
  import { useFetch } from '../../../utils/useFetch';
9
- import { getGeneDisplayName, getLinearGenomeView } from '../../util';
9
+ import { resolveQueryRowName, useQueryRowName } from '../../useQueryRowName';
10
+ import { getGeneDisplayName, getLinearGenomeView, getTranscriptDisplayName, } from '../../util';
10
11
  import LaunchPanelContent from '../LaunchPanelContent';
12
+ import QueryRowSelector from '../QueryRowSelector';
13
+ import SequenceStatusMessage from '../SequenceStatus';
11
14
  import SubmitCancelActions from '../SubmitCancelActions';
12
15
  import TranscriptSelector from '../TranscriptSelector';
16
+ import { launchConnectedView, useLaunchSubmit } from '../launchConnectedView';
13
17
  import { useTranscriptSelection } from '../useTranscriptSelection';
14
18
  import { fetchMSA, fetchMSAList } from './fetchMSAData';
15
- import { preCalculatedLaunchView } from './preCalculatedLaunchView';
16
19
  import { readMsaDatasets } from './types';
17
20
  const useStyles = makeStyles()({
18
21
  selectedContainer: {
19
22
  marginTop: 50,
20
23
  },
21
24
  });
22
- const PreLoadedMSA = observer(function ({ model, feature, handleClose, }) {
25
+ const PreLoadedMSA = observer(function ({ model, feature, handleClose, preferredTranscriptId, }) {
23
26
  const session = getSession(model);
24
27
  const view = getLinearGenomeView(model);
25
28
  const { classes } = useStyles();
26
29
  const { pluginManager } = getEnv(model);
27
30
  const { assemblyNames } = view;
28
- const [viewError, setViewError] = useState();
31
+ const { launchError, submit } = useLaunchSubmit(handleClose);
29
32
  const datasets = readMsaDatasets(session.jbrowse);
30
33
  const [selectedDatasetId, setSelectedDatasetId] = useState(datasets?.[0]?.datasetId);
31
34
  const selectedDataset = datasets?.find(d => d.datasetId === selectedDatasetId);
@@ -34,8 +37,9 @@ const PreLoadedMSA = observer(function ({ model, feature, handleClose, }) {
34
37
  feature,
35
38
  view,
36
39
  validIds: msaList,
40
+ preferredTranscriptId,
37
41
  });
38
- const { selectedId, selectedTranscript } = transcriptSelection;
42
+ const { selectedId, selectedTranscript, proteinSequence, sequenceStatus } = transcriptSelection;
39
43
  const { data: msaData, isLoading: msaDataLoading, error: msaDataFetchError, } = useFetch(selectedId && selectedDataset && msaList
40
44
  ? `${selectedDataset.datasetId}-${selectedId}-msa`
41
45
  : null, () => fetchMSA({
@@ -43,41 +47,60 @@ const PreLoadedMSA = observer(function ({ model, feature, handleClose, }) {
43
47
  config: selectedDataset.adapter,
44
48
  pluginManager,
45
49
  }));
50
+ const msaText = useMemo(() => msaData?.map(r => `>${r.get('refName')}\n${r.get('seq')}`).join('\n') ??
51
+ '', [msaData]);
52
+ // The dataset's row for this transcript used to be assumed -- the launch
53
+ // named `<transcriptId>_<assembly>` and hoped the file agreed. Nothing
54
+ // checked, and a name the alignment does not carry fails silently: the view
55
+ // opens, renders, and never navigates. The row is found by sequence instead,
56
+ // the same way the Manual tab finds it.
57
+ const queryRow = useQueryRowName(msaText, proteinSequence);
58
+ // The name this panel used to launch with unconditionally. A dataset built to
59
+ // that convention does carry the row, so it is worth falling back to when the
60
+ // residues do not match closely enough to find it -- but only when the
61
+ // alignment really has it, which is the check the old code never made.
62
+ const querySeqName = resolveQueryRowName(queryRow, `${selectedId}_${assemblyNames[0] ?? ''}`);
46
63
  const e = msaListFetchError ??
47
64
  msaDataFetchError ??
48
65
  transcriptSelection.error ??
49
- viewError;
66
+ launchError;
50
67
  return (React.createElement(React.Fragment, null,
51
68
  React.createElement(LaunchPanelContent, { error: e },
52
69
  React.createElement(TextField2, { select: true, label: "Select MSA dataset", value: selectedDatasetId, onChange: event => {
53
70
  setSelectedDatasetId(event.target.value);
54
71
  } }, datasets?.map(d => (React.createElement(MenuItem, { key: d.datasetId, value: d.datasetId }, d.name)))),
55
72
  selectedDataset ? (React.createElement("div", { className: classes.selectedContainer },
56
- !msaListLoading && msaDataLoading ? (React.createElement(LoadingEllipses, { variant: "h6", message: `Loading MSA for (${selectedId})` })) : null,
73
+ !msaListLoading && msaDataLoading ? (React.createElement(LoadingEllipses, { variant: "h6", message: `Loading MSA for ${getTranscriptDisplayName(selectedTranscript) || selectedId}` })) : null,
57
74
  msaListLoading ? (React.createElement(LoadingEllipses, { variant: "h6", message: `Loading available MSAs for (${selectedDataset.name})` })) : null,
58
75
  msaList ? (React.createElement("div", null,
59
76
  React.createElement(SanitizedHTML, { html: selectedDataset.description }),
60
- React.createElement(TranscriptSelector, { feature: feature, ...transcriptSelection }))) : null)) : null),
61
- React.createElement(SubmitCancelActions, { model: model, submitDisabled: !selectedTranscript || !msaData?.length, onSubmit: () => {
62
- try {
63
- if (selectedTranscript && msaData) {
64
- const querySeqName = `${selectedId}_${assemblyNames[0]}`;
65
- preCalculatedLaunchView({
66
- newViewTitle: getGeneDisplayName(selectedTranscript),
77
+ React.createElement(TranscriptSelector, { feature: feature, ...transcriptSelection }),
78
+ msaText ? (React.createElement(QueryRowSelector, { ...queryRow, querySeqName: querySeqName })) : null)) : null)) : null),
79
+ React.createElement(SubmitCancelActions, { model: model, hint: msaDataLoading ? (React.createElement(LoadingEllipses, { message: "Loading alignment" })) : !!msaData?.length && !querySeqName ? (React.createElement(Typography, { color: "textSecondary", variant: "body2" }, "no row matches this transcript")) : (React.createElement(SequenceStatusMessage, { status: sequenceStatus })),
80
+ // launching without a query row opens a view that renders and then
81
+ // never navigates, which reads as a broken feature rather than a
82
+ // dataset that does not cover this gene
83
+ submitDisabled: !selectedTranscript || !msaData?.length || !querySeqName, onSubmit: placement => {
84
+ if (selectedTranscript && msaText) {
85
+ submit(() => {
86
+ launchConnectedView({
67
87
  view,
68
- querySeqName,
69
88
  feature: selectedTranscript,
70
- data: {
71
- msa: msaData
72
- .map(r => `>${r.get('refName')}\n${r.get('seq')}`)
73
- .join('\n'),
74
- },
89
+ placement,
90
+ displayName: getGeneDisplayName(selectedTranscript),
91
+ treeAreaWidth: 200,
92
+ treeWidth: 100,
93
+ drawNodeBubbles: false,
94
+ labelsAlignRight: true,
95
+ showBranchLen: false,
96
+ colWidth: 10,
97
+ rowHeight: 12,
98
+ colorSchemeName: 'percent_identity_dynamic',
99
+ querySeqName,
100
+ querySeqOffset: queryRow.querySeqOffset,
101
+ data: { msa: msaText },
75
102
  });
76
- handleClose();
77
- }
78
- }
79
- catch (e) {
80
- setViewError(e);
103
+ });
81
104
  }
82
105
  }, onCancel: handleClose })));
83
106
  });
@@ -0,0 +1,14 @@
1
+ import React from 'react';
2
+ export type SequenceStatus = 'loading' | 'ready' | 'missing' | 'error';
3
+ /**
4
+ * Why Submit is grey. The query row is the selected transcript's translation,
5
+ * and fetching and translating it takes a round trip to the sequence adapter,
6
+ * so the button starts disabled on every panel — with nothing to distinguish
7
+ * "wait a moment" from "this gene has no protein and never will".
8
+ *
9
+ * An error says so through the panel's own ErrorMessage, so this stays quiet
10
+ * for that one rather than saying it twice.
11
+ */
12
+ export default function SequenceStatusMessage({ status, }: {
13
+ status: SequenceStatus;
14
+ }): React.JSX.Element | null;
@@ -0,0 +1,15 @@
1
+ import React from 'react';
2
+ import { LoadingEllipses } from '@jbrowse/core/ui';
3
+ import { Typography } from '@mui/material';
4
+ /**
5
+ * Why Submit is grey. The query row is the selected transcript's translation,
6
+ * and fetching and translating it takes a round trip to the sequence adapter,
7
+ * so the button starts disabled on every panel — with nothing to distinguish
8
+ * "wait a moment" from "this gene has no protein and never will".
9
+ *
10
+ * An error says so through the panel's own ErrorMessage, so this stays quiet
11
+ * for that one rather than saying it twice.
12
+ */
13
+ export default function SequenceStatusMessage({ status, }) {
14
+ return status === 'loading' ? (React.createElement(LoadingEllipses, { message: "Translating transcript" })) : status === 'missing' ? (React.createElement(Typography, { color: "textSecondary", variant: "body2" }, "no coding sequence to align")) : null;
15
+ }
@@ -1,9 +1,13 @@
1
1
  import React from 'react';
2
+ import type { MsaViewPlacement } from '../../utils/workspaces';
2
3
  import type { AbstractTrackModel } from '@jbrowse/core/util';
3
- export default function SubmitCancelActions({ onSubmit, onCancel, submitDisabled, submitLabel, cancelLabel, model, }: {
4
- onSubmit: () => void;
4
+ export default function SubmitCancelActions({ onSubmit, onCancel, submitDisabled, hint, submitLabel, cancelLabel, model, }: {
5
+ /** handed the placement the box states, so no launch re-reads storage */
6
+ onSubmit: (placement: MsaViewPlacement) => void;
5
7
  onCancel: () => void;
6
8
  submitDisabled?: boolean;
9
+ /** why Submit is grey, shown beside it */
10
+ hint?: React.ReactNode;
7
11
  submitLabel?: string;
8
12
  cancelLabel?: string;
9
13
  /** omitted by a panel that submits something other than a view launch */
@@ -1,7 +1,8 @@
1
- import React, { useState } from 'react';
1
+ import React from 'react';
2
2
  import { getSession } from '@jbrowse/core/util';
3
3
  import { Button, Checkbox, DialogActions, FormControlLabel, } from '@mui/material';
4
4
  import { readLaunchPlacement, sessionSupportsPlacement, writeLaunchPlacement, } from '../../utils/workspaces';
5
+ import { useLaunchPlacement } from './launchPlacement';
5
6
  /**
6
7
  * Where the launch puts the view, offered wherever a launch is submitted.
7
8
  *
@@ -13,21 +14,35 @@ import { readLaunchPlacement, sessionSupportsPlacement, writeLaunchPlacement, }
13
14
  * release that places views its own way — because the box would do nothing
14
15
  * there and every launch would quietly ignore it.
15
16
  */
16
- function PlacementToggle({ model }) {
17
- const session = getSession(model);
18
- const [sideBySide, setSideBySide] = useState(() => readLaunchPlacement() === 'splitRight');
19
- return sessionSupportsPlacement(session) ? (React.createElement(FormControlLabel, { label: "Open beside the genome view", control: React.createElement(Checkbox, { checked: sideBySide, onChange: event => {
20
- const { checked } = event.target;
21
- setSideBySide(checked);
22
- writeLaunchPlacement(checked ? 'splitRight' : 'stack');
23
- } }) })) : null;
17
+ function PlacementToggle({ checked, onChange, }) {
18
+ return (React.createElement(FormControlLabel, { label: "Open beside the genome view", control: React.createElement(Checkbox, { checked: checked, onChange: event => {
19
+ onChange(event.target.checked);
20
+ } }) }));
24
21
  }
25
- export default function SubmitCancelActions({ onSubmit, onCancel, submitDisabled, submitLabel = 'Submit', cancelLabel = 'Cancel', model, }) {
22
+ export default function SubmitCancelActions({ onSubmit, onCancel, submitDisabled, hint, submitLabel = 'Submit', cancelLabel = 'Cancel', model, }) {
23
+ const [sideBySide, setSideBySide] = useLaunchPlacement();
24
+ // The stored value is what the next dialog opens on, so it is written on
25
+ // submit rather than on the click: a dialog the user backed out of must not
26
+ // move where every future launch lands.
27
+ const offerPlacement = !!model && sessionSupportsPlacement(getSession(model));
26
28
  return (React.createElement(DialogActions, { sx: { flexWrap: 'wrap', rowGap: 1 } },
27
- model ? React.createElement(PlacementToggle, { model: model }) : null,
28
- React.createElement("div", { style: { display: 'flex', gap: 8, marginLeft: 'auto' } },
29
+ offerPlacement ? (React.createElement(PlacementToggle, { checked: sideBySide, onChange: setSideBySide })) : null,
30
+ React.createElement("div", { style: {
31
+ display: 'flex',
32
+ alignItems: 'center',
33
+ gap: 8,
34
+ marginLeft: 'auto',
35
+ } },
36
+ hint,
29
37
  React.createElement(Button, { sx: { flexShrink: 0 }, color: "primary", variant: "contained", disabled: submitDisabled, onClick: () => {
30
- onSubmit();
38
+ if (offerPlacement) {
39
+ const placement = sideBySide ? 'splitRight' : 'stack';
40
+ writeLaunchPlacement(placement);
41
+ onSubmit(placement);
42
+ }
43
+ else {
44
+ onSubmit(readLaunchPlacement());
45
+ }
31
46
  } }, submitLabel),
32
47
  React.createElement(Button, { sx: { flexShrink: 0 }, color: "secondary", variant: "contained", onClick: () => {
33
48
  onCancel();
@@ -4,6 +4,7 @@ import { cleanup, render, screen } from '@testing-library/react';
4
4
  import { afterEach, beforeEach, expect, test, vi } from 'vitest';
5
5
  import { LAUNCH_PLACEMENT_KEY } from '../../utils/workspaces';
6
6
  import SubmitCancelActions from './SubmitCancelActions';
7
+ import { LaunchPlacementProvider } from './launchPlacement';
7
8
  // getSession walks the MST tree, and this component only wants the two actions
8
9
  // off the far end of that walk
9
10
  vi.mock('@jbrowse/core/util', () => ({
@@ -39,14 +40,61 @@ test('a panel that launches nothing passes no model, and gets no box', () => {
39
40
  render(React.createElement(SubmitCancelActions, { onSubmit: () => { }, onCancel: () => { } }));
40
41
  expect(toggle()).toBeNull();
41
42
  });
42
- test('clicking it writes the placement the next launch will read', () => {
43
- render(React.createElement(SubmitCancelActions, { model: trackModel(tiling), onSubmit: () => { }, onCancel: () => { } }));
43
+ test('submitting hands the launch the placement and remembers it', () => {
44
+ const onSubmit = vi.fn();
45
+ render(React.createElement(SubmitCancelActions, { model: trackModel(tiling), onSubmit: onSubmit, onCancel: () => { } }));
44
46
  toggle().click();
45
- expect(localStorage.getItem(LAUNCH_PLACEMENT_KEY)).toBe('stack');
46
47
  expect(toggle().checked).toBe(false);
48
+ screen.getByText('Submit').click();
49
+ expect(onSubmit).toHaveBeenCalledWith('stack');
50
+ expect(localStorage.getItem(LAUNCH_PLACEMENT_KEY)).toBe('stack');
51
+ });
52
+ // an unticked box used to reach the launch only through storage, so a browser
53
+ // refusing localStorage launched side by side anyway
54
+ test('the box decides the launch even where storage throws', () => {
55
+ const onSubmit = vi.fn();
56
+ const setItem = vi
57
+ .spyOn(Storage.prototype, 'setItem')
58
+ .mockImplementation(() => {
59
+ throw new Error('SecurityError');
60
+ });
61
+ vi.spyOn(console, 'error').mockImplementation(() => { });
62
+ render(React.createElement(SubmitCancelActions, { model: trackModel(tiling), onSubmit: onSubmit, onCancel: () => { } }));
63
+ toggle().click();
64
+ screen.getByText('Submit').click();
65
+ expect(onSubmit).toHaveBeenCalledWith('stack');
66
+ setItem.mockRestore();
67
+ });
68
+ // the box is a property of this launch until it is launched; a dialog the user
69
+ // backed out of should not have moved where every future one lands
70
+ test('cancelling leaves the stored placement alone', () => {
71
+ render(React.createElement(SubmitCancelActions, { model: trackModel(tiling), onSubmit: () => { }, onCancel: () => { } }));
47
72
  toggle().click();
73
+ screen.getByText('Cancel').click();
74
+ expect(localStorage.getItem(LAUNCH_PLACEMENT_KEY)).toBeNull();
75
+ });
76
+ // a host with no tiling never shows the box, so submitting there must not
77
+ // overwrite the choice the user made on a host that does
78
+ test('a host that cannot tile writes nothing on submit', () => {
79
+ localStorage.setItem(LAUNCH_PLACEMENT_KEY, 'splitRight');
80
+ render(React.createElement(SubmitCancelActions, { model: trackModel({}), onSubmit: () => { }, onCancel: () => { } }));
81
+ screen.getByText('Submit').click();
48
82
  expect(localStorage.getItem(LAUNCH_PLACEMENT_KEY)).toBe('splitRight');
49
- expect(toggle().checked).toBe(true);
83
+ });
84
+ // every visited tab stays mounted, so each has an actions row of its own; with
85
+ // the answer held per row, ticking the box on one tab and submitting from
86
+ // another wrote the other tab's stale one
87
+ test('every tab in one dialog shares the answer', () => {
88
+ render(React.createElement(LaunchPlacementProvider, null,
89
+ React.createElement("div", { "data-testid": "tab-a" },
90
+ React.createElement(SubmitCancelActions, { model: trackModel(tiling), onSubmit: () => { }, onCancel: () => { } })),
91
+ React.createElement("div", { "data-testid": "tab-b" },
92
+ React.createElement(SubmitCancelActions, { model: trackModel(tiling), onSubmit: () => { }, onCancel: () => { }, submitLabel: "Launch" }))));
93
+ const boxes = screen.getAllByRole('checkbox');
94
+ boxes[0].click();
95
+ expect(boxes[1].checked).toBe(false);
96
+ screen.getByText('Launch').click();
97
+ expect(localStorage.getItem(LAUNCH_PLACEMENT_KEY)).toBe('stack');
50
98
  });
51
99
  test('a stored choice is what the box opens on', () => {
52
100
  localStorage.setItem(LAUNCH_PLACEMENT_KEY, 'stack');
@@ -1,5 +1,17 @@
1
- import React from 'react';
2
- // this is from MUI example
1
+ import React, { useState } from 'react';
2
+ // Once visited, a panel stays mounted and is hidden with the `hidden` attribute.
3
+ // Unmounting it discarded whatever the user had typed and re-ran every fetch the
4
+ // panel makes on the way back, so switching tabs to compare two of them cost an
5
+ // EBI round trip and the pasted alignment.
6
+ //
7
+ // Lazy on first visit rather than mounted up front, because a panel mounts
8
+ // fetches of its own: rendering all four on open would query the MSA dataset
9
+ // adapter and the BLAST cache for tabs nobody looked at.
3
10
  export default function TabPanel({ children, value, index, ...other }) {
4
- return (React.createElement("div", { role: "tabpanel", hidden: value !== index, ...other }, value === index ? children : null));
11
+ const active = value === index;
12
+ const [visited, setVisited] = useState(active);
13
+ if (active && !visited) {
14
+ setVisited(true);
15
+ }
16
+ return (React.createElement("div", { role: "tabpanel", hidden: !active, ...other }, visited || active ? children : null));
5
17
  }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,36 @@
1
+ // @vitest-environment jsdom
2
+ import React from 'react';
3
+ import { cleanup, render, screen } from '@testing-library/react';
4
+ import { afterEach, expect, test, vi } from 'vitest';
5
+ import TabPanel from './TabPanel';
6
+ afterEach(() => {
7
+ cleanup();
8
+ });
9
+ function Panels({ value }) {
10
+ return (React.createElement(React.Fragment, null,
11
+ React.createElement(TabPanel, { value: value, index: "a" },
12
+ React.createElement(Probe, { name: "a" })),
13
+ React.createElement(TabPanel, { value: value, index: "b" },
14
+ React.createElement(Probe, { name: "b" }))));
15
+ }
16
+ const mounted = vi.fn();
17
+ function Probe({ name }) {
18
+ React.useEffect(() => {
19
+ mounted(name);
20
+ }, [name]);
21
+ return React.createElement("input", { defaultValue: name });
22
+ }
23
+ test('a visited panel keeps its state while another tab is shown', () => {
24
+ const { rerender } = render(React.createElement(Panels, { value: "a" }));
25
+ const typed = screen.getByDisplayValue('a');
26
+ typed.value = 'edited';
27
+ rerender(React.createElement(Panels, { value: "b" }));
28
+ rerender(React.createElement(Panels, { value: "a" }));
29
+ expect(screen.getByDisplayValue('edited')).toBeTruthy();
30
+ expect(mounted.mock.calls.filter(([n]) => n === 'a')).toHaveLength(1);
31
+ });
32
+ test('an unvisited panel does not mount, so it fetches nothing', () => {
33
+ mounted.mockClear();
34
+ render(React.createElement(Panels, { value: "a" }));
35
+ expect(mounted.mock.calls.map(([n]) => n)).toEqual(['a']);
36
+ });
@@ -1,11 +1,13 @@
1
1
  import React from 'react';
2
+ import type { SequenceStatus } from './SequenceStatus';
2
3
  import type { Feature } from '@jbrowse/core/util';
3
- export default function TranscriptSelector({ feature, options, selectedId, selectedTranscript, setSelectedId, proteinSequence, validIds, }: {
4
+ export default function TranscriptSelector({ feature, options, selectedId, selectedTranscript, setSelectedId, proteinSequence, sequenceStatus, validIds, }: {
4
5
  feature: Feature;
5
6
  options: Feature[];
6
7
  selectedId: string;
7
8
  selectedTranscript: Feature | undefined;
8
9
  setSelectedId: (transcriptId: string) => void;
9
10
  proteinSequence: string | undefined;
11
+ sequenceStatus?: SequenceStatus;
10
12
  validIds?: string[];
11
13
  }): React.JSX.Element;
@@ -15,7 +15,7 @@ const useStyles = makeStyles()({
15
15
  marginLeft: 20,
16
16
  },
17
17
  });
18
- export default function TranscriptSelector({ feature, options, selectedId, selectedTranscript, setSelectedId, proteinSequence, validIds, }) {
18
+ export default function TranscriptSelector({ feature, options, selectedId, selectedTranscript, setSelectedId, proteinSequence, sequenceStatus = 'ready', validIds, }) {
19
19
  const { classes } = useStyles();
20
20
  const [showSequence, setShowSequence] = useState(false);
21
21
  return (React.createElement(React.Fragment, null,
@@ -48,5 +48,7 @@ export default function TranscriptSelector({ feature, options, selectedId, selec
48
48
  } }, showSequence ? 'Hide sequence' : 'Show sequence'))),
49
49
  showSequence ? (React.createElement(ReadOnlyTextField2, { value: proteinSequence
50
50
  ? `>${getTranscriptDisplayName(selectedTranscript)}\n${proteinSequence}`
51
- : 'Loading...' })) : null));
51
+ : sequenceStatus === 'missing'
52
+ ? 'This transcript has no coding sequence, so there is nothing to translate.'
53
+ : 'Loading...' })) : null));
52
54
  }
@@ -1,9 +1,11 @@
1
1
  import type { Feat } from './types';
2
2
  import type { Feature } from '@jbrowse/core/util';
3
- export declare function calculateProteinSequence({ cds, sequence, geneticCodeId, }: {
3
+ import type { TranslExcept } from '@jbrowse/core/util/geneticCodes';
4
+ export declare function calculateProteinSequence({ cds, sequence, geneticCodeId, translExcept, }: {
4
5
  cds: Feat[];
5
6
  sequence: string;
6
7
  geneticCodeId?: number;
8
+ translExcept?: TranslExcept[];
7
9
  }): string;
8
10
  export declare function revlist(list: Feat[], seqlen: number): {
9
11
  start: number;
@@ -11,7 +13,15 @@ export declare function revlist(list: Feat[], seqlen: number): {
11
13
  type?: string;
12
14
  phase?: number;
13
15
  }[];
14
- export declare function getProteinSequenceFromFeature({ feature, seq, }: {
16
+ /**
17
+ * The translation core's own feature panel shows: the contig's or the
18
+ * feature's genetic code, its alternative initiators, and any `transl_except`
19
+ * (RefSeq's selenocysteines), read off the transcript or its CDS as core does.
20
+ */
21
+ export declare function getProteinSequenceFromFeature({ feature, seq, assemblyGeneticCodeId, }: {
15
22
  seq: string;
16
23
  feature: Feature;
24
+ /** the assembly's code for the feature's contig, `{ chrM: 2 }` in hub
25
+ * configs; a transl_table on the feature wins */
26
+ assemblyGeneticCodeId?: number;
17
27
  }): string;
@@ -1,26 +1,29 @@
1
- import { dedupe, revcom } from '@jbrowse/core/util';
1
+ import { revcom } from '@jbrowse/core/util';
2
2
  import { convertCodingSequenceToPeptides } from '@jbrowse/core/util/convertCodingSequenceToPeptides';
3
- import { getGeneticCode, parseTranslTable } from './geneticCodes';
4
- // `@jbrowse/core/util/convertCodingSequenceToPeptides` is a deep path, so unlike
5
- // the `@jbrowse/core/util` barrel it is absent from ReExports and gets bundled
6
- // rather than resolved out of the host's JBrowseExports. That is what makes
7
- // reusing core's translation safe across every host a config names: this module
8
- // previously built its codon table at module scope from the barrel's
9
- // `defaultCodonTable`, and a core build that dropped that export turned it into
10
- // `Object.keys(undefined)` while the UMD was still evaluating -- the plugin
11
- // global was never assigned and PluginLoader error-paged the whole app.
12
- export function calculateProteinSequence({ cds, sequence, geneticCodeId, }) {
13
- // `starts` is deliberately not passed: @jbrowse/core 4.3.0's signature has no
14
- // such parameter, so alternative initiators (GTG under table 11, ATA under
15
- // table 2) render as their internal residue rather than M. Core main added it;
16
- // pass it here when msaview's @jbrowse/core floor reaches that release.
17
- const { codonTable } = getGeneticCode(geneticCodeId);
3
+ import { getGeneticCode, parseTranslTable, relativizeTranslExcept, } from '@jbrowse/core/util/geneticCodes';
4
+ // `@jbrowse/core/util/convertCodingSequenceToPeptides` and
5
+ // `@jbrowse/core/util/geneticCodes` are deep paths absent from ReExports, so
6
+ // they are bundled rather than resolved out of the host's JBrowseExports: every
7
+ // host runs the version this build installs. Importing translation from the
8
+ // `@jbrowse/core/util` barrel instead is what error-paged the whole app when a
9
+ // core build dropped `defaultCodonTable`.
10
+ export function calculateProteinSequence({ cds, sequence, geneticCodeId, translExcept, }) {
11
+ const { codonTable, starts } = getGeneticCode(geneticCodeId);
18
12
  return convertCodingSequenceToPeptides({
19
13
  cds,
20
14
  sequence,
21
15
  codonTable,
16
+ starts,
17
+ translExcept,
22
18
  });
23
19
  }
20
+ // The CDS list is sorted by start, so adjacent comparison is the whole job.
21
+ function cdsId(feat) {
22
+ return `${feat.start}-${feat.end}`;
23
+ }
24
+ function dedupe(list) {
25
+ return list.filter((item, pos, ary) => !pos || cdsId(item) !== cdsId(ary[pos - 1]));
26
+ }
24
27
  export function revlist(list, seqlen) {
25
28
  return list
26
29
  .map(sub => ({
@@ -30,8 +33,13 @@ export function revlist(list, seqlen) {
30
33
  }))
31
34
  .toSorted((a, b) => a.start - b.start);
32
35
  }
33
- export function getProteinSequenceFromFeature({ feature, seq, }) {
34
- const { subfeatures, start, strand } = feature.toJSON();
36
+ /**
37
+ * The translation core's own feature panel shows: the contig's or the
38
+ * feature's genetic code, its alternative initiators, and any `transl_except`
39
+ * (RefSeq's selenocysteines), read off the transcript or its CDS as core does.
40
+ */
41
+ export function getProteinSequenceFromFeature({ feature, seq, assemblyGeneticCodeId, }) {
42
+ const { subfeatures, start, end, strand } = feature.toJSON();
35
43
  const cds = dedupe(subfeatures
36
44
  ?.toSorted((a, b) => a.start - b.start)
37
45
  .map(sub => ({
@@ -39,18 +47,25 @@ export function getProteinSequenceFromFeature({ feature, seq, }) {
39
47
  start: sub.start - start,
40
48
  end: sub.end - start,
41
49
  }))
42
- .filter(subfeature => subfeature.type === 'CDS') ?? [], feat => `${feat.start}-${feat.end}`);
43
- // a mitochondrial gene declares e.g. transl_table=2, so it translates with
44
- // NCBI table 2 rather than the standard code. GFF3 usually carries the
45
- // attribute on the CDS rather than the transcript, so check both.
50
+ .filter(subfeature => subfeature.type === 'CDS') ?? []);
46
51
  const cdsSubfeature = feature
47
52
  .get('subfeatures')
48
53
  ?.find((f) => f.get('type')?.toLowerCase() === 'cds');
49
54
  const geneticCodeId = parseTranslTable(feature.get('transl_table')) ??
50
- parseTranslTable(cdsSubfeature?.get('transl_table'));
55
+ parseTranslTable(cdsSubfeature?.get('transl_table')) ??
56
+ assemblyGeneticCodeId;
57
+ const rawTranslExcept = feature.get('transl_except') ?? cdsSubfeature?.get('transl_except');
51
58
  return calculateProteinSequence({
52
59
  cds: strand === -1 ? revlist(cds, seq.length) : cds,
53
60
  sequence: strand === -1 ? revcom(seq) : seq,
54
61
  geneticCodeId,
62
+ translExcept: rawTranslExcept
63
+ ? relativizeTranslExcept({
64
+ raw: rawTranslExcept,
65
+ featureStart: start,
66
+ featureLength: end - start,
67
+ strand,
68
+ })
69
+ : undefined,
55
70
  });
56
71
  }
@@ -5,4 +5,7 @@ export declare function fetchSeq({ start, end, refName, session, assemblyName, }
5
5
  refName: string;
6
6
  assemblyName: string;
7
7
  session: AbstractSessionModel;
8
- }): Promise<string>;
8
+ }): Promise<{
9
+ seq: string;
10
+ assemblyGeneticCodeId: number | undefined;
11
+ }>;
@@ -6,17 +6,27 @@ export async function fetchSeq({ start, end, refName, session, assemblyName, })
6
6
  throw new Error('assembly not found');
7
7
  }
8
8
  const sessionId = 'getSequence';
9
- const feats = (await rpcManager.call(sessionId, 'CoreGetFeatures', {
9
+ // a named object keeps sessionId, which v4 hosts read from the args
10
+ const args = {
10
11
  adapterConfig: getConf(assembly, ['sequence', 'adapter']),
11
12
  sessionId,
12
13
  regions: [
13
14
  {
14
15
  start,
15
16
  end,
16
- refName: assembly.getCanonicalRefName(refName),
17
+ refName: assembly.getCanonicalRefName(refName) ?? refName,
17
18
  assemblyName,
18
19
  },
19
20
  ],
20
- }));
21
- return feats[0]?.get('seq') ?? '';
21
+ };
22
+ const feats = await rpcManager.call(sessionId, 'CoreGetFeatures', args);
23
+ return {
24
+ seq: feats[0]?.get('seq') ?? '',
25
+ // travels with the sequence because the caller needs both to translate, and
26
+ // resolving the assembly twice to get them is the shape that lost it
27
+ assemblyGeneticCodeId: geneticCodeId(assembly, refName),
28
+ };
29
+ }
30
+ function geneticCodeId(assembly, refName) {
31
+ return assembly.getGeneticCodeId?.(refName);
22
32
  }
@@ -0,0 +1,23 @@
1
+ import type { MsaViewPlacement } from '../../utils/workspaces';
2
+ import type { Feature } from '@jbrowse/core/util';
3
+ import type { LinearGenomeViewModel } from '@jbrowse/plugin-linear-genome-view';
4
+ /** how a view whose alignment the plugin builds opens */
5
+ export declare const builtAlignmentLook: {
6
+ drawNodeBubbles: boolean;
7
+ colWidth: number;
8
+ rowHeight: number;
9
+ };
10
+ /**
11
+ * Every dialog launch: an MSA view tied to the genome view it came from, and
12
+ * through `feature` to the transcript whose codons its query row is read by.
13
+ */
14
+ export declare function launchConnectedView({ view, feature, placement, ...snapshot }: {
15
+ view: LinearGenomeViewModel;
16
+ feature?: Feature;
17
+ placement: MsaViewPlacement;
18
+ } & Record<string, unknown>): void;
19
+ /** runs a panel's launch, closing the dialog on success and keeping the error */
20
+ export declare function useLaunchSubmit(handleClose: () => void): {
21
+ launchError: unknown;
22
+ submit: (launch: () => void) => void;
23
+ };