jbrowse-plugin-protein3d 0.5.6 → 0.5.7
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/LaunchProteinView/components/FoldseekActionMenu.js +6 -16
- package/dist/LaunchProteinView/components/FoldseekResultsTable.js +14 -7
- package/dist/LaunchProteinView/components/ProteinViewActions.js +21 -38
- package/dist/LaunchProteinView/components/UniProtResultsTable.js +11 -9
- package/dist/LaunchProteinView/components/UserProvidedStructure.js +8 -16
- package/dist/LaunchProteinView/components/launchProteinAnnotationView.js +1 -1
- package/dist/LaunchProteinView/hooks/useSafeLaunch.d.ts +1 -1
- package/dist/LaunchProteinView/utils/launchHelpers.d.ts +6 -11
- package/dist/LaunchProteinView/utils/launchHelpers.js +6 -7
- package/dist/LaunchProteinView/utils/launchViewUtils.d.ts +8 -2
- package/dist/LaunchProteinView/utils/launchViewUtils.js +31 -0
- package/dist/jbrowse-plugin-protein3d.umd.production.min.js +15 -15
- package/dist/jbrowse-plugin-protein3d.umd.production.min.js.map +3 -3
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
- package/src/LaunchProteinView/components/FoldseekActionMenu.tsx +8 -25
- package/src/LaunchProteinView/components/FoldseekResultsTable.tsx +15 -6
- package/src/LaunchProteinView/components/ProteinViewActions.tsx +21 -45
- package/src/LaunchProteinView/components/UniProtResultsTable.tsx +12 -9
- package/src/LaunchProteinView/components/UserProvidedStructure.tsx +8 -15
- package/src/LaunchProteinView/components/launchProteinAnnotationView.ts +1 -1
- package/src/LaunchProteinView/hooks/useSafeLaunch.ts +1 -1
- package/src/LaunchProteinView/utils/launchHelpers.ts +6 -16
- package/src/LaunchProteinView/utils/launchViewUtils.ts +49 -5
- package/src/version.ts +1 -1
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import React, { useState } from 'react';
|
|
2
2
|
import { ErrorMessage } from '@jbrowse/core/ui';
|
|
3
|
-
import { isSessionWithAddTracks } from '@jbrowse/core/util';
|
|
4
3
|
import { Button, Menu, MenuItem } from '@mui/material';
|
|
5
4
|
import { useSafeLaunch } from '../hooks/useSafeLaunch';
|
|
6
5
|
import { caCoordsToPdb, hasValidCaCoords } from '../utils/caCoordsToPdb';
|
|
7
|
-
import { getConfidenceUrlFromTarget, getUniprotIdFromAlphaFoldTarget,
|
|
6
|
+
import { getConditionalProteinLaunches, getConfidenceUrlFromTarget, getUniprotIdFromAlphaFoldTarget, launch3DProteinView, } from '../utils/launchViewUtils';
|
|
8
7
|
export default function FoldseekActionMenu({ hit, session, view, feature, selectedTranscript, userProvidedTranscriptSequence, onClose, }) {
|
|
9
8
|
const [anchorEl, setAnchorEl] = useState(null);
|
|
10
9
|
const open = Boolean(anchorEl);
|
|
@@ -29,28 +28,19 @@ export default function FoldseekActionMenu({ hit, session, view, feature, select
|
|
|
29
28
|
userProvidedTranscriptSequence,
|
|
30
29
|
});
|
|
31
30
|
});
|
|
32
|
-
const
|
|
33
|
-
|
|
31
|
+
const { launch1D, launchMsa } = getConditionalProteinLaunches({
|
|
32
|
+
...baseParams,
|
|
33
|
+
confidenceUrl: getConfidenceUrlFromTarget(hit.target),
|
|
34
34
|
});
|
|
35
35
|
const canLoad = !!hit.structureUrl || hasValidCaCoords(hit.tCa, hit.tSeq);
|
|
36
36
|
if (!canLoad) {
|
|
37
37
|
return React.createElement("span", null, "-");
|
|
38
38
|
}
|
|
39
|
-
// 1D launch needs an add-tracks session and a uniprotId; narrowing both here
|
|
40
|
-
// gates the menu item and types its handler from a single condition.
|
|
41
|
-
const addTracksSession = isSessionWithAddTracks(session) ? session : undefined;
|
|
42
39
|
return (React.createElement(React.Fragment, null,
|
|
43
40
|
launchError ? React.createElement(ErrorMessage, { error: launchError }) : null,
|
|
44
41
|
React.createElement(Button, { size: "small", variant: "outlined", onClick: handleClick }, "Load"),
|
|
45
42
|
React.createElement(Menu, { anchorEl: anchorEl, open: open, onClose: handleMenuClose },
|
|
46
43
|
React.createElement(MenuItem, { onClick: handleLaunch3D }, "Launch 3D protein view"),
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
view,
|
|
50
|
-
feature,
|
|
51
|
-
selectedTranscript,
|
|
52
|
-
uniprotId,
|
|
53
|
-
confidenceUrl: getConfidenceUrlFromTarget(hit.target),
|
|
54
|
-
})) }, "Launch 1D protein annotation view")) : null,
|
|
55
|
-
uniprotId && hasMsaViewPlugin() ? (React.createElement(MenuItem, { onClick: handleLaunchMSA }, "Launch MSA view (AlphaFoldDB a3m)")) : null)));
|
|
44
|
+
launch1D ? (React.createElement(MenuItem, { onClick: runLaunch(launch1D) }, "Launch 1D protein annotation view")) : null,
|
|
45
|
+
launchMsa ? (React.createElement(MenuItem, { onClick: runLaunch(launchMsa) }, "Launch MSA view (AlphaFoldDB a3m)")) : null)));
|
|
56
46
|
}
|
|
@@ -3,7 +3,7 @@ import { Paper, Table, TableBody, TableCell, TableContainer, TableHead, TableRow
|
|
|
3
3
|
import { makeStyles } from 'tss-react/mui';
|
|
4
4
|
import FoldseekActionMenu from './FoldseekActionMenu';
|
|
5
5
|
import { getStructureUrlFromTarget } from '../utils/launchViewUtils';
|
|
6
|
-
const useStyles = makeStyles()({
|
|
6
|
+
const useStyles = makeStyles()(theme => ({
|
|
7
7
|
root: {
|
|
8
8
|
display: 'flex',
|
|
9
9
|
flexDirection: 'column',
|
|
@@ -14,13 +14,16 @@ const useStyles = makeStyles()({
|
|
|
14
14
|
},
|
|
15
15
|
headerCell: {
|
|
16
16
|
fontWeight: 'bold',
|
|
17
|
-
backgroundColor: '
|
|
17
|
+
backgroundColor: theme.palette.mode === 'dark'
|
|
18
|
+
? theme.palette.grey[900]
|
|
19
|
+
: theme.palette.grey[100],
|
|
18
20
|
},
|
|
19
21
|
noResults: {
|
|
20
22
|
padding: 16,
|
|
21
23
|
textAlign: 'center',
|
|
22
24
|
},
|
|
23
|
-
});
|
|
25
|
+
}));
|
|
26
|
+
const MAX_HITS = 100;
|
|
24
27
|
function flattenResults(results) {
|
|
25
28
|
const hits = results.results.flatMap(dbResult => (dbResult.alignments ?? []).flat().map(alignment => ({
|
|
26
29
|
...alignment,
|
|
@@ -28,11 +31,12 @@ function flattenResults(results) {
|
|
|
28
31
|
structureUrl: getStructureUrlFromTarget(alignment.target, dbResult.db),
|
|
29
32
|
})));
|
|
30
33
|
hits.sort((a, b) => (a.eval ?? Infinity) - (b.eval ?? Infinity));
|
|
31
|
-
return hits
|
|
34
|
+
return hits;
|
|
32
35
|
}
|
|
33
36
|
export default function FoldseekResultsTable({ results, session, view, feature, selectedTranscript, userProvidedTranscriptSequence, onClose, }) {
|
|
34
37
|
const { classes } = useStyles();
|
|
35
|
-
const
|
|
38
|
+
const allHits = flattenResults(results);
|
|
39
|
+
const flatHits = allHits.slice(0, MAX_HITS);
|
|
36
40
|
if (flatHits.length === 0) {
|
|
37
41
|
return (React.createElement(Paper, { className: classes.noResults },
|
|
38
42
|
React.createElement(Typography, null, "No similar structures found")));
|
|
@@ -40,8 +44,11 @@ export default function FoldseekResultsTable({ results, session, view, feature,
|
|
|
40
44
|
return (React.createElement("div", { className: classes.root },
|
|
41
45
|
React.createElement(Typography, { variant: "subtitle2" },
|
|
42
46
|
"Found ",
|
|
43
|
-
|
|
44
|
-
" similar structures"
|
|
47
|
+
allHits.length,
|
|
48
|
+
" similar structures",
|
|
49
|
+
allHits.length > flatHits.length
|
|
50
|
+
? ` (showing top ${flatHits.length})`
|
|
51
|
+
: ''),
|
|
45
52
|
React.createElement(TableContainer, { component: Paper, className: classes.tableContainer },
|
|
46
53
|
React.createElement(Table, { size: "small", stickyHeader: true },
|
|
47
54
|
React.createElement(TableHead, null,
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import React, { useState } from 'react';
|
|
2
2
|
import { ErrorMessage } from '@jbrowse/core/ui';
|
|
3
|
-
import { isSessionWithAddTracks } from '@jbrowse/core/util';
|
|
4
3
|
import ArrowDropDownIcon from '@mui/icons-material/ArrowDropDown';
|
|
5
4
|
import SettingsIcon from '@mui/icons-material/Settings';
|
|
6
5
|
import { Button, ButtonGroup, IconButton, Tooltip, Typography } from '@mui/material';
|
|
@@ -9,36 +8,31 @@ import LaunchSettingsDialog from './LaunchSettingsDialog';
|
|
|
9
8
|
import SequenceMismatchNotice from './SequenceMismatchNotice';
|
|
10
9
|
import { useSafeLaunch } from '../hooks/useSafeLaunch';
|
|
11
10
|
import { getLaunchMissingReasons } from '../utils/launchHelpers';
|
|
12
|
-
import {
|
|
11
|
+
import { getConditionalProteinLaunches, launch3DProteinView, launch3DProteinViewWithMsa, } from '../utils/launchViewUtils';
|
|
13
12
|
export default function ProteinViewActions({ handleClose, uniprotId, userSelectedProteinSequence, selectedTranscript, url, confidenceUrl, feature, view, session, alignmentAlgorithm, onAlignmentAlgorithmChange, sequencesMatch, isLoading, error, }) {
|
|
14
13
|
const [dialogOpen, setDialogOpen] = useState(false);
|
|
15
14
|
const [settingsOpen, setSettingsOpen] = useState(false);
|
|
16
|
-
// Disable launch while loading — SWR's keepPreviousData would otherwise let
|
|
17
|
-
// a user click Launch on stale results (wrong UniProt ID) during a refetch.
|
|
18
|
-
const canLaunch = !isLoading &&
|
|
19
|
-
!!uniprotId &&
|
|
20
|
-
!!userSelectedProteinSequence &&
|
|
21
|
-
!!selectedTranscript;
|
|
22
15
|
const missingReasons = getLaunchMissingReasons({
|
|
23
16
|
uniprotId,
|
|
24
17
|
userSelectedProteinSequence,
|
|
25
18
|
selectedTranscript,
|
|
26
|
-
isLoading,
|
|
27
|
-
error,
|
|
28
19
|
});
|
|
20
|
+
// Disable launch while loading — SWR's keepPreviousData would otherwise let
|
|
21
|
+
// a user click Launch on stale results (wrong UniProt ID) during a refetch.
|
|
22
|
+
const canLaunch = !isLoading && missingReasons.length === 0;
|
|
23
|
+
// Suppress the derived reasons while loading or while a real upstream error
|
|
24
|
+
// is displayed above via <ErrorMessage> — a duplicate hint would mislead.
|
|
25
|
+
const showMissingReasons = !isLoading && !error && missingReasons.length > 0;
|
|
29
26
|
const closeMenu = () => {
|
|
30
27
|
setDialogOpen(false);
|
|
31
28
|
};
|
|
32
29
|
const { runLaunch, launchError } = useSafeLaunch(handleClose, closeMenu);
|
|
33
|
-
const
|
|
30
|
+
const launch3DParams = {
|
|
34
31
|
session,
|
|
35
32
|
view,
|
|
36
33
|
feature,
|
|
37
34
|
selectedTranscript,
|
|
38
35
|
uniprotId,
|
|
39
|
-
};
|
|
40
|
-
const launch3DParams = {
|
|
41
|
-
...baseParams,
|
|
42
36
|
url,
|
|
43
37
|
userProvidedTranscriptSequence: userSelectedProteinSequence?.seq,
|
|
44
38
|
alignmentAlgorithm,
|
|
@@ -46,18 +40,14 @@ export default function ProteinViewActions({ handleClose, uniprotId, userSelecte
|
|
|
46
40
|
const handleLaunch3DView = runLaunch(() => {
|
|
47
41
|
launch3DProteinView(launch3DParams);
|
|
48
42
|
});
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
43
|
+
const { launch1D, launchMsa } = getConditionalProteinLaunches({
|
|
44
|
+
session,
|
|
45
|
+
view,
|
|
46
|
+
feature,
|
|
47
|
+
selectedTranscript,
|
|
48
|
+
uniprotId,
|
|
49
|
+
confidenceUrl,
|
|
54
50
|
});
|
|
55
|
-
// The 1D annotation view needs an add-tracks session and a known uniprotId.
|
|
56
|
-
// Narrowing here is the single source of truth: the option only exists when
|
|
57
|
-
// both hold, and its handler is type-checked against those narrowed values —
|
|
58
|
-
// so a 1D launch that can't work is unrepresentable rather than a silent
|
|
59
|
-
// no-op.
|
|
60
|
-
const addTracksSession = isSessionWithAddTracks(session) ? session : undefined;
|
|
61
51
|
const launchOptions = [
|
|
62
52
|
{
|
|
63
53
|
key: '3d',
|
|
@@ -65,36 +55,29 @@ export default function ProteinViewActions({ handleClose, uniprotId, userSelecte
|
|
|
65
55
|
description: 'View protein structure with genome-to-structure coordinate mapping',
|
|
66
56
|
onClick: handleLaunch3DView,
|
|
67
57
|
},
|
|
68
|
-
...(
|
|
58
|
+
...(launch1D
|
|
69
59
|
? [
|
|
70
60
|
{
|
|
71
61
|
key: '1d',
|
|
72
62
|
title: 'Launch 1D protein annotation view',
|
|
73
63
|
description: 'View protein features and annotations as a linear track',
|
|
74
|
-
onClick: runLaunch(
|
|
75
|
-
session: addTracksSession,
|
|
76
|
-
view,
|
|
77
|
-
feature,
|
|
78
|
-
selectedTranscript,
|
|
79
|
-
uniprotId,
|
|
80
|
-
confidenceUrl,
|
|
81
|
-
})),
|
|
64
|
+
onClick: runLaunch(launch1D),
|
|
82
65
|
},
|
|
83
66
|
]
|
|
84
67
|
: []),
|
|
85
|
-
...(
|
|
68
|
+
...(launchMsa
|
|
86
69
|
? [
|
|
87
70
|
{
|
|
88
71
|
key: 'msa',
|
|
89
72
|
title: 'Launch MSA view',
|
|
90
73
|
description: 'View AlphaFold a3m multiple sequence alignment',
|
|
91
|
-
onClick:
|
|
74
|
+
onClick: runLaunch(launchMsa),
|
|
92
75
|
},
|
|
93
76
|
{
|
|
94
77
|
key: '3d-msa',
|
|
95
78
|
title: 'Launch 3D structure + MSA view',
|
|
96
79
|
description: 'Launch both views with AlphaFold a3m MSA',
|
|
97
|
-
onClick:
|
|
80
|
+
onClick: runLaunch(() => launch3DProteinViewWithMsa(launch3DParams)),
|
|
98
81
|
},
|
|
99
82
|
]
|
|
100
83
|
: []),
|
|
@@ -110,7 +93,7 @@ export default function ProteinViewActions({ handleClose, uniprotId, userSelecte
|
|
|
110
93
|
React.createElement(Button, { variant: "contained", color: "secondary", size: "small", onClick: () => {
|
|
111
94
|
handleClose();
|
|
112
95
|
} }, "Cancel"),
|
|
113
|
-
|
|
96
|
+
showMissingReasons ? (React.createElement(Typography, { variant: "body2", color: "error", sx: { mr: 2 } }, missingReasons.join('. '))) : null,
|
|
114
97
|
React.createElement(ButtonGroup, { variant: "contained", color: "primary", size: "small" },
|
|
115
98
|
React.createElement(Button, { disabled: !canLaunch, onClick: handleLaunch3DView }, "Launch"),
|
|
116
99
|
React.createElement(Button, { disabled: !canLaunch, onClick: () => {
|
|
@@ -2,36 +2,38 @@ import React from 'react';
|
|
|
2
2
|
import { Chip, Paper, Radio, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, Typography, } from '@mui/material';
|
|
3
3
|
import { makeStyles } from 'tss-react/mui';
|
|
4
4
|
import ExternalLink from '../../components/ExternalLink';
|
|
5
|
-
const useStyles = makeStyles()({
|
|
5
|
+
const useStyles = makeStyles()(theme => ({
|
|
6
6
|
tableContainer: {
|
|
7
7
|
maxHeight: 200,
|
|
8
8
|
},
|
|
9
9
|
headerCell: {
|
|
10
10
|
fontWeight: 'bold',
|
|
11
|
-
backgroundColor: '
|
|
11
|
+
backgroundColor: theme.palette.mode === 'dark'
|
|
12
|
+
? theme.palette.grey[900]
|
|
13
|
+
: theme.palette.grey[100],
|
|
12
14
|
},
|
|
13
15
|
selectedRow: {
|
|
14
|
-
backgroundColor:
|
|
16
|
+
backgroundColor: theme.palette.action.selected,
|
|
15
17
|
},
|
|
16
18
|
clickableRow: {
|
|
17
19
|
cursor: 'pointer',
|
|
18
20
|
'&:hover': {
|
|
19
|
-
backgroundColor:
|
|
21
|
+
backgroundColor: theme.palette.action.hover,
|
|
20
22
|
},
|
|
21
23
|
},
|
|
22
24
|
reviewedChip: {
|
|
23
|
-
backgroundColor:
|
|
24
|
-
color:
|
|
25
|
+
backgroundColor: theme.palette.success.main,
|
|
26
|
+
color: theme.palette.success.contrastText,
|
|
25
27
|
fontSize: '0.7rem',
|
|
26
28
|
height: 20,
|
|
27
29
|
},
|
|
28
30
|
unreviewedChip: {
|
|
29
|
-
backgroundColor:
|
|
30
|
-
color:
|
|
31
|
+
backgroundColor: theme.palette.warning.main,
|
|
32
|
+
color: theme.palette.warning.contrastText,
|
|
31
33
|
fontSize: '0.7rem',
|
|
32
34
|
height: 20,
|
|
33
35
|
},
|
|
34
|
-
});
|
|
36
|
+
}));
|
|
35
37
|
export default function UniProtResultsTable({ entries, selectedAccession, onSelect, }) {
|
|
36
38
|
const { classes, cx } = useStyles();
|
|
37
39
|
return entries.length === 0 ? null : (React.createElement("div", null,
|
|
@@ -8,6 +8,7 @@ import SequenceMismatchNotice from './SequenceMismatchNotice';
|
|
|
8
8
|
import StructureSourcePicker from './StructureSourcePicker';
|
|
9
9
|
import TranscriptSelector from './TranscriptSelector';
|
|
10
10
|
import ExternalLink from '../../components/ExternalLink';
|
|
11
|
+
import { useSafeLaunch } from '../hooks/useSafeLaunch';
|
|
11
12
|
import useStructureFileSequence from '../hooks/useStructureFileSequence';
|
|
12
13
|
import useTranscriptIsoformSelection from '../hooks/useTranscriptIsoformSelection';
|
|
13
14
|
import { launch3DProteinView } from '../utils/launchViewUtils';
|
|
@@ -33,43 +34,34 @@ const UserProvidedStructure = observer(function UserProvidedStructure({ feature,
|
|
|
33
34
|
const [file, setFile] = useState();
|
|
34
35
|
const [pdbId, setPdbId] = useState('');
|
|
35
36
|
const [choice, setChoice] = useState('file');
|
|
36
|
-
const [submitError, setSubmitError] = useState();
|
|
37
37
|
const [structureURL, setStructureURL] = useState('');
|
|
38
|
+
const { runLaunch, launchError } = useSafeLaunch(handleClose);
|
|
38
39
|
const activeFile = choice === 'file' ? file : undefined;
|
|
39
40
|
const activeURL = choice === 'file' ? '' : structureURL;
|
|
40
41
|
const { sequences: structureSequences, error: fileError } = useStructureFileSequence({ file: activeFile, url: activeURL });
|
|
41
42
|
const structureName = activeFile?.name ?? activeURL.slice(activeURL.lastIndexOf('/') + 1);
|
|
42
43
|
const structureSequence = structureSequences?.[0];
|
|
43
44
|
const { transcripts: options, isoformSequences, selectedTranscriptId: userSelection, setSelectedTranscriptId: setUserSelection, selectedTranscript, selectedIsoform: protein, error: isoformError, } = useTranscriptIsoformSelection({ feature, view, structureSequence });
|
|
44
|
-
const error = isoformError ??
|
|
45
|
+
const error = isoformError ?? launchError ?? fileError;
|
|
45
46
|
const canLaunch = !!(activeURL || activeFile) && !!protein && !!selectedTranscript;
|
|
46
47
|
const sequencesDiffer = !!protein?.seq &&
|
|
47
48
|
!!structureSequence &&
|
|
48
49
|
stripStopCodon(protein.seq) !== structureSequence;
|
|
49
|
-
const handleLaunch = async () => {
|
|
50
|
-
if (
|
|
51
|
-
return;
|
|
52
|
-
}
|
|
53
|
-
try {
|
|
50
|
+
const handleLaunch = runLaunch(async () => {
|
|
51
|
+
if (protein && selectedTranscript) {
|
|
54
52
|
const structureData = activeFile ? await activeFile.text() : undefined;
|
|
55
|
-
const url = activeURL ? activeURL : undefined;
|
|
56
53
|
launch3DProteinView({
|
|
57
54
|
session,
|
|
58
55
|
view,
|
|
59
56
|
feature,
|
|
60
57
|
selectedTranscript,
|
|
61
|
-
url,
|
|
58
|
+
url: activeURL ? activeURL : undefined,
|
|
62
59
|
data: structureData,
|
|
63
60
|
userProvidedTranscriptSequence: protein.seq,
|
|
64
61
|
alignmentAlgorithm,
|
|
65
62
|
});
|
|
66
|
-
handleClose();
|
|
67
|
-
}
|
|
68
|
-
catch (e) {
|
|
69
|
-
console.error(e);
|
|
70
|
-
setSubmitError(e);
|
|
71
63
|
}
|
|
72
|
-
};
|
|
64
|
+
});
|
|
73
65
|
return (React.createElement(React.Fragment, null,
|
|
74
66
|
React.createElement(DialogContent, { className: classes.dialogContent },
|
|
75
67
|
error ? React.createElement(ErrorMessage, { error: error }) : null,
|
|
@@ -84,7 +76,7 @@ const UserProvidedStructure = observer(function UserProvidedStructure({ feature,
|
|
|
84
76
|
handleClose();
|
|
85
77
|
} }, "Cancel"),
|
|
86
78
|
React.createElement(Button, { variant: "contained", color: "primary", disabled: !canLaunch, onClick: () => {
|
|
87
|
-
|
|
79
|
+
handleLaunch();
|
|
88
80
|
} }, "Launch 3-D protein structure view"))));
|
|
89
81
|
});
|
|
90
82
|
export default UserProvidedStructure;
|
|
@@ -11,7 +11,7 @@ export async function launchProteinAnnotationView({ session, feature, selectedTr
|
|
|
11
11
|
});
|
|
12
12
|
const view = session.addView('LinearGenomeView', {
|
|
13
13
|
type: 'LinearGenomeView',
|
|
14
|
-
displayName: formatViewName('Protein
|
|
14
|
+
displayName: formatViewName('Protein annotations', feature, selectedTranscript, uniprotId),
|
|
15
15
|
});
|
|
16
16
|
// Register for linked highlighting between 1D and 3D views
|
|
17
17
|
if (connectedViewId && selectedTranscript) {
|
|
@@ -4,6 +4,6 @@
|
|
|
4
4
|
* runs the launch via safeLaunch, and surfaces failures inline.
|
|
5
5
|
*/
|
|
6
6
|
export declare function useSafeLaunch(onSuccess: () => void, onBeforeLaunch?: () => void): {
|
|
7
|
-
runLaunch: (fn: () =>
|
|
7
|
+
runLaunch: (fn: () => unknown) => () => void;
|
|
8
8
|
launchError: unknown;
|
|
9
9
|
};
|
|
@@ -3,24 +3,19 @@
|
|
|
3
3
|
* Used to wrap `session.addView(...)` calls so MST validation errors don't
|
|
4
4
|
* fall silently into the React error boundary.
|
|
5
5
|
*/
|
|
6
|
-
export declare function safeLaunch(fn: () =>
|
|
6
|
+
export declare function safeLaunch(fn: () => unknown, onSuccess?: () => void, onError?: (e: unknown) => void): Promise<void>;
|
|
7
7
|
interface LaunchRequirements {
|
|
8
8
|
uniprotId?: string;
|
|
9
9
|
userSelectedProteinSequence?: {
|
|
10
10
|
seq: string;
|
|
11
11
|
};
|
|
12
12
|
selectedTranscript?: unknown;
|
|
13
|
-
isLoading?: boolean;
|
|
14
|
-
/**
|
|
15
|
-
* If a real error already surfaced (e.g. from UniProt lookup), suppress the
|
|
16
|
-
* derived "No UniProt ID found" reason — the underlying error is already
|
|
17
|
-
* shown via <ErrorMessage> and the duplicate hint is misleading.
|
|
18
|
-
*/
|
|
19
|
-
error?: unknown;
|
|
20
13
|
}
|
|
21
14
|
/**
|
|
22
|
-
*
|
|
23
|
-
*
|
|
15
|
+
* The requirements a launch needs, as user-facing reasons for any that are
|
|
16
|
+
* unmet. An empty array means the launch can proceed. Callers decide whether to
|
|
17
|
+
* surface these (e.g. suppressed while loading or while a real upstream error
|
|
18
|
+
* is already shown via <ErrorMessage>, where a duplicate hint would mislead).
|
|
24
19
|
*/
|
|
25
|
-
export declare function getLaunchMissingReasons({ uniprotId, userSelectedProteinSequence, selectedTranscript,
|
|
20
|
+
export declare function getLaunchMissingReasons({ uniprotId, userSelectedProteinSequence, selectedTranscript, }: LaunchRequirements): string[];
|
|
26
21
|
export {};
|
|
@@ -14,16 +14,15 @@ export async function safeLaunch(fn, onSuccess, onError) {
|
|
|
14
14
|
}
|
|
15
15
|
}
|
|
16
16
|
/**
|
|
17
|
-
*
|
|
18
|
-
*
|
|
17
|
+
* The requirements a launch needs, as user-facing reasons for any that are
|
|
18
|
+
* unmet. An empty array means the launch can proceed. Callers decide whether to
|
|
19
|
+
* surface these (e.g. suppressed while loading or while a real upstream error
|
|
20
|
+
* is already shown via <ErrorMessage>, where a duplicate hint would mislead).
|
|
19
21
|
*/
|
|
20
|
-
export function getLaunchMissingReasons({ uniprotId, userSelectedProteinSequence, selectedTranscript,
|
|
21
|
-
if (isLoading || error) {
|
|
22
|
-
return [];
|
|
23
|
-
}
|
|
22
|
+
export function getLaunchMissingReasons({ uniprotId, userSelectedProteinSequence, selectedTranscript, }) {
|
|
24
23
|
return [
|
|
25
24
|
!uniprotId && 'No UniProt ID found',
|
|
26
|
-
!userSelectedProteinSequence &&
|
|
25
|
+
!userSelectedProteinSequence?.seq &&
|
|
27
26
|
'Could not compute protein sequence (feature may be missing CDS subfeatures)',
|
|
28
27
|
!selectedTranscript && 'No transcript selected',
|
|
29
28
|
].filter((s) => typeof s === 'string');
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
+
import type { AbstractSessionModel, Feature, SessionWithAddTracks } from '@jbrowse/core/util';
|
|
2
|
+
import type { LinearGenomeViewModel } from '@jbrowse/plugin-linear-genome-view';
|
|
1
3
|
declare global {
|
|
2
4
|
interface Window {
|
|
3
5
|
JBrowsePluginMsaView?: unknown;
|
|
4
6
|
}
|
|
5
7
|
}
|
|
6
|
-
import type { AbstractSessionModel, Feature, SessionWithAddTracks } from '@jbrowse/core/util';
|
|
7
|
-
import type { LinearGenomeViewModel } from '@jbrowse/plugin-linear-genome-view';
|
|
8
8
|
export declare const ALPHAFOLD_VERSION = "v6";
|
|
9
9
|
export declare function getAlphaFoldStructureUrl(uniprotId: string, version?: string): string;
|
|
10
10
|
export declare function getAlphaFoldConfidenceUrl(uniprotId: string, version?: string): string;
|
|
@@ -41,5 +41,11 @@ export declare function launchMsaView({ session, view, feature, selectedTranscri
|
|
|
41
41
|
displayName?: string;
|
|
42
42
|
}): import("@jbrowse/core/util").AbstractViewModel | undefined;
|
|
43
43
|
export declare function hasMsaViewPlugin(): boolean;
|
|
44
|
+
export declare function getConditionalProteinLaunches({ session, view, feature, selectedTranscript, uniprotId, confidenceUrl, }: LaunchViewParams & {
|
|
45
|
+
confidenceUrl?: string;
|
|
46
|
+
}): {
|
|
47
|
+
launch1D: (() => Promise<void>) | undefined;
|
|
48
|
+
launchMsa: (() => import("@jbrowse/core/util").AbstractViewModel | undefined) | undefined;
|
|
49
|
+
};
|
|
44
50
|
export declare function launch3DProteinViewWithMsa(params: LaunchViewParams & Launch3DExtraParams): import("@jbrowse/core/util").AbstractViewModel | undefined;
|
|
45
51
|
export {};
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isSessionWithAddTracks } from '@jbrowse/core/util';
|
|
1
2
|
import { maybeLaunchSideBySide } from './sideBySide';
|
|
2
3
|
import { getGeneDisplayName, getTranscriptDisplayName } from './util';
|
|
3
4
|
import { launchProteinAnnotationView } from '../components/launchProteinAnnotationView';
|
|
@@ -120,6 +121,36 @@ export function launchMsaView({ session, view, feature, selectedTranscript, unip
|
|
|
120
121
|
export function hasMsaViewPlugin() {
|
|
121
122
|
return window.JBrowsePluginMsaView !== undefined;
|
|
122
123
|
}
|
|
124
|
+
// The 1D-annotation and MSA launches share identical availability rules across
|
|
125
|
+
// the AlphaFold and Foldseek launch menus: the 1D view needs an add-tracks
|
|
126
|
+
// session and a uniprotId, the MSA view needs the msaview plugin and a
|
|
127
|
+
// uniprotId. Returning each as a ready-to-run thunk (or undefined when
|
|
128
|
+
// unavailable) is the single source of truth — an unavailable action is
|
|
129
|
+
// unrepresentable rather than a menu item that silently no-ops.
|
|
130
|
+
export function getConditionalProteinLaunches({ session, view, feature, selectedTranscript, uniprotId, confidenceUrl, }) {
|
|
131
|
+
const addTracksSession = isSessionWithAddTracks(session) ? session : undefined;
|
|
132
|
+
return {
|
|
133
|
+
launch1D: addTracksSession && uniprotId
|
|
134
|
+
? () => launch1DProteinView({
|
|
135
|
+
session: addTracksSession,
|
|
136
|
+
view,
|
|
137
|
+
feature,
|
|
138
|
+
selectedTranscript,
|
|
139
|
+
uniprotId,
|
|
140
|
+
confidenceUrl,
|
|
141
|
+
})
|
|
142
|
+
: undefined,
|
|
143
|
+
launchMsa: uniprotId && hasMsaViewPlugin()
|
|
144
|
+
? () => launchMsaView({
|
|
145
|
+
session,
|
|
146
|
+
view,
|
|
147
|
+
feature,
|
|
148
|
+
selectedTranscript,
|
|
149
|
+
uniprotId,
|
|
150
|
+
})
|
|
151
|
+
: undefined,
|
|
152
|
+
};
|
|
153
|
+
}
|
|
123
154
|
export function launch3DProteinViewWithMsa(params) {
|
|
124
155
|
const { uniprotId } = params;
|
|
125
156
|
if (!uniprotId) {
|