jbrowse-plugin-msaview 5.1.0 → 5.1.1

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 (188) hide show
  1. package/dist/BgzipFastaMsaAdapter/BgzipFastaMsaAdapter.d.ts +3 -4
  2. package/dist/BgzipFastaMsaAdapter/BgzipFastaMsaAdapter.js +22 -17
  3. package/dist/LaunchMsaView/components/BlastQuery/BlastAutomaticPanel.js +4 -4
  4. package/dist/LaunchMsaView/components/BlastQuery/BlastPanel.js +1 -1
  5. package/dist/LaunchMsaView/components/BlastQuery/CachedBlastResults.d.ts +4 -1
  6. package/dist/LaunchMsaView/components/BlastQuery/CachedBlastResults.js +5 -8
  7. package/dist/LaunchMsaView/components/ManualMSALoader/ManualMSALoader.js +10 -9
  8. package/dist/LaunchMsaView/components/OrthologQuery/QuerySpeciesSelect.js +10 -1
  9. package/dist/LaunchMsaView/components/PreLoadedMSA/PreLoadedMSADataPanel.js +12 -7
  10. package/dist/LaunchMsaView/components/SubmitCancelActions.d.ts +1 -2
  11. package/dist/LaunchMsaView/components/SubmitCancelActions.js +2 -2
  12. package/dist/LaunchMsaView/components/TabPanel.d.ts +1 -1
  13. package/dist/LaunchMsaView/components/TabPanel.js +2 -2
  14. package/dist/LaunchMsaView/components/TranscriptSelector.js +3 -1
  15. package/dist/LaunchMsaView/components/launchConnectedView.js +8 -1
  16. package/dist/LaunchMsaView/components/useFeatureSequence.js +6 -4
  17. package/dist/LaunchMsaView/useQueryRowName.js +6 -2
  18. package/dist/LaunchMsaView/useQueryRowName.test.js +11 -0
  19. package/dist/MsaViewPanel/afterCreateAutoruns.d.ts +3 -13
  20. package/dist/MsaViewPanel/afterCreateAutoruns.js +57 -57
  21. package/dist/MsaViewPanel/components/MsaViewPanel.js +6 -6
  22. package/dist/MsaViewPanel/components/MsaViewPanel.test.js +13 -1
  23. package/dist/MsaViewPanel/doLaunchBlast.d.ts +2 -6
  24. package/dist/MsaViewPanel/doLaunchBlast.js +28 -15
  25. package/dist/MsaViewPanel/doLaunchOrthologs.d.ts +1 -0
  26. package/dist/MsaViewPanel/doLaunchOrthologs.js +3 -1
  27. package/dist/MsaViewPanel/fetchIndexedMsa.js +1 -1
  28. package/dist/MsaViewPanel/loadProteinDomains.d.ts +5 -14
  29. package/dist/MsaViewPanel/loadProteinDomains.js +6 -20
  30. package/dist/MsaViewPanel/loadProteinDomains.test.js +1 -2
  31. package/dist/MsaViewPanel/model.d.ts +21 -21
  32. package/dist/MsaViewPanel/model.js +0 -3
  33. package/dist/MsaViewPanel/runLaunch.d.ts +3 -2
  34. package/dist/MsaViewPanel/runLaunch.js +4 -1
  35. package/dist/MsaViewPanel/util.d.ts +1 -0
  36. package/dist/MsaViewPanel/util.js +1 -1
  37. package/dist/chunks/{AboutDialog-WT6GXMXU.js → AboutDialog-YNPKL64P.js} +1 -1
  38. package/dist/chunks/{AboutDialog-WT6GXMXU.js.map → AboutDialog-YNPKL64P.js.map} +1 -1
  39. package/dist/chunks/AnnotationFileDialog-NJGKHAVR.js +2 -0
  40. package/dist/chunks/{AnnotationFileDialog-34R7LFHB.js.map → AnnotationFileDialog-NJGKHAVR.js.map} +2 -2
  41. package/dist/chunks/{BgzipFastaMsaAdapter-ZMUARSUQ.js → BgzipFastaMsaAdapter-WWIGI57O.js} +2 -2
  42. package/dist/chunks/BgzipFastaMsaAdapter-WWIGI57O.js.map +7 -0
  43. package/dist/chunks/{ExportSVGDialog-NMVUHFGY.js → ExportSVGDialog-D5DKR6CZ.js} +2 -2
  44. package/dist/chunks/{ExportSVGDialog-NMVUHFGY.js.map → ExportSVGDialog-D5DKR6CZ.js.map} +1 -1
  45. package/dist/chunks/{FeatureDialog-XNHIA5PS.js → FeatureDialog-7XXL6XUK.js} +1 -1
  46. package/dist/chunks/{FeatureDialog-XNHIA5PS.js.map → FeatureDialog-7XXL6XUK.js.map} +1 -1
  47. package/dist/chunks/LaunchMsaViewDialog-C53VHT52.js +5 -0
  48. package/dist/chunks/LaunchMsaViewDialog-C53VHT52.js.map +7 -0
  49. package/dist/chunks/MetadataDialog-QCUWTDBU.js +2 -0
  50. package/dist/chunks/{MetadataDialog-2WT6EX4M.js.map → MetadataDialog-QCUWTDBU.js.map} +2 -2
  51. package/dist/chunks/MsaViewPanel-DS4STM3W.js +2 -0
  52. package/dist/chunks/MsaViewPanel-DS4STM3W.js.map +7 -0
  53. package/dist/chunks/{TrackInfoDialog-TYCMLUKG.js → TrackInfoDialog-BVZWIDEU.js} +2 -2
  54. package/dist/chunks/{TrackInfoDialog-TYCMLUKG.js.map → TrackInfoDialog-BVZWIDEU.js.map} +1 -1
  55. package/dist/chunks/TreeNodeInfoDialog-LPLF6BA6.js +2 -0
  56. package/dist/chunks/{TreeNodeInfoDialog-BFF6JRCO.js.map → TreeNodeInfoDialog-LPLF6BA6.js.map} +2 -2
  57. package/dist/chunks/chunk-F726IWCW.js +2 -0
  58. package/dist/chunks/chunk-F726IWCW.js.map +7 -0
  59. package/dist/chunks/chunk-HOEVB65D.js +2 -0
  60. package/dist/chunks/chunk-HOEVB65D.js.map +7 -0
  61. package/dist/chunks/{chunk-5ORPTTMR.js → chunk-IP2BEEIU.js} +1 -1
  62. package/dist/chunks/{chunk-5ORPTTMR.js.map → chunk-IP2BEEIU.js.map} +1 -1
  63. package/dist/chunks/{chunk-TITP6LCV.js → chunk-JFL3LJPO.js} +2 -2
  64. package/dist/chunks/{chunk-TITP6LCV.js.map → chunk-JFL3LJPO.js.map} +2 -2
  65. package/dist/chunks/chunk-NI3VGKQP.js +3 -0
  66. package/dist/chunks/chunk-NI3VGKQP.js.map +7 -0
  67. package/dist/chunks/{chunk-P77DPK6B.js → chunk-NSKY4K4T.js} +1 -1
  68. package/dist/chunks/{chunk-P77DPK6B.js.map → chunk-NSKY4K4T.js.map} +1 -1
  69. package/dist/chunks/{chunk-O7JNDIKD.js → chunk-OF4AZJER.js} +1 -1
  70. package/dist/chunks/{chunk-O7JNDIKD.js.map → chunk-OF4AZJER.js.map} +1 -1
  71. package/dist/chunks/chunk-P7ZJCOIY.js +11 -0
  72. package/dist/chunks/chunk-P7ZJCOIY.js.map +7 -0
  73. package/dist/chunks/{chunk-QMLPITS4.js → chunk-S2T7S4SB.js} +4 -4
  74. package/dist/chunks/{chunk-QMLPITS4.js.map → chunk-S2T7S4SB.js.map} +2 -2
  75. package/dist/chunks/{chunk-FCNT6B47.js → chunk-SKDGC3HW.js} +1 -1
  76. package/dist/chunks/{chunk-FCNT6B47.js.map → chunk-SKDGC3HW.js.map} +1 -1
  77. package/dist/chunks/{chunk-ENTWPX2K.js → chunk-VJQWGQ4V.js} +5 -5
  78. package/dist/chunks/{chunk-ENTWPX2K.js.map → chunk-VJQWGQ4V.js.map} +1 -1
  79. package/dist/chunks/{chunk-ZTQYZDWK.js → chunk-VWM6IHF2.js} +2 -2
  80. package/dist/chunks/{chunk-ZTQYZDWK.js.map → chunk-VWM6IHF2.js.map} +1 -1
  81. package/dist/chunks/dist-6DG5HXEW.js +2 -0
  82. package/dist/chunks/model-DXQOMSLC.js +12 -0
  83. package/dist/chunks/model-DXQOMSLC.js.map +7 -0
  84. package/dist/chunks/{renderToSvg-B7RCHRO3.js → renderToSvg-2LBAS6XS.js} +2 -2
  85. package/dist/jbrowse-plugin-msaview.esm.js +2 -2
  86. package/dist/jbrowse-plugin-msaview.esm.js.map +1 -1
  87. package/dist/utils/ebiBlast.d.ts +8 -13
  88. package/dist/utils/ebiBlast.js +20 -45
  89. package/dist/utils/ebiJobDispatcher.d.ts +4 -4
  90. package/dist/utils/ebiJobDispatcher.js +4 -4
  91. package/dist/utils/eutils.js +1 -1
  92. package/dist/utils/fetch.d.ts +4 -0
  93. package/dist/utils/fetch.js +7 -1
  94. package/dist/utils/homologSearch.d.ts +6 -2
  95. package/dist/utils/msaRows.d.ts +1 -2
  96. package/dist/utils/msaRows.js +13 -1
  97. package/dist/utils/ncbiDomains.d.ts +1 -1
  98. package/dist/utils/ncbiDomains.js +19 -7
  99. package/dist/utils/ncbiDomains.test.js +36 -0
  100. package/dist/utils/ncbiOrthologs.d.ts +24 -5
  101. package/dist/utils/ncbiOrthologs.js +55 -19
  102. package/dist/utils/ncbiOrthologs.test.js +58 -8
  103. package/dist/utils/pantherOrthologs.js +4 -1
  104. package/dist/utils/phmmer.js +4 -8
  105. package/dist/utils/taxonomyNames.d.ts +6 -1
  106. package/dist/utils/taxonomyNames.js +7 -1
  107. package/dist/utils/taxonomyNames.test.js +12 -0
  108. package/dist/utils/unirefHomologs.d.ts +6 -0
  109. package/dist/utils/unirefHomologs.js +25 -10
  110. package/dist/utils/unirefHomologs.test.js +49 -2
  111. package/dist/version.d.ts +1 -1
  112. package/dist/version.js +1 -1
  113. package/package.json +17 -17
  114. package/src/BgzipFastaMsaAdapter/BgzipFastaMsaAdapter.ts +23 -20
  115. package/src/LaunchMsaView/components/BlastQuery/BlastAutomaticPanel.tsx +4 -4
  116. package/src/LaunchMsaView/components/BlastQuery/BlastPanel.tsx +3 -1
  117. package/src/LaunchMsaView/components/BlastQuery/CachedBlastResults.tsx +7 -19
  118. package/src/LaunchMsaView/components/ManualMSALoader/ManualMSALoader.tsx +10 -9
  119. package/src/LaunchMsaView/components/OrthologQuery/QuerySpeciesSelect.tsx +12 -1
  120. package/src/LaunchMsaView/components/PreLoadedMSA/PreLoadedMSADataPanel.tsx +12 -6
  121. package/src/LaunchMsaView/components/SubmitCancelActions.tsx +1 -3
  122. package/src/LaunchMsaView/components/TabPanel.tsx +1 -2
  123. package/src/LaunchMsaView/components/TranscriptSelector.tsx +3 -1
  124. package/src/LaunchMsaView/components/launchConnectedView.ts +8 -1
  125. package/src/LaunchMsaView/components/useFeatureSequence.ts +8 -2
  126. package/src/LaunchMsaView/useQueryRowName.test.ts +15 -0
  127. package/src/LaunchMsaView/useQueryRowName.ts +7 -2
  128. package/src/MsaViewPanel/afterCreateAutoruns.ts +74 -58
  129. package/src/MsaViewPanel/components/MsaViewPanel.test.tsx +18 -1
  130. package/src/MsaViewPanel/components/MsaViewPanel.tsx +15 -16
  131. package/src/MsaViewPanel/doLaunchBlast.ts +37 -17
  132. package/src/MsaViewPanel/doLaunchOrthologs.ts +4 -1
  133. package/src/MsaViewPanel/fetchIndexedMsa.ts +1 -1
  134. package/src/MsaViewPanel/loadProteinDomains.test.ts +1 -2
  135. package/src/MsaViewPanel/loadProteinDomains.ts +13 -28
  136. package/src/MsaViewPanel/model.ts +0 -3
  137. package/src/MsaViewPanel/runLaunch.ts +8 -8
  138. package/src/MsaViewPanel/types.ts +1 -1
  139. package/src/MsaViewPanel/util.ts +1 -1
  140. package/src/utils/ebiBlast.ts +29 -56
  141. package/src/utils/ebiJobDispatcher.ts +4 -4
  142. package/src/utils/eutils.ts +1 -1
  143. package/src/utils/fetch.ts +12 -2
  144. package/src/utils/homologSearch.ts +6 -2
  145. package/src/utils/msaRows.ts +18 -4
  146. package/src/utils/ncbiDomains.test.ts +53 -0
  147. package/src/utils/ncbiDomains.ts +21 -8
  148. package/src/utils/ncbiOrthologs.test.ts +70 -7
  149. package/src/utils/ncbiOrthologs.ts +81 -24
  150. package/src/utils/pantherOrthologs.ts +7 -1
  151. package/src/utils/phmmer.ts +3 -7
  152. package/src/utils/taxonomyNames.test.ts +15 -0
  153. package/src/utils/taxonomyNames.ts +7 -0
  154. package/src/utils/unirefHomologs.test.ts +57 -2
  155. package/src/utils/unirefHomologs.ts +38 -13
  156. package/src/version.ts +1 -1
  157. package/dist/LaunchMsaView/components/util.d.ts +0 -7
  158. package/dist/LaunchMsaView/components/util.js +0 -16
  159. package/dist/MsaViewPanel/components/ErrorBoundary.d.ts +0 -19
  160. package/dist/MsaViewPanel/components/ErrorBoundary.js +0 -21
  161. package/dist/chunks/AnnotationFileDialog-34R7LFHB.js +0 -2
  162. package/dist/chunks/BgzipFastaMsaAdapter-ZMUARSUQ.js.map +0 -7
  163. package/dist/chunks/LaunchMsaViewDialog-WBXEEHJL.js +0 -5
  164. package/dist/chunks/LaunchMsaViewDialog-WBXEEHJL.js.map +0 -7
  165. package/dist/chunks/MetadataDialog-2WT6EX4M.js +0 -2
  166. package/dist/chunks/MsaViewPanel-YYP4CYYO.js +0 -2
  167. package/dist/chunks/MsaViewPanel-YYP4CYYO.js.map +0 -7
  168. package/dist/chunks/TreeNodeInfoDialog-BFF6JRCO.js +0 -2
  169. package/dist/chunks/chunk-MBI4TMKR.js +0 -2
  170. package/dist/chunks/chunk-MBI4TMKR.js.map +0 -7
  171. package/dist/chunks/chunk-MHBTV7BZ.js +0 -3
  172. package/dist/chunks/chunk-MHBTV7BZ.js.map +0 -7
  173. package/dist/chunks/chunk-PJZ3XTMG.js +0 -6
  174. package/dist/chunks/chunk-PJZ3XTMG.js.map +0 -7
  175. package/dist/chunks/chunk-QAC6JSDZ.js +0 -5
  176. package/dist/chunks/chunk-QAC6JSDZ.js.map +0 -7
  177. package/dist/chunks/chunk-RTKNCGDF.js +0 -8
  178. package/dist/chunks/chunk-RTKNCGDF.js.map +0 -7
  179. package/dist/chunks/dist-2T65ZS4E.js +0 -2
  180. package/dist/chunks/model-7VBTOIJX.js +0 -12
  181. package/dist/chunks/model-7VBTOIJX.js.map +0 -7
  182. package/dist/utils/types.d.ts +0 -18
  183. package/src/LaunchMsaView/components/util.ts +0 -22
  184. package/src/MsaViewPanel/components/ErrorBoundary.tsx +0 -41
  185. package/src/utils/types.ts +0 -17
  186. /package/dist/chunks/{dist-2T65ZS4E.js.map → dist-6DG5HXEW.js.map} +0 -0
  187. /package/dist/chunks/{renderToSvg-B7RCHRO3.js.map → renderToSvg-2LBAS6XS.js.map} +0 -0
  188. /package/dist/utils/{types.js → ncbiDomains.test.d.ts} +0 -0
@@ -2,12 +2,11 @@ import { BaseAdapter } from '@jbrowse/core/data_adapters/BaseAdapter';
2
2
  import type { BaseFeatureDataAdapter } from '@jbrowse/core/data_adapters/BaseAdapter';
3
3
  export default class BgzipFastaMsaAdapter extends BaseAdapter {
4
4
  configureP: Promise<BaseFeatureDataAdapter> | undefined;
5
- refNamesP: Promise<string[]> | undefined;
5
+ msaRowsP: Promise<Map<string, string[]>> | undefined;
6
6
  configurePre(): Promise<BaseFeatureDataAdapter<import("@jbrowse/core/configuration").AnyConfigurationModel>>;
7
7
  configure(): Promise<BaseFeatureDataAdapter<import("@jbrowse/core/configuration").AnyConfigurationModel>>;
8
- getMSARefs(): Promise<string[]>;
9
- getMsaRegex(): RegExp;
10
- refNameToMsaId(refName: string): string;
8
+ groupRows(): Promise<Map<string, string[]>>;
9
+ getMSARows(): Promise<Map<string, string[]>>;
11
10
  getMSAList(): Promise<string[]>;
12
11
  getMSA(id: string): Promise<import("@jbrowse/core/util").Feature[]>;
13
12
  }
@@ -22,30 +22,35 @@ export default class BgzipFastaMsaAdapter extends BaseAdapter {
22
22
  });
23
23
  return this.configureP;
24
24
  }
25
- async getMSARefs() {
26
- this.refNamesP ??= this.configure()
27
- .then(adapter => adapter.getRefNames())
28
- .catch((e) => {
29
- this.refNamesP = undefined;
25
+ async groupRows() {
26
+ const adapter = await this.configure();
27
+ const separator = new RegExp(this.getConf('msaRegex'));
28
+ const rows = new Map();
29
+ for (const refName of await adapter.getRefNames()) {
30
+ const msaId = refName.split(separator)[0];
31
+ const group = rows.get(msaId);
32
+ if (group) {
33
+ group.push(refName);
34
+ }
35
+ else {
36
+ rows.set(msaId, [refName]);
37
+ }
38
+ }
39
+ return rows;
40
+ }
41
+ getMSARows() {
42
+ this.msaRowsP ??= this.groupRows().catch((e) => {
43
+ this.msaRowsP = undefined;
30
44
  throw e;
31
45
  });
32
- return this.refNamesP;
33
- }
34
- getMsaRegex() {
35
- return new RegExp(this.getConf('msaRegex'));
36
- }
37
- refNameToMsaId(refName) {
38
- return refName.split(this.getMsaRegex())[0];
46
+ return this.msaRowsP;
39
47
  }
40
48
  async getMSAList() {
41
- const refNames = await this.getMSARefs();
42
- const list = new Set(refNames.map(name => this.refNameToMsaId(name)));
43
- return [...list];
49
+ return [...(await this.getMSARows()).keys()];
44
50
  }
45
51
  async getMSA(id) {
46
52
  const adapter = await this.configure();
47
- const refNames = await this.getMSARefs();
48
- const rows = refNames.filter(refName => this.refNameToMsaId(refName) === id);
53
+ const rows = (await this.getMSARows()).get(id) ?? [];
49
54
  return firstValueFrom(adapter
50
55
  .getFeaturesInMultipleRegions(rows.map(refName => ({
51
56
  refName,
@@ -46,14 +46,14 @@ const BlastAutomaticPanel = observer(function ({ handleClose, feature, model, ch
46
46
  const hitCountValid = Number.isInteger(hitCount) && hitCount >= 1;
47
47
  const isPhmmer = search.program === 'phmmer';
48
48
  const geneIds = useMemo(() => getGeneIdentifiers(feature), [feature]);
49
- const { results: cachedResults, error: cachedResultsError } = useCachedBlastResults(geneIds);
49
+ const cached = useCachedBlastResults(geneIds);
50
50
  const transcriptSelection = useTranscriptSelection({
51
51
  feature,
52
52
  view,
53
53
  preferredTranscriptId,
54
54
  });
55
55
  const { selectedTranscript, proteinSequence, sequenceStatus } = transcriptSelection;
56
- const e = transcriptSelection.error ?? launchError ?? cachedResultsError;
56
+ const e = transcriptSelection.error ?? launchError ?? cached.error;
57
57
  return (React.createElement(React.Fragment, null,
58
58
  React.createElement(LaunchPanelContent, { error: e },
59
59
  children,
@@ -80,11 +80,11 @@ const BlastAutomaticPanel = observer(function ({ handleClose, feature, model, ch
80
80
  : 'The hits come back from EBI and are then run through the chosen aligner.',
81
81
  ' ',
82
82
  "The EBI queue is the wait, and it runs from seconds to many minutes."),
83
- cachedResults.length > 0 ? (React.createElement(Accordion, { className: classes.cachedResultsAccordion },
83
+ cached.results.length > 0 ? (React.createElement(Accordion, { className: classes.cachedResultsAccordion },
84
84
  React.createElement(AccordionSummary, { expandIcon: React.createElement(ExpandMoreIcon, null) },
85
85
  React.createElement(Typography, null, "Previous BLAST Results")),
86
86
  React.createElement(AccordionDetails, null,
87
- React.createElement(CachedBlastResults, { model: model, handleClose: handleClose, feature: feature })))) : null),
87
+ React.createElement(CachedBlastResults, { model: model, handleClose: handleClose, feature: feature, cached: cached })))) : null),
88
88
  React.createElement(SubmitCancelActions, { model: model, hint: React.createElement(SequenceStatusMessage, { status: sequenceStatus }), submitDisabled: !proteinSequence || !hitCountValid, onSubmit: placement => {
89
89
  if (selectedTranscript) {
90
90
  const blastParams = search.program === 'phmmer'
@@ -24,7 +24,7 @@ export default function BlastPanel({ handleClose, model, feature, preferredTrans
24
24
  const { classes } = useStyles();
25
25
  const Panel = panelMap[lookupMethod];
26
26
  return (React.createElement(React.Fragment, null,
27
- React.createElement(Tooltip, { title: `EBI settings — jobs are submitted with ${ebiEmail}` },
27
+ React.createElement(Tooltip, { title: `EBI settings — jobs are submitted with ${ebiEmail.trim() || DEFAULT_EBI_EMAIL}` },
28
28
  React.createElement(IconButton, { className: classes.settingsButton, "aria-label": "EBI settings", size: "small", onClick: () => {
29
29
  setSettingsOpen(true);
30
30
  } },
@@ -1,5 +1,6 @@
1
1
  import React from 'react';
2
2
  import type { CachedBlastResult } from '../../../utils/blastCache';
3
+ import type { useCachedBlastResults } from './useCachedBlastResults';
3
4
  import type { AbstractTrackModel, Feature } from '@jbrowse/core/util';
4
5
  /**
5
6
  * How the row was produced: `uniprotkb_swissprot / blastp / clustalo`, or
@@ -12,9 +13,11 @@ import type { AbstractTrackModel, Feature } from '@jbrowse/core/util';
12
13
  * queried NCBI directly and blastp/quick-blastp was a real choice.
13
14
  */
14
15
  export declare function describeSearch(result: CachedBlastResult): string;
15
- declare const CachedBlastResults: ({ model, handleClose, feature, }: {
16
+ declare const CachedBlastResults: ({ model, handleClose, feature, cached: { results, handleDelete, handleClearAll }, }: {
16
17
  model: AbstractTrackModel;
17
18
  handleClose: () => void;
18
19
  feature: Feature;
20
+ /** the panel's own listing, so clearing it here empties it there too */
21
+ cached: ReturnType<typeof useCachedBlastResults>;
19
22
  }) => React.JSX.Element;
20
23
  export default CachedBlastResults;
@@ -1,13 +1,12 @@
1
- import React, { useMemo, useState } from 'react';
1
+ import React, { useState } from 'react';
2
2
  import { ErrorMessage } from '@jbrowse/core/ui';
3
3
  import DeleteIcon from '@mui/icons-material/Delete';
4
4
  import { Button, IconButton, List, ListItem, ListItemButton, ListItemText, Typography, } from '@mui/material';
5
5
  import { observer } from 'mobx-react';
6
6
  import { makeStyles } from 'tss-react/mui';
7
- import { featureMatchesId, getGeneIdentifiers, getLinearGenomeView, getSortedTranscriptFeatures, } from '../../util';
7
+ import { featureMatchesId, getLinearGenomeView, getSortedTranscriptFeatures, } from '../../util';
8
8
  import { builtAlignmentLook, launchConnectedView } from '../launchConnectedView';
9
9
  import { useLaunchPlacement } from '../launchPlacement';
10
- import { useCachedBlastResults } from './useCachedBlastResults';
11
10
  const useStyles = makeStyles()({
12
11
  header: {
13
12
  display: 'flex',
@@ -50,13 +49,11 @@ export function describeSearch(result) {
50
49
  .filter(Boolean)
51
50
  .join(' / ');
52
51
  }
53
- const CachedBlastResults = observer(function ({ model, handleClose, feature, }) {
52
+ const CachedBlastResults = observer(function ({ model, handleClose, feature, cached: { results, handleDelete, handleClearAll }, }) {
54
53
  const { classes } = useStyles();
55
54
  const view = getLinearGenomeView(model);
56
55
  const [operationError, setOperationError] = useState();
57
56
  const [sideBySide] = useLaunchPlacement();
58
- const geneIds = useMemo(() => getGeneIdentifiers(feature), [feature]);
59
- const { results, error, isLoading, handleDelete, handleClearAll } = useCachedBlastResults(geneIds);
60
57
  const handleUseCached = (cached) => {
61
58
  // the cached query row is the plugin's default `QUERY`, translated from
62
59
  // the transcript stored as transcriptId, so that transcript relinks it
@@ -75,8 +72,8 @@ const CachedBlastResults = observer(function ({ model, handleClose, feature, })
75
72
  setOperationError(e);
76
73
  });
77
74
  };
78
- const displayError = error ?? operationError;
79
- return displayError ? (React.createElement(ErrorMessage, { error: displayError })) : isLoading ? (React.createElement(Typography, null, "Loading cached results...")) : results.length === 0 ? (React.createElement(Typography, { color: "textSecondary" }, "No cached BLAST results found for this gene. Run a BLAST query to cache results.")) : (React.createElement("div", null,
75
+ return (React.createElement("div", null,
76
+ operationError ? React.createElement(ErrorMessage, { error: operationError }) : null,
80
77
  React.createElement("div", { className: classes.header },
81
78
  React.createElement(Typography, { variant: "subtitle1" },
82
79
  "Cached BLAST Results (",
@@ -22,13 +22,14 @@ import { useTranscriptSelection } from '../useTranscriptSelection';
22
22
  */
23
23
  function useMsaFileText(location) {
24
24
  const debounced = useDebounced(location, 500);
25
- const { data } = useFetch(debounced ? [JSON.stringify(debounced), 'msa-file-text'] : null, () => openLocation(debounced).readFile('utf8'));
26
- return data ?? '';
25
+ const { data, error, isLoading } = useFetch(debounced ? [JSON.stringify(debounced), 'msa-file-text'] : null, () => openLocation(debounced).readFile('utf8'));
26
+ return {
27
+ text: data ?? '',
28
+ error,
29
+ isLoading: isLoading || debounced !== location,
30
+ };
27
31
  }
28
32
  const useStyles = makeStyles()({
29
- textAreaFont: {
30
- fontFamily: 'Courier New',
31
- },
32
33
  inputContainer: {
33
34
  marginBottom: 30,
34
35
  },
@@ -54,9 +55,9 @@ const ManualMSALoader = observer(function PreLoadedMSA2({ model, feature, handle
54
55
  preferredTranscriptId,
55
56
  });
56
57
  const { selectedTranscript, proteinSequence, error, sequenceStatus } = transcriptSelection;
57
- const msaFileText = useMsaFileText(inputMethod === 'file' ? msaFileLocation : undefined);
58
- const queryRow = useQueryRowName(inputMethod === 'file' ? msaFileText : msaText, proteinSequence);
59
- const e = launchError ?? error;
58
+ const msaFile = useMsaFileText(inputMethod === 'file' ? msaFileLocation : undefined);
59
+ const queryRow = useQueryRowName(inputMethod === 'file' ? msaFile.text : msaText, proteinSequence);
60
+ const e = launchError ?? error ?? msaFile.error;
60
61
  return (React.createElement(React.Fragment, null,
61
62
  React.createElement(LaunchPanelContent, { error: e },
62
63
  React.createElement(FormControl, { component: "fieldset" },
@@ -77,7 +78,7 @@ const ManualMSALoader = observer(function PreLoadedMSA2({ model, feature, handle
77
78
  React.createElement(TranscriptSelector, { feature: feature, ...transcriptSelection }),
78
79
  React.createElement(QueryRowSelector, { ...queryRow })),
79
80
  React.createElement(SubmitCancelActions, { model: model, hint: React.createElement(SequenceStatusMessage, { status: sequenceStatus }), submitDisabled: !selectedTranscript ||
80
- (inputMethod === 'file' && !msaFileLocation) ||
81
+ (inputMethod === 'file' && (!msaFileLocation || msaFile.isLoading)) ||
81
82
  (inputMethod === 'text' && !msaText.trim()), onSubmit: placement => {
82
83
  if (selectedTranscript) {
83
84
  submit(() => launchConnectedView({
@@ -8,7 +8,16 @@ async function describeTaxon(query, signal) {
8
8
  if (!taxId) {
9
9
  throw new Error(`No NCBI taxon matches "${query}"`);
10
10
  }
11
- const info = (await fetchTaxonomyInfo([taxId], signal)).get(taxId);
11
+ const failures = [];
12
+ const info = (await fetchTaxonomyInfo([taxId], signal, e => {
13
+ failures.push(e);
14
+ })).get(taxId);
15
+ // a throttle would otherwise read as a taxon NCBI has no name for
16
+ if (!info && failures.length > 0) {
17
+ throw new Error(`NCBI did not answer for the name of taxon ${taxId}`, {
18
+ cause: failures[0],
19
+ });
20
+ }
12
21
  const label = [info?.sciname, info?.commonName && `(${info.commonName})`]
13
22
  .filter(Boolean)
14
23
  .join(' ');
@@ -7,7 +7,7 @@ import { makeStyles } from 'tss-react/mui';
7
7
  import TextField2 from '../../../components/TextField2';
8
8
  import { useFetch } from '../../../utils/useFetch';
9
9
  import { resolveQueryRowName, useQueryRowName } from '../../useQueryRowName';
10
- import { getGeneDisplayName, getLinearGenomeView, getTranscriptDisplayName, } from '../../util';
10
+ import { featureMatchesId, getGeneDisplayName, getLinearGenomeView, getTranscriptDisplayName, } from '../../util';
11
11
  import LaunchPanelContent from '../LaunchPanelContent';
12
12
  import QueryRowSelector from '../QueryRowSelector';
13
13
  import SequenceStatusMessage from '../SequenceStatus';
@@ -39,11 +39,16 @@ const PreLoadedMSA = observer(function ({ model, feature, handleClose, preferred
39
39
  validIds: msaList,
40
40
  preferredTranscriptId,
41
41
  });
42
- const { selectedId, selectedTranscript, proteinSequence, sequenceStatus } = transcriptSelection;
43
- const { data: msaData, isLoading: msaDataLoading, error: msaDataFetchError, } = useFetch(selectedId && selectedDataset && msaList
44
- ? `${selectedDataset.datasetId}-${selectedId}-msa`
42
+ const { selectedTranscript, proteinSequence, sequenceStatus } = transcriptSelection;
43
+ // the dataset's name for this transcript, which a track is free not to use
44
+ // as the feature's own id
45
+ const msaId = selectedTranscript
46
+ ? msaList?.find(id => featureMatchesId(selectedTranscript, id))
47
+ : undefined;
48
+ const { data: msaData, isLoading: msaDataLoading, error: msaDataFetchError, } = useFetch(msaId && selectedDataset
49
+ ? `${selectedDataset.datasetId}-${msaId}-msa`
45
50
  : null, () => fetchMSA({
46
- msaId: selectedId,
51
+ msaId: msaId,
47
52
  config: selectedDataset.adapter,
48
53
  pluginManager,
49
54
  }));
@@ -59,7 +64,7 @@ const PreLoadedMSA = observer(function ({ model, feature, handleClose, preferred
59
64
  // that convention does carry the row, so it is worth falling back to when the
60
65
  // residues do not match closely enough to find it -- but only when the
61
66
  // alignment really has it, which is the check the old code never made.
62
- const querySeqName = resolveQueryRowName(queryRow, `${selectedId}_${assemblyNames[0] ?? ''}`);
67
+ const querySeqName = resolveQueryRowName(queryRow, `${msaId ?? ''}_${assemblyNames[0] ?? ''}`);
63
68
  const e = msaListFetchError ??
64
69
  msaDataFetchError ??
65
70
  transcriptSelection.error ??
@@ -70,7 +75,7 @@ const PreLoadedMSA = observer(function ({ model, feature, handleClose, preferred
70
75
  setSelectedDatasetId(event.target.value);
71
76
  } }, datasets?.map(d => (React.createElement(MenuItem, { key: d.datasetId, value: d.datasetId }, d.name)))),
72
77
  selectedDataset ? (React.createElement("div", { className: classes.selectedContainer },
73
- !msaListLoading && msaDataLoading ? (React.createElement(LoadingEllipses, { variant: "h6", message: `Loading MSA for ${getTranscriptDisplayName(selectedTranscript) || selectedId}` })) : null,
78
+ !msaListLoading && msaDataLoading ? (React.createElement(LoadingEllipses, { variant: "h6", message: `Loading MSA for ${getTranscriptDisplayName(selectedTranscript) || msaId}` })) : null,
74
79
  msaListLoading ? (React.createElement(LoadingEllipses, { variant: "h6", message: `Loading available MSAs for (${selectedDataset.name})` })) : null,
75
80
  msaList ? (React.createElement("div", null,
76
81
  React.createElement(SanitizedHTML, { html: selectedDataset.description }),
@@ -1,7 +1,7 @@
1
1
  import React from 'react';
2
2
  import type { MsaViewPlacement } from '../../utils/workspaces';
3
3
  import type { AbstractTrackModel } from '@jbrowse/core/util';
4
- export default function SubmitCancelActions({ onSubmit, onCancel, submitDisabled, hint, submitLabel, cancelLabel, model, }: {
4
+ export default function SubmitCancelActions({ onSubmit, onCancel, submitDisabled, hint, submitLabel, model, }: {
5
5
  /** handed the placement the box states, so no launch re-reads storage */
6
6
  onSubmit: (placement: MsaViewPlacement) => void;
7
7
  onCancel: () => void;
@@ -9,7 +9,6 @@ export default function SubmitCancelActions({ onSubmit, onCancel, submitDisabled
9
9
  /** why Submit is grey, shown beside it */
10
10
  hint?: React.ReactNode;
11
11
  submitLabel?: string;
12
- cancelLabel?: string;
13
12
  /** omitted by a panel that submits something other than a view launch */
14
13
  model?: AbstractTrackModel;
15
14
  }): React.JSX.Element;
@@ -19,7 +19,7 @@ function PlacementToggle({ checked, onChange, }) {
19
19
  onChange(event.target.checked);
20
20
  } }) }));
21
21
  }
22
- export default function SubmitCancelActions({ onSubmit, onCancel, submitDisabled, hint, submitLabel = 'Submit', cancelLabel = 'Cancel', model, }) {
22
+ export default function SubmitCancelActions({ onSubmit, onCancel, submitDisabled, hint, submitLabel = 'Submit', model, }) {
23
23
  const [sideBySide, setSideBySide] = useLaunchPlacement();
24
24
  // The stored value is what the next dialog opens on, so it is written on
25
25
  // submit rather than on the click: a dialog the user backed out of must not
@@ -46,5 +46,5 @@ export default function SubmitCancelActions({ onSubmit, onCancel, submitDisabled
46
46
  } }, submitLabel),
47
47
  React.createElement(Button, { sx: { flexShrink: 0 }, color: "secondary", variant: "contained", onClick: () => {
48
48
  onCancel();
49
- } }, cancelLabel))));
49
+ } }, "Cancel"))));
50
50
  }
@@ -1,5 +1,5 @@
1
1
  import React from 'react';
2
- export default function TabPanel({ children, value, index, ...other }: {
2
+ export default function TabPanel({ children, value, index, }: {
3
3
  children?: React.ReactNode;
4
4
  index: number | string;
5
5
  value: number | string;
@@ -7,11 +7,11 @@ import React, { useState } from 'react';
7
7
  // Lazy on first visit rather than mounted up front, because a panel mounts
8
8
  // fetches of its own: rendering all four on open would query the MSA dataset
9
9
  // adapter and the BLAST cache for tabs nobody looked at.
10
- export default function TabPanel({ children, value, index, ...other }) {
10
+ export default function TabPanel({ children, value, index, }) {
11
11
  const active = value === index;
12
12
  const [visited, setVisited] = useState(active);
13
13
  if (active && !visited) {
14
14
  setVisited(true);
15
15
  }
16
- return (React.createElement("div", { role: "tabpanel", hidden: !active, ...other }, visited || active ? children : null));
16
+ return (React.createElement("div", { role: "tabpanel", hidden: !active }, visited || active ? children : null));
17
17
  }
@@ -50,5 +50,7 @@ export default function TranscriptSelector({ feature, options, selectedId, selec
50
50
  ? `>${getTranscriptDisplayName(selectedTranscript)}\n${proteinSequence}`
51
51
  : sequenceStatus === 'missing'
52
52
  ? 'This transcript has no coding sequence, so there is nothing to translate.'
53
- : 'Loading...' })) : null));
53
+ : sequenceStatus === 'error'
54
+ ? 'The sequence could not be fetched.'
55
+ : 'Loading...' })) : null));
54
56
  }
@@ -1,4 +1,4 @@
1
- import { useState } from 'react';
1
+ import { useRef, useState } from 'react';
2
2
  import { getSession } from '@jbrowse/core/util';
3
3
  import { launchMsaView } from '../../utils/launchMsaView';
4
4
  /** how a view whose alignment the plugin builds opens */
@@ -22,11 +22,18 @@ export function launchConnectedView({ view, feature, placement, ...snapshot }) {
22
22
  /** runs a panel's launch, closing the dialog on success and keeping the error */
23
23
  export function useLaunchSubmit(handleClose) {
24
24
  const [launchError, setLaunchError] = useState();
25
+ // the first launch loads the view's chunk, which is long enough to click twice
26
+ const launching = useRef(false);
25
27
  return {
26
28
  launchError,
27
29
  submit: (launch) => {
30
+ if (launching.current) {
31
+ return;
32
+ }
33
+ launching.current = true;
28
34
  setLaunchError(undefined);
29
35
  launch().then(handleClose, (e) => {
36
+ launching.current = false;
30
37
  console.error(e);
31
38
  setLaunchError(e);
32
39
  });
@@ -1,3 +1,4 @@
1
+ import { useMemo } from 'react';
1
2
  import { getSession } from '@jbrowse/core/util';
2
3
  import { translateTranscript } from '@jbrowse/core/util/translateTranscript';
3
4
  import { useFetch } from '../../utils/useFetch';
@@ -16,11 +17,12 @@ export function useFeatureSequence({ view, feature, }) {
16
17
  session: getSession(view),
17
18
  });
18
19
  });
20
+ const proteinSequence = useMemo(() => sequence && feature
21
+ ? (translateTranscript({ transcript: feature, ...sequence })?.protein ??
22
+ '')
23
+ : '', [sequence, feature]);
19
24
  return {
20
- proteinSequence: sequence && feature
21
- ? (translateTranscript({ transcript: feature, ...sequence })?.protein ??
22
- '')
23
- : '',
25
+ proteinSequence,
24
26
  sequence,
25
27
  error,
26
28
  isLoading,
@@ -24,7 +24,11 @@ export function useQueryRowName(msaText, proteinSequence) {
24
24
  // parsing runs on every keystroke in the paste box otherwise, and an
25
25
  // alignment of a few hundred rows is not free
26
26
  const { names, match } = useMemo(() => findQueryRow(msaText, proteinSequence), [msaText, proteinSequence]);
27
- const querySeqName = override ?? match?.name ?? '';
27
+ // a row picked from one alignment says nothing about the next one pasted
28
+ const picked = override !== undefined && (names.length === 0 || names.includes(override))
29
+ ? override
30
+ : undefined;
31
+ const querySeqName = picked ?? match?.name ?? '';
28
32
  return {
29
33
  detected: match,
30
34
  names,
@@ -33,6 +37,6 @@ export function useQueryRowName(msaText, proteinSequence) {
33
37
  // taken at face value, residue 1 to residue 1
34
38
  querySeqOffset: querySeqName === match?.name ? match.offset : 0,
35
39
  setQuerySeqName: setOverride,
36
- isAutoDetected: override === undefined && !!match,
40
+ isAutoDetected: picked === undefined && !!match,
37
41
  };
38
42
  }
@@ -20,6 +20,17 @@ describe('useQueryRowName', () => {
20
20
  expect(result.current.querySeqName).toBe('other');
21
21
  expect(result.current.querySeqOffset).toBe(0);
22
22
  });
23
+ // the pick outlived the alignment it was made in, and the launch then named
24
+ // a row the new one does not have
25
+ test('a picked row is dropped once the alignment no longer has it', () => {
26
+ const { result, rerender } = renderHook(({ msa }) => useQueryRowName(msa, protein), { initialProps: { msa: trimmed } });
27
+ act(() => {
28
+ result.current.setQuerySeqName('other');
29
+ });
30
+ rerender({ msa: trimmed.replace('>other', '>renamed') });
31
+ expect(result.current.querySeqName).toBe('aligned_query');
32
+ expect(result.current.isAutoDetected).toBe(true);
33
+ });
23
34
  });
24
35
  // a pre-loaded dataset names its rows by a convention of its own, and the
25
36
  // residues can be too diverged for detection to find the row that is right
@@ -8,22 +8,12 @@ export declare function loadStoredData(self: JBrowsePluginMsaViewModel): void;
8
8
  * refusing IndexedDB fails once rather than in a loop.
9
9
  */
10
10
  export declare function storeDataToIndexedDB(self: JBrowsePluginMsaViewModel): void;
11
- /**
12
- * Same shape as launchBlastIfNeeded, for the ortholog path: the params ARE the
13
- * request, and `launchCompleted` is what marks it done. They are left in place
14
- * either way -- on failure so the error stays attributable to a specific
15
- * request, and on success because they are the only durable statement of what
16
- * the view is, which is what a stored alignment that expired is rebuilt from.
17
- * The autorun tracks those two reads alone, so nothing refires until a new
18
- * request replaces them or a retry clears the mark.
19
- */
20
11
  export declare function launchOrthologsIfNeeded(self: JBrowsePluginMsaViewModel): void;
21
12
  export declare function launchBlastIfNeeded(self: JBrowsePluginMsaViewModel): void;
22
13
  /**
23
- * Once an accession-bearing alignment is present (fresh from BLAST or restored
24
- * from cache), fetch NCBI CDD domains for those accessions and overlay them.
25
- * Runs once per view; the domainsRequested guard prevents refiring when NCBI
26
- * returns no domains (which leaves the annotation list empty).
14
+ * Once an alignment whose rows carry accessions is present, fetch NCBI CDD
15
+ * domains for them and overlay them. `domainsRequested` makes this once per
16
+ * view, so NCBI returning no domains does not refire it.
27
17
  */
28
18
  export declare function autoLoadProteinDomains(self: JBrowsePluginMsaViewModel): void;
29
19
  export declare function processInit(self: JBrowsePluginMsaViewModel): void;