jbrowse-plugin-protein3d 0.0.4 → 0.0.5

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 (26) hide show
  1. package/dist/AddHighlightModel/HighlightComponents.d.ts +1 -2
  2. package/dist/AddHighlightModel/HighlightComponents.js.map +1 -1
  3. package/dist/LaunchProteinView/components/AlphaFoldDBSearch.js +7 -8
  4. package/dist/LaunchProteinView/components/AlphaFoldDBSearch.js.map +1 -1
  5. package/dist/LaunchProteinView/components/LaunchProteinViewDialog.js +8 -4
  6. package/dist/LaunchProteinView/components/LaunchProteinViewDialog.js.map +1 -1
  7. package/dist/LaunchProteinView/components/ManualUniProtIDEntry.d.ts +8 -0
  8. package/dist/LaunchProteinView/components/ManualUniProtIDEntry.js +225 -0
  9. package/dist/LaunchProteinView/components/ManualUniProtIDEntry.js.map +1 -0
  10. package/dist/LaunchProteinView/components/UserProvidedStructure.js +1 -1
  11. package/dist/LaunchProteinView/components/UserProvidedStructure.js.map +1 -1
  12. package/dist/ProteinView/components/ProteinView.js +1 -1
  13. package/dist/ProteinView/components/ProteinView.js.map +1 -1
  14. package/dist/ProteinView/model.d.ts +26 -30
  15. package/dist/ProteinView/model.js +1 -0
  16. package/dist/ProteinView/model.js.map +1 -1
  17. package/dist/jbrowse-plugin-protein3d.umd.production.min.js +1181 -318
  18. package/dist/jbrowse-plugin-protein3d.umd.production.min.js.map +4 -4
  19. package/package.json +6 -6
  20. package/src/AddHighlightModel/HighlightComponents.tsx +1 -3
  21. package/src/LaunchProteinView/components/AlphaFoldDBSearch.tsx +8 -9
  22. package/src/LaunchProteinView/components/LaunchProteinViewDialog.tsx +12 -3
  23. package/src/LaunchProteinView/components/ManualUniProtIDEntry.tsx +332 -0
  24. package/src/LaunchProteinView/components/UserProvidedStructure.tsx +1 -1
  25. package/src/ProteinView/components/ProteinView.tsx +1 -1
  26. package/src/ProteinView/model.ts +1 -0
package/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "0.0.4",
2
+ "version": "0.0.5",
3
3
  "name": "jbrowse-plugin-protein3d",
4
4
  "keywords": [
5
5
  "jbrowse",
@@ -22,7 +22,7 @@
22
22
  "postversion": "git push --follow-tags"
23
23
  },
24
24
  "dependencies": {
25
- "@mui/icons-material": "^5.15.10",
25
+ "@mui/icons-material": "^6.1.5",
26
26
  "clustal-js": "^2.0.1",
27
27
  "g2p_mapper": "^1.0.3",
28
28
  "molstar": "^4.0.0",
@@ -34,22 +34,22 @@
34
34
  "@fal-works/esbuild-plugin-global-externals": "^2.1.2",
35
35
  "@jbrowse/core": "^2.0.0",
36
36
  "@jbrowse/plugin-linear-genome-view": "^2.4.2",
37
- "@mui/material": "^5.12.0",
38
- "@mui/system": "^5.12.0",
37
+ "@mui/material": "^6.1.5",
38
+ "@mui/system": "^6.1.5",
39
39
  "@mui/x-data-grid": "^7.3.0",
40
40
  "@types/node": "^22.5.0",
41
41
  "@types/pako": "^2.0.0",
42
42
  "@types/react": "^18.2.54",
43
43
  "@typescript-eslint/eslint-plugin": "^8.1.0",
44
44
  "@typescript-eslint/parser": "^8.1.0",
45
- "esbuild": "^0.23.0",
45
+ "esbuild": "^0.24.0",
46
46
  "eslint": "^9.0.0",
47
47
  "eslint-config-prettier": "^9.0.0",
48
48
  "eslint-plugin-prettier": "^5.0.0",
49
49
  "eslint-plugin-react": "^7.20.3",
50
50
  "eslint-plugin-react-hooks": "^5.1.0-rc-eb3ad065-20240822",
51
51
  "eslint-plugin-react-refresh": "^0.4.11",
52
- "eslint-plugin-unicorn": "^55.0.0",
52
+ "eslint-plugin-unicorn": "^56.0.0",
53
53
  "fp-ts": "^2.16.9",
54
54
  "mobx": "^6.0.0",
55
55
  "mobx-react": "^9.0.1",
@@ -7,12 +7,10 @@ import ProteinToGenomeClickHighlight from './ProteinToGenomeClickHighlight'
7
7
  import ProteinToGenomeHoverHighlight from './ProteinToGenomeHoverHighlight'
8
8
  import GenomeMouseoverHighlight from './GenomeMouseoverHighlight'
9
9
 
10
- type LGV = LinearGenomeViewModel
11
-
12
10
  const HighlightComponents = observer(function Highlight({
13
11
  model,
14
12
  }: {
15
- model: LGV
13
+ model: LinearGenomeViewModel
16
14
  }) {
17
15
  return (
18
16
  <>
@@ -104,8 +104,8 @@ const AlphaFoldDBSearch = observer(function ({
104
104
  <DialogContent className={classes.dialogContent}>
105
105
  {e ? <ErrorMessage error={e} /> : null}
106
106
  <Typography>
107
- Automatically find AlphaFoldDB entry for given transcript{' '}
108
- <HelpButton />
107
+ Automatically find AlphaFoldDB entry for given transcript via gene ID
108
+ lookup <HelpButton />
109
109
  </Typography>
110
110
  {isRemoteStructureSequenceLoading ? (
111
111
  <LoadingEllipses
@@ -256,7 +256,7 @@ const AlphaFoldDBSearch = observer(function ({
256
256
  assemblyNames: [uniprotId],
257
257
  displays: [
258
258
  {
259
- displayId: `${type}-LinearBasicDisplay`,
259
+ displayId: `${s}-LinearBasicDisplay`,
260
260
  type: 'LinearBasicDisplay',
261
261
  jexlFilters: [`get(feature,'type')=='${type}'`],
262
262
  },
@@ -265,7 +265,7 @@ const AlphaFoldDBSearch = observer(function ({
265
265
  })
266
266
  session.addTrackConf({
267
267
  type: 'FeatureTrack',
268
- trackId: 'Antigen',
268
+ trackId: `${uniprotId}-Antigen`,
269
269
  name: 'Antigen',
270
270
  adapter: {
271
271
  type: 'Gff3Adapter',
@@ -277,7 +277,7 @@ const AlphaFoldDBSearch = observer(function ({
277
277
  })
278
278
  session.addTrackConf({
279
279
  type: 'FeatureTrack',
280
- trackId: 'Variation',
280
+ trackId: `${uniprotId}-Variation`,
281
281
  name: 'Variation',
282
282
  adapter: {
283
283
  type: 'UniProtVariationAdapter',
@@ -289,7 +289,7 @@ const AlphaFoldDBSearch = observer(function ({
289
289
  })
290
290
  session.addTrackConf({
291
291
  type: 'QuantitativeTrack',
292
- trackId: 'AlphaFold confidence',
292
+ trackId: `${uniprotId}-AlphaFold-confidence`,
293
293
  name: 'AlphaFold confidence',
294
294
  adapter: {
295
295
  type: 'AlphaFoldConfidenceAdapter',
@@ -301,7 +301,7 @@ const AlphaFoldDBSearch = observer(function ({
301
301
  })
302
302
  session.addTrackConf({
303
303
  type: 'MultiQuantitativeTrack',
304
- trackId: 'AlphaMissense scores',
304
+ trackId: `${uniprotId}-AlphaMissense-scores`,
305
305
  name: 'AlphaMissense scores',
306
306
  assemblyNames: [uniprotId],
307
307
  adapter: {
@@ -313,8 +313,7 @@ const AlphaFoldDBSearch = observer(function ({
313
313
  displays: [
314
314
  {
315
315
  type: 'MultiLinearWiggleDisplay',
316
- displayId:
317
- 'AlphaMissense scores-MultiLinearWiggleDisplay',
316
+ displayId: `${uniprotId}-AlphaMissense-scores-MultiLinearWiggleDisplay`,
318
317
  defaultRendering: 'multirowdensity',
319
318
  renderers: {
320
319
  MultiDensityRenderer: {
@@ -6,6 +6,7 @@ import { AbstractTrackModel, Feature } from '@jbrowse/core/util'
6
6
  // locals
7
7
  import AlphaFoldDBSearch from './AlphaFoldDBSearch'
8
8
  import UserProvidedStructure from './UserProvidedStructure'
9
+ import ManualUniProtIDEntry from './ManualUniProtIDEntry'
9
10
  import TabPanel from './TabPanel'
10
11
 
11
12
  export default function LaunchProteinViewDialog({
@@ -22,10 +23,10 @@ export default function LaunchProteinViewDialog({
22
23
  <Dialog
23
24
  maxWidth="xl"
24
25
  title="Launch protein view"
26
+ open
25
27
  onClose={() => {
26
28
  handleClose()
27
29
  }}
28
- open
29
30
  >
30
31
  <Tabs
31
32
  value={choice}
@@ -33,8 +34,9 @@ export default function LaunchProteinViewDialog({
33
34
  setChoice(val)
34
35
  }}
35
36
  >
36
- <Tab value={0} label="Automatic lookup" />
37
- <Tab value={1} label="Manual" />
37
+ <Tab value={0} label="Automatic UniProt lookup" />
38
+ <Tab value={1} label="Manual UniProt entry" />
39
+ <Tab value={2} label="Open file manually" />
38
40
  </Tabs>
39
41
  <TabPanel value={choice} index={0}>
40
42
  <AlphaFoldDBSearch
@@ -44,6 +46,13 @@ export default function LaunchProteinViewDialog({
44
46
  />
45
47
  </TabPanel>
46
48
  <TabPanel value={choice} index={1}>
49
+ <ManualUniProtIDEntry
50
+ model={model}
51
+ feature={feature}
52
+ handleClose={handleClose}
53
+ />
54
+ </TabPanel>
55
+ <TabPanel value={choice} index={2}>
47
56
  <UserProvidedStructure
48
57
  model={model}
49
58
  feature={feature}
@@ -0,0 +1,332 @@
1
+ import React, { useEffect, useState } from 'react'
2
+ import { observer } from 'mobx-react'
3
+ import {
4
+ Button,
5
+ DialogActions,
6
+ DialogContent,
7
+ TextField,
8
+ Typography,
9
+ } from '@mui/material'
10
+ import { makeStyles } from 'tss-react/mui'
11
+ import {
12
+ AbstractTrackModel,
13
+ Feature,
14
+ getContainingView,
15
+ getSession,
16
+ isSessionWithAddTracks,
17
+ } from '@jbrowse/core/util'
18
+ import { ErrorMessage, LoadingEllipses } from '@jbrowse/core/ui'
19
+ import { LinearGenomeViewModel } from '@jbrowse/plugin-linear-genome-view'
20
+
21
+ // locals
22
+ import {
23
+ getId,
24
+ getGeneDisplayName,
25
+ getTranscriptDisplayName,
26
+ getTranscriptFeatures,
27
+ } from './util'
28
+
29
+ // components
30
+ import TranscriptSelector from './TranscriptSelector'
31
+ import AlphaFoldDBSearchStatus from './AlphaFoldDBSearchStatus'
32
+
33
+ // hooks
34
+ import useRemoteStructureFileSequence from './useRemoteStructureFileSequence'
35
+ import useIsoformProteinSequences from './useIsoformProteinSequences'
36
+
37
+ const useStyles = makeStyles()(theme => ({
38
+ dialogContent: {
39
+ marginTop: theme.spacing(6),
40
+ width: '80em',
41
+ },
42
+ }))
43
+
44
+ const ManualUniProtIDEntry = observer(function ({
45
+ feature,
46
+ model,
47
+ handleClose,
48
+ }: {
49
+ feature: Feature
50
+ model: AbstractTrackModel
51
+ handleClose: () => void
52
+ }) {
53
+ const { classes } = useStyles()
54
+ const session = getSession(model)
55
+
56
+ // check if we are looking at a 'two-level' or 'three-level' feature by
57
+ // finding exon/CDS subfeatures. we want to select from transcript names
58
+ const options = getTranscriptFeatures(feature)
59
+ const [userSelection, setUserSelection] = useState<string>()
60
+ const view = getContainingView(model) as LinearGenomeViewModel
61
+ const selectedTranscript = options.find(val => getId(val) === userSelection)
62
+ const {
63
+ isoformSequences,
64
+ isLoading: isIsoformProteinSequencesLoading,
65
+ error: isoformProteinSequencesError,
66
+ } = useIsoformProteinSequences({
67
+ feature,
68
+ view,
69
+ })
70
+ const userSelectedProteinSequence = isoformSequences?.[userSelection ?? '']
71
+ const [uniprotId, setUniprotId] = useState('')
72
+ const url = uniprotId
73
+ ? `https://alphafold.ebi.ac.uk/files/AF-${uniprotId}-F1-model_v4.cif`
74
+ : undefined
75
+ const {
76
+ sequences: structureSequences,
77
+ isLoading: isRemoteStructureSequenceLoading,
78
+ error: remoteStructureSequenceError,
79
+ } = useRemoteStructureFileSequence({ url })
80
+ const e = isoformProteinSequencesError || remoteStructureSequenceError
81
+
82
+ const structureSequence = structureSequences?.[0]
83
+ useEffect(() => {
84
+ if (isoformSequences !== undefined) {
85
+ const ret =
86
+ options.find(
87
+ f =>
88
+ isoformSequences[f.id()]?.seq.replaceAll('*', '') ==
89
+ structureSequence,
90
+ ) ?? options.find(f => !!isoformSequences[f.id()])
91
+ setUserSelection(ret?.id())
92
+ }
93
+ }, [options, structureSequence, isoformSequences])
94
+
95
+ return (
96
+ <>
97
+ <DialogContent className={classes.dialogContent}>
98
+ {e ? <ErrorMessage error={e} /> : null}
99
+ <Typography>Manually enter a UniProt ID</Typography>
100
+ {isRemoteStructureSequenceLoading ? (
101
+ <LoadingEllipses
102
+ variant="h6"
103
+ message="Loading sequence from remote structure file"
104
+ />
105
+ ) : null}
106
+
107
+ <TextField
108
+ label="UniProt ID"
109
+ value={uniprotId}
110
+ onChange={event => {
111
+ setUniprotId(event.target.value)
112
+ }}
113
+ />
114
+ {isIsoformProteinSequencesLoading ? (
115
+ <LoadingEllipses
116
+ variant="h6"
117
+ message="Loading protein sequences from transcript isoforms"
118
+ />
119
+ ) : null}
120
+ {isoformSequences && structureSequence && selectedTranscript ? (
121
+ <>
122
+ <TranscriptSelector
123
+ val={userSelection ?? ''}
124
+ setVal={setUserSelection}
125
+ structureSequence={structureSequence}
126
+ feature={feature}
127
+ isoforms={options}
128
+ isoformSequences={isoformSequences}
129
+ />
130
+ <AlphaFoldDBSearchStatus
131
+ uniprotId={uniprotId}
132
+ selectedTranscript={selectedTranscript}
133
+ structureSequence={structureSequence}
134
+ isoformSequences={isoformSequences}
135
+ />
136
+ </>
137
+ ) : null}
138
+ </DialogContent>
139
+ <DialogActions>
140
+ <Button
141
+ variant="contained"
142
+ color="secondary"
143
+ onClick={() => {
144
+ handleClose()
145
+ }}
146
+ >
147
+ Cancel
148
+ </Button>
149
+ <Button
150
+ variant="contained"
151
+ color="primary"
152
+ disabled={
153
+ !uniprotId || !userSelectedProteinSequence || !selectedTranscript
154
+ }
155
+ onClick={() => {
156
+ session.addView('ProteinView', {
157
+ type: 'ProteinView',
158
+ isFloating: true,
159
+ structures: [
160
+ {
161
+ url,
162
+ userProvidedTranscriptSequence:
163
+ userSelectedProteinSequence?.seq,
164
+ feature: selectedTranscript?.toJSON(),
165
+ connectedViewId: view.id,
166
+ },
167
+ ],
168
+ displayName: [
169
+ 'Protein view',
170
+ uniprotId,
171
+ getGeneDisplayName(feature),
172
+ getTranscriptDisplayName(selectedTranscript),
173
+ ].join(' - '),
174
+ })
175
+ handleClose()
176
+ }}
177
+ >
178
+ Launch 3-D protein structure view
179
+ </Button>
180
+ <Button
181
+ variant="contained"
182
+ disabled={
183
+ !uniprotId || !userSelectedProteinSequence || !selectedTranscript
184
+ }
185
+ onClick={() => {
186
+ if (uniprotId && isSessionWithAddTracks(session)) {
187
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
188
+ ;(async () => {
189
+ try {
190
+ session.addTemporaryAssembly?.({
191
+ name: uniprotId,
192
+ sequence: {
193
+ type: 'ReferenceSequenceTrack',
194
+ trackId: `${uniprotId}-ReferenceSequenceTrack`,
195
+ sequenceType: 'pep',
196
+ adapter: {
197
+ type: 'UnindexedFastaAdapter',
198
+ rewriteRefNames: "jexl:split(refName,'|')[1]",
199
+ fastaLocation: {
200
+ uri: `https://rest.uniprot.org/uniprotkb/${uniprotId}.fasta`,
201
+ },
202
+ },
203
+ },
204
+ })
205
+ const url = `https://rest.uniprot.org/uniprotkb/${uniprotId}.gff`
206
+ const res = await fetch(url)
207
+ if (!res.ok) {
208
+ throw new Error(`HTTP ${res.status} fetching ${url}`)
209
+ }
210
+ const data = await res.text()
211
+
212
+ const types = [
213
+ ...new Set(
214
+ data
215
+ .split('\n')
216
+ .filter(f => !f.startsWith('#'))
217
+ .map(f => f.trim())
218
+ .filter(f => !!f)
219
+ .map(f => f.split('\t')[2]),
220
+ ),
221
+ ]
222
+ types.forEach(type => {
223
+ const s = `${uniprotId}-${type}`
224
+ session.addTrackConf({
225
+ type: 'FeatureTrack',
226
+ trackId: s,
227
+ name: type,
228
+ adapter: {
229
+ type: 'Gff3Adapter',
230
+ gffLocation: {
231
+ uri: `https://rest.uniprot.org/uniprotkb/${uniprotId}.gff`,
232
+ },
233
+ },
234
+ assemblyNames: [uniprotId],
235
+ displays: [
236
+ {
237
+ displayId: `${type}-LinearBasicDisplay`,
238
+ type: 'LinearBasicDisplay',
239
+ jexlFilters: [`get(feature,'type')=='${type}'`],
240
+ },
241
+ ],
242
+ })
243
+ })
244
+ session.addTrackConf({
245
+ type: 'FeatureTrack',
246
+ trackId: 'Antigen',
247
+ name: 'Antigen',
248
+ adapter: {
249
+ type: 'Gff3Adapter',
250
+ gffLocation: {
251
+ uri: `https://www.ebi.ac.uk/proteins/api/antigen/${uniprotId}?format=gff`,
252
+ },
253
+ },
254
+ assemblyNames: [uniprotId],
255
+ })
256
+ session.addTrackConf({
257
+ type: 'FeatureTrack',
258
+ trackId: 'Variation',
259
+ name: 'Variation',
260
+ adapter: {
261
+ type: 'UniProtVariationAdapter',
262
+ location: {
263
+ uri: `https://www.ebi.ac.uk/proteins/api/variation/${uniprotId}.json`,
264
+ },
265
+ },
266
+ assemblyNames: [uniprotId],
267
+ })
268
+ session.addTrackConf({
269
+ type: 'QuantitativeTrack',
270
+ trackId: 'AlphaFold confidence',
271
+ name: 'AlphaFold confidence',
272
+ adapter: {
273
+ type: 'AlphaFoldConfidenceAdapter',
274
+ location: {
275
+ uri: `https://alphafold.ebi.ac.uk/files/AF-${uniprotId}-F1-confidence_v4.json`,
276
+ },
277
+ },
278
+ assemblyNames: [uniprotId],
279
+ })
280
+ session.addTrackConf({
281
+ type: 'MultiQuantitativeTrack',
282
+ trackId: 'AlphaMissense scores',
283
+ name: 'AlphaMissense scores',
284
+ assemblyNames: [uniprotId],
285
+ adapter: {
286
+ type: 'AlphaMissensePathogenicityAdapter',
287
+ location: {
288
+ uri: `https://alphafold.ebi.ac.uk/files/AF-${uniprotId}-F1-aa-substitutions.csv`,
289
+ },
290
+ },
291
+ displays: [
292
+ {
293
+ type: 'MultiLinearWiggleDisplay',
294
+ displayId:
295
+ 'AlphaMissense scores-MultiLinearWiggleDisplay',
296
+ defaultRendering: 'multirowdensity',
297
+ renderers: {
298
+ MultiDensityRenderer: {
299
+ type: 'MultiDensityRenderer',
300
+ bicolorPivotValue: 0.5,
301
+ },
302
+ },
303
+ },
304
+ ],
305
+ })
306
+ const view = session.addView('LinearGenomeView', {
307
+ type: 'LinearGenomeView',
308
+ displayName: [
309
+ 'Protein view',
310
+ uniprotId,
311
+ getGeneDisplayName(feature),
312
+ getTranscriptDisplayName(selectedTranscript),
313
+ ].join(' - '),
314
+ }) as LinearGenomeViewModel
315
+ await view.navToLocString(uniprotId, uniprotId)
316
+ } catch (e) {
317
+ console.error(e)
318
+ session.notifyError(`${e}`, e)
319
+ }
320
+ })()
321
+ }
322
+ handleClose()
323
+ }}
324
+ >
325
+ Launch linear protein annotation view
326
+ </Button>
327
+ </DialogActions>
328
+ </>
329
+ )
330
+ })
331
+
332
+ export default ManualUniProtIDEntry
@@ -267,7 +267,7 @@ const UserProvidedStructure = observer(function ({
267
267
  })()
268
268
  }}
269
269
  >
270
- Submit
270
+ Launch 3-D protein structure view
271
271
  </Button>
272
272
  </DialogActions>
273
273
  </>
@@ -61,7 +61,7 @@ const ProteinViewContainer = observer(function ({
61
61
  <ResizeHandle
62
62
  style={{ height: 4, background: 'grey' }}
63
63
  onDrag={delta => {
64
- model.setHeight(model.height + delta)
64
+ return model.setHeight(model.height + delta)
65
65
  }}
66
66
  />
67
67
  </div>
@@ -78,6 +78,7 @@ function stateModelFactory() {
78
78
  */
79
79
  setHeight(n: number) {
80
80
  self.height = n
81
+ return n
81
82
  },
82
83
  /**
83
84
  * #action