react-msaview 3.1.10 → 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 -45
- package/dist/components/Loading.js +1 -1
- package/dist/components/Loading.js.map +1 -1
- package/dist/components/SequenceTextArea.js +5 -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/SettingsDialog.js +3 -3
- package/dist/components/dialogs/SettingsDialog.js.map +1 -1
- 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/MSACanvas.js +6 -8
- package/dist/components/msa/MSACanvas.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/components/tree/TreeCanvas.js +4 -6
- package/dist/components/tree/TreeCanvas.js.map +1 -1
- 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 +18 -38
- package/dist/model.js +5 -66
- package/dist/model.js.map +1 -1
- package/dist/parsers/StockholmMSA.d.ts +2 -2
- 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 -4
- package/src/components/Loading.tsx +7 -5
- package/src/components/SequenceTextArea.tsx +9 -0
- package/src/components/dialogs/DomainDialog.tsx +38 -0
- package/src/components/dialogs/{InterProScanDialog.tsx → InterProScanPanel.tsx} +41 -10
- package/src/components/dialogs/SettingsDialog.tsx +4 -4
- 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/MSACanvas.tsx +8 -10
- 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/components/tree/TreeCanvas.tsx +4 -6
- package/src/fetchUtils.ts +2 -2
- package/src/launchInterProScan.ts +70 -23
- package/src/model.ts +6 -87
- package/src/renderToSvg.tsx +0 -24
- package/src/version.ts +1 -1
- package/dist/components/dialogs/InterProScanDialog.js.map +0 -1
|
@@ -24,8 +24,8 @@ export default class StockholmMSA {
|
|
|
24
24
|
get alignmentNames(): string[];
|
|
25
25
|
getHeader(): {
|
|
26
26
|
General: {
|
|
27
|
-
DE?: string[]
|
|
28
|
-
NH?: string[]
|
|
27
|
+
DE?: string[];
|
|
28
|
+
NH?: string[];
|
|
29
29
|
};
|
|
30
30
|
Accessions: Record<string, string>;
|
|
31
31
|
Dbxref: Record<string, string>;
|
package/dist/renderToSvg.js
CHANGED
|
@@ -44,29 +44,6 @@ async function render({ width, height, offsetX, offsetY, theme, model, includeMi
|
|
|
44
44
|
React.createElement(Wrapper, { model: model },
|
|
45
45
|
React.createElement(CoreRendering, { Context: Context, model: model, theme: theme, offsetX: offsetX, offsetY: offsetY, width: width, height: height }))), createRoot);
|
|
46
46
|
}
|
|
47
|
-
// function renderMultiline({ model }: { model: MsaViewModel }) {
|
|
48
|
-
// const { treeAreaWidth, height } = model
|
|
49
|
-
// const clipId = 'tree'
|
|
50
|
-
// return (
|
|
51
|
-
// <Wrapper model={model}>
|
|
52
|
-
// <defs>
|
|
53
|
-
// <clipPath id={clipId}>
|
|
54
|
-
// <rect x={0} y={0} width={treeAreaWidth} height={height} />
|
|
55
|
-
// </clipPath>
|
|
56
|
-
// </defs>
|
|
57
|
-
// <g
|
|
58
|
-
// clipPath={`url(#${clipId})`}
|
|
59
|
-
// /* eslint-disable-next-line react/no-danger */
|
|
60
|
-
// dangerouslySetInnerHTML={{ __html: ctx1.getSvg().innerHTML }}
|
|
61
|
-
// />
|
|
62
|
-
// <g
|
|
63
|
-
// transform={`translate(${treeAreaWidth} 0)`}
|
|
64
|
-
// /* eslint-disable-next-line react/no-danger */
|
|
65
|
-
// dangerouslySetInnerHTML={{ __html: ctx2.getSvg().innerHTML }}
|
|
66
|
-
// />
|
|
67
|
-
// </Wrapper>
|
|
68
|
-
// )
|
|
69
|
-
// }
|
|
70
47
|
function CoreRendering({ model, theme, width, height, offsetX, offsetY, Context, }) {
|
|
71
48
|
const clipId1 = 'tree';
|
|
72
49
|
const clipId2 = 'msa';
|
package/dist/renderToSvg.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"renderToSvg.js","sourceRoot":"","sources":["../src/renderToSvg.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AACzB,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAC7C,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAA;AAC3B,OAAO,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAA;AAKzD,OAAO,EAAE,gBAAgB,EAAE,MAAM,oCAAoC,CAAA;AACrE,OAAO,EAAE,cAAc,EAAE,MAAM,iCAAiC,CAAA;AAChE,OAAO,EAAE,aAAa,EAAE,MAAM,QAAQ,CAAA;AAEtC,OAAO,UAAU,MAAM,iCAAiC,CAAA;AACxD,OAAO,EAAE,2BAA2B,EAAE,MAAM,8CAA8C,CAAA;AAE1F,MAAM,CAAC,KAAK,UAAU,WAAW,CAC/B,KAAmB,EACnB,IAAoE;IAEpE,MAAM,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,WAAW,CAAC,CAAA;IACrC,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,GAAG,KAAK,CAAA;IACjD,MAAM,EAAE,UAAU,EAAE,KAAK,EAAE,cAAc,EAAE,GAAG,IAAI,CAAA;IAElD,IAAI,UAAU,KAAK,QAAQ,EAAE,CAAC;QAC5B,OAAO,MAAM,CAAC;YACZ,KAAK,EAAE,KAAK,CAAC,UAAU,GAAG,KAAK,CAAC,aAAa;YAC7C,MAAM,EAAE,KAAK,CAAC,WAAW;YACzB,KAAK;YACL,KAAK;YACL,OAAO,EAAE,CAAC;YACV,OAAO,EAAE,CAAC;YACV,cAAc;SACf,CAAC,CAAA;IACJ,CAAC;SAAM,IAAI,UAAU,KAAK,UAAU,EAAE,CAAC;QACrC,OAAO,MAAM,CAAC;YACZ,KAAK;YACL,MAAM;YACN,KAAK;YACL,KAAK;YACL,OAAO,EAAE,OAAO;YAChB,OAAO,EAAE,CAAC,OAAO;YACjB,cAAc;SACf,CAAC,CAAA;IACJ,CAAC;SAAM,CAAC;QACN,MAAM,IAAI,KAAK,CAAC,qBAAqB,CAAC,CAAA;IACxC,CAAC;AACH,CAAC;AAED,KAAK,UAAU,MAAM,CAAC,EACpB,KAAK,EACL,MAAM,EACN,OAAO,EACP,OAAO,EACP,KAAK,EACL,KAAK,EACL,cAAc,GASf;IACC,MAAM,EAAE,OAAO,EAAE,GAAG,MAAM,MAAM,CAAC,WAAW,CAAC,CAAA;IAC7C,MAAM,OAAO,GAAG,cAAc,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,WAAW,CAAA;IAE7D,OAAO,oBAAoB,CACzB,oBAAC,UAAU,IAAC,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM;QACtC,oBAAC,OAAO,IAAC,KAAK,EAAE,KAAK;YACnB,oBAAC,aAAa,IACZ,OAAO,EAAE,OAAO,EAChB,KAAK,EAAE,KAAK,EACZ,KAAK,EAAE,KAAK,EACZ,OAAO,EAAE,OAAO,EAChB,OAAO,EAAE,OAAO,EAChB,KAAK,EAAE,KAAK,EACZ,MAAM,EAAE,MAAM,GACd,CACM,CACC,EACb,UAAU,CACX,CAAA;AACH,CAAC;AAED,
|
|
1
|
+
{"version":3,"file":"renderToSvg.js","sourceRoot":"","sources":["../src/renderToSvg.tsx"],"names":[],"mappings":"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"}
|
package/dist/version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const version = "3.1.
|
|
1
|
+
export declare const version = "3.1.12";
|
package/dist/version.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export const version = '3.1.
|
|
1
|
+
export const version = '3.1.12';
|
|
2
2
|
//# sourceMappingURL=version.js.map
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-msaview",
|
|
3
3
|
"author": "Colin",
|
|
4
|
-
"version": "3.1.
|
|
4
|
+
"version": "3.1.12",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"module": "dist/index.js",
|
|
@@ -16,7 +16,6 @@
|
|
|
16
16
|
"scripts": {
|
|
17
17
|
"clean": "rimraf dist",
|
|
18
18
|
"prebuild": "npm run clean",
|
|
19
|
-
"test": "jest",
|
|
20
19
|
"prepublishOnly": "node output-version.js > src/version.ts && git add -A src && git commit -m '[skip ci] Bump version.ts'",
|
|
21
20
|
"build:esm": "tsc",
|
|
22
21
|
"build:bundle": "rollup -c --bundleConfigAsCjs",
|
|
@@ -43,10 +42,8 @@
|
|
|
43
42
|
"d3-array": "^3.2.3",
|
|
44
43
|
"d3-hierarchy": "^3.1.2",
|
|
45
44
|
"file-saver": "^2.0.5",
|
|
46
|
-
"normalize-wheel": "^1.0.1",
|
|
47
45
|
"rbush": "^3.0.1",
|
|
48
46
|
"react-error-boundary": "^4.0.13",
|
|
49
|
-
"rollup": "^4.9.1",
|
|
50
47
|
"stockholm-js": "^1.0.10",
|
|
51
48
|
"svgcanvas": "^2.5.0"
|
|
52
49
|
}
|
|
@@ -28,12 +28,14 @@ const Loading = observer(function ({ model }: { model: MsaViewModel }) {
|
|
|
28
28
|
<ErrorBoundary
|
|
29
29
|
fallbackRender={props => <Reset model={model} error={props.error} />}
|
|
30
30
|
>
|
|
31
|
-
{
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
31
|
+
{initialized ? (
|
|
32
|
+
isLoading ? (
|
|
33
|
+
<Typography variant="h4">Loading...</Typography>
|
|
34
|
+
) : (
|
|
35
|
+
<MSAView model={model} />
|
|
36
|
+
)
|
|
35
37
|
) : (
|
|
36
|
-
<
|
|
38
|
+
<ImportForm model={model} />
|
|
37
39
|
)}
|
|
38
40
|
</ErrorBoundary>
|
|
39
41
|
</div>
|
|
@@ -9,6 +9,7 @@ import Checkbox2 from './Checkbox2'
|
|
|
9
9
|
const useStyles = makeStyles()({
|
|
10
10
|
textAreaFont: {
|
|
11
11
|
fontFamily: 'Courier New',
|
|
12
|
+
wordWrap: 'break-word',
|
|
12
13
|
},
|
|
13
14
|
dialogContent: {
|
|
14
15
|
background: 'lightgrey',
|
|
@@ -21,8 +22,11 @@ export default function SequenceTextArea({ str }: { str: [string, string][] }) {
|
|
|
21
22
|
const { classes } = useStyles()
|
|
22
23
|
const [copied, setCopied] = useState(false)
|
|
23
24
|
const [showGaps, setShowGaps] = useState(false)
|
|
25
|
+
const [showEmpty, setShowEmpty] = useState(false)
|
|
24
26
|
|
|
25
27
|
const disp = str
|
|
28
|
+
.map(([s1, s2]) => [s1, showGaps ? s2 : s2.replaceAll('-', '')])
|
|
29
|
+
.filter(f => (showEmpty ? true : !!f[1]))
|
|
26
30
|
.map(([s1, s2]) => `>${s1}\n${showGaps ? s2 : s2.replaceAll('-', '')}`)
|
|
27
31
|
.join('\n')
|
|
28
32
|
return (
|
|
@@ -43,6 +47,11 @@ export default function SequenceTextArea({ str }: { str: [string, string][] }) {
|
|
|
43
47
|
checked={showGaps}
|
|
44
48
|
onChange={() => setShowGaps(!showGaps)}
|
|
45
49
|
/>
|
|
50
|
+
<Checkbox2
|
|
51
|
+
label="Show empty"
|
|
52
|
+
checked={showEmpty}
|
|
53
|
+
onChange={() => setShowEmpty(!showEmpty)}
|
|
54
|
+
/>
|
|
46
55
|
<TextField
|
|
47
56
|
variant="outlined"
|
|
48
57
|
multiline
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import React, { useState } from 'react'
|
|
2
|
+
import { Dialog } from '@jbrowse/core/ui'
|
|
3
|
+
import { Tab, Tabs } from '@mui/material'
|
|
4
|
+
|
|
5
|
+
// locals
|
|
6
|
+
import InterProScanPanel from './InterProScanPanel'
|
|
7
|
+
import UserProvidedResultPanel from './UserProvidedResultPanel'
|
|
8
|
+
import TabPanel from './TabPanel'
|
|
9
|
+
import { MsaViewModel } from '../../model'
|
|
10
|
+
|
|
11
|
+
export default function LaunchDomainViewDialog({
|
|
12
|
+
handleClose,
|
|
13
|
+
model,
|
|
14
|
+
}: {
|
|
15
|
+
handleClose: () => void
|
|
16
|
+
model: MsaViewModel
|
|
17
|
+
}) {
|
|
18
|
+
const [choice, setChoice] = useState(0)
|
|
19
|
+
return (
|
|
20
|
+
<Dialog
|
|
21
|
+
maxWidth="xl"
|
|
22
|
+
title="Launch protein view"
|
|
23
|
+
onClose={() => handleClose()}
|
|
24
|
+
open
|
|
25
|
+
>
|
|
26
|
+
<Tabs value={choice} onChange={(_, val) => setChoice(val)}>
|
|
27
|
+
<Tab value={0} label="Automatic lookup" />
|
|
28
|
+
<Tab value={1} label="Manual" />
|
|
29
|
+
</Tabs>
|
|
30
|
+
<TabPanel value={choice} index={0}>
|
|
31
|
+
<InterProScanPanel model={model} handleClose={handleClose} />
|
|
32
|
+
</TabPanel>
|
|
33
|
+
<TabPanel value={choice} index={1}>
|
|
34
|
+
<UserProvidedResultPanel model={model} handleClose={handleClose} />
|
|
35
|
+
</TabPanel>
|
|
36
|
+
</Dialog>
|
|
37
|
+
)
|
|
38
|
+
}
|
|
@@ -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
|
|
@@ -82,7 +82,7 @@ const TreeSettings = observer(function TreeSettings({
|
|
|
82
82
|
onChange={() => model.setDrawLabels(!drawLabels)}
|
|
83
83
|
label="Draw labels"
|
|
84
84
|
/>
|
|
85
|
-
{
|
|
85
|
+
{noTree ? null : (
|
|
86
86
|
<div>
|
|
87
87
|
<Checkbox2
|
|
88
88
|
checked={treeWidthMatchesArea}
|
|
@@ -91,7 +91,7 @@ const TreeSettings = observer(function TreeSettings({
|
|
|
91
91
|
}
|
|
92
92
|
label="Make tree width fit to tree area?"
|
|
93
93
|
/>
|
|
94
|
-
{
|
|
94
|
+
{treeWidthMatchesArea ? null : (
|
|
95
95
|
<div className={classes.flex}>
|
|
96
96
|
<Typography>Tree width ({treeWidth}px)</Typography>
|
|
97
97
|
<Slider
|
|
@@ -102,9 +102,9 @@ const TreeSettings = observer(function TreeSettings({
|
|
|
102
102
|
onChange={(_, val) => model.setTreeWidth(val as number)}
|
|
103
103
|
/>
|
|
104
104
|
</div>
|
|
105
|
-
)
|
|
105
|
+
)}
|
|
106
106
|
</div>
|
|
107
|
-
)
|
|
107
|
+
)}
|
|
108
108
|
</div>
|
|
109
109
|
)
|
|
110
110
|
})
|
|
@@ -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)
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import React, { useEffect, useState, useRef } from 'react'
|
|
2
2
|
import { observer } from 'mobx-react'
|
|
3
|
-
import normalizeWheel from 'normalize-wheel'
|
|
4
3
|
|
|
5
4
|
// locals
|
|
6
5
|
import { MsaViewModel } from '../../model'
|
|
@@ -23,10 +22,9 @@ const MSACanvas = observer(function ({ model }: { model: MsaViewModel }) {
|
|
|
23
22
|
if (!curr) {
|
|
24
23
|
return
|
|
25
24
|
}
|
|
26
|
-
function onWheel(
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
deltaY.current += event.pixelY
|
|
25
|
+
function onWheel(event: WheelEvent) {
|
|
26
|
+
deltaX.current += event.deltaX
|
|
27
|
+
deltaY.current += event.deltaY
|
|
30
28
|
|
|
31
29
|
if (!scheduled.current) {
|
|
32
30
|
scheduled.current = true
|
|
@@ -38,8 +36,8 @@ const MSACanvas = observer(function ({ model }: { model: MsaViewModel }) {
|
|
|
38
36
|
scheduled.current = false
|
|
39
37
|
})
|
|
40
38
|
}
|
|
41
|
-
|
|
42
|
-
|
|
39
|
+
event.preventDefault()
|
|
40
|
+
event.stopPropagation()
|
|
43
41
|
}
|
|
44
42
|
curr.addEventListener('wheel', onWheel)
|
|
45
43
|
return () => {
|
|
@@ -121,9 +119,7 @@ const MSACanvas = observer(function ({ model }: { model: MsaViewModel }) {
|
|
|
121
119
|
overflow: 'hidden',
|
|
122
120
|
}}
|
|
123
121
|
>
|
|
124
|
-
{!MSA && !msaFilehandle ? null :
|
|
125
|
-
<Loading />
|
|
126
|
-
) : (
|
|
122
|
+
{!MSA && !msaFilehandle ? null : MSA ? (
|
|
127
123
|
blocks2d.map(([bx, by]) => (
|
|
128
124
|
<MSACanvasBlock
|
|
129
125
|
key={`${bx}_${by}`}
|
|
@@ -132,6 +128,8 @@ const MSACanvas = observer(function ({ model }: { model: MsaViewModel }) {
|
|
|
132
128
|
offsetY={by}
|
|
133
129
|
/>
|
|
134
130
|
))
|
|
131
|
+
) : (
|
|
132
|
+
<Loading />
|
|
135
133
|
)}
|
|
136
134
|
</div>
|
|
137
135
|
)
|