react-msaview 8.2.0 → 8.4.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/bundle/index.js +77 -300
- package/bundle/index.js.map +4 -4
- package/dist/blockFasta.d.ts +6 -0
- package/dist/blockFasta.js +12 -0
- package/dist/blockFasta.js.map +1 -0
- package/dist/colorSchemes.d.ts +7 -0
- package/dist/colorSchemes.js +15 -0
- package/dist/colorSchemes.js.map +1 -1
- package/dist/columnCounts.d.ts +4 -3
- package/dist/columnCounts.js +11 -6
- package/dist/columnCounts.js.map +1 -1
- package/dist/components/CopyButton.d.ts +1 -1
- package/dist/components/CopyButton.js +1 -1
- package/dist/components/CopyButton.js.map +1 -1
- package/dist/components/DragHandle.js +14 -2
- package/dist/components/DragHandle.js.map +1 -1
- package/dist/components/Loading.js +10 -11
- package/dist/components/Loading.js.map +1 -1
- package/dist/components/MSAView.js +15 -9
- package/dist/components/MSAView.js.map +1 -1
- package/dist/components/MSAViewer.d.ts +39 -7
- package/dist/components/MSAViewer.js +72 -22
- package/dist/components/MSAViewer.js.map +1 -1
- package/dist/components/MetadataTable.d.ts +5 -0
- package/dist/components/MetadataTable.js +29 -0
- package/dist/components/MetadataTable.js.map +1 -0
- package/dist/components/SequenceTextArea.d.ts +11 -0
- package/dist/components/SequenceTextArea.js +40 -8
- package/dist/components/SequenceTextArea.js.map +1 -1
- package/dist/components/StatusMessage.d.ts +12 -0
- package/dist/components/StatusMessage.js +43 -0
- package/dist/components/StatusMessage.js.map +1 -0
- package/dist/components/Track.js +13 -4
- package/dist/components/Track.js.map +1 -1
- package/dist/components/VerticalScrollbar.js +6 -1
- package/dist/components/VerticalScrollbar.js.map +1 -1
- package/dist/components/dialogs/ExportSVGDialog.d.ts +3 -2
- package/dist/components/dialogs/ExportSVGDialog.js +4 -2
- package/dist/components/dialogs/ExportSVGDialog.js.map +1 -1
- package/dist/components/dialogs/FeatureDialog.js +15 -10
- package/dist/components/dialogs/FeatureDialog.js.map +1 -1
- package/dist/components/dialogs/MetadataDialog.js +5 -6
- package/dist/components/dialogs/MetadataDialog.js.map +1 -1
- package/dist/components/getVisibleLeaves.d.ts +1 -1
- package/dist/components/header/ColorSchemeMenu.d.ts +6 -0
- package/dist/components/header/ColorSchemeMenu.js +23 -5
- package/dist/components/header/ColorSchemeMenu.js.map +1 -1
- package/dist/components/header/FileMenu.js +9 -0
- package/dist/components/header/FileMenu.js.map +1 -1
- package/dist/components/header/GappynessSlider.js +15 -3
- package/dist/components/header/GappynessSlider.js.map +1 -1
- package/dist/components/header/{HeaderStatusArea.d.ts → GoToBox.d.ts} +2 -2
- package/dist/components/header/GoToBox.js +21 -0
- package/dist/components/header/GoToBox.js.map +1 -0
- package/dist/components/header/Header.js +8 -7
- package/dist/components/header/Header.js.map +1 -1
- package/dist/components/header/SelectionMenu.d.ts +12 -0
- package/dist/components/header/SelectionMenu.js +72 -0
- package/dist/components/header/SelectionMenu.js.map +1 -0
- package/dist/components/header/goTo.d.ts +20 -0
- package/dist/components/header/goTo.js +69 -0
- package/dist/components/header/goTo.js.map +1 -0
- package/dist/components/header/settingsMenuItems.js +57 -1
- package/dist/components/header/settingsMenuItems.js.map +1 -1
- package/dist/components/import/ImportForm.js +10 -4
- package/dist/components/import/ImportForm.js.map +1 -1
- package/dist/components/import/ImportFormExamples.js +19 -27
- package/dist/components/import/ImportFormExamples.js.map +1 -1
- package/dist/components/minimap/Minimap.js +6 -1
- package/dist/components/minimap/Minimap.js.map +1 -1
- package/dist/components/msa/AnnotationLegend.js +11 -5
- package/dist/components/msa/AnnotationLegend.js.map +1 -1
- package/dist/components/msa/MSACanvas.js +17 -1
- package/dist/components/msa/MSACanvas.js.map +1 -1
- package/dist/components/msa/MSACanvasBlock.js +6 -6
- package/dist/components/msa/MSACanvasBlock.js.map +1 -1
- package/dist/components/msa/msaKeys.d.ts +5 -0
- package/dist/components/msa/msaKeys.js +41 -0
- package/dist/components/msa/msaKeys.js.map +1 -0
- package/dist/components/msa/msaRaster.js +1 -0
- package/dist/components/msa/msaRaster.js.map +1 -1
- package/dist/components/msa/renderHighlights.d.ts +14 -3
- package/dist/components/msa/renderHighlights.js +29 -4
- package/dist/components/msa/renderHighlights.js.map +1 -1
- package/dist/components/msa/tileColor.js +9 -8
- package/dist/components/msa/tileColor.js.map +1 -1
- package/dist/components/msa/useMsaBlockMouse.d.ts +2 -4
- package/dist/components/msa/useMsaBlockMouse.js +34 -14
- package/dist/components/msa/useMsaBlockMouse.js.map +1 -1
- package/dist/components/overlayColors.d.ts +2 -0
- package/dist/components/overlayColors.js +3 -0
- package/dist/components/overlayColors.js.map +1 -1
- package/dist/components/rowpanels/RowPanelBlock.js +10 -14
- package/dist/components/rowpanels/RowPanelBlock.js.map +1 -1
- package/dist/components/selectionDrag.d.ts +17 -0
- package/dist/components/selectionDrag.js +30 -0
- package/dist/components/selectionDrag.js.map +1 -0
- package/dist/components/tracks/drawTracks.d.ts +1 -0
- package/dist/components/tracks/drawTracks.js +14 -2
- package/dist/components/tracks/drawTracks.js.map +1 -1
- package/dist/components/tracks/useTrackHover.d.ts +4 -6
- package/dist/components/tracks/useTrackHover.js +30 -10
- package/dist/components/tracks/useTrackHover.js.map +1 -1
- package/dist/components/tree/ScaleBarSVG.d.ts +14 -0
- package/dist/components/tree/ScaleBarSVG.js +11 -0
- package/dist/components/tree/ScaleBarSVG.js.map +1 -0
- package/dist/components/tree/TreeCanvas.js +8 -26
- package/dist/components/tree/TreeCanvas.js.map +1 -1
- package/dist/components/tree/TreeCanvasBlock.js +11 -20
- package/dist/components/tree/TreeCanvasBlock.js.map +1 -1
- package/dist/components/tree/{TreeBranchMenu.d.ts → TreeMenu.d.ts} +5 -4
- package/dist/components/tree/TreeMenu.js +48 -0
- package/dist/components/tree/TreeMenu.js.map +1 -0
- package/dist/components/tree/TreeRuler.js +3 -3
- package/dist/components/tree/TreeRuler.js.map +1 -1
- package/dist/components/tree/dialogs/TreeNodeInfoDialog.js +9 -8
- package/dist/components/tree/dialogs/TreeNodeInfoDialog.js.map +1 -1
- package/dist/components/tree/renderTreeCanvas.js +19 -9
- package/dist/components/tree/renderTreeCanvas.js.map +1 -1
- package/dist/components/tree/renderTreeMouseover.d.ts +5 -0
- package/dist/components/tree/renderTreeMouseover.js +20 -0
- package/dist/components/tree/renderTreeMouseover.js.map +1 -0
- package/dist/components/tree/useTreeHover.d.ts +3 -8
- package/dist/components/tree/useTreeHover.js +8 -10
- package/dist/components/tree/useTreeHover.js.map +1 -1
- package/dist/components/useHoverAnchor.d.ts +19 -0
- package/dist/components/useHoverAnchor.js +18 -0
- package/dist/components/useHoverAnchor.js.map +1 -0
- package/dist/constants.d.ts +3 -0
- package/dist/constants.js +12 -3
- package/dist/constants.js.map +1 -1
- package/dist/element.d.ts +7 -3
- package/dist/element.js +32 -4
- package/dist/element.js.map +1 -1
- package/dist/expandSpec.js +3 -3
- package/dist/expandSpec.js.map +1 -1
- package/dist/exportFileName.d.ts +3 -3
- package/dist/exportFileName.js +4 -4
- package/dist/exportFileName.js.map +1 -1
- package/dist/featuresLayer.d.ts +8 -0
- package/dist/featuresLayer.js +42 -0
- package/dist/featuresLayer.js.map +1 -0
- package/dist/hierarchy.d.ts +51 -0
- package/dist/hierarchy.js +125 -0
- package/dist/hierarchy.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/model/DataModel.d.ts +1 -1
- package/dist/model/DataModel.js +3 -3
- package/dist/model/DataModel.js.map +1 -1
- package/dist/model/msaModel.d.ts +14 -1
- package/dist/model/msaModel.js +17 -1
- package/dist/model/msaModel.js.map +1 -1
- package/dist/model/treeModel.d.ts +12 -0
- package/dist/model/treeModel.js +14 -1
- package/dist/model/treeModel.js.map +1 -1
- package/dist/model.d.ts +285 -53
- package/dist/model.js +693 -231
- package/dist/model.js.map +1 -1
- package/dist/renderToSvg.js +2 -4
- package/dist/renderToSvg.js.map +1 -1
- package/dist/rerootTree.d.ts +33 -0
- package/dist/rerootTree.js +144 -0
- package/dist/rerootTree.js.map +1 -0
- package/dist/svgTestUtil.d.ts +172 -26
- package/dist/types.d.ts +75 -12
- package/dist/useMsaSvgFigure.d.ts +2 -1
- package/dist/useMsaSvgFigure.js +11 -4
- package/dist/useMsaSvgFigure.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/writeNewick.d.ts +8 -0
- package/dist/writeNewick.js +34 -0
- package/dist/writeNewick.js.map +1 -0
- package/package.json +3 -3
- package/src/blockFasta.ts +18 -0
- package/src/colorSchemes.ts +20 -0
- package/src/columnCounts.ts +14 -6
- package/src/components/CopyButton.tsx +2 -2
- package/src/components/DragHandle.tsx +21 -1
- package/src/components/Loading.tsx +14 -30
- package/src/components/MSAView.tsx +16 -9
- package/src/components/MSAViewer.tsx +121 -26
- package/src/components/MetadataTable.tsx +55 -0
- package/src/components/SequenceTextArea.tsx +54 -8
- package/src/components/StatusMessage.tsx +70 -0
- package/src/components/Track.tsx +21 -4
- package/src/components/VerticalScrollbar.tsx +7 -0
- package/src/components/dialogs/ExportSVGDialog.tsx +5 -2
- package/src/components/dialogs/FeatureDialog.tsx +59 -38
- package/src/components/dialogs/MetadataDialog.tsx +9 -8
- package/src/components/header/ColorSchemeMenu.tsx +31 -12
- package/src/components/header/FileMenu.tsx +9 -0
- package/src/components/header/GappynessSlider.tsx +17 -4
- package/src/components/header/GoToBox.tsx +50 -0
- package/src/components/header/Header.tsx +11 -8
- package/src/components/header/SelectionMenu.tsx +117 -0
- package/src/components/header/goTo.ts +75 -0
- package/src/components/header/settingsMenuItems.ts +66 -0
- package/src/components/import/ImportForm.tsx +14 -7
- package/src/components/import/ImportFormExamples.tsx +20 -28
- package/src/components/minimap/Minimap.tsx +7 -0
- package/src/components/msa/AnnotationLegend.tsx +12 -6
- package/src/components/msa/MSACanvas.tsx +19 -1
- package/src/components/msa/MSACanvasBlock.tsx +9 -11
- package/src/components/msa/msaKeys.ts +45 -0
- package/src/components/msa/msaRaster.ts +1 -0
- package/src/components/msa/renderHighlights.ts +46 -4
- package/src/components/msa/tileColor.ts +9 -8
- package/src/components/msa/useMsaBlockMouse.ts +44 -31
- package/src/components/overlayColors.ts +4 -0
- package/src/components/rowpanels/RowPanelBlock.tsx +16 -23
- package/src/components/selectionDrag.ts +42 -0
- package/src/components/tracks/drawTracks.ts +15 -2
- package/src/components/tracks/useTrackHover.ts +32 -10
- package/src/components/tree/ScaleBarSVG.tsx +30 -0
- package/src/components/tree/TreeCanvas.tsx +9 -40
- package/src/components/tree/TreeCanvasBlock.tsx +19 -39
- package/src/components/tree/TreeMenu.tsx +129 -0
- package/src/components/tree/TreeRuler.tsx +7 -13
- package/src/components/tree/dialogs/TreeNodeInfoDialog.tsx +14 -16
- package/src/components/tree/renderTreeCanvas.ts +20 -8
- package/src/components/tree/renderTreeMouseover.ts +41 -0
- package/src/components/tree/useTreeHover.ts +8 -17
- package/src/components/useHoverAnchor.ts +31 -0
- package/src/constants.ts +14 -3
- package/src/element.ts +45 -5
- package/src/expandSpec.ts +3 -3
- package/src/exportFileName.ts +4 -3
- package/src/featuresLayer.ts +48 -0
- package/src/hierarchy.ts +151 -4
- package/src/index.ts +8 -1
- package/src/model/DataModel.ts +3 -3
- package/src/model/msaModel.ts +19 -1
- package/src/model/treeModel.ts +22 -0
- package/src/model.ts +841 -272
- package/src/renderToSvg.tsx +6 -9
- package/src/rerootTree.ts +172 -0
- package/src/types.ts +92 -12
- package/src/useMsaSvgFigure.ts +11 -4
- package/src/version.ts +1 -1
- package/src/writeNewick.ts +38 -0
- package/dist/components/PortalTooltip.d.ts +0 -11
- package/dist/components/PortalTooltip.js +0 -29
- package/dist/components/PortalTooltip.js.map +0 -1
- package/dist/components/header/HeaderStatusArea.js +0 -21
- package/dist/components/header/HeaderStatusArea.js.map +0 -1
- package/dist/components/tree/TreeBranchMenu.js +0 -29
- package/dist/components/tree/TreeBranchMenu.js.map +0 -1
- package/dist/components/tree/TreeNodeMenu.d.ts +0 -13
- package/dist/components/tree/TreeNodeMenu.js +0 -41
- package/dist/components/tree/TreeNodeMenu.js.map +0 -1
- package/src/components/PortalTooltip.tsx +0 -47
- package/src/components/header/HeaderStatusArea.tsx +0 -32
- package/src/components/tree/TreeBranchMenu.tsx +0 -68
- package/src/components/tree/TreeNodeMenu.tsx +0 -95
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import React, { useState } from 'react'
|
|
2
2
|
|
|
3
3
|
import { ErrorMessage, FileSelector } from '@jbrowse/core/ui'
|
|
4
|
-
import { Button, Container, Typography } from '@mui/material'
|
|
4
|
+
import { Box, Button, Container, Typography } from '@mui/material'
|
|
5
5
|
import { observer } from 'mobx-react'
|
|
6
6
|
|
|
7
7
|
import ImportFormExamples from './ImportFormExamples.tsx'
|
|
@@ -10,6 +10,8 @@ import { load } from './util.ts'
|
|
|
10
10
|
import type { MsaViewModel } from '../../model.ts'
|
|
11
11
|
import type { FileLocation } from '@jbrowse/core/util/types'
|
|
12
12
|
|
|
13
|
+
const phone = '@media (max-width: 640px)'
|
|
14
|
+
|
|
13
15
|
const ImportForm = observer(function ({ model }: { model: MsaViewModel }) {
|
|
14
16
|
const [msaFile, setMsaFile] = useState<FileLocation>()
|
|
15
17
|
const [treeFile, setTreeFile] = useState<FileLocation>()
|
|
@@ -18,7 +20,7 @@ const ImportForm = observer(function ({ model }: { model: MsaViewModel }) {
|
|
|
18
20
|
|
|
19
21
|
return (
|
|
20
22
|
<Container>
|
|
21
|
-
<
|
|
23
|
+
<Box sx={{ width: '50%', [phone]: { width: '100%' } }}>
|
|
22
24
|
{error ? <ErrorMessage error={error} /> : null}
|
|
23
25
|
<Typography>
|
|
24
26
|
Open an MSA file (FASTA, Stockholm, Clustal, A3M or EMF format) and/or
|
|
@@ -28,14 +30,19 @@ const ImportForm = observer(function ({ model }: { model: MsaViewModel }) {
|
|
|
28
30
|
An MSA alone or a tree alone is enough, and a Stockholm file with an
|
|
29
31
|
embedded tree needs no separate tree file.
|
|
30
32
|
</Typography>
|
|
31
|
-
</
|
|
33
|
+
</Box>
|
|
32
34
|
|
|
33
|
-
<
|
|
34
|
-
|
|
35
|
+
<Box
|
|
36
|
+
sx={{
|
|
35
37
|
display: 'flex',
|
|
36
38
|
justifyContent: 'center',
|
|
37
39
|
alignItems: 'center',
|
|
38
|
-
gap:
|
|
40
|
+
gap: '40px',
|
|
41
|
+
[phone]: {
|
|
42
|
+
flexDirection: 'column',
|
|
43
|
+
alignItems: 'stretch',
|
|
44
|
+
gap: '16px',
|
|
45
|
+
},
|
|
39
46
|
}}
|
|
40
47
|
>
|
|
41
48
|
<div>
|
|
@@ -74,7 +81,7 @@ const ImportForm = observer(function ({ model }: { model: MsaViewModel }) {
|
|
|
74
81
|
<Typography>Examples</Typography>
|
|
75
82
|
<ImportFormExamples model={model} />
|
|
76
83
|
</div>
|
|
77
|
-
</
|
|
84
|
+
</Box>
|
|
78
85
|
</Container>
|
|
79
86
|
)
|
|
80
87
|
})
|
|
@@ -3,16 +3,17 @@ import React from 'react'
|
|
|
3
3
|
import { Link, Typography } from '@mui/material'
|
|
4
4
|
import { observer } from 'mobx-react'
|
|
5
5
|
|
|
6
|
-
import { smallMSA,
|
|
6
|
+
import { smallMSA, smallTree } from './data/seq2.ts'
|
|
7
7
|
import { load } from './util.ts'
|
|
8
8
|
|
|
9
9
|
import type { MsaViewModel } from '../../model.ts'
|
|
10
10
|
|
|
11
11
|
const BASE = 'https://jbrowse.org/genomes/multiple_sequence_alignments'
|
|
12
12
|
const TREES = 'https://jbrowse.org/genomes/newick_trees'
|
|
13
|
+
const DEMO = 'https://gmod.org/JBrowseMSA/demo/data'
|
|
13
14
|
|
|
14
15
|
// each example points at remote files (msa/tree/gff urls) or carries its data
|
|
15
|
-
// inline
|
|
16
|
+
// inline
|
|
16
17
|
interface Example {
|
|
17
18
|
label: string
|
|
18
19
|
msa?: string
|
|
@@ -23,42 +24,33 @@ interface Example {
|
|
|
23
24
|
|
|
24
25
|
const examples: Example[] = [
|
|
25
26
|
{
|
|
26
|
-
label: '
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
label: 'Small protein MSA+tree',
|
|
31
|
-
inline: { msa: smallMSA, tree: smallTree },
|
|
32
|
-
},
|
|
33
|
-
{
|
|
34
|
-
label: 'Small MSA only',
|
|
35
|
-
inline: { msa: smallMSAOnly },
|
|
36
|
-
},
|
|
37
|
-
{
|
|
38
|
-
label: 'PFAM SARS-CoV2 multi-stockholm with domains',
|
|
39
|
-
msa: `${BASE}/pfam-cov2.stock`,
|
|
40
|
-
gff: `${BASE}/pfam-cov2-domains.gff`,
|
|
27
|
+
label: 'NLRP1 in 12 mammals, with Pfam domains',
|
|
28
|
+
msa: `${DEMO}/nlrp1.aln`,
|
|
29
|
+
tree: `${DEMO}/nlrp1.nh`,
|
|
30
|
+
gff: `${DEMO}/nlrp1-domains.gff`,
|
|
41
31
|
},
|
|
42
32
|
{
|
|
43
|
-
label: '
|
|
44
|
-
msa: `${
|
|
33
|
+
label: '474 human protein kinase domains, with a FastTree tree',
|
|
34
|
+
msa: `${DEMO}/kinase-pocket/kinase-pocket.afa`,
|
|
35
|
+
tree: `${DEMO}/kinase-pocket/kinase-pocket.nwk`,
|
|
45
36
|
},
|
|
46
37
|
{
|
|
47
|
-
label: '
|
|
48
|
-
msa: `${
|
|
38
|
+
label: 'RSV-A G gene in 1,840 genomes, with the Nextstrain tree',
|
|
39
|
+
msa: `${DEMO}/scale/rsv-full.aln`,
|
|
40
|
+
tree: `${DEMO}/scale/rsv-full.nh`,
|
|
49
41
|
},
|
|
50
42
|
{
|
|
51
|
-
label: '
|
|
52
|
-
|
|
43
|
+
label: '230k COVID-19 samples (tree only)',
|
|
44
|
+
tree: `${TREES}/sarscov2phylo.pub.ft.nh`,
|
|
53
45
|
},
|
|
54
46
|
{
|
|
55
|
-
label: '
|
|
56
|
-
msa: `${BASE}/
|
|
57
|
-
|
|
47
|
+
label: 'Pfam SARS-CoV-2 families, multi-Stockholm with domains',
|
|
48
|
+
msa: `${BASE}/pfam-cov2.stock`,
|
|
49
|
+
gff: `${BASE}/pfam-cov2-domains.gff`,
|
|
58
50
|
},
|
|
59
51
|
{
|
|
60
|
-
label: '
|
|
61
|
-
msa:
|
|
52
|
+
label: 'Small protein alignment and tree',
|
|
53
|
+
inline: { msa: smallMSA, tree: smallTree },
|
|
62
54
|
},
|
|
63
55
|
]
|
|
64
56
|
|
|
@@ -70,6 +70,13 @@ const Minimap = observer(function ({ model }: { model: MsaViewModel }) {
|
|
|
70
70
|
ref={ref}
|
|
71
71
|
width={barWidth}
|
|
72
72
|
height={barHeight}
|
|
73
|
+
onMouseDown={event => {
|
|
74
|
+
if (event.button === 0 && unit > 0) {
|
|
75
|
+
const x =
|
|
76
|
+
event.clientX - event.currentTarget.getBoundingClientRect().left
|
|
77
|
+
model.setScrollX(-x / unit + msaCanvasWidth / 2)
|
|
78
|
+
}
|
|
79
|
+
}}
|
|
73
80
|
style={{
|
|
74
81
|
display: 'block',
|
|
75
82
|
width: msaCanvasWidth,
|
|
@@ -32,16 +32,17 @@ const AnnotationLegend = observer(function ({
|
|
|
32
32
|
display: 'flex',
|
|
33
33
|
flexDirection: 'column',
|
|
34
34
|
opacity: 0.95,
|
|
35
|
+
pointerEvents: 'none',
|
|
35
36
|
}}
|
|
36
37
|
>
|
|
37
38
|
<IconButton
|
|
38
39
|
size="small"
|
|
39
40
|
title={expanded ? 'Collapse key' : 'Expand key'}
|
|
40
|
-
style={
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
}
|
|
41
|
+
style={{
|
|
42
|
+
padding: 1,
|
|
43
|
+
pointerEvents: 'auto',
|
|
44
|
+
...(expanded && { position: 'absolute', top: 0, right: 0 }),
|
|
45
|
+
}}
|
|
45
46
|
onClick={() => {
|
|
46
47
|
model.setShowDomainLegend(!expanded)
|
|
47
48
|
}}
|
|
@@ -59,7 +60,6 @@ const AnnotationLegend = observer(function ({
|
|
|
59
60
|
gap: 4,
|
|
60
61
|
marginRight: i === 0 ? 18 : 0,
|
|
61
62
|
}}
|
|
62
|
-
title={row.hint}
|
|
63
63
|
>
|
|
64
64
|
{row.color ? (
|
|
65
65
|
<div
|
|
@@ -74,10 +74,16 @@ const AnnotationLegend = observer(function ({
|
|
|
74
74
|
<Typography
|
|
75
75
|
variant="caption"
|
|
76
76
|
noWrap
|
|
77
|
+
title={
|
|
78
|
+
row.hint && row.hint !== row.label
|
|
79
|
+
? `${row.label} (${row.hint})`
|
|
80
|
+
: row.label
|
|
81
|
+
}
|
|
77
82
|
style={{
|
|
78
83
|
fontSize: 10,
|
|
79
84
|
lineHeight: 1.4,
|
|
80
85
|
fontWeight: row.color ? undefined : 'bold',
|
|
86
|
+
pointerEvents: 'auto',
|
|
81
87
|
}}
|
|
82
88
|
>
|
|
83
89
|
{row.label}
|
|
@@ -5,6 +5,7 @@ import { observer } from 'mobx-react'
|
|
|
5
5
|
import { useWheelScroll } from '../../useWheelScroll.ts'
|
|
6
6
|
import Loading from './Loading.tsx'
|
|
7
7
|
import MSACanvasBlock from './MSACanvasBlock.tsx'
|
|
8
|
+
import { onMsaKey } from './msaKeys.ts'
|
|
8
9
|
|
|
9
10
|
import type { MsaViewModel } from '../../model.ts'
|
|
10
11
|
|
|
@@ -52,7 +53,24 @@ const MSACanvas = observer(function ({ model }: { model: MsaViewModel }) {
|
|
|
52
53
|
// screenshot callouts anchor to columns from this rect's origin:
|
|
53
54
|
// col*colWidth + scrollX, row*rowHeight + scrollY
|
|
54
55
|
data-testid="msa_canvas"
|
|
55
|
-
|
|
56
|
+
tabIndex={0}
|
|
57
|
+
aria-label="Alignment. Arrow keys scroll, shift pages, + and - zoom, Home and End jump to the first and last column. Shift-drag selects cells, and Escape clears the selection."
|
|
58
|
+
onKeyDown={event => {
|
|
59
|
+
if (onMsaKey(model, event)) {
|
|
60
|
+
event.preventDefault()
|
|
61
|
+
}
|
|
62
|
+
}}
|
|
63
|
+
onMouseDown={event => {
|
|
64
|
+
// a shifted press selects cells (useMsaBlockMouse), so it takes the
|
|
65
|
+
// focus Escape clears from, and leaves out the pan and the page's own
|
|
66
|
+
// text selection
|
|
67
|
+
if (event.shiftKey) {
|
|
68
|
+
event.preventDefault()
|
|
69
|
+
ref.current?.focus({ preventScroll: true })
|
|
70
|
+
} else {
|
|
71
|
+
onMouseDown(event)
|
|
72
|
+
}
|
|
73
|
+
}}
|
|
56
74
|
onMouseUp={onMouseUp}
|
|
57
75
|
style={{
|
|
58
76
|
position: 'relative',
|
|
@@ -23,13 +23,8 @@ const MSACanvasBlock = observer(function ({
|
|
|
23
23
|
}) {
|
|
24
24
|
const { blockSize, highResScaleFactor } = model
|
|
25
25
|
const theme = useTheme()
|
|
26
|
-
const {
|
|
27
|
-
{
|
|
28
|
-
model,
|
|
29
|
-
offsetX,
|
|
30
|
-
offsetY,
|
|
31
|
-
},
|
|
32
|
-
)
|
|
26
|
+
const { anchor, onMouseMove, onMouseDown, onClick, onMouseLeave } =
|
|
27
|
+
useMsaBlockMouse({ model, offsetX, offsetY })
|
|
33
28
|
|
|
34
29
|
const canvasSize = blockSize * highResScaleFactor
|
|
35
30
|
const ref = useCanvasAutorun({
|
|
@@ -76,6 +71,11 @@ const MSACanvasBlock = observer(function ({
|
|
|
76
71
|
onMouseMove(event, ref.current)
|
|
77
72
|
}
|
|
78
73
|
}}
|
|
74
|
+
onMouseDown={event => {
|
|
75
|
+
if (ref.current) {
|
|
76
|
+
onMouseDown(event, ref.current)
|
|
77
|
+
}
|
|
78
|
+
}}
|
|
79
79
|
onClick={event => {
|
|
80
80
|
if (ref.current) {
|
|
81
81
|
onClick(event, ref.current)
|
|
@@ -94,10 +94,8 @@ const MSACanvasBlock = observer(function ({
|
|
|
94
94
|
height: blockSize,
|
|
95
95
|
}}
|
|
96
96
|
/>
|
|
97
|
-
{
|
|
98
|
-
<BaseTooltip
|
|
99
|
-
clientPoint={{ x: tooltipPoint.x, y: tooltipPoint.y + 15 }}
|
|
100
|
-
>
|
|
97
|
+
{anchor ? (
|
|
98
|
+
<BaseTooltip clientPoint={anchor.clientPoint}>
|
|
101
99
|
{hoveredInsertion ? (
|
|
102
100
|
<>
|
|
103
101
|
Insertion ({hoveredInsertion.letters.length}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { MsaViewModel } from '../../model.ts'
|
|
2
|
+
|
|
3
|
+
export function onMsaKey(
|
|
4
|
+
model: MsaViewModel,
|
|
5
|
+
{ key, shiftKey }: { key: string; shiftKey: boolean },
|
|
6
|
+
) {
|
|
7
|
+
const { colWidth, rowHeight, msaCanvasWidth, msaAreaHeight } = model
|
|
8
|
+
const dx = shiftKey ? msaCanvasWidth : colWidth
|
|
9
|
+
const dy = shiftKey ? msaAreaHeight : rowHeight
|
|
10
|
+
switch (key) {
|
|
11
|
+
case 'ArrowLeft':
|
|
12
|
+
model.doScrollX(dx)
|
|
13
|
+
return true
|
|
14
|
+
case 'ArrowRight':
|
|
15
|
+
model.doScrollX(-dx)
|
|
16
|
+
return true
|
|
17
|
+
case 'ArrowUp':
|
|
18
|
+
model.doScrollY(dy)
|
|
19
|
+
return true
|
|
20
|
+
case 'ArrowDown':
|
|
21
|
+
model.doScrollY(-dy)
|
|
22
|
+
return true
|
|
23
|
+
case '+':
|
|
24
|
+
case '=':
|
|
25
|
+
model.zoomIn()
|
|
26
|
+
return true
|
|
27
|
+
case '-':
|
|
28
|
+
model.zoomOut()
|
|
29
|
+
return true
|
|
30
|
+
case 'Home':
|
|
31
|
+
model.setScrollX(0)
|
|
32
|
+
return true
|
|
33
|
+
case 'End':
|
|
34
|
+
model.setScrollX(model.maxScrollX)
|
|
35
|
+
return true
|
|
36
|
+
case 'Escape':
|
|
37
|
+
if (model.selection) {
|
|
38
|
+
model.clearSelection()
|
|
39
|
+
return true
|
|
40
|
+
}
|
|
41
|
+
return false
|
|
42
|
+
default:
|
|
43
|
+
return false
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import { setFontSize } from '../../setFontSize.ts'
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
referenceColor,
|
|
4
|
+
selectionBorder,
|
|
5
|
+
selectionFill,
|
|
6
|
+
} from '../overlayColors.ts'
|
|
3
7
|
|
|
4
8
|
import type { MsaViewModel } from '../../model.ts'
|
|
5
9
|
import type { RenderCtx } from '../renderCtx.ts'
|
|
@@ -172,11 +176,48 @@ export function renderHighlights({
|
|
|
172
176
|
}
|
|
173
177
|
}
|
|
174
178
|
|
|
179
|
+
/**
|
|
180
|
+
* The selected block, a bordered band per run of consecutive selected rows,
|
|
181
|
+
* culled to the rows this block covers
|
|
182
|
+
*/
|
|
183
|
+
export function renderSelection({
|
|
184
|
+
ctx,
|
|
185
|
+
model,
|
|
186
|
+
offsetX,
|
|
187
|
+
offsetY,
|
|
188
|
+
height,
|
|
189
|
+
}: {
|
|
190
|
+
ctx: RenderCtx
|
|
191
|
+
model: MsaViewModel
|
|
192
|
+
offsetX: number
|
|
193
|
+
offsetY: number
|
|
194
|
+
height: number
|
|
195
|
+
}) {
|
|
196
|
+
const { resolvedSelection, colWidth, rowHeight } = model
|
|
197
|
+
if (!resolvedSelection) {
|
|
198
|
+
return
|
|
199
|
+
}
|
|
200
|
+
const { startCol, endCol, rowRuns } = resolvedSelection
|
|
201
|
+
const x = startCol * colWidth - offsetX
|
|
202
|
+
const w = (endCol - startCol + 1) * colWidth
|
|
203
|
+
ctx.lineWidth = 1
|
|
204
|
+
for (const [first, last] of rowRuns) {
|
|
205
|
+
const y = first * rowHeight - offsetY
|
|
206
|
+
const h = (last - first + 1) * rowHeight
|
|
207
|
+
if (y < height && y + h > 0) {
|
|
208
|
+
ctx.fillStyle = selectionFill
|
|
209
|
+
ctx.fillRect(x, y, w, h)
|
|
210
|
+
ctx.strokeStyle = selectionBorder
|
|
211
|
+
ctx.strokeRect(x, y, w, h)
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
175
216
|
/**
|
|
176
217
|
* The overlay parts that come from the snapshot: the reference row's tint, a
|
|
177
|
-
* bordered band per run of highlighted columns,
|
|
178
|
-
* live overlay draws these under its hover and click
|
|
179
|
-
* draws only these.
|
|
218
|
+
* bordered band per run of highlighted columns, the `highlights` layer and the
|
|
219
|
+
* selected block. The live overlay draws these under its hover and click
|
|
220
|
+
* bands; the SVG export draws only these.
|
|
180
221
|
*/
|
|
181
222
|
export function renderPersistentHighlights({
|
|
182
223
|
ctx,
|
|
@@ -214,4 +255,5 @@ export function renderPersistentHighlights({
|
|
|
214
255
|
}
|
|
215
256
|
|
|
216
257
|
renderHighlights({ ctx, model, theme, offsetX, offsetY, width, height })
|
|
258
|
+
renderSelection({ ctx, model, offsetX, offsetY, height })
|
|
217
259
|
}
|
|
@@ -4,12 +4,12 @@ import type { MsaViewModel } from '../../model.ts'
|
|
|
4
4
|
// only the active scheme's column table leaves the other per-column table
|
|
5
5
|
// uncomputed.
|
|
6
6
|
export function tileColorFn(model: MsaViewModel) {
|
|
7
|
-
const {
|
|
8
|
-
if (
|
|
7
|
+
const { dynamicColorSchemeName } = model
|
|
8
|
+
if (dynamicColorSchemeName === 'clustalx_protein_dynamic') {
|
|
9
9
|
const { colClustalX } = model
|
|
10
10
|
return (col: number, letter: string) => colClustalX[col]![letter]
|
|
11
11
|
}
|
|
12
|
-
if (
|
|
12
|
+
if (dynamicColorSchemeName === 'percent_identity_dynamic') {
|
|
13
13
|
const { colConsensus } = model
|
|
14
14
|
return (col: number, letter: string) => {
|
|
15
15
|
const consensus = colConsensus[col]!
|
|
@@ -20,14 +20,15 @@ export function tileColorFn(model: MsaViewModel) {
|
|
|
20
20
|
return (_col: number, letter: string) => colorScheme[letter]
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
// The
|
|
24
|
-
//
|
|
23
|
+
// The table the active scheme colors from: a dynamic scheme's per-column table,
|
|
24
|
+
// or the letter table of a static scheme or a `customColorScheme` map. A cache
|
|
25
|
+
// of rendered colors keys on its identity plus the scheme name.
|
|
25
26
|
export function tileColorTable(model: MsaViewModel) {
|
|
26
|
-
const {
|
|
27
|
-
if (
|
|
27
|
+
const { dynamicColorSchemeName } = model
|
|
28
|
+
if (dynamicColorSchemeName === 'clustalx_protein_dynamic') {
|
|
28
29
|
return model.colClustalX
|
|
29
30
|
}
|
|
30
|
-
if (
|
|
31
|
+
if (dynamicColorSchemeName === 'percent_identity_dynamic') {
|
|
31
32
|
return model.colConsensus
|
|
32
33
|
}
|
|
33
34
|
return model.colorScheme
|
|
@@ -1,30 +1,12 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { useRef } from 'react'
|
|
2
|
+
|
|
3
|
+
import { dragSelection, maxClickTravel } from '../selectionDrag.ts'
|
|
4
|
+
import { useHoverAnchor } from '../useHoverAnchor.ts'
|
|
2
5
|
|
|
3
6
|
import type { MsaViewModel } from '../../model.ts'
|
|
7
|
+
import type { ClientPoint } from '../selectionDrag.ts'
|
|
4
8
|
import type React from 'react'
|
|
5
9
|
|
|
6
|
-
function eventToColRow({
|
|
7
|
-
event,
|
|
8
|
-
el,
|
|
9
|
-
offsetX,
|
|
10
|
-
offsetY,
|
|
11
|
-
colWidth,
|
|
12
|
-
rowHeight,
|
|
13
|
-
}: {
|
|
14
|
-
event: React.MouseEvent
|
|
15
|
-
el: HTMLElement
|
|
16
|
-
offsetX: number
|
|
17
|
-
offsetY: number
|
|
18
|
-
colWidth: number
|
|
19
|
-
rowHeight: number
|
|
20
|
-
}) {
|
|
21
|
-
const { left, top } = el.getBoundingClientRect()
|
|
22
|
-
return {
|
|
23
|
-
col: Math.floor((event.clientX - left + offsetX) / colWidth),
|
|
24
|
-
row: Math.floor((event.clientY - top + offsetY) / rowHeight),
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
|
|
28
10
|
/**
|
|
29
11
|
* Translates pointer events over one MSA block into model column/row hover and
|
|
30
12
|
* click state, and reports where a tooltip should be anchored.
|
|
@@ -44,11 +26,26 @@ export function useMsaBlockMouse({
|
|
|
44
26
|
offsetX: number
|
|
45
27
|
offsetY: number
|
|
46
28
|
}) {
|
|
47
|
-
const
|
|
29
|
+
const { anchor, hoverAt, clearAnchor } = useHoverAnchor<true>()
|
|
30
|
+
const downAt = useRef<{ x: number; y: number }>(undefined)
|
|
31
|
+
|
|
32
|
+
// The block's rect places its cells on the page. A drag keeps the mapping
|
|
33
|
+
// it started with, so each point subtracts the scroll since then.
|
|
34
|
+
function cellMapper(el: HTMLElement) {
|
|
35
|
+
const { left, top } = el.getBoundingClientRect()
|
|
36
|
+
const { scrollX, scrollY } = model
|
|
37
|
+
return ({ clientX, clientY }: ClientPoint) => ({
|
|
38
|
+
col: Math.floor(
|
|
39
|
+
(clientX - left + offsetX - (model.scrollX - scrollX)) / model.colWidth,
|
|
40
|
+
),
|
|
41
|
+
row: Math.floor(
|
|
42
|
+
(clientY - top + offsetY - (model.scrollY - scrollY)) / model.rowHeight,
|
|
43
|
+
),
|
|
44
|
+
})
|
|
45
|
+
}
|
|
48
46
|
|
|
49
47
|
function colRow(event: React.MouseEvent, el: HTMLElement) {
|
|
50
|
-
|
|
51
|
-
return eventToColRow({ event, el, offsetX, offsetY, colWidth, rowHeight })
|
|
48
|
+
return cellMapper(el)(event)
|
|
52
49
|
}
|
|
53
50
|
|
|
54
51
|
// a block can extend past the alignment (the last block is a full tile wide,
|
|
@@ -65,12 +62,28 @@ export function useMsaBlockMouse({
|
|
|
65
62
|
|
|
66
63
|
const hasTooltip =
|
|
67
64
|
!!model.hoveredInsertion || model.mouseOverDomains.length > 0
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
65
|
+
hoverAt(event, hasTooltip || undefined)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// shift+drag selects a block; MSACanvas leaves a shifted press out of its
|
|
69
|
+
// pan
|
|
70
|
+
function onMouseDown(event: React.MouseEvent, el: HTMLElement) {
|
|
71
|
+
downAt.current = { x: event.clientX, y: event.clientY }
|
|
72
|
+
if (event.shiftKey && event.button === 0) {
|
|
73
|
+
dragSelection(model, event, cellMapper(el))
|
|
74
|
+
}
|
|
71
75
|
}
|
|
72
76
|
|
|
73
77
|
function onClick(event: React.MouseEvent, el: HTMLElement) {
|
|
78
|
+
const down = downAt.current
|
|
79
|
+
downAt.current = undefined
|
|
80
|
+
if (
|
|
81
|
+
down &&
|
|
82
|
+
Math.hypot(event.clientX - down.x, event.clientY - down.y) >
|
|
83
|
+
maxClickTravel
|
|
84
|
+
) {
|
|
85
|
+
return
|
|
86
|
+
}
|
|
74
87
|
const pos = colRow(event, el)
|
|
75
88
|
const { col, row } = pos
|
|
76
89
|
const { mouseClickCol, mouseClickRow } = model
|
|
@@ -83,8 +96,8 @@ export function useMsaBlockMouse({
|
|
|
83
96
|
|
|
84
97
|
function onMouseLeave() {
|
|
85
98
|
model.setMousePos()
|
|
86
|
-
|
|
99
|
+
clearAnchor()
|
|
87
100
|
}
|
|
88
101
|
|
|
89
|
-
return {
|
|
102
|
+
return { anchor, onMouseMove, onMouseDown, onClick, onMouseLeave }
|
|
90
103
|
}
|
|
@@ -16,3 +16,7 @@ export const multiRowHoverColor = 'rgba(255,165,0,0.15)'
|
|
|
16
16
|
// match or the crosshair appears to change color as it crosses the tracks.
|
|
17
17
|
export const hoverColor = 'rgba(0,0,0,0.15)'
|
|
18
18
|
export const clickColor = 'rgba(128,128,0,0.2)'
|
|
19
|
+
|
|
20
|
+
// the selected block, a blue that reads apart from the orange highlights
|
|
21
|
+
export const selectionFill = 'rgba(25,118,210,0.22)'
|
|
22
|
+
export const selectionBorder = 'rgba(25,118,210,0.95)'
|
|
@@ -1,23 +1,17 @@
|
|
|
1
|
-
import React
|
|
1
|
+
import React from 'react'
|
|
2
2
|
|
|
3
|
+
import BaseTooltip from '@jbrowse/core/ui/BaseTooltip'
|
|
3
4
|
import { useTheme } from '@mui/material'
|
|
4
5
|
import { observer } from 'mobx-react'
|
|
5
6
|
|
|
6
7
|
import { featureField, featureName } from '../../featureFields.ts'
|
|
7
8
|
import { useCanvasAutorun } from '../../useCanvasAutorun.ts'
|
|
8
|
-
import
|
|
9
|
+
import { useHoverAnchor } from '../useHoverAnchor.ts'
|
|
9
10
|
import { renderRowPanel } from './renderRowPanel.ts'
|
|
10
11
|
|
|
11
12
|
import type { MsaViewModel } from '../../model.ts'
|
|
12
13
|
import type { ResolvedRowPanel } from '../../types.ts'
|
|
13
14
|
|
|
14
|
-
interface Hover {
|
|
15
|
-
name: string
|
|
16
|
-
lines: string[]
|
|
17
|
-
clientX: number
|
|
18
|
-
clientY: number
|
|
19
|
-
}
|
|
20
|
-
|
|
21
15
|
// what the cell or span under the pointer reads, which is the field a strip
|
|
22
16
|
// colors by, and a span's name, its coordinates and its encoded field
|
|
23
17
|
function hoverLines({
|
|
@@ -66,7 +60,10 @@ const RowPanelBlock = observer(function ({
|
|
|
66
60
|
panel: ResolvedRowPanel
|
|
67
61
|
offsetY: number
|
|
68
62
|
}) {
|
|
69
|
-
const
|
|
63
|
+
const { anchor, hoverAt, clearAnchor } = useHoverAnchor<{
|
|
64
|
+
name: string
|
|
65
|
+
lines: string[]
|
|
66
|
+
}>()
|
|
70
67
|
const theme = useTheme()
|
|
71
68
|
const { blockSize, highResScaleFactor, rowHeight } = model
|
|
72
69
|
const width = panel.width
|
|
@@ -107,30 +104,26 @@ const RowPanelBlock = observer(function ({
|
|
|
107
104
|
name,
|
|
108
105
|
x: event.clientX - left,
|
|
109
106
|
})
|
|
110
|
-
|
|
107
|
+
hoverAt(
|
|
108
|
+
event,
|
|
111
109
|
name === undefined || lines.length === 0
|
|
112
110
|
? undefined
|
|
113
|
-
: {
|
|
114
|
-
name,
|
|
115
|
-
lines,
|
|
116
|
-
clientX: event.clientX,
|
|
117
|
-
clientY: event.clientY,
|
|
118
|
-
},
|
|
111
|
+
: { name, lines },
|
|
119
112
|
)
|
|
120
113
|
model.setMousePos(undefined, name === undefined ? undefined : row)
|
|
121
114
|
}}
|
|
122
115
|
onMouseLeave={() => {
|
|
123
|
-
|
|
116
|
+
clearAnchor()
|
|
124
117
|
model.setMousePos(undefined, undefined)
|
|
125
118
|
}}
|
|
126
119
|
/>
|
|
127
|
-
{
|
|
128
|
-
<
|
|
129
|
-
<div>{
|
|
130
|
-
{
|
|
120
|
+
{anchor ? (
|
|
121
|
+
<BaseTooltip clientPoint={anchor.clientPoint}>
|
|
122
|
+
<div>{anchor.value.name}</div>
|
|
123
|
+
{anchor.value.lines.map(line => (
|
|
131
124
|
<div key={line}>{line}</div>
|
|
132
125
|
))}
|
|
133
|
-
</
|
|
126
|
+
</BaseTooltip>
|
|
134
127
|
) : null}
|
|
135
128
|
</>
|
|
136
129
|
)
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { isAlive } from '@jbrowse/mobx-state-tree'
|
|
2
|
+
|
|
3
|
+
import type { MsaViewModel } from '../model.ts'
|
|
4
|
+
|
|
5
|
+
// a press that travels further than this, in px, is a drag and not a click
|
|
6
|
+
export const maxClickTravel = 3
|
|
7
|
+
|
|
8
|
+
export interface ClientPoint {
|
|
9
|
+
clientX: number
|
|
10
|
+
clientY: number
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Follows a press through the window until its release, selecting the block
|
|
15
|
+
* between the cell it started on and the cell under the pointer. The selection
|
|
16
|
+
* starts once the pointer travels past `maxClickTravel`, so a press that stays
|
|
17
|
+
* put reaches the click handlers unchanged. `cellAt` maps a client point to a
|
|
18
|
+
* visible column and row index, or a column alone to select across every row.
|
|
19
|
+
*/
|
|
20
|
+
export function dragSelection(
|
|
21
|
+
model: MsaViewModel,
|
|
22
|
+
start: ClientPoint,
|
|
23
|
+
cellAt: (point: ClientPoint) => { col: number; row?: number },
|
|
24
|
+
) {
|
|
25
|
+
const anchor = cellAt(start)
|
|
26
|
+
const { clientX, clientY } = start
|
|
27
|
+
let dragging = false
|
|
28
|
+
function onMove(event: MouseEvent) {
|
|
29
|
+
dragging ||=
|
|
30
|
+
Math.hypot(event.clientX - clientX, event.clientY - clientY) >
|
|
31
|
+
maxClickTravel
|
|
32
|
+
if (dragging && isAlive(model)) {
|
|
33
|
+
model.selectBlock(anchor, cellAt(event))
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
function onUp() {
|
|
37
|
+
window.removeEventListener('mousemove', onMove)
|
|
38
|
+
window.removeEventListener('mouseup', onUp)
|
|
39
|
+
}
|
|
40
|
+
window.addEventListener('mousemove', onMove)
|
|
41
|
+
window.addEventListener('mouseup', onUp)
|
|
42
|
+
}
|