jbrowse-plugin-protein3d 0.4.13 → 0.5.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.
- package/dist/LaunchProteinView/components/FoldseekActionMenu.js +15 -14
- package/dist/LaunchProteinView/components/ProteinViewActions.js +27 -15
- package/dist/LaunchProteinView/components/UserProvidedStructure.js +1 -2
- package/dist/LaunchProteinView/hooks/useSafeLaunch.d.ts +9 -0
- package/dist/LaunchProteinView/hooks/useSafeLaunch.js +15 -0
- package/dist/LaunchProteinView/utils/launchViewUtils.d.ts +9 -11
- package/dist/LaunchProteinView/utils/launchViewUtils.js +6 -8
- package/dist/LaunchProteinView/utils/sideBySide.d.ts +5 -0
- package/dist/LaunchProteinView/utils/sideBySide.js +9 -0
- package/dist/LaunchProteinViewExtensionPoint/index.js +7 -4
- package/dist/ProteinView/applyLociInteractivity.d.ts +23 -17
- package/dist/ProteinView/applyLociInteractivity.js +33 -61
- package/dist/ProteinView/components/FeatureBar.d.ts +1 -1
- package/dist/ProteinView/components/FeatureBar.js +36 -34
- package/dist/ProteinView/components/FeatureTypeLabel.js +5 -9
- package/dist/ProteinView/components/ProteinFeatureTrack.js +7 -15
- package/dist/ProteinView/components/ProteinViewHeader.js +9 -1
- package/dist/ProteinView/components/ResidueValueTrack.js +26 -15
- package/dist/ProteinView/hooks/useProteinFeatureTrackData.d.ts +1 -0
- package/dist/ProteinView/hooks/useProteinFeatureTrackData.js +3 -2
- package/dist/ProteinView/model.d.ts +12 -0
- package/dist/ProteinView/structureModel.d.ts +14 -5
- package/dist/ProteinView/structureModel.js +69 -92
- package/dist/ProteinView/subscribeMolstarInteraction.d.ts +8 -0
- package/dist/ProteinView/subscribeMolstarInteraction.js +1 -1
- package/dist/ProteinView/util.d.ts +0 -5
- package/dist/ProteinView/util.js +0 -11
- package/dist/jbrowse-plugin-protein3d.umd.production.min.js +15 -15
- package/dist/jbrowse-plugin-protein3d.umd.production.min.js.map +4 -4
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
- package/src/LaunchProteinView/components/FoldseekActionMenu.tsx +22 -17
- package/src/LaunchProteinView/components/ProteinViewActions.tsx +32 -17
- package/src/LaunchProteinView/components/UserProvidedStructure.tsx +1 -6
- package/src/LaunchProteinView/hooks/useSafeLaunch.ts +17 -0
- package/src/LaunchProteinView/utils/launchViewUtils.ts +30 -29
- package/src/LaunchProteinView/utils/sideBySide.ts +14 -0
- package/src/LaunchProteinViewExtensionPoint/index.ts +8 -9
- package/src/ProteinView/applyLociInteractivity.ts +62 -114
- package/src/ProteinView/components/FeatureBar.tsx +40 -44
- package/src/ProteinView/components/FeatureTypeLabel.tsx +6 -11
- package/src/ProteinView/components/ProteinFeatureTrack.tsx +5 -17
- package/src/ProteinView/components/ProteinViewHeader.tsx +15 -0
- package/src/ProteinView/components/ResidueValueTrack.tsx +40 -23
- package/src/ProteinView/hooks/useProteinFeatureTrackData.ts +6 -2
- package/src/ProteinView/structureModel.ts +90 -108
- package/src/ProteinView/subscribeMolstarInteraction.ts +9 -1
- package/src/ProteinView/util.ts +0 -25
- package/src/version.ts +1 -1
- package/dist/ProteinView/highlightResidueRange.d.ts +0 -14
- package/dist/ProteinView/highlightResidueRange.js +0 -19
- package/src/ProteinView/highlightResidueRange.ts +0 -44
package/dist/version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const version = "0.
|
|
1
|
+
export declare const version = "0.5.0";
|
package/dist/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const version = '0.
|
|
1
|
+
export const version = '0.5.0';
|
package/package.json
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import React, { useState } from 'react'
|
|
2
2
|
|
|
3
3
|
import { ErrorMessage } from '@jbrowse/core/ui'
|
|
4
|
+
import { isSessionWithAddTracks } from '@jbrowse/core/util'
|
|
4
5
|
import { Button, Menu, MenuItem } from '@mui/material'
|
|
5
6
|
|
|
7
|
+
import { useSafeLaunch } from '../hooks/useSafeLaunch'
|
|
6
8
|
import { caCoordsToPdb, hasValidCaCoords } from '../utils/caCoordsToPdb'
|
|
7
|
-
import { safeLaunch } from '../utils/launchHelpers'
|
|
8
9
|
import {
|
|
9
10
|
getConfidenceUrlFromTarget,
|
|
10
11
|
getUniprotIdFromAlphaFoldTarget,
|
|
@@ -41,7 +42,6 @@ export default function FoldseekActionMenu({
|
|
|
41
42
|
onClose: () => void
|
|
42
43
|
}) {
|
|
43
44
|
const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null)
|
|
44
|
-
const [launchError, setLaunchError] = useState<unknown>()
|
|
45
45
|
const open = Boolean(anchorEl)
|
|
46
46
|
|
|
47
47
|
const uniprotId = getUniprotIdFromAlphaFoldTarget(hit.target)
|
|
@@ -54,12 +54,9 @@ export default function FoldseekActionMenu({
|
|
|
54
54
|
setAnchorEl(null)
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
-
const
|
|
57
|
+
const { runLaunch, launchError } = useSafeLaunch(onClose, handleMenuClose)
|
|
58
58
|
|
|
59
|
-
const
|
|
60
|
-
handleMenuClose()
|
|
61
|
-
void safeLaunch(fn, onClose, setLaunchError)
|
|
62
|
-
}
|
|
59
|
+
const baseParams = { session, view, feature, selectedTranscript, uniprotId }
|
|
63
60
|
|
|
64
61
|
const handleLaunch3D = runLaunch(() => {
|
|
65
62
|
// Use tCa coordinates to generate PDB data if no URL is available
|
|
@@ -75,22 +72,19 @@ export default function FoldseekActionMenu({
|
|
|
75
72
|
})
|
|
76
73
|
})
|
|
77
74
|
|
|
78
|
-
const handleLaunch1D = runLaunch(async () => {
|
|
79
|
-
await launch1DProteinView({
|
|
80
|
-
...baseParams,
|
|
81
|
-
confidenceUrl: getConfidenceUrlFromTarget(hit.target),
|
|
82
|
-
})
|
|
83
|
-
})
|
|
84
|
-
|
|
85
75
|
const handleLaunchMSA = runLaunch(() => {
|
|
86
76
|
launchMsaView(baseParams)
|
|
87
77
|
})
|
|
88
78
|
|
|
89
|
-
const canLoad = hit.structureUrl
|
|
79
|
+
const canLoad = !!hit.structureUrl || hasValidCaCoords(hit.tCa, hit.tSeq)
|
|
90
80
|
if (!canLoad) {
|
|
91
81
|
return <span>-</span>
|
|
92
82
|
}
|
|
93
83
|
|
|
84
|
+
// 1D launch needs an add-tracks session and a uniprotId; narrowing both here
|
|
85
|
+
// gates the menu item and types its handler from a single condition.
|
|
86
|
+
const addTracksSession = isSessionWithAddTracks(session) ? session : undefined
|
|
87
|
+
|
|
94
88
|
return (
|
|
95
89
|
<>
|
|
96
90
|
{launchError ? <ErrorMessage error={launchError} /> : null}
|
|
@@ -99,8 +93,19 @@ export default function FoldseekActionMenu({
|
|
|
99
93
|
</Button>
|
|
100
94
|
<Menu anchorEl={anchorEl} open={open} onClose={handleMenuClose}>
|
|
101
95
|
<MenuItem onClick={handleLaunch3D}>Launch 3D protein view</MenuItem>
|
|
102
|
-
{uniprotId ? (
|
|
103
|
-
<MenuItem
|
|
96
|
+
{addTracksSession && uniprotId ? (
|
|
97
|
+
<MenuItem
|
|
98
|
+
onClick={runLaunch(() =>
|
|
99
|
+
launch1DProteinView({
|
|
100
|
+
session: addTracksSession,
|
|
101
|
+
view,
|
|
102
|
+
feature,
|
|
103
|
+
selectedTranscript,
|
|
104
|
+
uniprotId,
|
|
105
|
+
confidenceUrl: getConfidenceUrlFromTarget(hit.target),
|
|
106
|
+
}),
|
|
107
|
+
)}
|
|
108
|
+
>
|
|
104
109
|
Launch 1D protein annotation view
|
|
105
110
|
</MenuItem>
|
|
106
111
|
) : null}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import React, { useState } from 'react'
|
|
2
2
|
|
|
3
3
|
import { ErrorMessage } from '@jbrowse/core/ui'
|
|
4
|
+
import { isSessionWithAddTracks } from '@jbrowse/core/util'
|
|
4
5
|
import ArrowDropDownIcon from '@mui/icons-material/ArrowDropDown'
|
|
5
6
|
import SettingsIcon from '@mui/icons-material/Settings'
|
|
6
7
|
import { Button, ButtonGroup, IconButton, Tooltip, Typography } from '@mui/material'
|
|
@@ -8,7 +9,8 @@ import { Button, ButtonGroup, IconButton, Tooltip, Typography } from '@mui/mater
|
|
|
8
9
|
import LaunchOptionsDialog from './LaunchOptionsDialog'
|
|
9
10
|
import LaunchSettingsDialog from './LaunchSettingsDialog'
|
|
10
11
|
import SequenceMismatchNotice from './SequenceMismatchNotice'
|
|
11
|
-
import {
|
|
12
|
+
import { useSafeLaunch } from '../hooks/useSafeLaunch'
|
|
13
|
+
import { getLaunchMissingReasons } from '../utils/launchHelpers'
|
|
12
14
|
import {
|
|
13
15
|
hasMsaViewPlugin,
|
|
14
16
|
launch1DProteinView,
|
|
@@ -61,7 +63,6 @@ export default function ProteinViewActions({
|
|
|
61
63
|
}: ProteinViewActionsProps) {
|
|
62
64
|
const [dialogOpen, setDialogOpen] = useState(false)
|
|
63
65
|
const [settingsOpen, setSettingsOpen] = useState(false)
|
|
64
|
-
const [launchError, setLaunchError] = useState<unknown>()
|
|
65
66
|
// Disable launch while loading — SWR's keepPreviousData would otherwise let
|
|
66
67
|
// a user click Launch on stale results (wrong UniProt ID) during a refetch.
|
|
67
68
|
const canLaunch =
|
|
@@ -82,6 +83,8 @@ export default function ProteinViewActions({
|
|
|
82
83
|
setDialogOpen(false)
|
|
83
84
|
}
|
|
84
85
|
|
|
86
|
+
const { runLaunch, launchError } = useSafeLaunch(handleClose, closeMenu)
|
|
87
|
+
|
|
85
88
|
const baseParams = {
|
|
86
89
|
session,
|
|
87
90
|
view,
|
|
@@ -96,19 +99,10 @@ export default function ProteinViewActions({
|
|
|
96
99
|
alignmentAlgorithm,
|
|
97
100
|
}
|
|
98
101
|
|
|
99
|
-
const runLaunch = (fn: () => void | Promise<void>) => () => {
|
|
100
|
-
closeMenu()
|
|
101
|
-
void safeLaunch(fn, handleClose, setLaunchError)
|
|
102
|
-
}
|
|
103
|
-
|
|
104
102
|
const handleLaunch3DView = runLaunch(() => {
|
|
105
103
|
launch3DProteinView(launch3DParams)
|
|
106
104
|
})
|
|
107
105
|
|
|
108
|
-
const handleLaunch1DView = runLaunch(async () => {
|
|
109
|
-
await launch1DProteinView({ ...baseParams, confidenceUrl })
|
|
110
|
-
})
|
|
111
|
-
|
|
112
106
|
const handleLaunchMsa = runLaunch(() => {
|
|
113
107
|
launchMsaView(baseParams)
|
|
114
108
|
})
|
|
@@ -117,6 +111,13 @@ export default function ProteinViewActions({
|
|
|
117
111
|
launch3DProteinViewWithMsa(launch3DParams)
|
|
118
112
|
})
|
|
119
113
|
|
|
114
|
+
// The 1D annotation view needs an add-tracks session and a known uniprotId.
|
|
115
|
+
// Narrowing here is the single source of truth: the option only exists when
|
|
116
|
+
// both hold, and its handler is type-checked against those narrowed values —
|
|
117
|
+
// so a 1D launch that can't work is unrepresentable rather than a silent
|
|
118
|
+
// no-op.
|
|
119
|
+
const addTracksSession = isSessionWithAddTracks(session) ? session : undefined
|
|
120
|
+
|
|
120
121
|
const launchOptions = [
|
|
121
122
|
{
|
|
122
123
|
key: '3d',
|
|
@@ -125,12 +126,26 @@ export default function ProteinViewActions({
|
|
|
125
126
|
'View protein structure with genome-to-structure coordinate mapping',
|
|
126
127
|
onClick: handleLaunch3DView,
|
|
127
128
|
},
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
129
|
+
...(addTracksSession && uniprotId
|
|
130
|
+
? [
|
|
131
|
+
{
|
|
132
|
+
key: '1d',
|
|
133
|
+
title: 'Launch 1D protein annotation view',
|
|
134
|
+
description:
|
|
135
|
+
'View protein features and annotations as a linear track',
|
|
136
|
+
onClick: runLaunch(() =>
|
|
137
|
+
launch1DProteinView({
|
|
138
|
+
session: addTracksSession,
|
|
139
|
+
view,
|
|
140
|
+
feature,
|
|
141
|
+
selectedTranscript,
|
|
142
|
+
uniprotId,
|
|
143
|
+
confidenceUrl,
|
|
144
|
+
}),
|
|
145
|
+
),
|
|
146
|
+
},
|
|
147
|
+
]
|
|
148
|
+
: []),
|
|
134
149
|
...(hasMsaViewPlugin()
|
|
135
150
|
? [
|
|
136
151
|
{
|
|
@@ -13,11 +13,7 @@ import ExternalLink from '../../components/ExternalLink'
|
|
|
13
13
|
import useStructureFileSequence from '../hooks/useStructureFileSequence'
|
|
14
14
|
import useTranscriptIsoformSelection from '../hooks/useTranscriptIsoformSelection'
|
|
15
15
|
import { launch3DProteinView } from '../utils/launchViewUtils'
|
|
16
|
-
import {
|
|
17
|
-
getGeneDisplayName,
|
|
18
|
-
getTranscriptDisplayName,
|
|
19
|
-
stripStopCodon,
|
|
20
|
-
} from '../utils/util'
|
|
16
|
+
import { stripStopCodon } from '../utils/util'
|
|
21
17
|
|
|
22
18
|
import type { AlignmentAlgorithm } from '../../ProteinView/types'
|
|
23
19
|
import type { AbstractSessionModel, Feature } from '@jbrowse/core/util'
|
|
@@ -117,7 +113,6 @@ const UserProvidedStructure = observer(function UserProvidedStructure({
|
|
|
117
113
|
data: structureData,
|
|
118
114
|
userProvidedTranscriptSequence: protein.seq,
|
|
119
115
|
alignmentAlgorithm,
|
|
120
|
-
displayName: `Protein view ${getGeneDisplayName(feature)} - ${getTranscriptDisplayName(selectedTranscript)}`,
|
|
121
116
|
})
|
|
122
117
|
handleClose()
|
|
123
118
|
} catch (e) {
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { useState } from 'react'
|
|
2
|
+
|
|
3
|
+
import { safeLaunch } from '../utils/launchHelpers'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Shared launch-button wiring for the action components: holds the launch
|
|
7
|
+
* error state and returns a `runLaunch` factory that closes any open menu,
|
|
8
|
+
* runs the launch via safeLaunch, and surfaces failures inline.
|
|
9
|
+
*/
|
|
10
|
+
export function useSafeLaunch(onSuccess: () => void, onBeforeLaunch?: () => void) {
|
|
11
|
+
const [launchError, setLaunchError] = useState<unknown>()
|
|
12
|
+
const runLaunch = (fn: () => void | Promise<void>) => () => {
|
|
13
|
+
onBeforeLaunch?.()
|
|
14
|
+
void safeLaunch(fn, onSuccess, setLaunchError)
|
|
15
|
+
}
|
|
16
|
+
return { runLaunch, launchError }
|
|
17
|
+
}
|
|
@@ -1,16 +1,18 @@
|
|
|
1
|
-
import { isSessionWithAddTracks } from '@jbrowse/core/util'
|
|
2
|
-
|
|
3
1
|
declare global {
|
|
4
2
|
interface Window {
|
|
5
3
|
JBrowsePluginMsaView?: unknown
|
|
6
4
|
}
|
|
7
5
|
}
|
|
8
6
|
|
|
9
|
-
import {
|
|
7
|
+
import { maybeLaunchSideBySide } from './sideBySide'
|
|
10
8
|
import { getGeneDisplayName, getTranscriptDisplayName } from './util'
|
|
11
9
|
import { launchProteinAnnotationView } from '../components/launchProteinAnnotationView'
|
|
12
10
|
|
|
13
|
-
import type {
|
|
11
|
+
import type {
|
|
12
|
+
AbstractSessionModel,
|
|
13
|
+
Feature,
|
|
14
|
+
SessionWithAddTracks,
|
|
15
|
+
} from '@jbrowse/core/util'
|
|
14
16
|
import type { LinearGenomeViewModel } from '@jbrowse/plugin-linear-genome-view'
|
|
15
17
|
|
|
16
18
|
export const ALPHAFOLD_VERSION = 'v6'
|
|
@@ -84,6 +86,14 @@ interface LaunchViewParams {
|
|
|
84
86
|
uniprotId?: string
|
|
85
87
|
}
|
|
86
88
|
|
|
89
|
+
interface Launch3DExtraParams {
|
|
90
|
+
url?: string
|
|
91
|
+
data?: string
|
|
92
|
+
userProvidedTranscriptSequence?: string
|
|
93
|
+
alignmentAlgorithm?: string
|
|
94
|
+
displayName?: string
|
|
95
|
+
}
|
|
96
|
+
|
|
87
97
|
export function formatViewName(
|
|
88
98
|
prefix: string,
|
|
89
99
|
feature: Feature,
|
|
@@ -115,17 +125,13 @@ export function launch3DProteinView({
|
|
|
115
125
|
displayName,
|
|
116
126
|
connectedMsaViewId,
|
|
117
127
|
sideBySide,
|
|
118
|
-
}: LaunchViewParams &
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
// explicit override; when undefined the launch-dialog localStorage preference
|
|
126
|
-
// decides (left genome | right protein)
|
|
127
|
-
sideBySide?: boolean
|
|
128
|
-
}) {
|
|
128
|
+
}: LaunchViewParams &
|
|
129
|
+
Launch3DExtraParams & {
|
|
130
|
+
connectedMsaViewId?: string
|
|
131
|
+
// explicit override; when undefined the launch-dialog localStorage
|
|
132
|
+
// preference decides (left genome | right protein)
|
|
133
|
+
sideBySide?: boolean
|
|
134
|
+
}) {
|
|
129
135
|
const snap = {
|
|
130
136
|
type: 'ProteinView',
|
|
131
137
|
alignmentAlgorithm,
|
|
@@ -144,12 +150,14 @@ export function launch3DProteinView({
|
|
|
144
150
|
formatViewName('Protein view', feature, selectedTranscript, uniprotId),
|
|
145
151
|
}
|
|
146
152
|
const proteinView = session.addView('ProteinView', snap)
|
|
147
|
-
|
|
148
|
-
launchViewSideBySide(session, proteinView.id)
|
|
149
|
-
}
|
|
153
|
+
maybeLaunchSideBySide(session, proteinView.id, sideBySide)
|
|
150
154
|
return proteinView
|
|
151
155
|
}
|
|
152
156
|
|
|
157
|
+
// The 1D annotation view adds temporary tracks/assemblies, so it requires a
|
|
158
|
+
// SessionWithAddTracks and a known uniprotId. Demanding both in the signature
|
|
159
|
+
// forces callers to narrow up front — there's no silent no-op when a wide
|
|
160
|
+
// session or missing id slips through.
|
|
153
161
|
export async function launch1DProteinView({
|
|
154
162
|
session,
|
|
155
163
|
view,
|
|
@@ -157,12 +165,11 @@ export async function launch1DProteinView({
|
|
|
157
165
|
selectedTranscript,
|
|
158
166
|
uniprotId,
|
|
159
167
|
confidenceUrl,
|
|
160
|
-
}: LaunchViewParams & {
|
|
168
|
+
}: Omit<LaunchViewParams, 'session' | 'uniprotId'> & {
|
|
169
|
+
session: SessionWithAddTracks
|
|
170
|
+
uniprotId: string
|
|
161
171
|
confidenceUrl?: string
|
|
162
172
|
}) {
|
|
163
|
-
if (!uniprotId || !isSessionWithAddTracks(session)) {
|
|
164
|
-
return
|
|
165
|
-
}
|
|
166
173
|
await launchProteinAnnotationView({
|
|
167
174
|
session,
|
|
168
175
|
selectedTranscript,
|
|
@@ -209,13 +216,7 @@ export function hasMsaViewPlugin() {
|
|
|
209
216
|
}
|
|
210
217
|
|
|
211
218
|
export function launch3DProteinViewWithMsa(
|
|
212
|
-
params: LaunchViewParams &
|
|
213
|
-
url?: string
|
|
214
|
-
data?: string
|
|
215
|
-
userProvidedTranscriptSequence?: string
|
|
216
|
-
alignmentAlgorithm?: string
|
|
217
|
-
displayName?: string
|
|
218
|
-
},
|
|
219
|
+
params: LaunchViewParams & Launch3DExtraParams,
|
|
219
220
|
) {
|
|
220
221
|
const { uniprotId } = params
|
|
221
222
|
if (!uniprotId) {
|
|
@@ -53,3 +53,17 @@ export function launchViewSideBySide(
|
|
|
53
53
|
session.setUseWorkspaces(true)
|
|
54
54
|
}
|
|
55
55
|
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Apply the side-by-side split honoring an explicit override, falling back to
|
|
59
|
+
* the launch-dialog localStorage preference when undefined.
|
|
60
|
+
*/
|
|
61
|
+
export function maybeLaunchSideBySide(
|
|
62
|
+
session: AbstractSessionModel,
|
|
63
|
+
viewId: string,
|
|
64
|
+
sideBySide?: boolean,
|
|
65
|
+
) {
|
|
66
|
+
if (sideBySide ?? getLaunchSideBySide()) {
|
|
67
|
+
launchViewSideBySide(session, viewId)
|
|
68
|
+
}
|
|
69
|
+
}
|
|
@@ -1,8 +1,5 @@
|
|
|
1
1
|
import { type ConnectedViewSpec, resolveShortLaunch } from './resolveShortLaunch'
|
|
2
|
-
import {
|
|
3
|
-
getLaunchSideBySide,
|
|
4
|
-
launchViewSideBySide,
|
|
5
|
-
} from '../LaunchProteinView/utils/sideBySide'
|
|
2
|
+
import { maybeLaunchSideBySide } from '../LaunchProteinView/utils/sideBySide'
|
|
6
3
|
|
|
7
4
|
import type PluginManager from '@jbrowse/core/PluginManager'
|
|
8
5
|
import type { AbstractSessionModel } from '@jbrowse/core/util'
|
|
@@ -76,9 +73,11 @@ export default function LaunchProteinViewExtensionPointF(
|
|
|
76
73
|
|
|
77
74
|
const finalUrl = url ?? resolved?.url
|
|
78
75
|
if (!finalUrl) {
|
|
79
|
-
|
|
80
|
-
'No url or uniprotId provided when launching protein view'
|
|
81
|
-
)
|
|
76
|
+
const message =
|
|
77
|
+
'No url or uniprotId provided when launching protein view'
|
|
78
|
+
console.error(message)
|
|
79
|
+
session.notify(`Could not launch protein view: ${message}`, 'error')
|
|
80
|
+
return
|
|
82
81
|
}
|
|
83
82
|
|
|
84
83
|
// A session spec launches each view independently with an auto-generated
|
|
@@ -119,8 +118,8 @@ export default function LaunchProteinViewExtensionPointF(
|
|
|
119
118
|
],
|
|
120
119
|
})
|
|
121
120
|
|
|
122
|
-
if (ownsConnectedView
|
|
123
|
-
|
|
121
|
+
if (ownsConnectedView) {
|
|
122
|
+
maybeLaunchSideBySide(session, proteinView.id, sideBySide)
|
|
124
123
|
}
|
|
125
124
|
},
|
|
126
125
|
)
|
|
@@ -1,133 +1,81 @@
|
|
|
1
1
|
import loadMolstar from './loadMolstar'
|
|
2
|
-
import { getMolstarStructureSelection } from './util'
|
|
3
2
|
|
|
4
|
-
import type {
|
|
5
|
-
Structure,
|
|
6
|
-
StructureElement,
|
|
7
|
-
} from 'molstar/lib/mol-model/structure'
|
|
3
|
+
import type { Structure } from 'molstar/lib/mol-model/structure'
|
|
8
4
|
import type { PluginContext } from 'molstar/lib/mol-plugin/context'
|
|
5
|
+
import type { MolScriptBuilder } from 'molstar/lib/mol-script/language/builder'
|
|
6
|
+
import type { Expression } from 'molstar/lib/mol-script/language/expression'
|
|
9
7
|
|
|
10
|
-
type
|
|
8
|
+
type ResidueTest = (Q: typeof MolScriptBuilder) => Expression
|
|
11
9
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
10
|
+
/**
|
|
11
|
+
* Which residues a highlight/selection should cover, in the plugin's native
|
|
12
|
+
* 0-based structure-sequence coordinates (see coordinates.ts). `range` is the
|
|
13
|
+
* half-open span [start, end); `list` is an explicit set of positions. The one
|
|
14
|
+
* conversion to molstar's 1-based inclusive label_seq_id happens in specToTest
|
|
15
|
+
* below — the single boundary where structure positions cross into molstar.
|
|
16
|
+
*/
|
|
17
|
+
export type ResidueSpec =
|
|
18
|
+
| { kind: 'range'; start: number; end: number }
|
|
19
|
+
| { kind: 'list'; residues: number[] }
|
|
16
20
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
loci: StructureElement.Loci,
|
|
20
|
-
mode: 'highlight' | 'select',
|
|
21
|
-
) {
|
|
22
|
-
if (mode === 'highlight') {
|
|
23
|
-
plugin.managers.interactivity.lociHighlights.clearHighlights()
|
|
24
|
-
plugin.managers.interactivity.lociHighlights.highlight({ loci })
|
|
25
|
-
} else {
|
|
26
|
-
plugin.managers.interactivity.lociSelects.deselectAll()
|
|
27
|
-
plugin.managers.interactivity.lociSelects.select({ loci })
|
|
28
|
-
}
|
|
29
|
-
}
|
|
21
|
+
const seqId = (Q: typeof MolScriptBuilder) =>
|
|
22
|
+
Q.struct.atomProperty.macromolecular.label_seq_id()
|
|
30
23
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
if (mode === 'clear' || residues.length === 0) {
|
|
43
|
-
clearLoci(plugin)
|
|
44
|
-
return
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
const { StructureSelection, Script } = await loadMolstar()
|
|
24
|
+
const specToTest = (spec: ResidueSpec): ResidueTest =>
|
|
25
|
+
spec.kind === 'range'
|
|
26
|
+
? Q =>
|
|
27
|
+
Q.core.logic.and([
|
|
28
|
+
Q.core.rel.gre([seqId(Q), spec.start + 1]),
|
|
29
|
+
Q.core.rel.lte([seqId(Q), spec.end]),
|
|
30
|
+
])
|
|
31
|
+
: Q =>
|
|
32
|
+
Q.core.logic.or(
|
|
33
|
+
spec.residues.map(pos => Q.core.rel.eq([seqId(Q), pos + 1])),
|
|
34
|
+
)
|
|
48
35
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
'residue-test': Q.core.logic.or(
|
|
53
|
-
residues.map(residue =>
|
|
54
|
-
Q.core.rel.eq([
|
|
55
|
-
Q.struct.atomProperty.macromolecular.label_seq_id(),
|
|
56
|
-
residue,
|
|
57
|
-
]),
|
|
58
|
-
),
|
|
59
|
-
),
|
|
60
|
-
'group-by': Q.struct.atomProperty.macromolecular.residueKey(),
|
|
61
|
-
}),
|
|
62
|
-
structure,
|
|
63
|
-
)
|
|
36
|
+
const isActive = (spec: ResidueSpec | undefined): spec is ResidueSpec =>
|
|
37
|
+
spec !== undefined &&
|
|
38
|
+
(spec.kind === 'range' ? spec.end > spec.start : spec.residues.length > 0)
|
|
64
39
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
40
|
+
/**
|
|
41
|
+
* Reconcile one interactivity channel (hover-`highlight` or click-`select`) to
|
|
42
|
+
* the desired residue spec. Passing `undefined` (or an empty `list`) clears the
|
|
43
|
+
* channel, so callers describe the target state declaratively rather than
|
|
44
|
+
* juggling clear/apply calls.
|
|
45
|
+
*/
|
|
46
|
+
export async function setMolstarLoci({
|
|
70
47
|
structure,
|
|
71
|
-
startResidue,
|
|
72
|
-
endResidue,
|
|
73
48
|
plugin,
|
|
74
|
-
|
|
49
|
+
channel,
|
|
50
|
+
spec,
|
|
75
51
|
}: {
|
|
76
52
|
structure: Structure
|
|
77
|
-
startResidue: number
|
|
78
|
-
endResidue: number
|
|
79
53
|
plugin: PluginContext
|
|
80
|
-
|
|
54
|
+
channel: 'highlight' | 'select'
|
|
55
|
+
spec: ResidueSpec | undefined
|
|
81
56
|
}) {
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
57
|
+
const { lociHighlights, lociSelects } = plugin.managers.interactivity
|
|
58
|
+
if (channel === 'highlight') {
|
|
59
|
+
lociHighlights.clearHighlights()
|
|
60
|
+
} else {
|
|
61
|
+
lociSelects.deselectAll()
|
|
85
62
|
}
|
|
86
63
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
Q
|
|
91
|
-
|
|
92
|
-
Q
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
structure,
|
|
104
|
-
)
|
|
105
|
-
|
|
106
|
-
const loci = StructureSelection.toLociWithSourceUnits(sel)
|
|
107
|
-
applyLoci(plugin, loci, mode)
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
export async function applyLociInteractivitySingle({
|
|
111
|
-
structure,
|
|
112
|
-
selectedResidue,
|
|
113
|
-
plugin,
|
|
114
|
-
mode,
|
|
115
|
-
}: {
|
|
116
|
-
structure: Structure
|
|
117
|
-
selectedResidue: number
|
|
118
|
-
plugin: PluginContext
|
|
119
|
-
mode: InteractivityMode
|
|
120
|
-
}) {
|
|
121
|
-
if (mode === 'clear') {
|
|
122
|
-
clearLoci(plugin)
|
|
123
|
-
return
|
|
64
|
+
if (isActive(spec)) {
|
|
65
|
+
const { StructureSelection, Script } = await loadMolstar()
|
|
66
|
+
const sel = Script.getStructureSelection(
|
|
67
|
+
Q =>
|
|
68
|
+
Q.struct.generator.atomGroups({
|
|
69
|
+
'residue-test': specToTest(spec)(Q),
|
|
70
|
+
'group-by': Q.struct.atomProperty.macromolecular.residueKey(),
|
|
71
|
+
}),
|
|
72
|
+
structure,
|
|
73
|
+
)
|
|
74
|
+
const loci = StructureSelection.toLociWithSourceUnits(sel)
|
|
75
|
+
if (channel === 'highlight') {
|
|
76
|
+
lociHighlights.highlight({ loci })
|
|
77
|
+
} else {
|
|
78
|
+
lociSelects.select({ loci })
|
|
79
|
+
}
|
|
124
80
|
}
|
|
125
|
-
|
|
126
|
-
const { StructureSelection } = await loadMolstar()
|
|
127
|
-
const sel = await getMolstarStructureSelection({
|
|
128
|
-
structure,
|
|
129
|
-
selectedResidue: selectedResidue + 1,
|
|
130
|
-
})
|
|
131
|
-
const loci = StructureSelection.toLociWithSourceUnits(sel)
|
|
132
|
-
applyLoci(plugin, loci, mode)
|
|
133
81
|
}
|