jbrowse-plugin-msaview 3.10.0 → 5.0.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 (150) hide show
  1. package/dist/AddHighlightModel/GenomeMouseoverHighlight.js +1 -1
  2. package/dist/AddHighlightModel/index.js +3 -12
  3. package/dist/LaunchMsaView/codingFeature.d.ts +0 -12
  4. package/dist/LaunchMsaView/codingFeature.js +0 -30
  5. package/dist/LaunchMsaView/codingFeature.test.js +1 -48
  6. package/dist/LaunchMsaView/components/BlastQuery/BlastAutomaticPanel.js +8 -10
  7. package/dist/LaunchMsaView/components/BlastQuery/BlastManualPanel.js +9 -11
  8. package/dist/LaunchMsaView/components/BlastQuery/CachedBlastResults.js +3 -1
  9. package/dist/LaunchMsaView/components/ManualMSALoader/ManualMSALoader.js +14 -16
  10. package/dist/LaunchMsaView/components/OrthologQuery/OrthologPanel.js +16 -18
  11. package/dist/LaunchMsaView/components/PreLoadedMSA/PreLoadedMSADataPanel.js +17 -19
  12. package/dist/LaunchMsaView/components/fetchSeq.d.ts +1 -1
  13. package/dist/LaunchMsaView/components/fetchSeq.js +3 -10
  14. package/dist/LaunchMsaView/components/launchConnectedView.d.ts +2 -2
  15. package/dist/LaunchMsaView/components/launchConnectedView.js +4 -8
  16. package/dist/LaunchMsaView/components/useFeatureSequence.d.ts +1 -1
  17. package/dist/LaunchMsaView/index.js +4 -13
  18. package/dist/LaunchMsaView/launchTarget.d.ts +7 -30
  19. package/dist/LaunchMsaView/launchTarget.js +10 -49
  20. package/dist/LaunchMsaView/launchTarget.test.js +17 -109
  21. package/dist/LaunchMsaViewExtensionPoint/index.js +3 -3
  22. package/dist/LaunchMsaViewExtensionPoint/index.test.js +24 -21
  23. package/dist/MsaViewPanel/index.js +3 -3
  24. package/dist/MsaViewPanel/isMsaView.d.ts +4 -0
  25. package/dist/MsaViewPanel/isMsaView.js +3 -0
  26. package/dist/MsaViewPanel/model.d.ts +8 -11
  27. package/dist/MsaViewPanel/model.js +3 -13
  28. package/dist/MsaViewPanel/resolveConnectedTranscript.js +1 -7
  29. package/dist/chunks/AboutDialog-WT6GXMXU.js +2 -0
  30. package/dist/chunks/AboutDialog-WT6GXMXU.js.map +7 -0
  31. package/dist/chunks/AnnotationFileDialog-34R7LFHB.js +2 -0
  32. package/dist/chunks/AnnotationFileDialog-34R7LFHB.js.map +7 -0
  33. package/dist/chunks/BgzipFastaMsaAdapter-ZMUARSUQ.js +4 -0
  34. package/dist/chunks/BgzipFastaMsaAdapter-ZMUARSUQ.js.map +7 -0
  35. package/dist/chunks/ExportSVGDialog-NMVUHFGY.js +2 -0
  36. package/dist/chunks/ExportSVGDialog-NMVUHFGY.js.map +7 -0
  37. package/dist/chunks/FeatureDialog-XNHIA5PS.js +2 -0
  38. package/dist/chunks/FeatureDialog-XNHIA5PS.js.map +7 -0
  39. package/dist/chunks/HelpDialog-DF4Y536X.js +2 -0
  40. package/dist/chunks/HelpDialog-DF4Y536X.js.map +7 -0
  41. package/dist/chunks/LaunchMsaViewDialog-LMZOJ7T6.js +5 -0
  42. package/dist/chunks/LaunchMsaViewDialog-LMZOJ7T6.js.map +7 -0
  43. package/dist/chunks/MetadataDialog-2WT6EX4M.js +2 -0
  44. package/dist/chunks/MetadataDialog-2WT6EX4M.js.map +7 -0
  45. package/dist/chunks/MsaViewPanel-YYP4CYYO.js +2 -0
  46. package/dist/chunks/MsaViewPanel-YYP4CYYO.js.map +7 -0
  47. package/dist/chunks/TrackInfoDialog-TYCMLUKG.js +2 -0
  48. package/dist/chunks/TrackInfoDialog-TYCMLUKG.js.map +7 -0
  49. package/dist/chunks/TreeNodeInfoDialog-BFF6JRCO.js +2 -0
  50. package/dist/chunks/TreeNodeInfoDialog-BFF6JRCO.js.map +7 -0
  51. package/dist/chunks/chunk-2IWD4OO4.js +2 -0
  52. package/dist/chunks/chunk-2IWD4OO4.js.map +7 -0
  53. package/dist/chunks/chunk-5ORPTTMR.js +2 -0
  54. package/dist/chunks/chunk-5ORPTTMR.js.map +7 -0
  55. package/dist/chunks/chunk-6YFCSBEE.js +2 -0
  56. package/dist/chunks/chunk-6YFCSBEE.js.map +7 -0
  57. package/dist/chunks/chunk-73KAPCJ2.js +2 -0
  58. package/dist/chunks/chunk-73KAPCJ2.js.map +7 -0
  59. package/dist/chunks/chunk-BX4Z274Q.js +2 -0
  60. package/dist/chunks/chunk-BX4Z274Q.js.map +7 -0
  61. package/dist/chunks/chunk-CVLB4VFP.js +2 -0
  62. package/dist/chunks/chunk-CVLB4VFP.js.map +7 -0
  63. package/dist/chunks/chunk-DNFD5II2.js +2 -0
  64. package/dist/chunks/chunk-DNFD5II2.js.map +7 -0
  65. package/dist/chunks/chunk-ENTWPX2K.js +6 -0
  66. package/dist/chunks/chunk-ENTWPX2K.js.map +7 -0
  67. package/dist/chunks/chunk-FCNT6B47.js +2 -0
  68. package/dist/chunks/chunk-FCNT6B47.js.map +7 -0
  69. package/dist/chunks/chunk-FCWXYIFC.js +2 -0
  70. package/dist/chunks/chunk-FCWXYIFC.js.map +7 -0
  71. package/dist/chunks/chunk-FY3BTKY4.js +2 -0
  72. package/dist/chunks/chunk-FY3BTKY4.js.map +7 -0
  73. package/dist/chunks/chunk-IBBQUV2I.js +2 -0
  74. package/dist/chunks/chunk-IBBQUV2I.js.map +7 -0
  75. package/dist/chunks/chunk-J6KYLXRD.js +2 -0
  76. package/dist/chunks/chunk-J6KYLXRD.js.map +7 -0
  77. package/dist/chunks/chunk-MBI4TMKR.js +2 -0
  78. package/dist/chunks/chunk-MBI4TMKR.js.map +7 -0
  79. package/dist/chunks/chunk-MHBTV7BZ.js +3 -0
  80. package/dist/chunks/chunk-MHBTV7BZ.js.map +7 -0
  81. package/dist/chunks/chunk-NSASZ5FM.js +2 -0
  82. package/dist/chunks/chunk-NSASZ5FM.js.map +7 -0
  83. package/dist/chunks/chunk-O7JNDIKD.js +2 -0
  84. package/dist/chunks/chunk-O7JNDIKD.js.map +7 -0
  85. package/dist/chunks/chunk-P77DPK6B.js +2 -0
  86. package/dist/chunks/chunk-P77DPK6B.js.map +7 -0
  87. package/dist/chunks/chunk-PJZ3XTMG.js +6 -0
  88. package/dist/chunks/chunk-PJZ3XTMG.js.map +7 -0
  89. package/dist/chunks/chunk-QAC6JSDZ.js +5 -0
  90. package/dist/chunks/chunk-QAC6JSDZ.js.map +7 -0
  91. package/dist/chunks/chunk-QMLPITS4.js +18 -0
  92. package/dist/chunks/chunk-QMLPITS4.js.map +7 -0
  93. package/dist/chunks/chunk-RTKNCGDF.js +8 -0
  94. package/dist/chunks/chunk-RTKNCGDF.js.map +7 -0
  95. package/dist/chunks/chunk-TITP6LCV.js +2 -0
  96. package/dist/chunks/chunk-TITP6LCV.js.map +7 -0
  97. package/dist/chunks/chunk-VOBS7IVV.js +2 -0
  98. package/dist/chunks/chunk-VOBS7IVV.js.map +7 -0
  99. package/dist/chunks/chunk-YUKJI2AH.js +2 -0
  100. package/dist/chunks/chunk-YUKJI2AH.js.map +7 -0
  101. package/dist/chunks/chunk-ZTQYZDWK.js +2 -0
  102. package/dist/chunks/chunk-ZTQYZDWK.js.map +7 -0
  103. package/dist/chunks/dist-2T65ZS4E.js +2 -0
  104. package/dist/chunks/dist-2T65ZS4E.js.map +7 -0
  105. package/dist/chunks/dist-UX5CKLQJ.js +2 -0
  106. package/dist/chunks/dist-UX5CKLQJ.js.map +7 -0
  107. package/dist/chunks/model-7VBTOIJX.js +12 -0
  108. package/dist/chunks/model-7VBTOIJX.js.map +7 -0
  109. package/dist/chunks/renderToSvg-B7RCHRO3.js +2 -0
  110. package/dist/chunks/renderToSvg-B7RCHRO3.js.map +7 -0
  111. package/dist/config.json +1 -1
  112. package/dist/index.js +5 -2
  113. package/dist/jbrowse-plugin-msaview.esm.js +3 -0
  114. package/dist/jbrowse-plugin-msaview.esm.js.map +7 -0
  115. package/dist/utils/launchMsaView.d.ts +1 -1
  116. package/dist/utils/launchMsaView.js +5 -2
  117. package/dist/utils/workspaces.js +6 -25
  118. package/dist/utils/workspaces.test.js +5 -5
  119. package/dist/version.d.ts +1 -1
  120. package/dist/version.js +1 -1
  121. package/package.json +3 -5
  122. package/src/AddHighlightModel/GenomeMouseoverHighlight.tsx +1 -1
  123. package/src/AddHighlightModel/index.tsx +6 -16
  124. package/src/LaunchMsaView/codingFeature.test.ts +1 -64
  125. package/src/LaunchMsaView/codingFeature.ts +0 -38
  126. package/src/LaunchMsaView/components/BlastQuery/BlastAutomaticPanel.tsx +3 -3
  127. package/src/LaunchMsaView/components/BlastQuery/BlastManualPanel.tsx +3 -3
  128. package/src/LaunchMsaView/components/BlastQuery/CachedBlastResults.tsx +3 -1
  129. package/src/LaunchMsaView/components/ManualMSALoader/ManualMSALoader.tsx +3 -3
  130. package/src/LaunchMsaView/components/OrthologQuery/OrthologPanel.tsx +3 -3
  131. package/src/LaunchMsaView/components/PreLoadedMSA/PreLoadedMSADataPanel.tsx +3 -3
  132. package/src/LaunchMsaView/components/fetchSeq.ts +3 -20
  133. package/src/LaunchMsaView/components/launchConnectedView.ts +5 -8
  134. package/src/LaunchMsaView/index.ts +8 -13
  135. package/src/LaunchMsaView/launchTarget.test.ts +20 -133
  136. package/src/LaunchMsaView/launchTarget.ts +18 -70
  137. package/src/LaunchMsaViewExtensionPoint/index.test.ts +26 -21
  138. package/src/LaunchMsaViewExtensionPoint/index.ts +3 -4
  139. package/src/MsaViewPanel/index.ts +3 -4
  140. package/src/MsaViewPanel/isMsaView.ts +7 -0
  141. package/src/MsaViewPanel/model.ts +3 -16
  142. package/src/MsaViewPanel/resolveConnectedTranscript.ts +1 -7
  143. package/src/index.ts +5 -2
  144. package/src/utils/launchMsaView.ts +5 -2
  145. package/src/utils/useFetch.ts +1 -1
  146. package/src/utils/workspaces.test.ts +5 -5
  147. package/src/utils/workspaces.ts +6 -25
  148. package/src/version.ts +1 -1
  149. package/dist/jbrowse-plugin-msaview.umd.production.min.js +0 -137
  150. package/dist/jbrowse-plugin-msaview.umd.production.min.js.map +0 -7
@@ -1,7 +1,7 @@
1
1
  import React from 'react';
2
2
  import { getSession } from '@jbrowse/core/util';
3
3
  import { observer } from 'mobx-react';
4
- import { isMsaView } from '../MsaViewPanel/model';
4
+ import { isMsaView } from '../MsaViewPanel/isMsaView';
5
5
  import { hasHoverPosition, useStyles } from './util';
6
6
  const GenomeMouseoverHighlight = observer(function ({ model, }) {
7
7
  const { hovered, views } = getSession(model);
@@ -1,19 +1,10 @@
1
1
  import React from 'react';
2
2
  import { getSession } from '@jbrowse/core/util';
3
- import { isMsaView } from '../MsaViewPanel/model';
3
+ import { isMsaView } from '../MsaViewPanel/isMsaView';
4
4
  import HighlightComponents from './HighlightComponents';
5
5
  export default function AddHighlightComponentsModelF(pluginManager) {
6
- pluginManager.addToExtensionPoint(
7
- // @ts-expect-error v4 hosts have no contributeToExtensionPoint
8
- 'LinearGenomeView-TracksContainerComponent', (rest, { model }) => {
6
+ pluginManager.contributeToExtensionPoint('LinearGenomeView-TracksContainerComponent', ({ model }) => {
9
7
  const { views } = getSession(model);
10
- const hasMsaView = views.some(v => isMsaView(v) && v.connectedViewId === model.id);
11
- if (!hasMsaView) {
12
- return rest;
13
- }
14
- return [
15
- ...rest,
16
- React.createElement(HighlightComponents, { key: "highlight_protein_viewer_msaview", model: model }),
17
- ];
8
+ return views.some(v => isMsaView(v) && v.connectedViewId === model.id) ? (React.createElement(HighlightComponents, { key: "highlight_protein_viewer_msaview", model: model })) : undefined;
18
9
  });
19
10
  }
@@ -1,14 +1,2 @@
1
1
  import type { Feature } from '@jbrowse/core/util';
2
- export declare function isGeneLikeType(type: unknown): boolean;
3
2
  export declare function isCodingFeature(feature: Feature): boolean;
4
- /**
5
- * Whether the feature is known not to code for anything.
6
- *
7
- * Not simply `!isCodingFeature`: a feature that arrived with no subfeatures at
8
- * all says nothing either way, and a host is free to hand one over that way.
9
- * Reading that as "no protein here" takes the menu item off a perfectly
10
- * ordinary gene, silently, which is worse than opening a dialog that then has
11
- * nothing to translate.
12
- */
13
- export declare function isKnownNonCoding(feature: Feature): boolean;
14
- export declare function geneLikeRoot(feature: Feature): Feature;
@@ -1,12 +1,3 @@
1
- // Copied from @jbrowse/core's featureTypes rather than imported: the barrel
2
- // only exports it from v5, and a bundle resolving it on a v4 host reads
3
- // undefined.
4
- const GENE_LIKE_TYPE = /gene(_segment)?$|rna$|transcript/;
5
- // `unknown` rather than `string | undefined`, because the type comes off a
6
- // host's hit-test item and nothing here built it.
7
- export function isGeneLikeType(type) {
8
- return typeof type === 'string' && GENE_LIKE_TYPE.test(type.toLowerCase());
9
- }
10
1
  function isCDS(feature) {
11
2
  return feature.get('type')?.toLowerCase() === 'cds';
12
3
  }
@@ -15,24 +6,3 @@ function isCDS(feature) {
15
6
  export function isCodingFeature(feature) {
16
7
  return isCDS(feature) || !!feature.get('subfeatures')?.some(isCodingFeature);
17
8
  }
18
- /**
19
- * Whether the feature is known not to code for anything.
20
- *
21
- * Not simply `!isCodingFeature`: a feature that arrived with no subfeatures at
22
- * all says nothing either way, and a host is free to hand one over that way.
23
- * Reading that as "no protein here" takes the menu item off a perfectly
24
- * ordinary gene, silently, which is worse than opening a dialog that then has
25
- * nothing to translate.
26
- */
27
- export function isKnownNonCoding(feature) {
28
- return !!feature.get('subfeatures')?.length && !isCodingFeature(feature);
29
- }
30
- // The outermost gene-like ancestor, so a click on an isoform opens the dialog
31
- // on the gene with every transcript to choose from, as the canvas host does.
32
- export function geneLikeRoot(feature) {
33
- let root = feature;
34
- for (let parent = root.parent?.(); parent && isGeneLikeType(parent.get('type')); parent = parent.parent?.()) {
35
- root = parent;
36
- }
37
- return root;
38
- }
@@ -1,6 +1,6 @@
1
1
  import { SimpleFeature } from '@jbrowse/core/util';
2
2
  import { describe, expect, it } from 'vitest';
3
- import { geneLikeRoot, isCodingFeature, isGeneLikeType, isKnownNonCoding, } from './codingFeature';
3
+ import { isCodingFeature } from './codingFeature';
4
4
  function feature(type, subfeatures = []) {
5
5
  return new SimpleFeature({
6
6
  uniqueId: `${type}-${Math.random()}`,
@@ -11,29 +11,6 @@ function feature(type, subfeatures = []) {
11
11
  subfeatures: subfeatures.map(s => s.toJSON()),
12
12
  });
13
13
  }
14
- describe('isGeneLikeType', () => {
15
- it('accepts the SO spellings of gene, transcript and RNA', () => {
16
- for (const t of [
17
- 'gene',
18
- 'mRNA',
19
- 'transcript',
20
- 'ncRNA_gene',
21
- 'protein_coding_gene',
22
- 'V_gene_segment',
23
- 'primary_transcript',
24
- 'pseudogenic_transcript',
25
- 'lnc_RNA',
26
- ]) {
27
- expect(isGeneLikeType(t)).toBe(true);
28
- }
29
- });
30
- it('rejects regions, matches and CDS', () => {
31
- for (const t of ['intergenic_region', 'cDNA_match', 'CDS', 'exon']) {
32
- expect(isGeneLikeType(t)).toBe(false);
33
- }
34
- expect(isGeneLikeType(undefined)).toBe(false);
35
- });
36
- });
37
14
  describe('isCodingFeature', () => {
38
15
  it('finds a CDS anywhere below the feature', () => {
39
16
  const gene = feature('gene', [
@@ -49,27 +26,3 @@ describe('isCodingFeature', () => {
49
26
  expect(isCodingFeature(feature('CDS'))).toBe(true);
50
27
  });
51
28
  });
52
- describe('isKnownNonCoding', () => {
53
- it('is true for a transcript whose subfeatures are all exons', () => {
54
- expect(isKnownNonCoding(feature('lnc_RNA', [feature('exon'), feature('exon')]))).toBe(true);
55
- });
56
- // a host is free to hand over a bare record, and reading that as "no protein
57
- // here" takes the menu item off an ordinary gene without a word
58
- it('is false for a feature that arrived with no subfeatures at all', () => {
59
- expect(isKnownNonCoding(feature('gene'))).toBe(false);
60
- });
61
- it('is false when a CDS is somewhere below', () => {
62
- expect(isKnownNonCoding(feature('gene', [feature('mRNA', [feature('CDS')])]))).toBe(false);
63
- });
64
- });
65
- describe('geneLikeRoot', () => {
66
- it('climbs from an isoform to its gene', () => {
67
- const gene = feature('gene', [feature('mRNA', [feature('CDS')])]);
68
- const mrna = gene.get('subfeatures')[0];
69
- expect(geneLikeRoot(mrna)).toBe(gene);
70
- });
71
- it('stays put with no gene-like parent', () => {
72
- const mrna = feature('mRNA', [feature('CDS')]);
73
- expect(geneLikeRoot(mrna)).toBe(mrna);
74
- });
75
- });
@@ -103,16 +103,14 @@ const BlastAutomaticPanel = observer(function ({ handleClose, feature, model, ch
103
103
  selectedTranscript: selectedTranscript.toJSON(),
104
104
  proteinSequence,
105
105
  };
106
- submit(() => {
107
- launchConnectedView({
108
- view,
109
- feature: selectedTranscript,
110
- placement,
111
- displayName: getBlastViewTitle(feature, selectedTranscript),
112
- ...builtAlignmentLook,
113
- blastParams,
114
- });
115
- });
106
+ submit(() => launchConnectedView({
107
+ view,
108
+ feature: selectedTranscript,
109
+ placement,
110
+ displayName: getBlastViewTitle(feature, selectedTranscript),
111
+ ...builtAlignmentLook,
112
+ blastParams,
113
+ }));
116
114
  }
117
115
  }, onCancel: handleClose })));
118
116
  });
@@ -85,17 +85,15 @@ const BlastManualPanel = observer(function ({ handleClose, feature, model, child
85
85
  React.createElement(QueryRowSelector, { ...queryRow })))),
86
86
  React.createElement(SubmitCancelActions, { model: model, submitDisabled: !selectedTranscript || !msaText.trim(), onSubmit: placement => {
87
87
  if (selectedTranscript) {
88
- submit(() => {
89
- launchConnectedView({
90
- view,
91
- feature: selectedTranscript,
92
- placement,
93
- displayName: getGeneDisplayName(selectedTranscript),
94
- querySeqName: queryRow.querySeqName,
95
- querySeqOffset: queryRow.querySeqOffset,
96
- data: { msa: msaText, tree: treeText },
97
- });
98
- });
88
+ submit(() => launchConnectedView({
89
+ view,
90
+ feature: selectedTranscript,
91
+ placement,
92
+ displayName: getGeneDisplayName(selectedTranscript),
93
+ querySeqName: queryRow.querySeqName,
94
+ querySeqOffset: queryRow.querySeqOffset,
95
+ data: { msa: msaText, tree: treeText },
96
+ }));
99
97
  }
100
98
  }, onCancel: handleClose })));
101
99
  });
@@ -70,8 +70,10 @@ const CachedBlastResults = observer(function ({ model, handleClose, feature, })
70
70
  displayName: `BLAST - ${getResultDisplayName(cached)}`,
71
71
  ...builtAlignmentLook,
72
72
  data: { msa, tree, treeMetadata },
73
+ }).then(handleClose, (e) => {
74
+ console.error(e);
75
+ setOperationError(e);
73
76
  });
74
- handleClose();
75
77
  };
76
78
  const displayError = error ?? operationError;
77
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,
@@ -80,22 +80,20 @@ const ManualMSALoader = observer(function PreLoadedMSA2({ model, feature, handle
80
80
  (inputMethod === 'file' && !msaFileLocation) ||
81
81
  (inputMethod === 'text' && !msaText.trim()), onSubmit: placement => {
82
82
  if (selectedTranscript) {
83
- submit(() => {
84
- launchConnectedView({
85
- view,
86
- feature: selectedTranscript,
87
- placement,
88
- displayName: getGeneDisplayName(selectedTranscript),
89
- querySeqName: queryRow.querySeqName,
90
- querySeqOffset: queryRow.querySeqOffset,
91
- ...(inputMethod === 'file'
92
- ? {
93
- msaFilehandle: msaFileLocation,
94
- treeFilehandle: treeFileLocation,
95
- }
96
- : { data: { msa: msaText, tree: treeText } }),
97
- });
98
- });
83
+ submit(() => launchConnectedView({
84
+ view,
85
+ feature: selectedTranscript,
86
+ placement,
87
+ displayName: getGeneDisplayName(selectedTranscript),
88
+ querySeqName: queryRow.querySeqName,
89
+ querySeqOffset: queryRow.querySeqOffset,
90
+ ...(inputMethod === 'file'
91
+ ? {
92
+ msaFilehandle: msaFileLocation,
93
+ treeFilehandle: treeFileLocation,
94
+ }
95
+ : { data: { msa: msaText, tree: treeText } }),
96
+ }));
99
97
  }
100
98
  }, onCancel: handleClose })));
101
99
  });
@@ -60,24 +60,22 @@ const OrthologPanel = observer(function ({ handleClose, feature, model, preferre
60
60
  React.createElement(TranscriptSelector, { feature: feature, ...transcriptSelection })),
61
61
  React.createElement(SubmitCancelActions, { model: model, hint: React.createElement(SequenceStatusMessage, { status: sequenceStatus }), submitDisabled: !proteinSequence || !rowCountValid, onSubmit: placement => {
62
62
  if (selectedTranscript) {
63
- submit(() => {
64
- launchConnectedView({
65
- view,
66
- feature: selectedTranscript,
67
- placement,
68
- displayName: `Orthologs - ${getGeneDisplayName(feature)} - ${getTranscriptDisplayName(selectedTranscript)}`,
69
- ...builtAlignmentLook,
70
- orthologParams: {
71
- taxId,
72
- source,
73
- maxSpecies: rowCount,
74
- geneCandidates,
75
- msaAlgorithm,
76
- selectedTranscript: selectedTranscript.toJSON(),
77
- proteinSequence,
78
- },
79
- });
80
- });
63
+ submit(() => launchConnectedView({
64
+ view,
65
+ feature: selectedTranscript,
66
+ placement,
67
+ displayName: `Orthologs - ${getGeneDisplayName(feature)} - ${getTranscriptDisplayName(selectedTranscript)}`,
68
+ ...builtAlignmentLook,
69
+ orthologParams: {
70
+ taxId,
71
+ source,
72
+ maxSpecies: rowCount,
73
+ geneCandidates,
74
+ msaAlgorithm,
75
+ selectedTranscript: selectedTranscript.toJSON(),
76
+ proteinSequence,
77
+ },
78
+ }));
81
79
  }
82
80
  }, onCancel: handleClose })));
83
81
  });
@@ -82,25 +82,23 @@ const PreLoadedMSA = observer(function ({ model, feature, handleClose, preferred
82
82
  // dataset that does not cover this gene
83
83
  submitDisabled: !selectedTranscript || !msaData?.length || !querySeqName, onSubmit: placement => {
84
84
  if (selectedTranscript && msaText) {
85
- submit(() => {
86
- launchConnectedView({
87
- view,
88
- feature: selectedTranscript,
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 },
102
- });
103
- });
85
+ submit(() => launchConnectedView({
86
+ view,
87
+ feature: selectedTranscript,
88
+ placement,
89
+ displayName: getGeneDisplayName(selectedTranscript),
90
+ treeAreaWidth: 200,
91
+ treeWidth: 100,
92
+ drawNodeBubbles: false,
93
+ labelsAlignRight: true,
94
+ showBranchLen: false,
95
+ colWidth: 10,
96
+ rowHeight: 12,
97
+ colorSchemeName: 'percent_identity_dynamic',
98
+ querySeqName,
99
+ querySeqOffset: queryRow.querySeqOffset,
100
+ data: { msa: msaText },
101
+ }));
104
102
  }
105
103
  }, onCancel: handleClose })));
106
104
  });
@@ -7,5 +7,5 @@ export declare function fetchSeq({ start, end, refName, session, assemblyName, }
7
7
  session: AbstractSessionModel;
8
8
  }): Promise<{
9
9
  seq: string;
10
- assemblyGeneticCodeId: number | undefined;
10
+ assemblyGeneticCodeId: number;
11
11
  }>;
@@ -5,11 +5,8 @@ export async function fetchSeq({ start, end, refName, session, assemblyName, })
5
5
  if (!assembly) {
6
6
  throw new Error('assembly not found');
7
7
  }
8
- const sessionId = 'getSequence';
9
- // a named object keeps sessionId, which v4 hosts read from the args
10
- const args = {
8
+ const feats = await rpcManager.call('getSequence', 'CoreGetFeatures', {
11
9
  adapterConfig: getConf(assembly, ['sequence', 'adapter']),
12
- sessionId,
13
10
  regions: [
14
11
  {
15
12
  start,
@@ -18,15 +15,11 @@ export async function fetchSeq({ start, end, refName, session, assemblyName, })
18
15
  assemblyName,
19
16
  },
20
17
  ],
21
- };
22
- const feats = await rpcManager.call(sessionId, 'CoreGetFeatures', args);
18
+ });
23
19
  return {
24
20
  seq: feats[0]?.get('seq') ?? '',
25
21
  // travels with the sequence because the caller needs both to translate, and
26
22
  // resolving the assembly twice to get them is the shape that lost it
27
- assemblyGeneticCodeId: geneticCodeId(assembly, refName),
23
+ assemblyGeneticCodeId: assembly.getGeneticCodeId(refName),
28
24
  };
29
25
  }
30
- function geneticCodeId(assembly, refName) {
31
- return assembly.getGeneticCodeId?.(refName);
32
- }
@@ -15,9 +15,9 @@ export declare function launchConnectedView({ view, feature, placement, ...snaps
15
15
  view: LinearGenomeViewModel;
16
16
  feature?: Feature;
17
17
  placement: MsaViewPlacement;
18
- } & Record<string, unknown>): void;
18
+ } & Record<string, unknown>): Promise<import("@jbrowse/core/util").AbstractViewModel>;
19
19
  /** runs a panel's launch, closing the dialog on success and keeping the error */
20
20
  export declare function useLaunchSubmit(handleClose: () => void): {
21
21
  launchError: unknown;
22
- submit: (launch: () => void) => void;
22
+ submit: (launch: () => Promise<unknown>) => void;
23
23
  };
@@ -12,7 +12,7 @@ export const builtAlignmentLook = {
12
12
  * through `feature` to the transcript whose codons its query row is read by.
13
13
  */
14
14
  export function launchConnectedView({ view, feature, placement, ...snapshot }) {
15
- launchMsaView(getSession(view), {
15
+ return launchMsaView(getSession(view), {
16
16
  placement,
17
17
  connectedViewId: view.id,
18
18
  connectedFeature: feature?.toJSON(),
@@ -25,15 +25,11 @@ export function useLaunchSubmit(handleClose) {
25
25
  return {
26
26
  launchError,
27
27
  submit: (launch) => {
28
- try {
29
- setLaunchError(undefined);
30
- launch();
31
- handleClose();
32
- }
33
- catch (e) {
28
+ setLaunchError(undefined);
29
+ launch().then(handleClose, (e) => {
34
30
  console.error(e);
35
31
  setLaunchError(e);
36
- }
32
+ });
37
33
  },
38
34
  };
39
35
  }
@@ -9,7 +9,7 @@ export declare function useFeatureSequence({ view, feature, }: {
9
9
  proteinSequence: string;
10
10
  sequence: {
11
11
  seq: string;
12
- assemblyGeneticCodeId: number | undefined;
12
+ assemblyGeneticCodeId: number;
13
13
  } | undefined;
14
14
  error: unknown;
15
15
  isLoading: boolean;
@@ -1,8 +1,9 @@
1
+ import { lazy } from 'react';
1
2
  import { getContainingTrack, getSession } from '@jbrowse/core/util';
2
3
  import AddIcon from '@mui/icons-material/Add';
3
4
  import { isCodingFeature } from './codingFeature';
4
- import LaunchMsaViewDialog from './components/LaunchMsaViewDialog';
5
5
  import { launchTarget } from './launchTarget';
6
+ const LaunchMsaViewDialog = lazy(() => import('./components/LaunchMsaViewDialog'));
6
7
  function isDisplay(elt) {
7
8
  return elt.name === 'LinearBasicDisplay';
8
9
  }
@@ -23,10 +24,6 @@ function openDialog(self, target) {
23
24
  { model: track, handleClose, feature, preferredTranscriptId },
24
25
  ]);
25
26
  };
26
- if ('feature' in target) {
27
- open(target.feature);
28
- return;
29
- }
30
27
  target
31
28
  .fetchFeature()
32
29
  .then(feature => {
@@ -54,14 +51,8 @@ export function extendStateModel(stateModel) {
54
51
  contextMenuItems() {
55
52
  const target = launchTarget(self);
56
53
  return [
57
- // .call(self), not a bare call: a host's own contextMenuItems may
58
- // reach its sibling views through `this`, which is undefined when the
59
- // captured super is invoked detached. It throws, the ErrorBoundary the
60
- // menu builds inside swallows it, and the user right-clicks a feature
61
- // and gets no menu at all -- the host's own rows gone too, which is
62
- // worse than this plugin contributing nothing. jbrowse-components hit
63
- // exactly this with `this.isGeneLike` and fixed its side in
64
- // 104bbfc581, but a plugin cannot choose which host build it runs on.
54
+ // a detached call leaves `this` undefined for a host method that
55
+ // reads it, and the throw takes the host's own rows with it
65
56
  ...superContextMenuItems.call(self),
66
57
  ...(target
67
58
  ? [
@@ -1,4 +1,3 @@
1
- import { isGeneLikeType } from './codingFeature';
2
1
  import type { MenuItem } from '@jbrowse/core/ui';
3
2
  import type { Feature } from '@jbrowse/core/util';
4
3
  export interface ContextMenuInfo {
@@ -14,43 +13,21 @@ export interface ContextMenuInfo {
14
13
  export interface DisplayModel {
15
14
  contextMenuItems: () => MenuItem[];
16
15
  contextMenuInfo?: ContextMenuInfo;
17
- fetchFullFeature?: (featureId: string, displayedRegionIndex: number) => Promise<Feature | undefined>;
18
- contextMenuFeature?: Feature;
16
+ fetchFullFeature: (featureId: string, displayedRegionIndex: number) => Promise<Feature | undefined>;
19
17
  }
20
18
  /**
21
- * What the menu item launches on. A legacy host hands over the whole feature,
22
- * so whether it codes for anything is known while the menu is built; a canvas
23
- * host's hit test carries a type and an id, so that question can only be
24
- * answered after the fetch, and the caller answers it there.
19
+ * What the menu item launches on. The hit test carries a type and an id, so
20
+ * whether the feature codes for anything can only be answered after the fetch,
21
+ * and the caller answers it there.
25
22
  */
26
- interface ClickedTranscript {
23
+ export interface MenuTarget {
24
+ fetchFeature: () => Promise<Feature | undefined>;
27
25
  /**
28
26
  * The isoform the click actually landed on, when the dialog opens on its
29
27
  * gene. Climbing to the gene is what puts every isoform in the picker, and it
30
28
  * threw away which one the user pointed at: the picker then opened on the
31
- * longest transcript, and on a v4 host that is the only place the choice was
32
- * ever stated.
29
+ * longest transcript.
33
30
  */
34
31
  preferredTranscriptId?: string;
35
32
  }
36
- export type MenuTarget = (ClickedTranscript & {
37
- feature: Feature;
38
- }) | (ClickedTranscript & {
39
- fetchFeature: () => Promise<Feature | undefined>;
40
- });
41
- export { isGeneLikeType };
42
- /**
43
- * How to get the right-clicked feature, or nothing when there is nothing to
44
- * launch on. Both host shapes reduce to one target, so the menu item is built
45
- * and the dialog is opened by one code path — and the same gene test decides
46
- * both. The strict three-name set the legacy branch used to carry disagreed
47
- * with the loose one above, so a `lnc_RNA` offered the menu item on a 4.3 host
48
- * and not on a 3.7 one.
49
- *
50
- * Gene-like is not enough on its own: an lncRNA has no protein to align, and
51
- * accepting it opened a dialog whose Submit never left grey, with nothing
52
- * saying why. Where the whole feature is in hand the CDS decides here -- but
53
- * only where there are subfeatures to read it off, since a feature that came
54
- * with none has not answered the question.
55
- */
56
33
  export declare function launchTarget(self: DisplayModel): MenuTarget | undefined;
@@ -1,53 +1,14 @@
1
- import { geneLikeRoot, isGeneLikeType, isKnownNonCoding } from './codingFeature';
2
- // Read off the clicked item rather than off the display.
3
- //
4
- // LinearBasicDisplay used to publish an `isGeneLike` getter and this gated on
5
- // it. jbrowse-components 684142b3 (2026-08-16) inlined that getter into its own
6
- // `contextMenuItems`, and every host built after it returns `undefined` here --
7
- // so the gate was never satisfied, `onClick` stayed undefined, and the item
8
- // silently left the right-click menu on every gene track. Nothing failed loudly:
9
- // the display still had contextMenuInfo and fetchFullFeature, and the menu still
10
- // opened with its own items in it.
11
- //
12
- // A predicate over the type we were already given cannot go the same way, and it
13
- // costs one comparison. Deliberately the same loose case-insensitive test the
14
- // host applies (`isGeneLikeType` in collapseIntronsMenu.ts): real GFFs carry
15
- // 'mRNA', 'lnc_RNA', 'protein_coding_gene', 'transcript'.
16
- export { isGeneLikeType };
17
- /**
18
- * How to get the right-clicked feature, or nothing when there is nothing to
19
- * launch on. Both host shapes reduce to one target, so the menu item is built
20
- * and the dialog is opened by one code path — and the same gene test decides
21
- * both. The strict three-name set the legacy branch used to carry disagreed
22
- * with the loose one above, so a `lnc_RNA` offered the menu item on a 4.3 host
23
- * and not on a 3.7 one.
24
- *
25
- * Gene-like is not enough on its own: an lncRNA has no protein to align, and
26
- * accepting it opened a dialog whose Submit never left grey, with nothing
27
- * saying why. Where the whole feature is in hand the CDS decides here -- but
28
- * only where there are subfeatures to read it off, since a feature that came
29
- * with none has not answered the question.
30
- */
1
+ import { isGeneLikeType } from '@jbrowse/core/util';
2
+ // Read off the clicked item rather than off the display: LinearBasicDisplay's
3
+ // `isGeneLike` getter was inlined into its own `contextMenuItems`
4
+ // (jbrowse-components 684142b3), and gating on it silently took the item off
5
+ // every gene track.
31
6
  export function launchTarget(self) {
32
7
  const info = self.contextMenuInfo;
33
- const fetchFullFeature = self.fetchFullFeature;
34
- // exclusive, not a fallthrough: a display publishing contextMenuInfo has
35
- // already said what was clicked, and reading contextMenuFeature after it
36
- // rejects the click can only answer with some other feature
37
- if (info && fetchFullFeature) {
38
- return isGeneLikeType(info.item.type)
39
- ? {
40
- fetchFeature: () => fetchFullFeature(info.item.featureId, info.displayedRegionIndex),
41
- preferredTranscriptId: info.subfeature?.featureId,
42
- }
43
- : undefined;
44
- }
45
- const legacy = self.contextMenuFeature;
46
- if (!legacy) {
47
- return undefined;
48
- }
49
- const root = geneLikeRoot(legacy);
50
- return isGeneLikeType(root.get('type')) && !isKnownNonCoding(root)
51
- ? { feature: root, preferredTranscriptId: legacy.id() }
8
+ return info && isGeneLikeType(info.item.type)
9
+ ? {
10
+ fetchFeature: () => self.fetchFullFeature(info.item.featureId, info.displayedRegionIndex),
11
+ preferredTranscriptId: info.subfeature?.featureId,
12
+ }
52
13
  : undefined;
53
14
  }