react-msaview 3.1.11 → 3.1.12
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/bundle/index.js +31 -31
- package/dist/components/SequenceTextArea.js +4 -0
- package/dist/components/SequenceTextArea.js.map +1 -1
- package/dist/components/dialogs/DomainDialog.d.ts +6 -0
- package/dist/components/dialogs/DomainDialog.js +19 -0
- package/dist/components/dialogs/DomainDialog.js.map +1 -0
- package/dist/components/dialogs/InterProScanPanel.d.ts +7 -0
- package/dist/components/dialogs/{InterProScanDialog.js → InterProScanPanel.js} +36 -8
- package/dist/components/dialogs/InterProScanPanel.js.map +1 -0
- package/dist/components/dialogs/TabPanel.d.ts +6 -0
- package/dist/components/dialogs/TabPanel.js +6 -0
- package/dist/components/dialogs/TabPanel.js.map +1 -0
- package/dist/components/dialogs/{InterProScanDialog.d.ts → UserProvidedResultPanel.d.ts} +2 -2
- package/dist/components/dialogs/UserProvidedResultPanel.js +56 -0
- package/dist/components/dialogs/UserProvidedResultPanel.js.map +1 -0
- package/dist/components/header/Header.js +0 -2
- package/dist/components/header/Header.js.map +1 -1
- package/dist/components/header/HeaderMenuExtra.js +36 -25
- package/dist/components/header/HeaderMenuExtra.js.map +1 -1
- package/dist/components/header/HeaderStatusArea.d.ts +1 -1
- package/dist/components/header/HeaderStatusArea.js +3 -2
- package/dist/components/header/HeaderStatusArea.js.map +1 -1
- package/dist/components/msa/MSACanvasBlock.js +1 -1
- package/dist/components/msa/renderBoxFeatureCanvasBlock.js +2 -2
- package/dist/components/msa/renderMSABlock.js +4 -4
- package/dist/fetchUtils.d.ts +1 -1
- package/dist/fetchUtils.js.map +1 -1
- package/dist/launchInterProScan.d.ts +9 -3
- package/dist/launchInterProScan.js +58 -21
- package/dist/launchInterProScan.js.map +1 -1
- package/dist/model.d.ts +11 -33
- package/dist/model.js +4 -65
- package/dist/model.js.map +1 -1
- package/dist/renderToSvg.js +0 -23
- package/dist/renderToSvg.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
- package/src/components/SequenceTextArea.tsx +8 -0
- package/src/components/dialogs/DomainDialog.tsx +38 -0
- package/src/components/dialogs/{InterProScanDialog.tsx → InterProScanPanel.tsx} +41 -10
- package/src/components/dialogs/TabPanel.tsx +19 -0
- package/src/components/dialogs/UserProvidedResultPanel.tsx +119 -0
- package/src/components/header/Header.tsx +0 -2
- package/src/components/header/HeaderMenuExtra.tsx +41 -28
- package/src/components/header/HeaderStatusArea.tsx +7 -2
- package/src/components/msa/MSACanvasBlock.tsx +1 -1
- package/src/components/msa/renderBoxFeatureCanvasBlock.ts +2 -2
- package/src/components/msa/renderMSABlock.ts +4 -4
- package/src/fetchUtils.ts +2 -2
- package/src/launchInterProScan.ts +70 -23
- package/src/model.ts +5 -86
- package/src/renderToSvg.tsx +0 -24
- package/src/version.ts +1 -1
- package/dist/components/dialogs/InterProScanDialog.js.map +0 -1
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
import React, { useState } from 'react'
|
|
2
2
|
import { observer } from 'mobx-react'
|
|
3
|
-
import { Dialog } from '@jbrowse/core/ui'
|
|
4
3
|
import { Button, DialogActions, DialogContent, Typography } from '@mui/material'
|
|
5
4
|
|
|
6
5
|
// locals
|
|
7
6
|
import { MsaViewModel } from '../../model'
|
|
7
|
+
import { getSession } from '@jbrowse/core/util'
|
|
8
|
+
import { launchInterProScan } from '../../launchInterProScan'
|
|
8
9
|
|
|
9
|
-
const
|
|
10
|
-
|
|
10
|
+
const InterProScanDialog = observer(function ({
|
|
11
|
+
handleClose,
|
|
11
12
|
model,
|
|
12
13
|
}: {
|
|
13
|
-
|
|
14
|
+
handleClose: () => void
|
|
14
15
|
model: MsaViewModel
|
|
15
16
|
}) {
|
|
16
17
|
const [vals, setVals] = useState([
|
|
@@ -146,7 +147,7 @@ const FeatureTypeDialog = observer(function ({
|
|
|
146
147
|
const [show, setShow] = useState(false)
|
|
147
148
|
|
|
148
149
|
return (
|
|
149
|
-
|
|
150
|
+
<>
|
|
150
151
|
<DialogContent>
|
|
151
152
|
<Typography>
|
|
152
153
|
This will run InterProScan on all rows of the current MSA
|
|
@@ -209,22 +210,52 @@ const FeatureTypeDialog = observer(function ({
|
|
|
209
210
|
) : null}
|
|
210
211
|
</DialogContent>
|
|
211
212
|
<DialogActions>
|
|
212
|
-
<Button
|
|
213
|
+
<Button
|
|
214
|
+
variant="contained"
|
|
215
|
+
color="secondary"
|
|
216
|
+
onClick={() => handleClose()}
|
|
217
|
+
>
|
|
213
218
|
Cancel
|
|
214
219
|
</Button>
|
|
215
220
|
<Button
|
|
216
221
|
variant="contained"
|
|
217
222
|
color="primary"
|
|
218
223
|
onClick={() => {
|
|
219
|
-
|
|
220
|
-
|
|
224
|
+
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
225
|
+
;(async () => {
|
|
226
|
+
try {
|
|
227
|
+
const { rows } = model
|
|
228
|
+
if (rows.length > 140) {
|
|
229
|
+
throw new Error(
|
|
230
|
+
'Too many sequences, please run InterProScan offline',
|
|
231
|
+
)
|
|
232
|
+
}
|
|
233
|
+
await launchInterProScan({
|
|
234
|
+
algorithm: 'interproscan',
|
|
235
|
+
programs: programs,
|
|
236
|
+
seq: rows
|
|
237
|
+
.map(row => [row[0], row[1].replaceAll('-', '')])
|
|
238
|
+
.filter(f => !!f[1])
|
|
239
|
+
.map(row => `>${row[0]}\n${row[1]}`)
|
|
240
|
+
.join('\n'),
|
|
241
|
+
onProgress: arg => model.setStatus(arg),
|
|
242
|
+
model,
|
|
243
|
+
})
|
|
244
|
+
} catch (e) {
|
|
245
|
+
console.error(e)
|
|
246
|
+
getSession(model).notifyError(`${e}`, e)
|
|
247
|
+
} finally {
|
|
248
|
+
model.setStatus()
|
|
249
|
+
}
|
|
250
|
+
})()
|
|
251
|
+
handleClose()
|
|
221
252
|
}}
|
|
222
253
|
>
|
|
223
254
|
Send sequences to InterProScan
|
|
224
255
|
</Button>
|
|
225
256
|
</DialogActions>
|
|
226
|
-
|
|
257
|
+
</>
|
|
227
258
|
)
|
|
228
259
|
})
|
|
229
260
|
|
|
230
|
-
export default
|
|
261
|
+
export default InterProScanDialog
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
|
|
3
|
+
// this is from MUI example
|
|
4
|
+
export default function TabPanel({
|
|
5
|
+
children,
|
|
6
|
+
value,
|
|
7
|
+
index,
|
|
8
|
+
...other
|
|
9
|
+
}: {
|
|
10
|
+
children?: React.ReactNode
|
|
11
|
+
index: number
|
|
12
|
+
value: number
|
|
13
|
+
}) {
|
|
14
|
+
return (
|
|
15
|
+
<div role="tabpanel" hidden={value !== index} {...other}>
|
|
16
|
+
{value === index && <div>{children}</div>}
|
|
17
|
+
</div>
|
|
18
|
+
)
|
|
19
|
+
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import React, { useState } from 'react'
|
|
2
|
+
import { observer } from 'mobx-react'
|
|
3
|
+
import {
|
|
4
|
+
Button,
|
|
5
|
+
DialogActions,
|
|
6
|
+
DialogContent,
|
|
7
|
+
FormControlLabel,
|
|
8
|
+
FormControl,
|
|
9
|
+
Radio,
|
|
10
|
+
RadioGroup,
|
|
11
|
+
TextField,
|
|
12
|
+
Typography,
|
|
13
|
+
} from '@mui/material'
|
|
14
|
+
import { getSession } from '@jbrowse/core/util'
|
|
15
|
+
|
|
16
|
+
// locals
|
|
17
|
+
import { MsaViewModel } from '../../model'
|
|
18
|
+
import { jsonfetch } from '../../fetchUtils'
|
|
19
|
+
import { InterProScanResponse } from '../../launchInterProScan'
|
|
20
|
+
|
|
21
|
+
const FeatureTypeDialog = observer(function ({
|
|
22
|
+
handleClose,
|
|
23
|
+
model,
|
|
24
|
+
}: {
|
|
25
|
+
handleClose: () => void
|
|
26
|
+
model: MsaViewModel
|
|
27
|
+
}) {
|
|
28
|
+
const [file, setFile] = useState<File>()
|
|
29
|
+
const [choice, setChoice] = useState('file')
|
|
30
|
+
const [interProURL, setInterProURL] = useState('')
|
|
31
|
+
|
|
32
|
+
return (
|
|
33
|
+
<>
|
|
34
|
+
<DialogContent>
|
|
35
|
+
<div style={{ display: 'flex', margin: 30 }}>
|
|
36
|
+
<Typography>
|
|
37
|
+
Open a JSON file of InterProScan results that you run remotely on
|
|
38
|
+
EBI servers or locally
|
|
39
|
+
</Typography>
|
|
40
|
+
|
|
41
|
+
<FormControl component="fieldset">
|
|
42
|
+
<RadioGroup
|
|
43
|
+
value={choice}
|
|
44
|
+
onChange={event => setChoice(event.target.value)}
|
|
45
|
+
>
|
|
46
|
+
<FormControlLabel value="url" control={<Radio />} label="URL" />
|
|
47
|
+
<FormControlLabel value="file" control={<Radio />} label="File" />
|
|
48
|
+
</RadioGroup>
|
|
49
|
+
</FormControl>
|
|
50
|
+
{choice === 'url' ? (
|
|
51
|
+
<div>
|
|
52
|
+
<Typography>Open a InterProScan JSON file remote URL</Typography>
|
|
53
|
+
<TextField
|
|
54
|
+
label="URL"
|
|
55
|
+
value={interProURL}
|
|
56
|
+
onChange={event => setInterProURL(event.target.value)}
|
|
57
|
+
/>
|
|
58
|
+
</div>
|
|
59
|
+
) : null}
|
|
60
|
+
{choice === 'file' ? (
|
|
61
|
+
<div style={{ paddingTop: 20 }}>
|
|
62
|
+
<Typography>
|
|
63
|
+
Open a InterProScan JSON file file from your local drive
|
|
64
|
+
</Typography>
|
|
65
|
+
<Button variant="outlined" component="label">
|
|
66
|
+
Choose File
|
|
67
|
+
<input
|
|
68
|
+
type="file"
|
|
69
|
+
hidden
|
|
70
|
+
onChange={({ target }) => {
|
|
71
|
+
const file = target?.files?.[0]
|
|
72
|
+
if (file) {
|
|
73
|
+
setFile(file)
|
|
74
|
+
}
|
|
75
|
+
}}
|
|
76
|
+
/>
|
|
77
|
+
</Button>
|
|
78
|
+
</div>
|
|
79
|
+
) : null}
|
|
80
|
+
</div>
|
|
81
|
+
</DialogContent>
|
|
82
|
+
<DialogActions>
|
|
83
|
+
<Button
|
|
84
|
+
variant="contained"
|
|
85
|
+
color="primary"
|
|
86
|
+
onClick={() => {
|
|
87
|
+
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
88
|
+
;(async () => {
|
|
89
|
+
try {
|
|
90
|
+
const ret: InterProScanResponse = file
|
|
91
|
+
? JSON.parse(await file.text())
|
|
92
|
+
: await jsonfetch(interProURL)
|
|
93
|
+
|
|
94
|
+
model.setLoadedInterProAnnotations(
|
|
95
|
+
Object.fromEntries(ret.results.map(r => [r.xref[0].id, r])),
|
|
96
|
+
)
|
|
97
|
+
model.setShowDomains(true)
|
|
98
|
+
getSession(model).notify(
|
|
99
|
+
'Loaded interproscan results',
|
|
100
|
+
'success',
|
|
101
|
+
)
|
|
102
|
+
} catch (e) {
|
|
103
|
+
console.error(e)
|
|
104
|
+
getSession(model).notifyError(`${e}`, e)
|
|
105
|
+
} finally {
|
|
106
|
+
model.setStatus()
|
|
107
|
+
}
|
|
108
|
+
})()
|
|
109
|
+
handleClose()
|
|
110
|
+
}}
|
|
111
|
+
>
|
|
112
|
+
Open results
|
|
113
|
+
</Button>
|
|
114
|
+
</DialogActions>
|
|
115
|
+
</>
|
|
116
|
+
)
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
export default FeatureTypeDialog
|
|
@@ -13,7 +13,6 @@ import ZoomControls from './ZoomControls'
|
|
|
13
13
|
import MultiAlignmentSelector from './MultiAlignmentSelector'
|
|
14
14
|
import HeaderInfoArea from './HeaderInfoArea'
|
|
15
15
|
import HeaderStatusArea from './HeaderStatusArea'
|
|
16
|
-
import HeaderMenu from './HeaderMenu'
|
|
17
16
|
import HeaderMenuExtra from './HeaderMenuExtra'
|
|
18
17
|
|
|
19
18
|
const AboutDialog = lazy(() => import('../dialogs/AboutDialog'))
|
|
@@ -21,7 +20,6 @@ const AboutDialog = lazy(() => import('../dialogs/AboutDialog'))
|
|
|
21
20
|
const Header = observer(function ({ model }: { model: MsaViewModel }) {
|
|
22
21
|
return (
|
|
23
22
|
<div style={{ display: 'flex' }}>
|
|
24
|
-
<HeaderMenu model={model} />
|
|
25
23
|
<ZoomControls model={model} />
|
|
26
24
|
<HeaderMenuExtra model={model} />
|
|
27
25
|
<MultiAlignmentSelector model={model} />
|
|
@@ -3,7 +3,6 @@ import { observer } from 'mobx-react'
|
|
|
3
3
|
import CascadingMenuButton from '@jbrowse/core/ui/CascadingMenuButton'
|
|
4
4
|
|
|
5
5
|
// locals
|
|
6
|
-
import { MsaViewModel } from '../../model'
|
|
7
6
|
|
|
8
7
|
// icons
|
|
9
8
|
import MoreVert from '@mui/icons-material/MoreVert'
|
|
@@ -13,18 +12,50 @@ import FilterAlt from '@mui/icons-material/FilterAlt'
|
|
|
13
12
|
import Search from '@mui/icons-material/Search'
|
|
14
13
|
import PhotoCamera from '@mui/icons-material/PhotoCamera'
|
|
15
14
|
import RestartAlt from '@mui/icons-material/RestartAlt'
|
|
15
|
+
import FolderOpen from '@mui/icons-material/FolderOpen'
|
|
16
|
+
import Settings from '@mui/icons-material/Settings'
|
|
17
|
+
import Assignment from '@mui/icons-material/Assignment'
|
|
18
|
+
import List from '@mui/icons-material/List'
|
|
19
|
+
|
|
20
|
+
// locals
|
|
21
|
+
import { MsaViewModel } from '../../model'
|
|
16
22
|
|
|
17
23
|
// lazies
|
|
24
|
+
const SettingsDialog = lazy(() => import('../dialogs/SettingsDialog'))
|
|
25
|
+
const MetadataDialog = lazy(() => import('../dialogs/MetadataDialog'))
|
|
26
|
+
const TracklistDialog = lazy(() => import('../dialogs/TracklistDialog'))
|
|
18
27
|
const ExportSVGDialog = lazy(() => import('../dialogs/ExportSVGDialog'))
|
|
19
28
|
const FeatureFilterDialog = lazy(() => import('../dialogs/FeatureDialog'))
|
|
20
|
-
const
|
|
29
|
+
const DomainDialog = lazy(() => import('../dialogs/DomainDialog'))
|
|
21
30
|
|
|
22
31
|
const HeaderMenuExtra = observer(function ({ model }: { model: MsaViewModel }) {
|
|
23
|
-
const {
|
|
24
|
-
model
|
|
32
|
+
const { showDomains, subFeatureRows, noAnnotations } = model
|
|
25
33
|
return (
|
|
26
34
|
<CascadingMenuButton
|
|
27
35
|
menuItems={[
|
|
36
|
+
{
|
|
37
|
+
label: 'Return to import form',
|
|
38
|
+
icon: FolderOpen,
|
|
39
|
+
onClick: () => model.reset(),
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
label: 'Settings',
|
|
43
|
+
onClick: () =>
|
|
44
|
+
model.queueDialog(onClose => [SettingsDialog, { model, onClose }]),
|
|
45
|
+
icon: Settings,
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
label: 'Metadata',
|
|
49
|
+
onClick: () =>
|
|
50
|
+
model.queueDialog(onClose => [MetadataDialog, { model, onClose }]),
|
|
51
|
+
icon: Assignment,
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
label: 'Extra tracks',
|
|
55
|
+
onClick: () =>
|
|
56
|
+
model.queueDialog(onClose => [TracklistDialog, { model, onClose }]),
|
|
57
|
+
icon: List,
|
|
58
|
+
},
|
|
28
59
|
{
|
|
29
60
|
label: 'Reset zoom to default',
|
|
30
61
|
icon: RestartAlt,
|
|
@@ -47,9 +78,9 @@ const HeaderMenuExtra = observer(function ({ model }: { model: MsaViewModel }) {
|
|
|
47
78
|
label:
|
|
48
79
|
'Show domains' + (noAnnotations ? ' (no domains loaded)' : ''),
|
|
49
80
|
icon: Visibility,
|
|
50
|
-
checked:
|
|
81
|
+
checked: showDomains,
|
|
51
82
|
type: 'checkbox',
|
|
52
|
-
onClick: () => model.
|
|
83
|
+
onClick: () => model.setShowDomains(!showDomains),
|
|
53
84
|
},
|
|
54
85
|
{
|
|
55
86
|
label: 'Use sub-row layout',
|
|
@@ -69,32 +100,14 @@ const HeaderMenuExtra = observer(function ({ model }: { model: MsaViewModel }) {
|
|
|
69
100
|
},
|
|
70
101
|
},
|
|
71
102
|
{
|
|
72
|
-
label: '
|
|
103
|
+
label: 'View domains',
|
|
73
104
|
icon: Search,
|
|
74
105
|
onClick: () =>
|
|
75
|
-
model.queueDialog(
|
|
76
|
-
|
|
77
|
-
{
|
|
106
|
+
model.queueDialog(handleClose => [
|
|
107
|
+
DomainDialog,
|
|
108
|
+
{ handleClose, model },
|
|
78
109
|
]),
|
|
79
110
|
},
|
|
80
|
-
{
|
|
81
|
-
label: 'Load previous InterProScan results...',
|
|
82
|
-
icon: Search,
|
|
83
|
-
type: 'subMenu',
|
|
84
|
-
subMenu: interProScanJobIds.length
|
|
85
|
-
? interProScanJobIds.map(({ jobId, date }) => ({
|
|
86
|
-
label:
|
|
87
|
-
new Date(date).toLocaleString('en-US') + ' - ' + jobId,
|
|
88
|
-
onClick: () => model.loadInterProScanResults(jobId),
|
|
89
|
-
}))
|
|
90
|
-
: [
|
|
91
|
-
{
|
|
92
|
-
label: 'No previous searches',
|
|
93
|
-
disabled: true,
|
|
94
|
-
onClick: () => {},
|
|
95
|
-
},
|
|
96
|
-
],
|
|
97
|
-
},
|
|
98
111
|
],
|
|
99
112
|
},
|
|
100
113
|
...(model.extraViewMenuItems?.() || []),
|
|
@@ -2,6 +2,7 @@ import React from 'react'
|
|
|
2
2
|
import { Typography } from '@mui/material'
|
|
3
3
|
import { observer } from 'mobx-react'
|
|
4
4
|
import { makeStyles } from 'tss-react/mui'
|
|
5
|
+
import { LoadingEllipses } from '@jbrowse/core/ui'
|
|
5
6
|
|
|
6
7
|
// locals
|
|
7
8
|
import { MsaViewModel } from '../../model'
|
|
@@ -13,12 +14,16 @@ const useStyles = makeStyles()({
|
|
|
13
14
|
},
|
|
14
15
|
})
|
|
15
16
|
|
|
16
|
-
const HeaderStatusArea = observer(({
|
|
17
|
+
const HeaderStatusArea = observer(function ({
|
|
18
|
+
model,
|
|
19
|
+
}: {
|
|
20
|
+
model: MsaViewModel
|
|
21
|
+
}) {
|
|
17
22
|
const { status } = model
|
|
18
23
|
const { classes } = useStyles()
|
|
19
24
|
return status ? (
|
|
20
25
|
<Typography className={classes.margin}>
|
|
21
|
-
{status.msg}{' '}
|
|
26
|
+
<LoadingEllipses message={status.msg} component="span" />{' '}
|
|
22
27
|
{status.url ? (
|
|
23
28
|
<a href={status.url} target="_blank" rel="noreferrer">
|
|
24
29
|
(status)
|
|
@@ -19,9 +19,9 @@ export function renderBoxFeatureCanvasBlock({
|
|
|
19
19
|
highResScaleFactorOverride?: number
|
|
20
20
|
blockSizeYOverride?: number
|
|
21
21
|
}) {
|
|
22
|
-
const { hierarchy, blockSize, rowHeight, highResScaleFactor,
|
|
22
|
+
const { hierarchy, blockSize, rowHeight, highResScaleFactor, showDomains } =
|
|
23
23
|
model
|
|
24
|
-
if (
|
|
24
|
+
if (showDomains) {
|
|
25
25
|
const k = highResScaleFactorOverride || highResScaleFactor
|
|
26
26
|
const by = blockSizeYOverride || blockSize
|
|
27
27
|
ctx.resetTransform()
|
|
@@ -34,7 +34,7 @@ export function renderMSABlock({
|
|
|
34
34
|
rowHeight,
|
|
35
35
|
fontSize,
|
|
36
36
|
highResScaleFactor,
|
|
37
|
-
|
|
37
|
+
showDomains,
|
|
38
38
|
} = model
|
|
39
39
|
const k = highResScaleFactorOverride || highResScaleFactor
|
|
40
40
|
const bx = blockSizeXOverride || blockSize
|
|
@@ -53,7 +53,7 @@ export function renderMSABlock({
|
|
|
53
53
|
const xEnd = Math.max(0, Math.ceil((offsetX + bx) / colWidth))
|
|
54
54
|
const visibleLeaves = leaves.slice(yStart, yEnd)
|
|
55
55
|
|
|
56
|
-
if (!
|
|
56
|
+
if (!showDomains) {
|
|
57
57
|
drawTiles({
|
|
58
58
|
model,
|
|
59
59
|
ctx,
|
|
@@ -152,7 +152,7 @@ function drawText({
|
|
|
152
152
|
xStart: number
|
|
153
153
|
xEnd: number
|
|
154
154
|
}) {
|
|
155
|
-
const { bgColor,
|
|
155
|
+
const { bgColor, showDomains, colorScheme, columns, colWidth, rowHeight } =
|
|
156
156
|
model
|
|
157
157
|
if (rowHeight >= 5 && colWidth > rowHeight / 2) {
|
|
158
158
|
for (const node of visibleLeaves) {
|
|
@@ -168,7 +168,7 @@ function drawText({
|
|
|
168
168
|
const x = i * colWidth + offsetX - (offsetX % colWidth)
|
|
169
169
|
|
|
170
170
|
// note: -rowHeight/4 matches +rowHeight/4 in tree
|
|
171
|
-
ctx.fillStyle =
|
|
171
|
+
ctx.fillStyle = showDomains
|
|
172
172
|
? 'black'
|
|
173
173
|
: bgColor
|
|
174
174
|
? contrast
|
package/src/fetchUtils.ts
CHANGED
|
@@ -15,9 +15,9 @@ export async function textfetch(url: string, args?: RequestInit) {
|
|
|
15
15
|
return response.text()
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
export async function jsonfetch(url: string, args?: RequestInit) {
|
|
18
|
+
export async function jsonfetch<T>(url: string, args?: RequestInit) {
|
|
19
19
|
const response = await myfetch(url, args)
|
|
20
|
-
return response.json()
|
|
20
|
+
return response.json() as T
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
export async function arraybufferfetch(url: string) {
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import { getSession } from '@jbrowse/core/util'
|
|
1
2
|
import { jsonfetch, textfetch, timeout } from './fetchUtils'
|
|
3
|
+
import { MsaViewModel } from './model'
|
|
2
4
|
|
|
3
5
|
const base = `https://www.ebi.ac.uk/Tools/services/rest`
|
|
4
6
|
|
|
@@ -24,11 +26,13 @@ async function runInterProScan({
|
|
|
24
26
|
onProgress,
|
|
25
27
|
onJobId,
|
|
26
28
|
programs,
|
|
29
|
+
model,
|
|
27
30
|
}: {
|
|
28
31
|
seq: string
|
|
29
32
|
programs: string[]
|
|
30
33
|
onProgress: (arg?: { msg: string; url?: string }) => void
|
|
31
|
-
onJobId
|
|
34
|
+
onJobId?: (arg: string) => void
|
|
35
|
+
model: MsaViewModel
|
|
32
36
|
}) {
|
|
33
37
|
const jobId = await textfetch(`${base}/iprscan5/run`, {
|
|
34
38
|
method: 'POST',
|
|
@@ -38,18 +42,18 @@ async function runInterProScan({
|
|
|
38
42
|
programs: programs.join(','),
|
|
39
43
|
}),
|
|
40
44
|
})
|
|
41
|
-
onJobId(jobId)
|
|
45
|
+
onJobId?.(jobId)
|
|
42
46
|
await wait({
|
|
43
47
|
jobId,
|
|
44
48
|
onProgress,
|
|
45
49
|
})
|
|
46
|
-
return
|
|
50
|
+
return loadInterProScanResultsWithStatus({ jobId, model })
|
|
47
51
|
}
|
|
48
52
|
|
|
49
|
-
export
|
|
50
|
-
return
|
|
53
|
+
export function loadInterProScanResults(jobId: string) {
|
|
54
|
+
return jsonfetch<InterProScanResponse>(
|
|
51
55
|
`${base}/iprscan5/result/${jobId}/json`,
|
|
52
|
-
)
|
|
56
|
+
)
|
|
53
57
|
}
|
|
54
58
|
|
|
55
59
|
async function wait({
|
|
@@ -60,17 +64,20 @@ async function wait({
|
|
|
60
64
|
onProgress: (arg?: { msg: string; url?: string }) => void
|
|
61
65
|
}) {
|
|
62
66
|
const url = `${base}/iprscan5/status/${jobId}`
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
67
|
+
try {
|
|
68
|
+
// eslint-disable-next-line no-constant-condition
|
|
69
|
+
while (true) {
|
|
70
|
+
for (let i = 0; i < 10; i++) {
|
|
71
|
+
await timeout(1000)
|
|
72
|
+
onProgress({ msg: `Checking status ${10 - i}`, url })
|
|
73
|
+
}
|
|
74
|
+
const result = await textfetch(url)
|
|
75
|
+
if (result.includes('FINISHED')) {
|
|
76
|
+
break
|
|
77
|
+
}
|
|
73
78
|
}
|
|
79
|
+
} finally {
|
|
80
|
+
onProgress()
|
|
74
81
|
}
|
|
75
82
|
}
|
|
76
83
|
|
|
@@ -80,19 +87,59 @@ export async function launchInterProScan({
|
|
|
80
87
|
programs,
|
|
81
88
|
onJobId,
|
|
82
89
|
onProgress,
|
|
90
|
+
model,
|
|
83
91
|
}: {
|
|
84
92
|
algorithm: string
|
|
85
93
|
seq: string
|
|
86
94
|
programs: string[]
|
|
87
95
|
onProgress: (arg?: { msg: string; url?: string }) => void
|
|
88
|
-
onJobId
|
|
96
|
+
onJobId?: (arg: string) => void
|
|
97
|
+
model: MsaViewModel
|
|
89
98
|
}) {
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
99
|
+
try {
|
|
100
|
+
onProgress({ msg: `Launching ${algorithm} MSA` })
|
|
101
|
+
if (algorithm === 'interproscan') {
|
|
102
|
+
const result = await runInterProScan({
|
|
103
|
+
seq,
|
|
104
|
+
onJobId,
|
|
105
|
+
onProgress,
|
|
106
|
+
programs,
|
|
107
|
+
model,
|
|
108
|
+
})
|
|
109
|
+
return result
|
|
110
|
+
} else {
|
|
111
|
+
throw new Error('unknown algorithm')
|
|
112
|
+
}
|
|
113
|
+
} finally {
|
|
93
114
|
onProgress()
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export async function loadInterProScanResultsWithStatus({
|
|
119
|
+
jobId,
|
|
120
|
+
model,
|
|
121
|
+
}: {
|
|
122
|
+
jobId: string
|
|
123
|
+
model: MsaViewModel
|
|
124
|
+
}) {
|
|
125
|
+
try {
|
|
126
|
+
model.setStatus({
|
|
127
|
+
msg:
|
|
128
|
+
'Downloading results of ' +
|
|
129
|
+
jobId +
|
|
130
|
+
' (for larger sequences this can be slow, click status to download and upload in the manual tab)',
|
|
131
|
+
url: `https://www.ebi.ac.uk/Tools/services/rest/iprscan5/result/${jobId}/json`,
|
|
132
|
+
})
|
|
133
|
+
const ret = await loadInterProScanResults(jobId)
|
|
134
|
+
model.setLoadedInterProAnnotations(
|
|
135
|
+
Object.fromEntries(ret.results.map(r => [r.xref[0].id, r])),
|
|
136
|
+
)
|
|
137
|
+
model.setShowDomains(true)
|
|
138
|
+
getSession(model).notify(`Loaded interproscan ${jobId} results`, 'success')
|
|
139
|
+
} catch (e) {
|
|
140
|
+
console.error(e)
|
|
141
|
+
getSession(model).notifyError(`${e}`, e)
|
|
142
|
+
} finally {
|
|
143
|
+
model.setStatus()
|
|
97
144
|
}
|
|
98
145
|
}
|