jbrowse-plugin-protein3d 0.16.0 → 1.0.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.
- package/dist/AddHighlightModel/Highlight.js +2 -2
- package/dist/AddHighlightModel/index.js +2 -11
- package/dist/AddHighlightModel/util.js +0 -20
- package/dist/LaunchProteinView/codingFeature.js +1 -15
- package/dist/LaunchProteinView/components/PdbSearch.js +18 -4
- package/dist/LaunchProteinView/components/proteinTrackSetup.js +17 -37
- package/dist/LaunchProteinView/components/wiggleBandColors.js +3 -17
- package/dist/LaunchProteinView/hooks/useAlphaFoldData.js +5 -2
- package/dist/LaunchProteinView/hooks/useUniProtIdLookup.js +11 -15
- package/dist/LaunchProteinView/index.js +27 -55
- package/dist/LaunchProteinView/services/foldseekApi.js +33 -8
- package/dist/LaunchProteinView/utils/launchHelpers.js +3 -3
- package/dist/LaunchProteinView/utils/launchViewUtils.js +5 -3
- package/dist/LaunchProteinView/utils/sideBySide.js +6 -29
- package/dist/LaunchProteinView/utils/translateTranscripts.js +3 -12
- package/dist/LaunchProteinView/utils/util.js +2 -1
- package/dist/LaunchProteinViewExtensionPoint/index.js +4 -7
- package/dist/LaunchProteinViewExtensionPoint/resolveShortLaunch.js +3 -23
- package/dist/Protein1DLinkage/index.js +1 -2
- package/dist/ProteinView/alignOffThread.js +1 -5
- package/dist/ProteinView/components/ChainSelect.js +6 -3
- package/dist/ProteinView/components/FeatureBar.js +1 -1
- package/dist/ProteinView/components/HeaderStructureRow.js +13 -4
- package/dist/ProteinView/components/ManualAlignmentDialog.js +14 -18
- package/dist/ProteinView/components/ProteinViewHeader.js +2 -2
- package/dist/ProteinView/entityAlignedTo.js +1 -1
- package/dist/ProteinView/frameSelection.js +10 -4
- package/dist/ProteinView/model.js +14 -7
- package/dist/ProteinView/molstarExports.js +1 -0
- package/dist/ProteinView/storedSettings.js +17 -1
- package/dist/ProteinView/structureLoader.js +12 -2
- package/dist/ProteinView/structureModel.js +93 -56
- package/dist/ProteinView/structureSuperposer.js +23 -14
- package/dist/ProteinView/structureVisibility.js +17 -0
- package/dist/chunks/{HelpDialog-KAG73BNB.js → HelpDialog-I4UK7TQB.js} +2 -2
- package/dist/chunks/{ProteinAlignmentHelpDialog-L2GH5YTM.js → ProteinAlignmentHelpDialog-6VFZFN6X.js} +2 -2
- package/dist/chunks/ProteinView-CFQG6IHO.js +9 -0
- package/dist/chunks/ProteinView-CFQG6IHO.js.map +7 -0
- package/dist/chunks/chunk-V2WE2V7Z.js +16 -0
- package/dist/chunks/chunk-V2WE2V7Z.js.map +7 -0
- package/dist/chunks/{molstarExports-HAGQ7LSM.js → molstarExports-YN6TKVEW.js} +36 -36
- package/dist/jbrowse-plugin-protein3d.esm.js +7 -7
- package/dist/jbrowse-plugin-protein3d.esm.js.map +4 -4
- package/dist/version.js +1 -1
- package/package.json +2 -15
- package/src/AddHighlightModel/Highlight.tsx +2 -2
- package/src/AddHighlightModel/index.test.ts +7 -9
- package/src/AddHighlightModel/index.tsx +4 -17
- package/src/AddHighlightModel/proteinViewLookup.test.ts +3 -1
- package/src/AddHighlightModel/util.ts +0 -30
- package/src/LaunchProteinView/codingFeature.test.ts +1 -44
- package/src/LaunchProteinView/codingFeature.ts +2 -22
- package/src/LaunchProteinView/components/PdbSearch.tsx +21 -5
- package/src/LaunchProteinView/components/launchProteinAnnotationView.ts +2 -3
- package/src/LaunchProteinView/components/proteinAssemblySetup.ts +2 -2
- package/src/LaunchProteinView/components/proteinTrackSetup.ts +24 -47
- package/src/LaunchProteinView/components/wiggleBandColors.test.ts +3 -30
- package/src/LaunchProteinView/components/wiggleBandColors.ts +3 -22
- package/src/LaunchProteinView/hooks/useAlphaFoldData.test.ts +44 -0
- package/src/LaunchProteinView/hooks/useAlphaFoldData.ts +6 -3
- package/src/LaunchProteinView/hooks/useUniProtIdLookup.ts +14 -18
- package/src/LaunchProteinView/index.ts +39 -74
- package/src/LaunchProteinView/menuTarget.test.ts +7 -14
- package/src/LaunchProteinView/services/foldseekApi.test.ts +43 -0
- package/src/LaunchProteinView/services/foldseekApi.ts +40 -12
- package/src/LaunchProteinView/utils/launchHelpers.ts +3 -3
- package/src/LaunchProteinView/utils/launchViewUtils.ts +12 -6
- package/src/LaunchProteinView/utils/sideBySide.test.ts +0 -6
- package/src/LaunchProteinView/utils/sideBySide.ts +6 -29
- package/src/LaunchProteinView/utils/translateTranscripts.test.ts +9 -1
- package/src/LaunchProteinView/utils/translateTranscripts.ts +3 -15
- package/src/LaunchProteinView/utils/util.ts +2 -1
- package/src/LaunchProteinViewExtensionPoint/index.ts +4 -13
- package/src/LaunchProteinViewExtensionPoint/resolveShortLaunch.ts +6 -33
- package/src/Protein1DLinkage/index.ts +1 -2
- package/src/ProteinView/alignOffThread.ts +1 -5
- package/src/ProteinView/components/ChainSelect.tsx +8 -3
- package/src/ProteinView/components/FeatureBar.tsx +1 -1
- package/src/ProteinView/components/HeaderStructureRow.tsx +30 -3
- package/src/ProteinView/components/ManualAlignmentDialog.tsx +19 -25
- package/src/ProteinView/components/ProteinViewHeader.tsx +0 -2
- package/src/ProteinView/connectedHover.test.ts +3 -1
- package/src/ProteinView/entityAlignedTo.ts +3 -1
- package/src/ProteinView/frameSelection.test.ts +46 -1
- package/src/ProteinView/frameSelection.ts +16 -5
- package/src/ProteinView/geneExplorerLinkage.test.ts +3 -2
- package/src/ProteinView/kyteDoolittleColorTheme.test.ts +4 -1
- package/src/ProteinView/mappedChainColorTheme.test.ts +4 -1
- package/src/ProteinView/model.test.ts +13 -6
- package/src/ProteinView/model.ts +20 -10
- package/src/ProteinView/molstarExports.ts +1 -0
- package/src/ProteinView/proteinViewSpec.ts +2 -0
- package/src/ProteinView/storedSettings.ts +18 -2
- package/src/ProteinView/structureLoader.test.ts +34 -1
- package/src/ProteinView/structureLoader.ts +14 -2
- package/src/ProteinView/structureModel.test.ts +112 -3
- package/src/ProteinView/structureModel.ts +106 -67
- package/src/ProteinView/structureSuperposer.test.ts +46 -0
- package/src/ProteinView/structureSuperposer.ts +26 -19
- package/src/ProteinView/structureVisibility.ts +33 -0
- package/src/ProteinView/viewInteractions.ts +0 -1
- package/src/ProteinView/withStoredSettings.test.ts +30 -5
- package/src/UniProtVariationAdapter/parseUniProtVariants.test.ts +4 -3
- package/src/version.ts +1 -1
- package/dist/LaunchProteinView/utils/sessionWithAddTracks.js +0 -6
- package/dist/ProteinView/proteinAbbreviationMapping.js +0 -22
- package/dist/chunks/ProteinView-7GUUQZL4.js +0 -9
- package/dist/chunks/ProteinView-7GUUQZL4.js.map +0 -7
- package/dist/chunks/chunk-3IKI3UVA.js +0 -16
- package/dist/chunks/chunk-3IKI3UVA.js.map +0 -7
- package/dist/extendStateModel.js +0 -9
- package/src/LaunchProteinView/utils/sessionWithAddTracks.ts +0 -15
- package/src/ProteinView/proteinAbbreviationMapping.ts +0 -24
- package/src/extendStateModel.ts +0 -29
- /package/dist/chunks/{HelpDialog-KAG73BNB.js.map → HelpDialog-I4UK7TQB.js.map} +0 -0
- /package/dist/chunks/{ProteinAlignmentHelpDialog-L2GH5YTM.js.map → ProteinAlignmentHelpDialog-6VFZFN6X.js.map} +0 -0
- /package/dist/chunks/{molstarExports-HAGQ7LSM.js.map → molstarExports-YN6TKVEW.js.map} +0 -0
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { observer } from 'mobx-react';
|
|
3
|
-
import {
|
|
3
|
+
import { useStyles } from './util';
|
|
4
4
|
const Highlight = observer(function Highlight({ region, model, }) {
|
|
5
5
|
const { cx, classes } = useStyles();
|
|
6
|
-
const coords = getHighlightCoords(
|
|
6
|
+
const coords = model.getHighlightCoords(region);
|
|
7
7
|
return coords ? (React.createElement("div", { className: cx(classes.highlight, coords.width <= 3 ? classes.thinborder : undefined), style: { left: coords.left, width: coords.width } })) : null;
|
|
8
8
|
});
|
|
9
9
|
export default Highlight;
|
|
@@ -1,14 +1,5 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { addExtensionElement } from '@jbrowse/core/ui';
|
|
2
2
|
import HighlightComponents from './HighlightComponents';
|
|
3
3
|
export default function AddHighlightModelF(pluginManager) {
|
|
4
|
-
pluginManager
|
|
5
|
-
// @ts-expect-error v4 hosts have no contributeToExtensionPoint
|
|
6
|
-
'LinearGenomeView-TracksContainerComponent',
|
|
7
|
-
// v4 hosts seed this point with undefined rather than []
|
|
8
|
-
(rest, { model }) => {
|
|
9
|
-
return [
|
|
10
|
-
...(rest ?? []),
|
|
11
|
-
React.createElement(HighlightComponents, { key: "highlight_protein_viewer_protein3d", model: model }),
|
|
12
|
-
];
|
|
13
|
-
});
|
|
4
|
+
addExtensionElement(pluginManager, 'LinearGenomeView-TracksContainerComponent', HighlightComponents);
|
|
14
5
|
}
|
|
@@ -1,24 +1,4 @@
|
|
|
1
|
-
import { getSession } from '@jbrowse/core/util';
|
|
2
1
|
import { makeStyles } from 'tss-react/mui';
|
|
3
|
-
// Local mirror of core's getLayoutHighlightCoords / model.getHighlightCoords,
|
|
4
|
-
// added to jbrowse after 4.3.0. Kept here so the plugin still builds against
|
|
5
|
-
// 4.3.0; once the minimum jbrowse version ships the model method, replace the
|
|
6
|
-
// body with `model.getHighlightCoords(region)`.
|
|
7
|
-
export function getHighlightCoords(model, region) {
|
|
8
|
-
const { assemblyManager } = getSession(model);
|
|
9
|
-
const assembly = region.assemblyName
|
|
10
|
-
? assemblyManager.get(region.assemblyName)
|
|
11
|
-
: undefined;
|
|
12
|
-
const refName = assembly?.getCanonicalRefName(region.refName) ?? region.refName;
|
|
13
|
-
const s = model.bpToPx({ refName, coord: region.start });
|
|
14
|
-
const e = model.bpToPx({ refName, coord: region.end });
|
|
15
|
-
return s && e
|
|
16
|
-
? {
|
|
17
|
-
width: Math.max(Math.abs(e.offsetPx - s.offsetPx), 3),
|
|
18
|
-
left: Math.min(s.offsetPx, e.offsetPx) - model.offsetPx,
|
|
19
|
-
}
|
|
20
|
-
: undefined;
|
|
21
|
-
}
|
|
22
2
|
export const useStyles = makeStyles()({
|
|
23
3
|
highlight: {
|
|
24
4
|
height: '100%',
|
|
@@ -1,9 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
// on a v4 host, and the deep path is not in core's exports map.
|
|
3
|
-
const GENE_LIKE_TYPE = /gene(_segment)?$|rna$|transcript/;
|
|
4
|
-
export function isGeneLikeType(type) {
|
|
5
|
-
return type !== undefined && GENE_LIKE_TYPE.test(type.toLowerCase());
|
|
6
|
-
}
|
|
1
|
+
import { isGeneLikeType } from '@jbrowse/core/util';
|
|
7
2
|
function isCDS(feature) {
|
|
8
3
|
return feature.get('type')?.toLowerCase() === 'cds';
|
|
9
4
|
}
|
|
@@ -22,12 +17,3 @@ export function codingTranscripts(feature) {
|
|
|
22
17
|
export function isCodingFeature(feature) {
|
|
23
18
|
return codingTranscripts(feature).length > 0;
|
|
24
19
|
}
|
|
25
|
-
// The outermost gene-like ancestor, so a click on an isoform opens the dialog
|
|
26
|
-
// on the gene with every transcript to choose from, as the canvas host does.
|
|
27
|
-
export function geneLikeRoot(feature) {
|
|
28
|
-
let root = feature;
|
|
29
|
-
for (let parent = root.parent?.(); parent && isGeneLikeType(parent.get('type')); parent = parent.parent?.()) {
|
|
30
|
-
root = parent;
|
|
31
|
-
}
|
|
32
|
-
return root;
|
|
33
|
-
}
|
|
@@ -70,16 +70,30 @@ const PdbSearch = observer(function PdbSearch({ feature, preferredTranscriptId,
|
|
|
70
70
|
preferredTranscriptId,
|
|
71
71
|
resetKey: uniprotId,
|
|
72
72
|
});
|
|
73
|
-
const
|
|
74
|
-
isLookupLoading && 'Looking up UniProt ID',
|
|
73
|
+
const isoformStatuses = [
|
|
75
74
|
isIsoformLoading && 'Loading protein sequences from transcript isoforms',
|
|
76
75
|
isRanking && 'Aligning isoforms to the structure',
|
|
76
|
+
];
|
|
77
|
+
const lookupStatuses = [
|
|
78
|
+
isLookupLoading && 'Looking up UniProt ID',
|
|
77
79
|
isPdbLoading && 'Listing PDB entries from PDBe',
|
|
78
|
-
]
|
|
80
|
+
];
|
|
81
|
+
const loadingStatuses = [...lookupStatuses, ...isoformStatuses].filter((s) => !!s);
|
|
79
82
|
const isLoading = loadingStatuses.length > 0;
|
|
80
83
|
const error = isLoading
|
|
81
84
|
? undefined
|
|
82
85
|
: (isoformError ?? lookup.lookupError ?? pdbError);
|
|
86
|
+
// A typed PDB ID exists to get around a lookup that is slow, failing or
|
|
87
|
+
// wrong, so only the isoforms it is ranked against hold its launch back
|
|
88
|
+
const typedIdOverrides = isPdbId(debouncedTypedPdbId);
|
|
89
|
+
const launchWaiting = typedIdOverrides
|
|
90
|
+
? isoformStatuses.some(Boolean)
|
|
91
|
+
: isLoading;
|
|
92
|
+
const launchError = typedIdOverrides
|
|
93
|
+
? launchWaiting
|
|
94
|
+
? undefined
|
|
95
|
+
: isoformError
|
|
96
|
+
: error;
|
|
83
97
|
return (React.createElement(React.Fragment, null,
|
|
84
98
|
React.createElement(DialogContent, { className: classes.dialogContent },
|
|
85
99
|
error ? React.createElement(ErrorMessage, { error: error }) : null,
|
|
@@ -113,6 +127,6 @@ const PdbSearch = observer(function PdbSearch({ feature, preferredTranscriptId,
|
|
|
113
127
|
". The AlphaFoldDB tab has a predicted one."))) : null,
|
|
114
128
|
ranking && selectedTranscript ? (React.createElement(TranscriptSelector, { val: selectedTranscriptId, setVal: setSelectedTranscriptId, structureSequence: structureSequence, feature: feature, isoforms: transcripts, ranking: ranking })) : null),
|
|
115
129
|
React.createElement(DialogActions, null,
|
|
116
|
-
React.createElement(ProteinViewActions, { handleClose: handleClose, uniprotId: uniprotId, userSelectedProteinSequence: selectedIsoform, selectedTranscript: selectedTranscript, url: structureUrl, pdbId: selectedPdbId, feature: feature, view: view, session: session, sideBySide: sideBySide, onSideBySideChange: onSideBySideChange, isLoading:
|
|
130
|
+
React.createElement(ProteinViewActions, { handleClose: handleClose, uniprotId: uniprotId, userSelectedProteinSequence: selectedIsoform, selectedTranscript: selectedTranscript, url: structureUrl, pdbId: selectedPdbId, feature: feature, view: view, session: session, sideBySide: sideBySide, onSideBySideChange: onSideBySideChange, isLoading: launchWaiting, error: launchError }))));
|
|
117
131
|
});
|
|
118
132
|
export default PdbSearch;
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import { getEnv } from '@jbrowse/mobx-state-tree';
|
|
2
1
|
import { myfetch, uniprotGffUrl } from 'p2s_mapper';
|
|
3
|
-
import {
|
|
2
|
+
import { thresholdBandColor } from './wiggleBandColors';
|
|
4
3
|
import { PLDDT_BANDS } from '../../ProteinView/residueTracks';
|
|
5
4
|
/**
|
|
6
5
|
* Fetches UniProt GFF data and extracts unique feature types
|
|
@@ -23,7 +22,7 @@ async function fetchUniProtFeatureTypes(uniprotId) {
|
|
|
23
22
|
function addUniProtFeatureTracks({ session, uniprotId, featureTypes, }) {
|
|
24
23
|
featureTypes.forEach(type => {
|
|
25
24
|
const trackId = `${uniprotId}-${type}`;
|
|
26
|
-
session.
|
|
25
|
+
session.addSessionTrackConf({
|
|
27
26
|
type: 'FeatureTrack',
|
|
28
27
|
trackId,
|
|
29
28
|
name: type,
|
|
@@ -48,7 +47,7 @@ function addUniProtFeatureTracks({ session, uniprotId, featureTypes, }) {
|
|
|
48
47
|
* Adds antigen annotation track from EBI
|
|
49
48
|
*/
|
|
50
49
|
function addAntigenTrack({ session, uniprotId, }) {
|
|
51
|
-
session.
|
|
50
|
+
session.addSessionTrackConf({
|
|
52
51
|
type: 'FeatureTrack',
|
|
53
52
|
trackId: `${uniprotId}-Antigen`,
|
|
54
53
|
name: 'Antigen',
|
|
@@ -65,7 +64,7 @@ function addAntigenTrack({ session, uniprotId, }) {
|
|
|
65
64
|
* Adds variation track from EBI
|
|
66
65
|
*/
|
|
67
66
|
function addVariationTrack({ session, uniprotId, }) {
|
|
68
|
-
session.
|
|
67
|
+
session.addSessionTrackConf({
|
|
69
68
|
type: 'FeatureTrack',
|
|
70
69
|
trackId: `${uniprotId}-Variation`,
|
|
71
70
|
name: 'Variation',
|
|
@@ -83,7 +82,7 @@ function addVariationTrack({ session, uniprotId, }) {
|
|
|
83
82
|
*/
|
|
84
83
|
function addAlphaFoldConfidenceTrack({ session, uniprotId, confidenceUrl, }) {
|
|
85
84
|
if (confidenceUrl) {
|
|
86
|
-
session.
|
|
85
|
+
session.addSessionTrackConf({
|
|
87
86
|
type: 'QuantitativeTrack',
|
|
88
87
|
trackId: `${uniprotId}-AlphaFold-confidence`,
|
|
89
88
|
name: 'AlphaFold confidence',
|
|
@@ -98,12 +97,6 @@ function addAlphaFoldConfidenceTrack({ session, uniprotId, confidenceUrl, }) {
|
|
|
98
97
|
{
|
|
99
98
|
type: 'LinearWiggleDisplay',
|
|
100
99
|
displayId: `${uniprotId}-AlphaFold-confidence-LinearWiggleDisplay`,
|
|
101
|
-
// Each host drops the key it does not declare: v4 reads the
|
|
102
|
-
// renderers, v5 the display's `color`.
|
|
103
|
-
renderers: {
|
|
104
|
-
XYPlotRenderer: { color: jexlBandColor('score', PLDDT_BANDS) },
|
|
105
|
-
DensityRenderer: { color: jexlBandColor('score', PLDDT_BANDS) },
|
|
106
|
-
},
|
|
107
100
|
color: thresholdBandColor('score', PLDDT_BANDS),
|
|
108
101
|
},
|
|
109
102
|
],
|
|
@@ -113,8 +106,8 @@ function addAlphaFoldConfidenceTrack({ session, uniprotId, confidenceUrl, }) {
|
|
|
113
106
|
/**
|
|
114
107
|
* Adds AlphaMissense pathogenicity scores track
|
|
115
108
|
*/
|
|
116
|
-
function addAlphaMissenseTrack({ session, uniprotId,
|
|
117
|
-
session.
|
|
109
|
+
function addAlphaMissenseTrack({ session, uniprotId, }) {
|
|
110
|
+
session.addSessionTrackConf({
|
|
118
111
|
type: 'MultiQuantitativeTrack',
|
|
119
112
|
trackId: `${uniprotId}-AlphaMissense-scores`,
|
|
120
113
|
name: 'AlphaMissense scores',
|
|
@@ -125,28 +118,18 @@ function addAlphaMissenseTrack({ session, uniprotId, hasMultiWiggleDisplay, }) {
|
|
|
125
118
|
uri: `https://alphafold.ebi.ac.uk/files/AF-${uniprotId}-F1-aa-substitutions.csv`,
|
|
126
119
|
},
|
|
127
120
|
},
|
|
128
|
-
// v5 folded MultiLinearWiggleDisplay into LinearWiggleDisplay and warns on
|
|
129
|
-
// a config naming the old type. v4's density renderer ramps from white to
|
|
130
|
-
// posColor, so pathogenic reads red; v5 diverges at AlphaMissense's 0.5.
|
|
131
121
|
displays: [
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
displayId: `${uniprotId}-AlphaMissense-scores-LinearWiggleDisplay`,
|
|
142
|
-
defaultRendering: 'density',
|
|
143
|
-
color: {
|
|
144
|
-
field: 'score',
|
|
145
|
-
scale: 'linear',
|
|
146
|
-
range: ['#2c7bb6', '#ffffff', '#d7191c'],
|
|
147
|
-
domainMid: 0.5,
|
|
148
|
-
},
|
|
122
|
+
{
|
|
123
|
+
type: 'LinearWiggleDisplay',
|
|
124
|
+
displayId: `${uniprotId}-AlphaMissense-scores-LinearWiggleDisplay`,
|
|
125
|
+
defaultRendering: 'density',
|
|
126
|
+
color: {
|
|
127
|
+
field: 'score',
|
|
128
|
+
scale: 'linear',
|
|
129
|
+
range: ['#2c7bb6', '#ffffff', '#d7191c'],
|
|
130
|
+
domainMid: 0.5,
|
|
149
131
|
},
|
|
132
|
+
},
|
|
150
133
|
],
|
|
151
134
|
});
|
|
152
135
|
}
|
|
@@ -178,9 +161,6 @@ export async function addAllProteinTracks({ session, uniprotId, confidenceUrl, }
|
|
|
178
161
|
addAlphaMissenseTrack({
|
|
179
162
|
session,
|
|
180
163
|
uniprotId,
|
|
181
|
-
hasMultiWiggleDisplay: getEnv(session)
|
|
182
|
-
.pluginManager.getDisplayElements()
|
|
183
|
-
.some(d => d.name === 'MultiLinearWiggleDisplay'),
|
|
184
164
|
});
|
|
185
165
|
}
|
|
186
166
|
}
|
|
@@ -1,21 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* worker. v4 also evaluates it once with no feature, and a throw there leaves
|
|
6
|
-
* the track an error message, hence the guard.
|
|
7
|
-
*/
|
|
8
|
-
export function jexlBandColor(field, bands) {
|
|
9
|
-
const value = `get(feature,'${field}')`;
|
|
10
|
-
const body = bands.reduceRight((rest, band) => band.upTo === Infinity
|
|
11
|
-
? `'${band.color}'`
|
|
12
|
-
: `(${value}<=${band.upTo}?'${band.color}':${rest})`, "'#cccccc'");
|
|
13
|
-
return `jexl:feature?${body}:'#cccccc'`;
|
|
14
|
-
}
|
|
15
|
-
/**
|
|
16
|
-
* The same bands as the display-level threshold scale v5 hosts read. That
|
|
17
|
-
* scale puts a value equal to a cut in the upper band, so each cut moves just
|
|
18
|
-
* past its bound to keep the bound in its own band, as `bandColor` does.
|
|
2
|
+
* The display-level threshold scale puts a value equal to a cut in the upper
|
|
3
|
+
* band, so each cut moves just past its bound to keep the bound in its own
|
|
4
|
+
* band, as `bandColor` does.
|
|
19
5
|
*/
|
|
20
6
|
export function thresholdBandColor(field, bands) {
|
|
21
7
|
return {
|
|
@@ -7,8 +7,11 @@ import useIsoformProteinSequences from './useIsoformProteinSequences';
|
|
|
7
7
|
export default function useAlphaFoldData({ uniprotId, feature, view, }) {
|
|
8
8
|
const { data, isLoading, isValidating, error } = useSWR(uniprotId ? ['alphafold-models', uniprotId] : null, ([, id]) => fetchAlphaFoldModels(id), { ...STATIC_SWR_OPTIONS, keepPreviousData: true });
|
|
9
9
|
const { isoformSequences } = useIsoformProteinSequences({ feature, view });
|
|
10
|
-
//
|
|
11
|
-
|
|
10
|
+
// keepPreviousData hands back the last accession's models after an error
|
|
11
|
+
// and after the accession is cleared, when the key is null
|
|
12
|
+
const model = useMemo(() => uniprotId && data && !error
|
|
13
|
+
? pickAlphaFoldModel(data, isoformSequences)
|
|
14
|
+
: undefined, [uniprotId, data, error, isoformSequences]);
|
|
12
15
|
return {
|
|
13
16
|
isLoading,
|
|
14
17
|
isValidating,
|
|
@@ -17,10 +17,16 @@ export function describeOrganism(taxonId, source) {
|
|
|
17
17
|
* to every tab, so all of them run one search and agree on what the gene is.
|
|
18
18
|
*/
|
|
19
19
|
export default function useUniProtIdLookup({ feature, view, }) {
|
|
20
|
-
const
|
|
20
|
+
const geneIds = extractFeatureIdentifiers(feature);
|
|
21
|
+
const featureUniprotId = geneIds.uniprotId;
|
|
22
|
+
const hasSearchableIdentifier = geneIds.recognizedIds.length > 0 || !!geneIds.geneName;
|
|
23
|
+
// Nothing to search and no accession on the feature: the auto mode has no
|
|
24
|
+
// query to run, so the dialog opens on the manual field instead of reporting
|
|
25
|
+
// an empty result for an empty query. Only the opening mode is chosen here;
|
|
26
|
+
// deciding it on every render snapped the user's pick back.
|
|
27
|
+
const [lookupMode, setLookupMode] = useState(() => featureUniprotId ? 'feature' : hasSearchableIdentifier ? 'auto' : 'manual');
|
|
21
28
|
const [manualUniprotId, setManualUniprotId] = useState('');
|
|
22
29
|
const [taxonIdInput, setTaxonIdInput] = useState('');
|
|
23
|
-
const geneIds = extractFeatureIdentifiers(feature);
|
|
24
30
|
// The gene-name UniProt search is ambiguous across species, so scope it to
|
|
25
31
|
// the assembly's organism where the assembly says what that is. jb2hubs
|
|
26
32
|
// assemblies carry the NCBI taxon in the reference-sequence track metadata
|
|
@@ -46,17 +52,7 @@ export default function useUniProtIdLookup({ feature, view, }) {
|
|
|
46
52
|
const taxonIdError = typedTaxon !== '' && !hasOverride;
|
|
47
53
|
const [selectedQueryId, setSelectedQueryId] = useState('auto');
|
|
48
54
|
const [selectedUniprotId, setSelectedUniprotId] = useState();
|
|
49
|
-
const
|
|
50
|
-
const hasSearchableIdentifier = geneIds.recognizedIds.length > 0 || !!geneIds.geneName;
|
|
51
|
-
// Nothing to search and no accession on the feature: the auto mode has no
|
|
52
|
-
// query to run, so the dialog opens on the manual field instead of reporting
|
|
53
|
-
// an empty result for an empty query.
|
|
54
|
-
const effectiveLookupMode = lookupMode === 'auto' && featureUniprotId
|
|
55
|
-
? 'feature'
|
|
56
|
-
: lookupMode === 'auto' && !hasSearchableIdentifier
|
|
57
|
-
? 'manual'
|
|
58
|
-
: lookupMode;
|
|
59
|
-
const isAutoMode = effectiveLookupMode === 'auto';
|
|
55
|
+
const isAutoMode = lookupMode === 'auto';
|
|
60
56
|
const { entries: uniprotEntries, isLoading: isLookupLoading, error: lookupError, partialFailure: lookupPartialFailure, } = useUniProtSearch({
|
|
61
57
|
recognizedIds: geneIds.recognizedIds,
|
|
62
58
|
geneId: geneIds.geneId,
|
|
@@ -74,13 +70,13 @@ export default function useUniProtIdLookup({ feature, view, }) {
|
|
|
74
70
|
? selectedUniprotId
|
|
75
71
|
: undefined;
|
|
76
72
|
const autoUniprotId = uniprotEntries[0]?.accession;
|
|
77
|
-
const uniprotId =
|
|
73
|
+
const uniprotId = lookupMode === 'feature'
|
|
78
74
|
? featureUniprotId
|
|
79
75
|
: isAutoMode
|
|
80
76
|
? (pickedUniprotId ?? autoUniprotId)
|
|
81
77
|
: debouncedManualUniprotId;
|
|
82
78
|
return {
|
|
83
|
-
lookupMode
|
|
79
|
+
lookupMode,
|
|
84
80
|
setLookupMode,
|
|
85
81
|
manualUniprotId,
|
|
86
82
|
setManualUniprotId,
|
|
@@ -1,12 +1,15 @@
|
|
|
1
|
-
import { getContainingTrack, getSession } from '@jbrowse/core/util';
|
|
1
|
+
import { getContainingTrack, getSession, isGeneLikeType, } from '@jbrowse/core/util';
|
|
2
2
|
import AddIcon from '@mui/icons-material/Add';
|
|
3
|
-
import {
|
|
4
|
-
import { geneLikeRoot, isCodingFeature, isGeneLikeType } from './codingFeature';
|
|
3
|
+
import { isCodingFeature } from './codingFeature';
|
|
5
4
|
import LaunchProteinViewDialog from './components/LaunchProteinViewDialog';
|
|
6
5
|
function isDisplay(elt) {
|
|
7
6
|
return elt.name === 'LinearBasicDisplay';
|
|
8
7
|
}
|
|
9
|
-
function
|
|
8
|
+
export function resolveTarget(self) {
|
|
9
|
+
const info = self.contextMenuInfo;
|
|
10
|
+
if (!info) {
|
|
11
|
+
return undefined;
|
|
12
|
+
}
|
|
10
13
|
const { item, subfeature, displayedRegionIndex } = info;
|
|
11
14
|
const type = subfeature ? subfeature.type : item.type;
|
|
12
15
|
// The parent gene, not the clicked isoform: the dialog picks the transcript
|
|
@@ -16,32 +19,10 @@ function canvasTarget(info, fetchFullFeature) {
|
|
|
16
19
|
? undefined
|
|
17
20
|
: {
|
|
18
21
|
type,
|
|
19
|
-
fetchFeature: () => fetchFullFeature(parentId, displayedRegionIndex),
|
|
22
|
+
fetchFeature: () => self.fetchFullFeature(parentId, displayedRegionIndex),
|
|
20
23
|
preferredTranscriptId: subfeature?.featureId,
|
|
21
24
|
};
|
|
22
25
|
}
|
|
23
|
-
// The hit test only carries a type, so a canvas host learns whether the gene
|
|
24
|
-
// codes for anything after the fetch; a legacy host has the whole feature and
|
|
25
|
-
// can decline up front.
|
|
26
|
-
function legacyTarget(feature) {
|
|
27
|
-
const root = geneLikeRoot(feature);
|
|
28
|
-
const type = root.get('type');
|
|
29
|
-
return type === undefined || !isCodingFeature(root)
|
|
30
|
-
? undefined
|
|
31
|
-
: {
|
|
32
|
-
type,
|
|
33
|
-
feature: root,
|
|
34
|
-
preferredTranscriptId: root === feature ? undefined : feature.id(),
|
|
35
|
-
};
|
|
36
|
-
}
|
|
37
|
-
export function resolveTarget(self) {
|
|
38
|
-
const { contextMenuFeature, contextMenuInfo, fetchFullFeature } = self;
|
|
39
|
-
return contextMenuInfo && fetchFullFeature
|
|
40
|
-
? canvasTarget(contextMenuInfo, fetchFullFeature)
|
|
41
|
-
: contextMenuFeature
|
|
42
|
-
? legacyTarget(contextMenuFeature)
|
|
43
|
-
: undefined;
|
|
44
|
-
}
|
|
45
26
|
function launchProteinView(self, target) {
|
|
46
27
|
const track = getContainingTrack(self);
|
|
47
28
|
const session = getSession(track);
|
|
@@ -52,41 +33,32 @@ function launchProteinView(self, target) {
|
|
|
52
33
|
{ model: track, handleClose, feature, preferredTranscriptId },
|
|
53
34
|
]);
|
|
54
35
|
};
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
.catch((e) => {
|
|
73
|
-
console.error(e);
|
|
74
|
-
session.notifyError(`${e}`, e);
|
|
75
|
-
});
|
|
76
|
-
}
|
|
36
|
+
target
|
|
37
|
+
.fetchFeature()
|
|
38
|
+
.then(feature => {
|
|
39
|
+
if (!feature) {
|
|
40
|
+
session.notify('Could not load feature for protein view', 'warning');
|
|
41
|
+
}
|
|
42
|
+
else if (!isCodingFeature(feature)) {
|
|
43
|
+
session.notify(`${feature.get('name') ?? feature.get('id') ?? 'This feature'} has no coding sequence, so there is no protein to show`, 'info');
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
openDialog(feature);
|
|
47
|
+
}
|
|
48
|
+
})
|
|
49
|
+
.catch((e) => {
|
|
50
|
+
console.error(e);
|
|
51
|
+
session.notifyError(`${e}`, e);
|
|
52
|
+
});
|
|
77
53
|
}
|
|
78
54
|
function extendStateModel(stateModel) {
|
|
79
55
|
return stateModel.views((self) => {
|
|
80
|
-
// .call(self), not a bare call: the canvas display's own contextMenuItems
|
|
81
|
-
// reads `this.isGeneLike`, so invoking it detached throws on undefined and
|
|
82
|
-
// the ErrorBoundary around the menu swallows it -- the user right-clicks a
|
|
83
|
-
// feature and gets no menu at all, not merely no protein item.
|
|
84
56
|
const superContextMenuItems = self.contextMenuItems;
|
|
85
57
|
return {
|
|
86
58
|
contextMenuItems() {
|
|
87
59
|
const target = resolveTarget(self);
|
|
88
60
|
return [
|
|
89
|
-
...superContextMenuItems
|
|
61
|
+
...superContextMenuItems(),
|
|
90
62
|
...(target && isGeneLikeType(target.type)
|
|
91
63
|
? [
|
|
92
64
|
{
|
|
@@ -106,7 +78,7 @@ function extendStateModel(stateModel) {
|
|
|
106
78
|
export default function LaunchProteinViewF(pluginManager) {
|
|
107
79
|
pluginManager.addToExtensionPoint('Core-extendPluggableElement', (elt) => {
|
|
108
80
|
if (isDisplay(elt)) {
|
|
109
|
-
|
|
81
|
+
elt.extendStateModel(extendStateModel);
|
|
110
82
|
}
|
|
111
83
|
return elt;
|
|
112
84
|
});
|
|
@@ -69,11 +69,35 @@ export async function submitFoldseekSearch({ aaSequence, di3Sequence, databases,
|
|
|
69
69
|
if (!response.ok) {
|
|
70
70
|
throw await httpError(response, url);
|
|
71
71
|
}
|
|
72
|
-
//
|
|
73
|
-
//
|
|
74
|
-
// SyntaxError.
|
|
72
|
+
// The server answers a refusal (RATELIMIT, MAINTENANCE) with a 200 and no
|
|
73
|
+
// id, which used to be polled as ticket "undefined" for three minutes
|
|
75
74
|
const text = await response.text();
|
|
76
|
-
|
|
75
|
+
const ticket = parseTicket(text);
|
|
76
|
+
if (!ticket) {
|
|
77
|
+
throw new Error(`Foldseek did not accept the search: ${text.slice(0, 200)}`);
|
|
78
|
+
}
|
|
79
|
+
return ticket;
|
|
80
|
+
}
|
|
81
|
+
function parseTicket(text) {
|
|
82
|
+
let body;
|
|
83
|
+
try {
|
|
84
|
+
body = JSON.parse(text);
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
return undefined;
|
|
88
|
+
}
|
|
89
|
+
if (typeof body !== 'object' || body === null) {
|
|
90
|
+
return undefined;
|
|
91
|
+
}
|
|
92
|
+
const id = Reflect.get(body, 'id');
|
|
93
|
+
const status = Reflect.get(body, 'status');
|
|
94
|
+
return typeof id === 'string' &&
|
|
95
|
+
(status === 'PENDING' ||
|
|
96
|
+
status === 'RUNNING' ||
|
|
97
|
+
status === 'COMPLETE' ||
|
|
98
|
+
status === 'ERROR')
|
|
99
|
+
? { id, status }
|
|
100
|
+
: undefined;
|
|
77
101
|
}
|
|
78
102
|
async function pollFoldseekStatus({ ticketId, signal, }) {
|
|
79
103
|
// Use the /tickets endpoint (plural) with POST
|
|
@@ -114,10 +138,6 @@ export async function waitForFoldseekResults({ ticketId, onStatusChange, signal,
|
|
|
114
138
|
throw abortError(signal);
|
|
115
139
|
}
|
|
116
140
|
const status = await pollFoldseekStatus({ ticketId, signal });
|
|
117
|
-
if (status.status === 'ERROR') {
|
|
118
|
-
console.error('[Foldseek] Search error:', status);
|
|
119
|
-
throw new Error(`Foldseek search failed: ${status.error ?? 'Unknown error'}`);
|
|
120
|
-
}
|
|
121
141
|
if (status.status === 'COMPLETE') {
|
|
122
142
|
onStatusChange?.('Fetching results...');
|
|
123
143
|
const apiResponse = await getFoldseekResults({ ticketId, signal });
|
|
@@ -131,6 +151,11 @@ export async function waitForFoldseekResults({ ticketId, onStatusChange, signal,
|
|
|
131
151
|
};
|
|
132
152
|
return results;
|
|
133
153
|
}
|
|
154
|
+
// the server also answers RATELIMIT, MAINTENANCE and UNKNOWN, none of
|
|
155
|
+
// which a wait resolves
|
|
156
|
+
if (status.status !== 'PENDING' && status.status !== 'RUNNING') {
|
|
157
|
+
throw new Error(`Foldseek search failed: ${status.error ?? status.status}`);
|
|
158
|
+
}
|
|
134
159
|
onStatusChange?.(`Search ${status.status.toLowerCase()}... (${elapsedSeconds()}s)`);
|
|
135
160
|
await timeout(1000, signal);
|
|
136
161
|
}
|
|
@@ -22,9 +22,9 @@ export async function safeLaunch(fn, onSuccess, onError) {
|
|
|
22
22
|
* A launch that names its own structure needs no accession. Bypassing a lookup
|
|
23
23
|
* that failed or resolved the wrong gene is the whole point of typing a PDB id,
|
|
24
24
|
* and the view resolves SIFTS from the entry itself; the accession only feeds
|
|
25
|
-
* the feature tracks and the view's name, both of which do without it.
|
|
26
|
-
* AlphaFold tab
|
|
27
|
-
*
|
|
25
|
+
* the feature tracks and the view's name, both of which do without it. On the
|
|
26
|
+
* AlphaFold tab the url is derived from the accession, so no accession means
|
|
27
|
+
* no url either; `useAlphaFoldData` makes sure of it.
|
|
28
28
|
*/
|
|
29
29
|
export function getLaunchMissingReasons({ uniprotId, userSelectedProteinSequence, selectedTranscript, url, pdbId, }) {
|
|
30
30
|
const namesOwnStructure = !!url || !!pdbId;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { isSessionWithAddSessionTrack } from '@jbrowse/core/util';
|
|
2
2
|
import { maybeLaunchSideBySide } from './sideBySide';
|
|
3
3
|
import { getGeneDisplayName, getTranscriptDisplayName } from './util';
|
|
4
4
|
import { proteinViewSnapshot } from '../../ProteinView/proteinViewSpec';
|
|
@@ -35,7 +35,7 @@ export function launch3DProteinView({ session, view, feature, selectedTranscript
|
|
|
35
35
|
return proteinView;
|
|
36
36
|
}
|
|
37
37
|
// The 1D annotation view adds temporary tracks/assemblies, so it requires a
|
|
38
|
-
//
|
|
38
|
+
// SessionWithAddSessionTrack and a known uniprotId. Demanding both in the signature
|
|
39
39
|
// forces callers to narrow up front — there's no silent no-op when a wide
|
|
40
40
|
// session or missing id slips through.
|
|
41
41
|
async function launch1DProteinView({ session, view, feature, selectedTranscript, uniprotId, confidenceUrl, }) {
|
|
@@ -63,7 +63,9 @@ export const PROTEIN_LAUNCH_LABELS = {
|
|
|
63
63
|
// single source of truth — an unavailable action is unrepresentable rather than
|
|
64
64
|
// a menu item that silently no-ops.
|
|
65
65
|
export function getConditionalProteinLaunches({ session, view, feature, selectedTranscript, uniprotId, confidenceUrl, }) {
|
|
66
|
-
const addTracksSession =
|
|
66
|
+
const addTracksSession = isSessionWithAddSessionTrack(session)
|
|
67
|
+
? session
|
|
68
|
+
: undefined;
|
|
67
69
|
return {
|
|
68
70
|
launch1D: addTracksSession && uniprotId
|
|
69
71
|
? () => launch1DProteinView({
|
|
@@ -21,39 +21,16 @@ let warnedPartial = false;
|
|
|
21
21
|
function isSessionWithWorkspaces(session) {
|
|
22
22
|
const canEnable = hasAction(session, 'setUseWorkspaces');
|
|
23
23
|
const canPlace = hasAction(session, 'setPendingMove');
|
|
24
|
-
// Missing BOTH is an embedded session
|
|
25
|
-
//
|
|
26
|
-
//
|
|
27
|
-
//
|
|
28
|
-
// and silence there is how this broke before. jbrowse-web folded
|
|
29
|
-
// `setPendingMove` into its layout `init`, this guard went false, and the
|
|
30
|
-
// plugin simply stopped asking for the split: no error, no missing feature,
|
|
31
|
-
// just two views quietly stacking. Nobody noticed for weeks. Feature
|
|
32
|
-
// detection cannot ask the host to announce a change, but it can tell "not
|
|
33
|
-
// supported here" from "supported, and gone".
|
|
34
|
-
//
|
|
35
|
-
// Two very different hosts produce this one shape, and nothing on the session
|
|
36
|
-
// tells them apart, so the message carries both rather than the guess:
|
|
37
|
-
//
|
|
38
|
-
// - Releases through v4.3.0, where placement is `setPendingMoveToSplitRight`,
|
|
39
|
-
// a module function in @jbrowse/app-core's DockviewContext, not a session
|
|
40
|
-
// action. Nothing is wrong and nothing needs fixing; this plugin targets v5+
|
|
41
|
-
// and does not reach for v4's door. Measured 2026-08-17: v4.3.0 and latest
|
|
42
|
-
// stack, main splits.
|
|
43
|
-
// - A newer host that moved the action out from under us, which is the
|
|
44
|
-
// regression this warning exists to catch.
|
|
45
|
-
//
|
|
46
|
-
// Do not quiet the first case by sniffing the version. The alarm is only worth
|
|
47
|
-
// having if it fires on a shape it cannot explain, and these two shapes are
|
|
48
|
-
// identical.
|
|
24
|
+
// Missing BOTH is an embedded session, with no workspaces to ask for. Missing
|
|
25
|
+
// ONE means the host moved the action out from under us: jbrowse-web once
|
|
26
|
+
// folded `setPendingMove` into its layout `init`, and the views quietly
|
|
27
|
+
// stacked for weeks, so that shape warns.
|
|
49
28
|
if (canEnable !== canPlace && !warnedPartial) {
|
|
50
29
|
warnedPartial = true;
|
|
51
30
|
console.warn(`jbrowse-plugin-protein3d: this session supports workspaces but not ` +
|
|
52
31
|
`${canPlace ? 'setUseWorkspaces' : 'setPendingMove'}, so the ` +
|
|
53
|
-
`side-by-side launch was skipped and the views will stack.
|
|
54
|
-
`
|
|
55
|
-
`instead; on a newer host it means the session API moved and the plugin ` +
|
|
56
|
-
`needs updating to match.`);
|
|
32
|
+
`side-by-side launch was skipped and the views will stack. The session ` +
|
|
33
|
+
`API moved and the plugin needs updating to match.`);
|
|
57
34
|
}
|
|
58
35
|
return canEnable && canPlace;
|
|
59
36
|
}
|
|
@@ -16,11 +16,8 @@ export async function fetchRegionSequence({ session, assemblyName, refName, star
|
|
|
16
16
|
if (!assembly) {
|
|
17
17
|
throw new Error('assembly not found');
|
|
18
18
|
}
|
|
19
|
-
const
|
|
20
|
-
// a named object keeps sessionId, which v4 hosts read from the args
|
|
21
|
-
const args = {
|
|
19
|
+
const [feat] = await rpcManager.call('getSequence', 'CoreGetFeatures', {
|
|
22
20
|
adapterConfig: getConf(assembly, ['sequence', 'adapter']),
|
|
23
|
-
sessionId,
|
|
24
21
|
regions: [
|
|
25
22
|
{
|
|
26
23
|
start,
|
|
@@ -29,10 +26,9 @@ export async function fetchRegionSequence({ session, assemblyName, refName, star
|
|
|
29
26
|
assemblyName: assembly.name,
|
|
30
27
|
},
|
|
31
28
|
],
|
|
32
|
-
};
|
|
33
|
-
const [feat] = await rpcManager.call(sessionId, 'CoreGetFeatures', args);
|
|
29
|
+
});
|
|
34
30
|
const seq = feat?.get('seq');
|
|
35
|
-
return { seq, assemblyGeneticCodeId:
|
|
31
|
+
return { seq, assemblyGeneticCodeId: assembly.getGeneticCodeId(refName) };
|
|
36
32
|
}
|
|
37
33
|
export async function fetchProteinSeq({ feature, session, assemblyName, }) {
|
|
38
34
|
const { seq, assemblyGeneticCodeId } = await fetchRegionSequence({
|
|
@@ -85,8 +81,3 @@ export async function fetchTranscriptProteinSeqs({ transcripts, session, assembl
|
|
|
85
81
|
fetchSpan: span => fetchRegionSequence({ session, assemblyName, ...span }),
|
|
86
82
|
});
|
|
87
83
|
}
|
|
88
|
-
// v5 hosts only; a v4 assembly has neither the method nor the config slot
|
|
89
|
-
function assemblyGeneticCode(assembly, refName) {
|
|
90
|
-
const { getGeneticCodeId } = assembly;
|
|
91
|
-
return getGeneticCodeId?.call(assembly, refName);
|
|
92
|
-
}
|