jbrowse-plugin-msaview 3.5.0 → 3.6.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 (130) hide show
  1. package/dist/AddHighlightModel/index.js +3 -3
  2. package/dist/BgzipFastaMsaAdapter/BgzipFastaMsaAdapter.d.ts +8 -2
  3. package/dist/BgzipFastaMsaAdapter/configSchema.d.ts +3 -3
  4. package/dist/BgzipFastaMsaAdapter/configSchema.js +1 -1
  5. package/dist/LaunchMsaView/codingFeature.d.ts +14 -0
  6. package/dist/LaunchMsaView/codingFeature.js +38 -0
  7. package/dist/LaunchMsaView/codingFeature.test.d.ts +1 -0
  8. package/dist/LaunchMsaView/codingFeature.test.js +75 -0
  9. package/dist/LaunchMsaView/components/BlastQuery/BlastAutomaticPanel.d.ts +3 -1
  10. package/dist/LaunchMsaView/components/BlastQuery/BlastAutomaticPanel.js +19 -24
  11. package/dist/LaunchMsaView/components/BlastQuery/BlastManualPanel.d.ts +3 -1
  12. package/dist/LaunchMsaView/components/BlastQuery/BlastManualPanel.js +6 -2
  13. package/dist/LaunchMsaView/components/BlastQuery/BlastPanel.d.ts +7 -3
  14. package/dist/LaunchMsaView/components/BlastQuery/BlastPanel.js +8 -7
  15. package/dist/LaunchMsaView/components/BlastQuery/BlastSettingsDialog.js +2 -2
  16. package/dist/LaunchMsaView/components/BlastQuery/consts.d.ts +2 -0
  17. package/dist/LaunchMsaView/components/BlastQuery/consts.js +28 -4
  18. package/dist/LaunchMsaView/components/HelpButton.d.ts +2 -0
  19. package/dist/LaunchMsaView/components/HelpButton.js +17 -0
  20. package/dist/LaunchMsaView/components/HelpDialog.d.ts +4 -0
  21. package/dist/LaunchMsaView/components/HelpDialog.js +18 -0
  22. package/dist/LaunchMsaView/components/LaunchMsaViewDialog.d.ts +3 -1
  23. package/dist/LaunchMsaView/components/LaunchMsaViewDialog.js +21 -16
  24. package/dist/LaunchMsaView/components/ManualMSALoader/ManualMSALoader.d.ts +3 -1
  25. package/dist/LaunchMsaView/components/ManualMSALoader/ManualMSALoader.js +9 -4
  26. package/dist/LaunchMsaView/components/OrthologQuery/OrthologPanel.d.ts +3 -1
  27. package/dist/LaunchMsaView/components/OrthologQuery/OrthologPanel.js +17 -8
  28. package/dist/LaunchMsaView/components/PreLoadedMSA/PreLoadedMSADataPanel.d.ts +3 -1
  29. package/dist/LaunchMsaView/components/PreLoadedMSA/PreLoadedMSADataPanel.js +33 -15
  30. package/dist/LaunchMsaView/components/PreLoadedMSA/preCalculatedLaunchView.d.ts +3 -1
  31. package/dist/LaunchMsaView/components/PreLoadedMSA/preCalculatedLaunchView.js +2 -1
  32. package/dist/LaunchMsaView/components/SequenceStatus.d.ts +14 -0
  33. package/dist/LaunchMsaView/components/SequenceStatus.js +15 -0
  34. package/dist/LaunchMsaView/components/SubmitCancelActions.d.ts +3 -1
  35. package/dist/LaunchMsaView/components/SubmitCancelActions.js +25 -13
  36. package/dist/LaunchMsaView/components/SubmitCancelActions.test.js +33 -3
  37. package/dist/LaunchMsaView/components/TabPanel.js +15 -3
  38. package/dist/LaunchMsaView/components/TabPanel.test.d.ts +1 -0
  39. package/dist/LaunchMsaView/components/TabPanel.test.js +36 -0
  40. package/dist/LaunchMsaView/components/TranscriptSelector.d.ts +3 -1
  41. package/dist/LaunchMsaView/components/TranscriptSelector.js +4 -2
  42. package/dist/LaunchMsaView/components/calculateProteinSequence.d.ts +4 -1
  43. package/dist/LaunchMsaView/components/calculateProteinSequence.js +20 -10
  44. package/dist/LaunchMsaView/components/fetchSeq.d.ts +4 -1
  45. package/dist/LaunchMsaView/components/fetchSeq.js +14 -4
  46. package/dist/LaunchMsaView/components/launchPlacement.d.ts +8 -0
  47. package/dist/LaunchMsaView/components/launchPlacement.js +25 -0
  48. package/dist/LaunchMsaView/components/useFeatureSequence.d.ts +2 -0
  49. package/dist/LaunchMsaView/components/useFeatureSequence.js +14 -11
  50. package/dist/LaunchMsaView/components/useTranscriptSelection.d.ts +10 -1
  51. package/dist/LaunchMsaView/components/useTranscriptSelection.js +21 -3
  52. package/dist/LaunchMsaView/components/useTranscriptSelection.test.d.ts +1 -0
  53. package/dist/LaunchMsaView/components/useTranscriptSelection.test.js +57 -0
  54. package/dist/LaunchMsaView/index.js +28 -9
  55. package/dist/LaunchMsaView/launchTarget.d.ts +36 -8
  56. package/dist/LaunchMsaView/launchTarget.js +23 -13
  57. package/dist/LaunchMsaView/launchTarget.test.js +80 -15
  58. package/dist/LaunchMsaView/useQueryRowName.d.ts +12 -0
  59. package/dist/LaunchMsaView/useQueryRowName.js +11 -0
  60. package/dist/LaunchMsaView/useQueryRowName.test.js +18 -1
  61. package/dist/LaunchMsaView/util.js +5 -2
  62. package/dist/MsaViewPanel/afterCreateAutoruns.d.ts +6 -4
  63. package/dist/MsaViewPanel/afterCreateAutoruns.js +26 -11
  64. package/dist/MsaViewPanel/components/LaunchProgress.d.ts +4 -3
  65. package/dist/MsaViewPanel/components/LaunchProgress.js +10 -7
  66. package/dist/MsaViewPanel/components/MsaViewPanel.js +7 -3
  67. package/dist/MsaViewPanel/components/MsaViewPanel.test.js +24 -0
  68. package/dist/MsaViewPanel/genomeToMSA.js +21 -3
  69. package/dist/MsaViewPanel/genomeToMSA.test.js +153 -25
  70. package/dist/MsaViewPanel/model.d.ts +143 -52
  71. package/dist/MsaViewPanel/model.js +52 -3
  72. package/dist/MsaViewPanel/model.test.js +66 -0
  73. package/dist/MsaViewPanel/resolveConnectedTranscript.js +10 -4
  74. package/dist/MsaViewPanel/storedData.test.js +20 -0
  75. package/dist/MsaViewPanel/syncGenomeHoverToMsaColumn.test.js +12 -5
  76. package/dist/index.d.ts +11 -11
  77. package/dist/jbrowse-plugin-msaview.umd.production.min.js +54 -67
  78. package/dist/jbrowse-plugin-msaview.umd.production.min.js.map +4 -4
  79. package/dist/version.d.ts +1 -1
  80. package/dist/version.js +1 -1
  81. package/package.json +21 -21
  82. package/src/AddHighlightModel/index.tsx +1 -1
  83. package/src/BgzipFastaMsaAdapter/configSchema.ts +1 -1
  84. package/src/LaunchMsaView/codingFeature.test.ts +96 -0
  85. package/src/LaunchMsaView/codingFeature.ts +49 -0
  86. package/src/LaunchMsaView/components/BlastQuery/BlastAutomaticPanel.tsx +25 -24
  87. package/src/LaunchMsaView/components/BlastQuery/BlastManualPanel.tsx +8 -1
  88. package/src/LaunchMsaView/components/BlastQuery/BlastPanel.tsx +24 -11
  89. package/src/LaunchMsaView/components/BlastQuery/BlastSettingsDialog.tsx +4 -4
  90. package/src/LaunchMsaView/components/BlastQuery/consts.ts +31 -4
  91. package/src/LaunchMsaView/components/HelpButton.tsx +33 -0
  92. package/src/LaunchMsaView/components/HelpDialog.tsx +78 -0
  93. package/src/LaunchMsaView/components/LaunchMsaViewDialog.tsx +54 -34
  94. package/src/LaunchMsaView/components/ManualMSALoader/ManualMSALoader.tsx +12 -2
  95. package/src/LaunchMsaView/components/OrthologQuery/OrthologPanel.tsx +23 -7
  96. package/src/LaunchMsaView/components/PreLoadedMSA/PreLoadedMSADataPanel.tsx +63 -13
  97. package/src/LaunchMsaView/components/PreLoadedMSA/preCalculatedLaunchView.ts +4 -0
  98. package/src/LaunchMsaView/components/SequenceStatus.tsx +29 -0
  99. package/src/LaunchMsaView/components/SubmitCancelActions.test.tsx +62 -3
  100. package/src/LaunchMsaView/components/SubmitCancelActions.tsx +37 -15
  101. package/src/LaunchMsaView/components/TabPanel.test.tsx +51 -0
  102. package/src/LaunchMsaView/components/TabPanel.tsx +16 -4
  103. package/src/LaunchMsaView/components/TranscriptSelector.tsx +6 -1
  104. package/src/LaunchMsaView/components/calculateProteinSequence.ts +29 -10
  105. package/src/LaunchMsaView/components/fetchSeq.ts +25 -5
  106. package/src/LaunchMsaView/components/launchPlacement.tsx +41 -0
  107. package/src/LaunchMsaView/components/useFeatureSequence.ts +18 -11
  108. package/src/LaunchMsaView/components/useTranscriptSelection.test.tsx +66 -0
  109. package/src/LaunchMsaView/components/useTranscriptSelection.ts +41 -2
  110. package/src/LaunchMsaView/index.ts +32 -13
  111. package/src/LaunchMsaView/launchTarget.test.ts +88 -15
  112. package/src/LaunchMsaView/launchTarget.ts +49 -14
  113. package/src/LaunchMsaView/useQueryRowName.test.ts +28 -1
  114. package/src/LaunchMsaView/useQueryRowName.ts +17 -0
  115. package/src/LaunchMsaView/util.ts +8 -4
  116. package/src/MsaViewPanel/afterCreateAutoruns.ts +34 -11
  117. package/src/MsaViewPanel/components/LaunchProgress.tsx +12 -5
  118. package/src/MsaViewPanel/components/MsaViewPanel.test.tsx +26 -0
  119. package/src/MsaViewPanel/components/MsaViewPanel.tsx +7 -5
  120. package/src/MsaViewPanel/genomeToMSA.test.ts +184 -40
  121. package/src/MsaViewPanel/genomeToMSA.ts +27 -3
  122. package/src/MsaViewPanel/model.test.ts +75 -0
  123. package/src/MsaViewPanel/model.ts +54 -3
  124. package/src/MsaViewPanel/resolveConnectedTranscript.ts +14 -4
  125. package/src/MsaViewPanel/storedData.test.ts +26 -0
  126. package/src/MsaViewPanel/syncGenomeHoverToMsaColumn.test.ts +14 -5
  127. package/src/version.ts +1 -1
  128. package/dist/LaunchMsaView/components/geneticCodes.d.ts +0 -15
  129. package/dist/LaunchMsaView/components/geneticCodes.js +0 -227
  130. package/src/LaunchMsaView/components/geneticCodes.ts +0 -298
@@ -3,9 +3,9 @@ import { getSession } from '@jbrowse/core/util';
3
3
  import { isMsaView } from '../MsaViewPanel/model';
4
4
  import HighlightComponents from './HighlightComponents';
5
5
  export default function AddHighlightComponentsModelF(pluginManager) {
6
- pluginManager.addToExtensionPoint('LinearGenomeView-TracksContainerComponent',
7
- // @ts-expect-error
8
- (rest, { model }) => {
6
+ pluginManager.addToExtensionPoint(
7
+ // @ts-expect-error v4 hosts have no contributeToExtensionPoint
8
+ 'LinearGenomeView-TracksContainerComponent', (rest, { model }) => {
9
9
  // Quick check: don't add any components if no MSA view exists
10
10
  const { views } = getSession(model);
11
11
  const hasMsaView = views.some(v => isMsaView(v) && v.connectedViewId === model.id);
@@ -3,8 +3,14 @@ import type { BaseFeatureDataAdapter } from '@jbrowse/core/data_adapters/BaseAda
3
3
  export default class BgzipFastaMsaAdapter extends BaseAdapter {
4
4
  configureP: Promise<BaseFeatureDataAdapter> | undefined;
5
5
  refNamesP: Promise<string[]> | undefined;
6
- configurePre(): Promise<BaseFeatureDataAdapter>;
7
- configure(): Promise<BaseFeatureDataAdapter>;
6
+ configurePre(): Promise<BaseFeatureDataAdapter<import("@jbrowse/mobx-state-tree").ModelInstanceTypeProps<Record<string, any>> & {
7
+ setSubschema(slotName: string, data: Record<string, unknown>): any;
8
+ setSlot(slotName: string, value: unknown): void;
9
+ } & import("@jbrowse/mobx-state-tree").IStateTreeNode<import("@jbrowse/core/configuration").AnyConfigurationSchemaType>>>;
10
+ configure(): Promise<BaseFeatureDataAdapter<import("@jbrowse/mobx-state-tree").ModelInstanceTypeProps<Record<string, any>> & {
11
+ setSubschema(slotName: string, data: Record<string, unknown>): any;
12
+ setSlot(slotName: string, value: unknown): void;
13
+ } & import("@jbrowse/mobx-state-tree").IStateTreeNode<import("@jbrowse/core/configuration").AnyConfigurationSchemaType>>>;
8
14
  getMSARefs(): Promise<string[]>;
9
15
  getMsaRegex(): RegExp;
10
16
  refNameToMsaId(refName: string): string;
@@ -1,7 +1,7 @@
1
1
  import type PluginManager from '@jbrowse/core/PluginManager';
2
2
  export default function configSchemaF(pluginManager: PluginManager): import("@jbrowse/core/configuration/configurationSchema").ConfigurationSchemaType<{
3
- msaRegex: {
4
- type: string;
5
- defaultValue: string;
3
+ readonly msaRegex: {
4
+ readonly type: "string";
5
+ readonly defaultValue: "_";
6
6
  };
7
7
  }, import("@jbrowse/core/configuration/configurationSchema").ConfigurationSchemaOptions<import("@jbrowse/core/configuration").AnyConfigurationSchemaType, undefined>>;
@@ -7,6 +7,6 @@ export default function configSchemaF(pluginManager) {
7
7
  defaultValue: '_',
8
8
  },
9
9
  }, {
10
- baseConfiguration: base?.configSchema,
10
+ baseConfiguration: base.configSchema,
11
11
  });
12
12
  }
@@ -0,0 +1,14 @@
1
+ import type { Feature } from '@jbrowse/core/util';
2
+ export declare function isGeneLikeType(type: unknown): boolean;
3
+ 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;
@@ -0,0 +1,38 @@
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
+ function isCDS(feature) {
11
+ return feature.get('type')?.toLowerCase() === 'cds';
12
+ }
13
+ // The feature itself counts: a viral polyprotein hangs its cleavage products
14
+ // off its CDS rather than off further CDSs.
15
+ export function isCodingFeature(feature) {
16
+ return isCDS(feature) || !!feature.get('subfeatures')?.some(isCodingFeature);
17
+ }
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
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,75 @@
1
+ import { SimpleFeature } from '@jbrowse/core/util';
2
+ import { describe, expect, it } from 'vitest';
3
+ import { geneLikeRoot, isCodingFeature, isGeneLikeType, isKnownNonCoding, } from './codingFeature';
4
+ function feature(type, subfeatures = []) {
5
+ return new SimpleFeature({
6
+ uniqueId: `${type}-${Math.random()}`,
7
+ refName: 'chr1',
8
+ start: 0,
9
+ end: 100,
10
+ type,
11
+ subfeatures: subfeatures.map(s => s.toJSON()),
12
+ });
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
+ describe('isCodingFeature', () => {
38
+ it('finds a CDS anywhere below the feature', () => {
39
+ const gene = feature('gene', [
40
+ feature('mRNA', [feature('exon'), feature('CDS')]),
41
+ ]);
42
+ expect(isCodingFeature(gene)).toBe(true);
43
+ });
44
+ it('is false for a transcript with exons only', () => {
45
+ const lnc = feature('lnc_RNA', [feature('exon'), feature('exon')]);
46
+ expect(isCodingFeature(lnc)).toBe(false);
47
+ });
48
+ it('counts the feature itself', () => {
49
+ expect(isCodingFeature(feature('CDS'))).toBe(true);
50
+ });
51
+ });
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
+ });
@@ -1,9 +1,11 @@
1
1
  import React from 'react';
2
2
  import type { AbstractTrackModel, Feature } from '@jbrowse/core/util';
3
- declare const BlastAutomaticPanel: ({ handleClose, feature, model, children, }: {
3
+ declare const BlastAutomaticPanel: ({ handleClose, feature, model, children, preferredTranscriptId, }: {
4
4
  model: AbstractTrackModel;
5
5
  feature: Feature;
6
6
  handleClose: () => void;
7
7
  children: React.ReactNode;
8
+ /** the isoform the user right-clicked, preselected in the picker */
9
+ preferredTranscriptId?: string;
8
10
  }) => React.JSX.Element;
9
11
  export default BlastAutomaticPanel;
@@ -6,24 +6,24 @@ import { makeStyles } from 'tss-react/mui';
6
6
  import TextField2 from '../../../components/TextField2';
7
7
  import { getBlastViewTitle, getGeneIdentifiers, getLinearGenomeView, } from '../../util';
8
8
  import LaunchPanelContent from '../LaunchPanelContent';
9
+ import SequenceStatusMessage from '../SequenceStatus';
9
10
  import SubmitCancelActions from '../SubmitCancelActions';
10
11
  import TranscriptSelector from '../TranscriptSelector';
11
12
  import { useTranscriptSelection } from '../useTranscriptSelection';
12
13
  import CachedBlastResults from './CachedBlastResults';
13
14
  import MsaAlgorithmSelect from './MsaAlgorithmSelect';
14
15
  import { blastLaunchView } from './blastLaunchView';
15
- import { databaseOptionsFor, defaultMaxHits, defaultSearchFor, searchPrograms, } from './consts';
16
+ import { databaseLabel, databaseOptionsFor, defaultMaxHits, defaultSearchFor, searchProgramLabels, searchPrograms, } from './consts';
16
17
  import { useStoredMsaAlgorithm, useStoredSearchChoice, } from './searchChoiceStorage';
17
18
  import { useCachedBlastResults } from './useCachedBlastResults';
18
19
  const useStyles = makeStyles()({
19
20
  selectField: {
20
21
  width: 150,
21
22
  },
22
- // wider than the rest because the values are what the user came to read, and
23
- // `uniprotkb_swissprot` is 19 characters — at 150 the field showed
24
- // `uniprotkb_swis…`, which does not distinguish it from `uniprotkb_trembl`
23
+ // wider than the rest because the collection names are what the user came to
24
+ // read, and truncating them does not distinguish Swiss-Prot from TrEMBL
25
25
  databaseField: {
26
- width: 230,
26
+ width: 260,
27
27
  },
28
28
  cachedResultsAccordion: {
29
29
  marginTop: 20,
@@ -32,7 +32,7 @@ const useStyles = makeStyles()({
32
32
  marginTop: 20,
33
33
  },
34
34
  });
35
- const BlastAutomaticPanel = observer(function ({ handleClose, feature, model, children, }) {
35
+ const BlastAutomaticPanel = observer(function ({ handleClose, feature, model, children, preferredTranscriptId, }) {
36
36
  const { classes } = useStyles();
37
37
  const view = getLinearGenomeView(model);
38
38
  const [launchViewError, setLaunchViewError] = useState();
@@ -47,8 +47,12 @@ const BlastAutomaticPanel = observer(function ({ handleClose, feature, model, ch
47
47
  const isPhmmer = search.program === 'phmmer';
48
48
  const geneIds = useMemo(() => getGeneIdentifiers(feature), [feature]);
49
49
  const { results: cachedResults, error: cachedResultsError } = useCachedBlastResults(geneIds);
50
- const transcriptSelection = useTranscriptSelection({ feature, view });
51
- const { selectedTranscript, proteinSequence } = transcriptSelection;
50
+ const transcriptSelection = useTranscriptSelection({
51
+ feature,
52
+ view,
53
+ preferredTranscriptId,
54
+ });
55
+ const { selectedTranscript, proteinSequence, sequenceStatus } = transcriptSelection;
52
56
  const e = transcriptSelection.error ?? launchViewError ?? cachedResultsError;
53
57
  return (React.createElement(React.Fragment, null,
54
58
  React.createElement(LaunchPanelContent, { error: e },
@@ -58,39 +62,30 @@ const BlastAutomaticPanel = observer(function ({ handleClose, feature, model, ch
58
62
  // program replaces the database rather than keeping a name the new
59
63
  // one has never heard of
60
64
  setSearch(defaultSearchFor(event.target.value));
61
- } }, searchPrograms.map(val => (React.createElement(MenuItem, { value: val, key: val }, val)))),
65
+ } }, searchPrograms.map(val => (React.createElement(MenuItem, { value: val, key: val }, searchProgramLabels[val])))),
62
66
  React.createElement(TextField2, { variant: "outlined", label: "Database", className: classes.databaseField, select: true, value: search.database, onChange: event => {
63
67
  setSearch({
64
68
  program: search.program,
65
69
  database: event.target.value,
66
70
  });
67
- } }, databaseOptionsFor(search.program).map(val => (React.createElement(MenuItem, { value: val, key: val }, val)))),
71
+ } }, databaseOptionsFor(search.program).map(val => (React.createElement(MenuItem, { value: val, key: val }, databaseLabel(val))))),
68
72
  isPhmmer ? null : (React.createElement(MsaAlgorithmSelect, { className: classes.selectField, value: selectedMsaAlgorithm, onChange: setSelectedMsaAlgorithm })),
69
73
  React.createElement(TextField2, { variant: "outlined", label: "Hits", className: classes.selectField, type: "number", value: maxHits, onChange: event => {
70
74
  setMaxHits(event.target.value);
71
75
  }, error: !hitCountValid, helperText: "best-scoring sequences to keep" }),
72
76
  React.createElement(TranscriptSelector, { feature: feature, ...transcriptSelection }),
73
- React.createElement(Typography, { className: classes.infoText },
77
+ React.createElement(Typography, { variant: "body2", className: classes.infoText },
74
78
  isPhmmer
75
- ? `phmmer searches UniProtKB with a profile HMM built from the query,
76
- so it aligns the hits as it finds them and that alignment is used
77
- directly — nothing is realigned afterwards. The tree is then built
78
- from it in the browser. A hit matching the query in more than one
79
- place appears once per matched region. rp15 spreads the hits
80
- across all of life; swissprot keeps to the curated set.`
81
- : `This panel will automatically submit a blastp query to EBI, which
82
- searches UniProtKB. swissprot returns curated sequences that align
83
- more cleanly than the many near-identical entries a TrEMBL search
84
- brings back. After completion, all the hits will be run through
85
- the chosen aligner; "in browser" needs no second EBI job.`,
79
+ ? 'phmmer aligns the hits as it finds them, so nothing is realigned afterwards.'
80
+ : 'The hits come back from EBI and are then run through the chosen aligner.',
86
81
  ' ',
87
- "EBI's queue is the wait, and it varies from seconds to many minutes. For a homolog panel with no job at all, the Orthologs tab's UniRef source is a lookup. Searching NCBI's nr needs the manual approach: NCBI no longer lets a browser read responses from Blast.cgi."),
82
+ "The EBI queue is the wait, and it runs from seconds to many minutes."),
88
83
  cachedResults.length > 0 ? (React.createElement(Accordion, { className: classes.cachedResultsAccordion },
89
84
  React.createElement(AccordionSummary, { expandIcon: React.createElement(ExpandMoreIcon, null) },
90
85
  React.createElement(Typography, null, "Previous BLAST Results")),
91
86
  React.createElement(AccordionDetails, null,
92
87
  React.createElement(CachedBlastResults, { model: model, handleClose: handleClose, feature: feature })))) : null),
93
- React.createElement(SubmitCancelActions, { model: model, submitDisabled: !proteinSequence || !hitCountValid, onSubmit: () => {
88
+ React.createElement(SubmitCancelActions, { model: model, hint: React.createElement(SequenceStatusMessage, { status: sequenceStatus }), submitDisabled: !proteinSequence || !hitCountValid, onSubmit: () => {
94
89
  try {
95
90
  if (selectedTranscript) {
96
91
  setLaunchViewError(undefined);
@@ -11,10 +11,12 @@ import type { AbstractTrackModel, Feature } from '@jbrowse/core/util';
11
11
  * button -- leaving them to find the Manual upload tab, re-pick the transcript
12
12
  * they had already chosen here, and hand-type the row name.
13
13
  */
14
- declare const BlastManualPanel: ({ handleClose, feature, model, children, }: {
14
+ declare const BlastManualPanel: ({ handleClose, feature, model, children, preferredTranscriptId, }: {
15
15
  children: React.ReactNode;
16
16
  model: AbstractTrackModel;
17
17
  feature: Feature;
18
18
  handleClose: () => void;
19
+ /** the isoform the user right-clicked, preselected in the picker */
20
+ preferredTranscriptId?: string;
19
21
  }) => React.JSX.Element;
20
22
  export default BlastManualPanel;
@@ -43,13 +43,17 @@ const useStyles = makeStyles()({
43
43
  * button -- leaving them to find the Manual upload tab, re-pick the transcript
44
44
  * they had already chosen here, and hand-type the row name.
45
45
  */
46
- const BlastManualPanel = observer(function ({ handleClose, feature, model, children, }) {
46
+ const BlastManualPanel = observer(function ({ handleClose, feature, model, children, preferredTranscriptId, }) {
47
47
  const { classes } = useStyles();
48
48
  const view = getLinearGenomeView(model);
49
49
  const [launchViewError, setLaunchViewError] = useState();
50
50
  const [msaText, setMsaText] = useState('');
51
51
  const [treeText, setTreeText] = useState('');
52
- const transcriptSelection = useTranscriptSelection({ feature, view });
52
+ const transcriptSelection = useTranscriptSelection({
53
+ feature,
54
+ view,
55
+ preferredTranscriptId,
56
+ });
53
57
  const { proteinSequence, selectedTranscript, error } = transcriptSelection;
54
58
  const queryRow = useQueryRowName(msaText, proteinSequence);
55
59
  const s2 = cleanProteinSequence(proteinSequence);
@@ -1,23 +1,27 @@
1
1
  import React from 'react';
2
2
  import type { AbstractTrackModel, Feature } from '@jbrowse/core/util';
3
3
  declare const panelMap: {
4
- readonly automatic: ({ handleClose, feature, model, children, }: {
4
+ readonly automatic: ({ handleClose, feature, model, children, preferredTranscriptId, }: {
5
5
  model: AbstractTrackModel;
6
6
  feature: Feature;
7
7
  handleClose: () => void;
8
8
  children: React.ReactNode;
9
+ preferredTranscriptId?: string;
9
10
  }) => React.JSX.Element;
10
- readonly manual: ({ handleClose, feature, model, children, }: {
11
+ readonly manual: ({ handleClose, feature, model, children, preferredTranscriptId, }: {
11
12
  children: React.ReactNode;
12
13
  model: AbstractTrackModel;
13
14
  feature: Feature;
14
15
  handleClose: () => void;
16
+ preferredTranscriptId?: string;
15
17
  }) => React.JSX.Element;
16
18
  };
17
19
  export type BlastLookupMethod = keyof typeof panelMap;
18
- export default function BlastPanel({ handleClose, model, feature, }: {
20
+ export default function BlastPanel({ handleClose, model, feature, preferredTranscriptId, }: {
19
21
  handleClose: () => void;
20
22
  model: AbstractTrackModel;
21
23
  feature: Feature;
24
+ /** the isoform the user right-clicked, preselected in the picker */
25
+ preferredTranscriptId?: string;
22
26
  }): React.JSX.Element;
23
27
  export {};
@@ -1,6 +1,6 @@
1
1
  import React, { useState } from 'react';
2
2
  import SettingsIcon from '@mui/icons-material/Settings';
3
- import { IconButton } from '@mui/material';
3
+ import { IconButton, Tooltip } from '@mui/material';
4
4
  import { makeStyles } from 'tss-react/mui';
5
5
  import { DEFAULT_EBI_EMAIL, EBI_EMAIL_STORAGE_KEY, } from '../../../utils/ebiJobDispatcher';
6
6
  import { useLocalStorage } from '../../../utils/useLocalStorage';
@@ -17,18 +17,19 @@ const panelMap = {
17
17
  automatic: BlastAutomaticPanel,
18
18
  manual: BlastManualPanel,
19
19
  };
20
- export default function BlastPanel({ handleClose, model, feature, }) {
20
+ export default function BlastPanel({ handleClose, model, feature, preferredTranscriptId, }) {
21
21
  const [lookupMethod, setLookupMethod] = useState('automatic');
22
22
  const [ebiEmail, setEbiEmail] = useLocalStorage(EBI_EMAIL_STORAGE_KEY, DEFAULT_EBI_EMAIL);
23
23
  const [settingsOpen, setSettingsOpen] = useState(false);
24
24
  const { classes } = useStyles();
25
25
  const Panel = panelMap[lookupMethod];
26
26
  return (React.createElement(React.Fragment, null,
27
- React.createElement(IconButton, { className: classes.settingsButton, size: "small", onClick: () => {
28
- setSettingsOpen(true);
29
- } },
30
- React.createElement(SettingsIcon, null)),
31
- React.createElement(Panel, { model: model, feature: feature, handleClose: handleClose },
27
+ React.createElement(Tooltip, { title: `EBI settings — jobs are submitted with ${ebiEmail}` },
28
+ React.createElement(IconButton, { className: classes.settingsButton, "aria-label": "EBI settings", size: "small", onClick: () => {
29
+ setSettingsOpen(true);
30
+ } },
31
+ React.createElement(SettingsIcon, null))),
32
+ React.createElement(Panel, { model: model, feature: feature, handleClose: handleClose, preferredTranscriptId: preferredTranscriptId },
32
33
  React.createElement(BlastMethodSelector, { lookupMethod: lookupMethod, setLookupMethod: setLookupMethod })),
33
34
  settingsOpen ? (React.createElement(BlastSettingsDialog, { ebiEmail: ebiEmail, handleClose: settings => {
34
35
  if (settings) {
@@ -17,9 +17,9 @@ export default function BlastSettingsDialog({ handleClose, ebiEmail, }) {
17
17
  return (React.createElement(Dialog, { open: true, maxWidth: "lg", onClose: () => {
18
18
  handleClose();
19
19
  } },
20
- React.createElement(DialogTitle, null, "BLAST Settings"),
20
+ React.createElement(DialogTitle, null, "EBI settings"),
21
21
  React.createElement(DialogContent, null,
22
- React.createElement(Typography, { variant: "subtitle2", className: classes.help }, "Searches run at EBI, which asks for a contact address on every job so they can reach whoever is generating the load. If your site sends real volume, use your own."),
22
+ React.createElement(Typography, { variant: "subtitle2", className: classes.help }, "Every search this tab runs is submitted to EBI with this address, which is how they reach whoever is generating the load. If your site sends real volume, use your own."),
23
23
  React.createElement(TextField2, { autoFocus: true, margin: "dense", label: "EBI contact email", fullWidth: true, variant: "outlined", value: tempEbiEmail, className: classes.field, onChange: e => {
24
24
  setTempEbiEmail(e.target.value);
25
25
  } }),
@@ -31,6 +31,7 @@ export type BlastDatabase = (typeof blastDatabaseOptions)[number];
31
31
  export declare const defaultBlastDatabase: BlastDatabase;
32
32
  export declare const searchPrograms: readonly ["blastp", "phmmer"];
33
33
  export type SearchProgram = (typeof searchPrograms)[number];
34
+ export declare const searchProgramLabels: Record<SearchProgram, string>;
34
35
  /**
35
36
  * phmmer offers PDB, AlphaFold, Ensembl Genomes, MEROPS and ChEMBL too, but
36
37
  * targets outside UniProt carry no species in their description, so those rows
@@ -65,3 +66,4 @@ export type SearchChoice = {
65
66
  };
66
67
  export declare function defaultSearchFor(program: SearchProgram): SearchChoice;
67
68
  export declare function databaseOptionsFor(program: SearchProgram): readonly ["uniprotkb_swissprot", "uniprotkb", "pan_proteomes", "uniprotkb_trembl"] | readonly ["swissprot", "uniprotkb", "uniprotrefprot", "rp75", "rp55", "rp35", "rp15"];
69
+ export declare function databaseLabel(database: BlastDatabase | PhmmerDatabase): string;
@@ -22,10 +22,10 @@ export const ebiMsaAlgorithms = [
22
22
  */
23
23
  export const msaAlgorithms = [...ebiMsaAlgorithms, 'browser'];
24
24
  export const msaAlgorithmLabels = {
25
- clustalo: 'clustalo (EBI)',
26
- muscle: 'muscle (EBI)',
27
- kalign: 'kalign (EBI)',
28
- mafft: 'mafft (EBI)',
25
+ clustalo: 'Clustal Omega (EBI)',
26
+ muscle: 'MUSCLE (EBI)',
27
+ kalign: 'Kalign (EBI)',
28
+ mafft: 'MAFFT (EBI)',
29
29
  browser: 'in browser, query-anchored',
30
30
  };
31
31
  /**
@@ -45,6 +45,10 @@ export const blastDatabaseOptions = [
45
45
  // many near-identical TrEMBL entries an alignment reads poorly
46
46
  export const defaultBlastDatabase = 'uniprotkb_swissprot';
47
47
  export const searchPrograms = ['blastp', 'phmmer'];
48
+ export const searchProgramLabels = {
49
+ blastp: 'blastp (sequence search)',
50
+ phmmer: 'phmmer (profile HMM search)',
51
+ };
48
52
  /**
49
53
  * phmmer offers PDB, AlphaFold, Ensembl Genomes, MEROPS and ChEMBL too, but
50
54
  * targets outside UniProt carry no species in their description, so those rows
@@ -85,3 +89,23 @@ export function defaultSearchFor(program) {
85
89
  export function databaseOptionsFor(program) {
86
90
  return program === 'phmmer' ? phmmerDatabaseOptions : blastDatabaseOptions;
87
91
  }
92
+ /**
93
+ * What each database is called in the menu. The keys are what EBI is sent and
94
+ * are not negotiable; `uniprotkb_swissprot` and `swissprot` are the same
95
+ * collection under each service's own name, so they read the same here.
96
+ */
97
+ const databaseLabels = {
98
+ uniprotkb_swissprot: 'UniProtKB/Swiss-Prot (curated)',
99
+ swissprot: 'UniProtKB/Swiss-Prot (curated)',
100
+ uniprotkb: 'UniProtKB (all entries)',
101
+ uniprotkb_trembl: 'UniProtKB/TrEMBL (unreviewed)',
102
+ pan_proteomes: 'Pan-proteomes',
103
+ uniprotrefprot: 'UniProt reference proteomes',
104
+ rp75: 'Representative proteomes, 75%',
105
+ rp55: 'Representative proteomes, 55%',
106
+ rp35: 'Representative proteomes, 35%',
107
+ rp15: 'Representative proteomes, 15% (widest spread)',
108
+ };
109
+ export function databaseLabel(database) {
110
+ return databaseLabels[database];
111
+ }
@@ -0,0 +1,2 @@
1
+ import React from 'react';
2
+ export default function HelpButton(): React.JSX.Element;
@@ -0,0 +1,17 @@
1
+ import React, { Suspense, lazy, useState } from 'react';
2
+ import Help from '@mui/icons-material/Help';
3
+ import { IconButton, Tooltip } from '@mui/material';
4
+ const HelpDialog = lazy(() => import('./HelpDialog'));
5
+ export default function HelpButton() {
6
+ const [show, setShow] = useState(false);
7
+ return (React.createElement(React.Fragment, null,
8
+ React.createElement(Tooltip, { title: "What each tab does" },
9
+ React.createElement(IconButton, { "aria-label": "Help", onClick: () => {
10
+ setShow(true);
11
+ } },
12
+ React.createElement(Help, null))),
13
+ show ? (React.createElement(Suspense, { fallback: null },
14
+ React.createElement(HelpDialog, { handleClose: () => {
15
+ setShow(false);
16
+ } }))) : null));
17
+ }
@@ -0,0 +1,4 @@
1
+ import React from 'react';
2
+ export default function HelpDialog({ handleClose, }: {
3
+ handleClose: () => void;
4
+ }): React.JSX.Element;
@@ -0,0 +1,18 @@
1
+ import React from 'react';
2
+ import { Dialog } from '@jbrowse/core/ui';
3
+ import { Button, DialogActions, DialogContent, Divider, Typography, } from '@mui/material';
4
+ export default function HelpDialog({ handleClose, }) {
5
+ return (React.createElement(Dialog, { open: true, maxWidth: "md", onClose: handleClose, title: "Launching an MSA" },
6
+ React.createElement(DialogContent, null,
7
+ React.createElement(Typography, { gutterBottom: true }, "Every tab aligns the same thing: the protein the selected transcript translates to. That transcript is the query row, which is what ties alignment columns back to codons in the genome view \u2014 hovering one highlights the other, and clicking navigates."),
8
+ React.createElement(Typography, { variant: "h6", gutterBottom: true }, "Orthologs"),
9
+ React.createElement(Typography, { gutterBottom: true }, "Precomputed sets, looked up rather than searched for: NCBI and PANTHER give one gene per species, and a UniRef cluster gives every UniProtKB entry within 50% identity of the query, one per species, from any organism. Nothing is queued, so this is the quickest route to \"this gene across species\"."),
10
+ React.createElement(Typography, { variant: "h6", gutterBottom: true }, "BLAST query"),
11
+ React.createElement(Typography, { gutterBottom: true }, "Searches run at EBI's Job Dispatcher, which searches UniProtKB. Swiss-Prot returns curated sequences that align more cleanly than the many near-identical entries a TrEMBL search brings back. blastp finds the hits and the chosen aligner then aligns them \u2014 \"in browser\" needs no second EBI job. phmmer instead searches with a profile HMM built from the query and aligns as it goes, so its output is the alignment and nothing is realigned; a hit matching the query in more than one place appears once per matched region. The Representative Proteomes (15% to 75%) spread the hits across all of life."),
12
+ React.createElement(Typography, { gutterBottom: true }, "The EBI queue is the wait, and it runs from seconds to many minutes. Searching NCBI's nr needs the Manual option, which links out to NCBI's own site: NCBI no longer lets a browser read responses from Blast.cgi."),
13
+ React.createElement(Typography, { variant: "h6", gutterBottom: true }, "Pre-loaded MSA datasets and Manual upload"),
14
+ React.createElement(Typography, { gutterBottom: true }, "Both take an alignment that already exists \u2014 one a dataset the site configured, the other a file or pasted text. In each case the row matching the selected transcript is found by comparing residues, not by name, because aligners rename the query on the way through.")),
15
+ React.createElement(Divider, null),
16
+ React.createElement(DialogActions, null,
17
+ React.createElement(Button, { onClick: handleClose, color: "primary" }, "Close"))));
18
+ }
@@ -1,7 +1,9 @@
1
1
  import React from 'react';
2
2
  import type { AbstractTrackModel, Feature } from '@jbrowse/core/util';
3
- export default function LaunchMsaViewDialog({ handleClose, feature, model, }: {
3
+ export default function LaunchMsaViewDialog({ handleClose, feature, model, preferredTranscriptId, }: {
4
4
  handleClose: () => void;
5
5
  feature: Feature;
6
6
  model: AbstractTrackModel;
7
+ /** the isoform the right-click landed on, when this opened on its gene */
8
+ preferredTranscriptId?: string;
7
9
  }): React.JSX.Element;
@@ -3,12 +3,14 @@ import { Dialog } from '@jbrowse/core/ui';
3
3
  import { getSession } from '@jbrowse/core/util';
4
4
  import { Tab, Tabs } from '@mui/material';
5
5
  import BlastPanel from './BlastQuery/BlastPanel';
6
+ import HelpButton from './HelpButton';
6
7
  import ManualMSALoader from './ManualMSALoader/ManualMSALoader';
7
8
  import OrthologPanel from './OrthologQuery/OrthologPanel';
8
9
  import PreLoadedMSA from './PreLoadedMSA/PreLoadedMSADataPanel';
9
10
  import { readMsaDatasets } from './PreLoadedMSA/types';
10
11
  import TabPanel from './TabPanel';
11
- export default function LaunchMsaViewDialog({ handleClose, feature, model, }) {
12
+ import { LaunchPlacementProvider } from './launchPlacement';
13
+ export default function LaunchMsaViewDialog({ handleClose, feature, model, preferredTranscriptId, }) {
12
14
  const session = getSession(model);
13
15
  const datasets = readMsaDatasets(session.jbrowse);
14
16
  const hasPreloadedDatasets = !!datasets?.length;
@@ -16,19 +18,22 @@ export default function LaunchMsaViewDialog({ handleClose, feature, model, }) {
16
18
  // that BLAST takes 10+ minutes to answer worse (see utils/ncbiOrthologs.ts)
17
19
  const [value, setValue] = useState('orthologs');
18
20
  return (React.createElement(Dialog, { maxWidth: "xl", title: "Launch MSA view", open: true, onClose: handleClose },
19
- React.createElement(Tabs, { value: value, onChange: (_event, newValue) => {
20
- setValue(newValue);
21
- } },
22
- React.createElement(Tab, { label: "Orthologs (fast)", value: "orthologs" }),
23
- React.createElement(Tab, { label: "BLAST query", value: "ncbi_blast" }),
24
- hasPreloadedDatasets ? (React.createElement(Tab, { label: "Pre-loaded MSA datasets", value: "preloaded_msa" })) : null,
25
- React.createElement(Tab, { label: "Manual upload", value: "manual_msa" })),
26
- React.createElement(TabPanel, { value: value, index: "orthologs" },
27
- React.createElement(OrthologPanel, { handleClose: handleClose, feature: feature, model: model })),
28
- React.createElement(TabPanel, { value: value, index: "ncbi_blast" },
29
- React.createElement(BlastPanel, { handleClose: handleClose, feature: feature, model: model })),
30
- hasPreloadedDatasets ? (React.createElement(TabPanel, { value: value, index: "preloaded_msa" },
31
- React.createElement(PreLoadedMSA, { model: model, feature: feature, handleClose: handleClose }))) : null,
32
- React.createElement(TabPanel, { value: value, index: "manual_msa" },
33
- React.createElement(ManualMSALoader, { model: model, feature: feature, handleClose: handleClose }))));
21
+ React.createElement(LaunchPlacementProvider, null,
22
+ React.createElement("div", { style: { display: 'flex', alignItems: 'center' } },
23
+ React.createElement(Tabs, { value: value, onChange: (_event, newValue) => {
24
+ setValue(newValue);
25
+ } },
26
+ React.createElement(Tab, { label: "Orthologs", value: "orthologs" }),
27
+ React.createElement(Tab, { label: "BLAST query", value: "ncbi_blast" }),
28
+ hasPreloadedDatasets ? (React.createElement(Tab, { label: "Pre-loaded MSA datasets", value: "preloaded_msa" })) : null,
29
+ React.createElement(Tab, { label: "Manual upload", value: "manual_msa" })),
30
+ React.createElement(HelpButton, null)),
31
+ React.createElement(TabPanel, { value: value, index: "orthologs" },
32
+ React.createElement(OrthologPanel, { handleClose: handleClose, feature: feature, model: model, preferredTranscriptId: preferredTranscriptId })),
33
+ React.createElement(TabPanel, { value: value, index: "ncbi_blast" },
34
+ React.createElement(BlastPanel, { handleClose: handleClose, feature: feature, model: model, preferredTranscriptId: preferredTranscriptId })),
35
+ hasPreloadedDatasets ? (React.createElement(TabPanel, { value: value, index: "preloaded_msa" },
36
+ React.createElement(PreLoadedMSA, { model: model, feature: feature, handleClose: handleClose, preferredTranscriptId: preferredTranscriptId }))) : null,
37
+ React.createElement(TabPanel, { value: value, index: "manual_msa" },
38
+ React.createElement(ManualMSALoader, { model: model, feature: feature, handleClose: handleClose, preferredTranscriptId: preferredTranscriptId })))));
34
39
  }
@@ -1,8 +1,10 @@
1
1
  import React from 'react';
2
2
  import type { AbstractTrackModel, Feature } from '@jbrowse/core/util';
3
- declare const ManualMSALoader: ({ model, feature, handleClose, }: {
3
+ declare const ManualMSALoader: ({ model, feature, handleClose, preferredTranscriptId, }: {
4
4
  model: AbstractTrackModel;
5
5
  feature: Feature;
6
6
  handleClose: () => void;
7
+ /** the isoform the user right-clicked, preselected in the picker */
8
+ preferredTranscriptId?: string;
7
9
  }) => React.JSX.Element;
8
10
  export default ManualMSALoader;