@apollo-annotation/jbrowse-plugin-apollo 0.3.6 → 0.3.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.esm.js +4603 -2045
- package/dist/index.esm.js.map +1 -1
- package/dist/jbrowse-plugin-apollo.cjs.development.js +4611 -2039
- package/dist/jbrowse-plugin-apollo.cjs.development.js.map +1 -1
- package/dist/jbrowse-plugin-apollo.cjs.production.min.js +1 -1
- package/dist/jbrowse-plugin-apollo.cjs.production.min.js.map +1 -1
- package/dist/jbrowse-plugin-apollo.umd.development.js +9387 -4016
- package/dist/jbrowse-plugin-apollo.umd.development.js.map +1 -1
- package/dist/jbrowse-plugin-apollo.umd.production.min.js +1 -1
- package/dist/jbrowse-plugin-apollo.umd.production.min.js.map +1 -1
- package/package.json +15 -15
- package/src/ApolloInternetAccount/model.ts +48 -13
- package/src/BackendDrivers/CollaborationServerDriver.ts +23 -2
- package/src/ChangeManager.ts +42 -18
- package/src/FeatureDetailsWidget/ApolloTranscriptDetailsWidget.tsx +64 -5
- package/src/FeatureDetailsWidget/Attributes.tsx +8 -3
- package/src/FeatureDetailsWidget/TranscriptSequence.tsx +70 -81
- package/src/FeatureDetailsWidget/TranscriptWidgetEditLocation.tsx +946 -190
- package/src/FeatureDetailsWidget/TranscriptWidgetSummary.tsx +4 -0
- package/src/LinearApolloDisplay/components/LinearApolloDisplay.tsx +61 -73
- package/src/LinearApolloDisplay/glyphs/BoxGlyph.ts +55 -211
- package/src/LinearApolloDisplay/glyphs/GeneGlyph.ts +562 -108
- package/src/LinearApolloDisplay/glyphs/GenericChildGlyph.ts +78 -14
- package/src/LinearApolloDisplay/glyphs/Glyph.ts +15 -9
- package/src/LinearApolloDisplay/stateModel/base.ts +63 -43
- package/src/LinearApolloDisplay/stateModel/layouts.ts +3 -2
- package/src/LinearApolloDisplay/stateModel/mouseEvents.ts +79 -292
- package/src/LinearApolloDisplay/stateModel/rendering.ts +45 -344
- package/src/LinearApolloReferenceSequenceDisplay/components/LinearApolloReferenceSequenceDisplay.tsx +87 -0
- package/src/LinearApolloReferenceSequenceDisplay/components/index.ts +1 -0
- package/src/LinearApolloReferenceSequenceDisplay/configSchema.ts +7 -0
- package/src/LinearApolloReferenceSequenceDisplay/index.ts +3 -0
- package/src/LinearApolloReferenceSequenceDisplay/stateModel/base.ts +227 -0
- package/src/LinearApolloReferenceSequenceDisplay/stateModel/index.ts +25 -0
- package/src/LinearApolloReferenceSequenceDisplay/stateModel/rendering.ts +481 -0
- package/src/LinearApolloSixFrameDisplay/components/LinearApolloSixFrameDisplay.tsx +102 -40
- package/src/LinearApolloSixFrameDisplay/components/TrackLines.tsx +12 -20
- package/src/LinearApolloSixFrameDisplay/glyphs/GeneGlyph.ts +382 -243
- package/src/LinearApolloSixFrameDisplay/glyphs/Glyph.ts +12 -8
- package/src/LinearApolloSixFrameDisplay/stateModel/base.ts +83 -4
- package/src/LinearApolloSixFrameDisplay/stateModel/layouts.ts +23 -11
- package/src/LinearApolloSixFrameDisplay/stateModel/mouseEvents.ts +118 -123
- package/src/LinearApolloSixFrameDisplay/stateModel/rendering.ts +53 -63
- package/src/OntologyManager/index.ts +4 -1
- package/src/TabularEditor/HybridGrid/Feature.tsx +20 -14
- package/src/TabularEditor/HybridGrid/HybridGrid.tsx +7 -5
- package/src/TabularEditor/HybridGrid/featureContextMenuItems.ts +108 -16
- package/src/components/AddAssembly.tsx +1 -1
- package/src/components/AddAssemblyAliases.tsx +114 -0
- package/src/components/AddChildFeature.tsx +7 -7
- package/src/components/AddFeature.tsx +20 -15
- package/src/components/AddRefSeqAliases.tsx +9 -9
- package/src/components/CopyFeature.tsx +4 -4
- package/src/components/CreateApolloAnnotation.tsx +335 -151
- package/src/components/DeleteAssembly.tsx +1 -1
- package/src/components/DeleteFeature.tsx +358 -11
- package/src/components/DownloadGFF3.tsx +20 -1
- package/src/components/EditZoomThresholdDialog.tsx +69 -0
- package/src/components/FilterFeatures.tsx +7 -7
- package/src/components/FilterTranscripts.tsx +86 -0
- package/src/components/ImportFeatures.tsx +1 -1
- package/src/components/ManageChecks.tsx +1 -1
- package/src/components/MergeExons.tsx +193 -0
- package/src/components/MergeTranscripts.tsx +182 -0
- package/src/components/OntologyTermMultiSelect.tsx +11 -11
- package/src/components/OpenLocalFile.tsx +11 -7
- package/src/components/SplitExon.tsx +134 -0
- package/src/components/ViewCheckResults.tsx +1 -1
- package/src/components/index.ts +4 -0
- package/src/config.ts +11 -0
- package/src/extensions/annotationFromJBrowseFeature.ts +2 -0
- package/src/extensions/annotationFromPileup.ts +99 -89
- package/src/index.ts +42 -105
- package/src/makeDisplayComponent.tsx +0 -1
- package/src/menus/index.ts +1 -0
- package/src/{ApolloInternetAccount/addMenuItems.ts → menus/topLevelMenu.ts} +60 -33
- package/src/menus/topLevelMenuAdmin.ts +154 -0
- package/src/session/session.ts +163 -104
- package/src/util/annotationFeatureUtils.ts +59 -0
- package/src/util/copyToClipboard.ts +21 -0
- package/src/util/displayUtils.ts +149 -0
- package/src/util/glyphUtils.ts +201 -0
- package/src/util/index.ts +2 -0
- package/src/util/mouseEventsUtils.ts +145 -0
|
@@ -37,6 +37,10 @@ export const TranscriptWidgetSummary = observer(
|
|
|
37
37
|
<TableCell>{getFeatureId(feature)}</TableCell>
|
|
38
38
|
</TableRow>
|
|
39
39
|
)}
|
|
40
|
+
<TableRow>
|
|
41
|
+
<HeaderTableCell>Type</HeaderTableCell>
|
|
42
|
+
<TableCell>{feature.type}</TableCell>
|
|
43
|
+
</TableRow>
|
|
40
44
|
<TableRow>
|
|
41
45
|
<HeaderTableCell>Location</HeaderTableCell>
|
|
42
46
|
<TableCell>
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/* eslint-disable @typescript-eslint/unbound-method */
|
|
2
2
|
/* eslint-disable @typescript-eslint/no-misused-promises */
|
|
3
3
|
/* eslint-disable @typescript-eslint/no-unnecessary-condition */
|
|
4
|
+
import { type CheckResultI } from '@apollo-annotation/mst'
|
|
4
5
|
import { Menu, type MenuItem } from '@jbrowse/core/ui'
|
|
5
6
|
import {
|
|
6
7
|
type AbstractSessionModel,
|
|
@@ -9,50 +10,31 @@ import {
|
|
|
9
10
|
} from '@jbrowse/core/util'
|
|
10
11
|
import { type LinearGenomeViewModel } from '@jbrowse/plugin-linear-genome-view'
|
|
11
12
|
import ErrorIcon from '@mui/icons-material/Error'
|
|
13
|
+
import LockIcon from '@mui/icons-material/Lock'
|
|
12
14
|
import {
|
|
13
15
|
Alert,
|
|
14
16
|
Avatar,
|
|
17
|
+
Badge,
|
|
18
|
+
Box,
|
|
15
19
|
CircularProgress,
|
|
16
20
|
Tooltip,
|
|
17
21
|
useTheme,
|
|
18
22
|
} from '@mui/material'
|
|
19
23
|
import { observer } from 'mobx-react'
|
|
20
24
|
import React, { useEffect, useState } from 'react'
|
|
21
|
-
import { makeStyles } from 'tss-react/mui'
|
|
22
25
|
|
|
26
|
+
import {
|
|
27
|
+
type Coord,
|
|
28
|
+
clusterResultByMessage,
|
|
29
|
+
useStyles,
|
|
30
|
+
} from '../../util/displayUtils'
|
|
23
31
|
import { type LinearApolloDisplay as LinearApolloDisplayI } from '../stateModel'
|
|
24
32
|
|
|
25
33
|
interface LinearApolloDisplayProps {
|
|
26
34
|
model: LinearApolloDisplayI
|
|
27
35
|
}
|
|
28
|
-
export type Coord = [number, number]
|
|
29
36
|
|
|
30
|
-
|
|
31
|
-
canvasContainer: {
|
|
32
|
-
position: 'relative',
|
|
33
|
-
left: 0,
|
|
34
|
-
},
|
|
35
|
-
canvas: {
|
|
36
|
-
position: 'absolute',
|
|
37
|
-
left: 0,
|
|
38
|
-
},
|
|
39
|
-
ellipses: {
|
|
40
|
-
textOverflow: 'ellipsis',
|
|
41
|
-
overflow: 'hidden',
|
|
42
|
-
},
|
|
43
|
-
avatar: {
|
|
44
|
-
position: 'absolute',
|
|
45
|
-
color: theme.palette.warning.light,
|
|
46
|
-
backgroundColor: theme.palette.warning.contrastText,
|
|
47
|
-
},
|
|
48
|
-
loading: {
|
|
49
|
-
position: 'absolute',
|
|
50
|
-
right: theme.spacing(3),
|
|
51
|
-
zIndex: 10,
|
|
52
|
-
pointerEvents: 'none',
|
|
53
|
-
textAlign: 'right',
|
|
54
|
-
},
|
|
55
|
-
}))
|
|
37
|
+
// Lock icon when isLocked === true
|
|
56
38
|
|
|
57
39
|
export const LinearApolloDisplay = observer(function LinearApolloDisplay(
|
|
58
40
|
props: LinearApolloDisplayProps,
|
|
@@ -76,9 +58,8 @@ export const LinearApolloDisplay = observer(function LinearApolloDisplay(
|
|
|
76
58
|
setCanvas,
|
|
77
59
|
setCollaboratorCanvas,
|
|
78
60
|
setOverlayCanvas,
|
|
79
|
-
setSeqTrackCanvas,
|
|
80
|
-
setSeqTrackOverlayCanvas,
|
|
81
61
|
setTheme,
|
|
62
|
+
showCheckResults,
|
|
82
63
|
} = model
|
|
83
64
|
const { classes } = useStyles()
|
|
84
65
|
const lgv = getContainingView(model) as unknown as LinearGenomeViewModel
|
|
@@ -95,36 +76,6 @@ export const LinearApolloDisplay = observer(function LinearApolloDisplay(
|
|
|
95
76
|
const { assemblyManager } = session as unknown as AbstractSessionModel
|
|
96
77
|
return (
|
|
97
78
|
<>
|
|
98
|
-
{3 / lgv.bpPerPx >= 1 ? (
|
|
99
|
-
<div
|
|
100
|
-
className={classes.canvasContainer}
|
|
101
|
-
style={{
|
|
102
|
-
width: lgv.dynamicBlocks.totalWidthPx,
|
|
103
|
-
height: lgv.bpPerPx <= 1 ? 125 : 95,
|
|
104
|
-
}}
|
|
105
|
-
>
|
|
106
|
-
<canvas
|
|
107
|
-
ref={async (node: HTMLCanvasElement) => {
|
|
108
|
-
await Promise.resolve()
|
|
109
|
-
setSeqTrackCanvas(node)
|
|
110
|
-
}}
|
|
111
|
-
width={lgv.dynamicBlocks.totalWidthPx}
|
|
112
|
-
height={lgv.bpPerPx <= 1 ? 125 : 95}
|
|
113
|
-
className={classes.canvas}
|
|
114
|
-
data-testid="seqTrackCanvas"
|
|
115
|
-
/>
|
|
116
|
-
<canvas
|
|
117
|
-
ref={async (node: HTMLCanvasElement) => {
|
|
118
|
-
await Promise.resolve()
|
|
119
|
-
setSeqTrackOverlayCanvas(node)
|
|
120
|
-
}}
|
|
121
|
-
width={lgv.dynamicBlocks.totalWidthPx}
|
|
122
|
-
height={lgv.bpPerPx <= 1 ? 125 : 95}
|
|
123
|
-
className={classes.canvas}
|
|
124
|
-
data-testid="seqTrackOverlayCanvas"
|
|
125
|
-
/>
|
|
126
|
-
</div>
|
|
127
|
-
) : null}
|
|
128
79
|
<div
|
|
129
80
|
className={classes.canvasContainer}
|
|
130
81
|
style={{
|
|
@@ -139,17 +90,26 @@ export const LinearApolloDisplay = observer(function LinearApolloDisplay(
|
|
|
139
90
|
} else {
|
|
140
91
|
const coord: [number, number] = [event.clientX, event.clientY]
|
|
141
92
|
setContextCoord(coord)
|
|
142
|
-
setContextMenuItems(getContextMenuItems(
|
|
93
|
+
setContextMenuItems(getContextMenuItems(event))
|
|
143
94
|
}
|
|
144
95
|
}}
|
|
145
96
|
>
|
|
97
|
+
{session.isLocked ? (
|
|
98
|
+
<div className={classes.locked} data-testid="lock-icon">
|
|
99
|
+
<LockIcon />
|
|
100
|
+
</div>
|
|
101
|
+
) : null}
|
|
146
102
|
{loading ? (
|
|
147
103
|
<div className={classes.loading}>
|
|
148
104
|
<CircularProgress size="18px" />
|
|
149
105
|
</div>
|
|
150
106
|
) : null}
|
|
151
107
|
{message ? (
|
|
152
|
-
<Alert
|
|
108
|
+
<Alert
|
|
109
|
+
severity="warning"
|
|
110
|
+
classes={{ message: classes.ellipses }}
|
|
111
|
+
slotProps={{ root: { className: classes.center } }}
|
|
112
|
+
>
|
|
153
113
|
<Tooltip title={message}>
|
|
154
114
|
<div>{message}</div>
|
|
155
115
|
</Tooltip>
|
|
@@ -194,9 +154,12 @@ export const LinearApolloDisplay = observer(function LinearApolloDisplay(
|
|
|
194
154
|
data-testid="overlayCanvas"
|
|
195
155
|
/>
|
|
196
156
|
{lgv.displayedRegions.flatMap((region, idx) => {
|
|
157
|
+
const widthBp = lgv.bpPerPx * apolloRowHeight
|
|
197
158
|
const assembly = assemblyManager.get(region.assemblyName)
|
|
198
|
-
|
|
199
|
-
|
|
159
|
+
if (showCheckResults) {
|
|
160
|
+
const filteredCheckResults = [
|
|
161
|
+
...session.apolloDataStore.checkResults.values(),
|
|
162
|
+
].filter(
|
|
200
163
|
(checkResult) =>
|
|
201
164
|
assembly?.isValidRefName(checkResult.refSeq) &&
|
|
202
165
|
assembly.getCanonicalRefName(checkResult.refSeq) ===
|
|
@@ -208,14 +171,19 @@ export const LinearApolloDisplay = observer(function LinearApolloDisplay(
|
|
|
208
171
|
checkResult.end,
|
|
209
172
|
),
|
|
210
173
|
)
|
|
211
|
-
|
|
174
|
+
const checkResults = clusterResultByMessage<CheckResultI>(
|
|
175
|
+
filteredCheckResults,
|
|
176
|
+
widthBp,
|
|
177
|
+
true,
|
|
178
|
+
)
|
|
179
|
+
return checkResults.map((checkResult) => {
|
|
212
180
|
const left =
|
|
213
181
|
(lgv.bpToPx({
|
|
214
182
|
refName: region.refName,
|
|
215
183
|
coord: checkResult.start,
|
|
216
184
|
regionNumber: idx,
|
|
217
185
|
})?.offsetPx ?? 0) - lgv.offsetPx
|
|
218
|
-
const [feature] = checkResult.
|
|
186
|
+
const [feature] = checkResult.featureIds
|
|
219
187
|
if (!feature) {
|
|
220
188
|
return null
|
|
221
189
|
}
|
|
@@ -228,8 +196,8 @@ export const LinearApolloDisplay = observer(function LinearApolloDisplay(
|
|
|
228
196
|
const height = apolloRowHeight
|
|
229
197
|
return (
|
|
230
198
|
<Tooltip key={checkResult._id} title={checkResult.message}>
|
|
231
|
-
<
|
|
232
|
-
className={classes.
|
|
199
|
+
<Box
|
|
200
|
+
className={classes.box}
|
|
233
201
|
style={{
|
|
234
202
|
top,
|
|
235
203
|
left,
|
|
@@ -238,11 +206,29 @@ export const LinearApolloDisplay = observer(function LinearApolloDisplay(
|
|
|
238
206
|
pointerEvents: apolloDragging ? 'none' : 'auto',
|
|
239
207
|
}}
|
|
240
208
|
>
|
|
241
|
-
<
|
|
242
|
-
|
|
209
|
+
<Badge
|
|
210
|
+
className={classes.badge}
|
|
211
|
+
badgeContent={checkResult.count}
|
|
212
|
+
color="primary"
|
|
213
|
+
overlap="circular"
|
|
214
|
+
anchorOrigin={{
|
|
215
|
+
vertical: 'bottom',
|
|
216
|
+
horizontal: 'right',
|
|
217
|
+
}}
|
|
218
|
+
invisible={checkResult.count <= 1}
|
|
219
|
+
>
|
|
220
|
+
<Avatar className={classes.avatar}>
|
|
221
|
+
<ErrorIcon
|
|
222
|
+
data-testid={`ErrorIcon-${checkResult.start}`}
|
|
223
|
+
/>
|
|
224
|
+
</Avatar>
|
|
225
|
+
</Badge>
|
|
226
|
+
</Box>
|
|
243
227
|
</Tooltip>
|
|
244
228
|
)
|
|
245
229
|
})
|
|
230
|
+
}
|
|
231
|
+
return null
|
|
246
232
|
})}
|
|
247
233
|
<Menu
|
|
248
234
|
open={contextMenuItems.length > 0}
|
|
@@ -253,9 +239,11 @@ export const LinearApolloDisplay = observer(function LinearApolloDisplay(
|
|
|
253
239
|
onClose={() => {
|
|
254
240
|
setContextMenuItems([])
|
|
255
241
|
}}
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
242
|
+
slotProps={{
|
|
243
|
+
transition: {
|
|
244
|
+
onExit: () => {
|
|
245
|
+
setContextMenuItems([])
|
|
246
|
+
},
|
|
259
247
|
},
|
|
260
248
|
}}
|
|
261
249
|
anchorReference="anchorPosition"
|
|
@@ -1,20 +1,16 @@
|
|
|
1
1
|
import { type AnnotationFeature } from '@apollo-annotation/mst'
|
|
2
2
|
import { type MenuItem } from '@jbrowse/core/ui'
|
|
3
|
-
import {
|
|
4
|
-
type AbstractSessionModel,
|
|
5
|
-
type SessionWithWidgets,
|
|
6
|
-
isSessionModelWithWidgets,
|
|
7
|
-
} from '@jbrowse/core/util'
|
|
8
|
-
import { type Theme, alpha } from '@mui/material'
|
|
3
|
+
import { alpha } from '@mui/material'
|
|
9
4
|
|
|
10
|
-
import { AddChildFeature, CopyFeature, DeleteFeature } from '../../components'
|
|
11
|
-
import { type LinearApolloDisplay } from '../stateModel'
|
|
12
5
|
import {
|
|
13
|
-
type LinearApolloDisplayMouseEvents,
|
|
14
6
|
type MousePosition,
|
|
15
|
-
type
|
|
16
|
-
|
|
17
|
-
|
|
7
|
+
type MousePositionWithFeature,
|
|
8
|
+
getContextMenuItemsForFeature,
|
|
9
|
+
isMousePositionWithFeature,
|
|
10
|
+
isSelectedFeature,
|
|
11
|
+
} from '../../util'
|
|
12
|
+
import { type LinearApolloDisplay } from '../stateModel'
|
|
13
|
+
import { type LinearApolloDisplayMouseEvents } from '../stateModel/mouseEvents'
|
|
18
14
|
import { type LinearApolloDisplayRendering } from '../stateModel/rendering'
|
|
19
15
|
import { type CanvasMouseEvent } from '../types'
|
|
20
16
|
|
|
@@ -46,20 +42,6 @@ function drawBoxFill(
|
|
|
46
42
|
drawBox(ctx, x + 1, y + 1, width - 2, height - 2, color)
|
|
47
43
|
}
|
|
48
44
|
|
|
49
|
-
function drawBoxText(
|
|
50
|
-
ctx: CanvasRenderingContext2D,
|
|
51
|
-
x: number,
|
|
52
|
-
y: number,
|
|
53
|
-
width: number,
|
|
54
|
-
color: string,
|
|
55
|
-
text: string,
|
|
56
|
-
) {
|
|
57
|
-
ctx.fillStyle = color
|
|
58
|
-
const textStart = Math.max(x + 1, 0)
|
|
59
|
-
const textWidth = x - 1 + width - textStart
|
|
60
|
-
ctx.fillText(text, textStart, y + 11, textWidth)
|
|
61
|
-
}
|
|
62
|
-
|
|
63
45
|
function draw(
|
|
64
46
|
ctx: CanvasRenderingContext2D,
|
|
65
47
|
feature: AnnotationFeature,
|
|
@@ -67,7 +49,7 @@ function draw(
|
|
|
67
49
|
stateModel: LinearApolloDisplayRendering,
|
|
68
50
|
displayedRegionIndex: number,
|
|
69
51
|
) {
|
|
70
|
-
const { apolloRowHeight: heightPx, lgv,
|
|
52
|
+
const { apolloRowHeight: heightPx, lgv, selectedFeature, theme } = stateModel
|
|
71
53
|
const { bpPerPx, displayedRegions, offsetPx } = lgv
|
|
72
54
|
const displayedRegion = displayedRegions[displayedRegionIndex]
|
|
73
55
|
const minX =
|
|
@@ -77,13 +59,11 @@ function draw(
|
|
|
77
59
|
regionNumber: displayedRegionIndex,
|
|
78
60
|
})?.offsetPx ?? 0) - offsetPx
|
|
79
61
|
const { reversed } = displayedRegion
|
|
80
|
-
const { apolloSelectedFeature } = session
|
|
81
62
|
const widthPx = feature.length / bpPerPx
|
|
82
63
|
const startPx = reversed ? minX - widthPx : minX
|
|
83
64
|
const top = row * heightPx
|
|
84
|
-
const
|
|
85
|
-
const
|
|
86
|
-
const textColor = getTextColor(theme, isSelected)
|
|
65
|
+
const backgroundColor = theme.palette.background.default
|
|
66
|
+
const textColor = theme.palette.text.primary
|
|
87
67
|
const featureBox: [number, number, number, number] = [
|
|
88
68
|
startPx,
|
|
89
69
|
top,
|
|
@@ -97,7 +77,9 @@ function draw(
|
|
|
97
77
|
}
|
|
98
78
|
|
|
99
79
|
drawBoxFill(ctx, startPx, top, widthPx, heightPx, backgroundColor)
|
|
100
|
-
|
|
80
|
+
if (isSelectedFeature(feature, selectedFeature)) {
|
|
81
|
+
drawHighlight(stateModel, ctx, feature, true)
|
|
82
|
+
}
|
|
101
83
|
}
|
|
102
84
|
|
|
103
85
|
function drawDragPreview(
|
|
@@ -123,22 +105,20 @@ function drawDragPreview(
|
|
|
123
105
|
const rectY = row * apolloRowHeight
|
|
124
106
|
const rectWidth = Math.abs(current.x - featureEdgePx)
|
|
125
107
|
const rectHeight = apolloRowHeight * rowCount
|
|
126
|
-
overlayCtx.strokeStyle = theme
|
|
108
|
+
overlayCtx.strokeStyle = theme.palette.info.main
|
|
127
109
|
overlayCtx.setLineDash([6])
|
|
128
110
|
overlayCtx.strokeRect(rectX, rectY, rectWidth, rectHeight)
|
|
129
|
-
overlayCtx.fillStyle = alpha(theme
|
|
111
|
+
overlayCtx.fillStyle = alpha(theme.palette.info.main, 0.2)
|
|
130
112
|
overlayCtx.fillRect(rectX, rectY, rectWidth, rectHeight)
|
|
131
113
|
}
|
|
132
114
|
|
|
133
|
-
function
|
|
134
|
-
stateModel:
|
|
115
|
+
function drawHighlight(
|
|
116
|
+
stateModel: LinearApolloDisplayRendering,
|
|
135
117
|
ctx: CanvasRenderingContext2D,
|
|
118
|
+
feature: AnnotationFeature,
|
|
119
|
+
selected = false,
|
|
136
120
|
) {
|
|
137
|
-
const {
|
|
138
|
-
if (!apolloHover) {
|
|
139
|
-
return
|
|
140
|
-
}
|
|
141
|
-
const { feature } = apolloHover
|
|
121
|
+
const { apolloRowHeight, lgv, theme } = stateModel
|
|
142
122
|
const position = stateModel.getFeatureLayoutPosition(feature)
|
|
143
123
|
if (!position) {
|
|
144
124
|
return
|
|
@@ -156,19 +136,32 @@ function drawHover(
|
|
|
156
136
|
})?.offsetPx ?? 0) - offsetPx
|
|
157
137
|
const top = layoutRow * apolloRowHeight
|
|
158
138
|
const widthPx = length / bpPerPx
|
|
159
|
-
ctx.fillStyle =
|
|
139
|
+
ctx.fillStyle = selected
|
|
140
|
+
? theme.palette.action.disabled
|
|
141
|
+
: theme.palette.action.focus
|
|
160
142
|
ctx.fillRect(startPx, top, widthPx, apolloRowHeight)
|
|
161
143
|
}
|
|
162
144
|
|
|
145
|
+
function drawHover(
|
|
146
|
+
stateModel: LinearApolloDisplay,
|
|
147
|
+
ctx: CanvasRenderingContext2D,
|
|
148
|
+
) {
|
|
149
|
+
const { hoveredFeature } = stateModel
|
|
150
|
+
if (!hoveredFeature) {
|
|
151
|
+
return
|
|
152
|
+
}
|
|
153
|
+
drawHighlight(stateModel, ctx, hoveredFeature.feature)
|
|
154
|
+
}
|
|
155
|
+
|
|
163
156
|
function drawTooltip(
|
|
164
157
|
display: LinearApolloDisplayMouseEvents,
|
|
165
158
|
context: CanvasRenderingContext2D,
|
|
166
159
|
): void {
|
|
167
|
-
const {
|
|
168
|
-
if (!
|
|
160
|
+
const { hoveredFeature, apolloRowHeight, lgv, theme } = display
|
|
161
|
+
if (!hoveredFeature) {
|
|
169
162
|
return
|
|
170
163
|
}
|
|
171
|
-
const { feature } =
|
|
164
|
+
const { feature } = hoveredFeature
|
|
172
165
|
const position = display.getFeatureLayoutPosition(feature)
|
|
173
166
|
if (!position) {
|
|
174
167
|
return
|
|
@@ -205,14 +198,14 @@ function drawTooltip(
|
|
|
205
198
|
const maxWidth = Math.max(...textWidth)
|
|
206
199
|
|
|
207
200
|
startPx = startPx + widthPx + 5
|
|
208
|
-
context.fillStyle = alpha(theme
|
|
201
|
+
context.fillStyle = alpha(theme.palette.text.primary, 0.7)
|
|
209
202
|
context.fillRect(startPx, top, maxWidth + 4, textWidth.length === 3 ? 45 : 35)
|
|
210
203
|
context.beginPath()
|
|
211
204
|
context.moveTo(startPx, top)
|
|
212
205
|
context.lineTo(startPx - 5, top + 5)
|
|
213
206
|
context.lineTo(startPx, top + 10)
|
|
214
207
|
context.fill()
|
|
215
|
-
context.fillStyle = theme
|
|
208
|
+
context.fillStyle = theme.palette.background.default
|
|
216
209
|
let textTop = top + 12
|
|
217
210
|
context.fillText(featureType, startPx + 2, textTop)
|
|
218
211
|
if (featureName) {
|
|
@@ -223,26 +216,6 @@ function drawTooltip(
|
|
|
223
216
|
context.fillText(location, startPx + 2, textTop)
|
|
224
217
|
}
|
|
225
218
|
|
|
226
|
-
export function isSelectedFeature(
|
|
227
|
-
feature: AnnotationFeature,
|
|
228
|
-
selectedFeature: AnnotationFeature | undefined,
|
|
229
|
-
) {
|
|
230
|
-
return Boolean(selectedFeature && feature._id === selectedFeature._id)
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
function getBackgroundColor(theme: Theme | undefined, selected: boolean) {
|
|
234
|
-
return selected
|
|
235
|
-
? theme?.palette.text.primary ?? 'black'
|
|
236
|
-
: theme?.palette.background.default ?? 'white'
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
function getTextColor(theme: Theme | undefined, selected: boolean) {
|
|
240
|
-
return selected
|
|
241
|
-
? theme?.palette.getContrastText(getBackgroundColor(theme, selected)) ??
|
|
242
|
-
'white'
|
|
243
|
-
: theme?.palette.text.primary ?? 'black'
|
|
244
|
-
}
|
|
245
|
-
|
|
246
219
|
export function drawBox(
|
|
247
220
|
ctx: CanvasRenderingContext2D,
|
|
248
221
|
x: number,
|
|
@@ -258,141 +231,11 @@ export function drawBox(
|
|
|
258
231
|
function getContextMenuItems(
|
|
259
232
|
display: LinearApolloDisplayMouseEvents,
|
|
260
233
|
): MenuItem[] {
|
|
261
|
-
const {
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
changeManager,
|
|
265
|
-
regions,
|
|
266
|
-
selectedFeature,
|
|
267
|
-
session,
|
|
268
|
-
} = display
|
|
269
|
-
const menuItems: MenuItem[] = []
|
|
270
|
-
if (!apolloHover) {
|
|
271
|
-
return menuItems
|
|
272
|
-
}
|
|
273
|
-
const { feature: sourceFeature } = apolloHover
|
|
274
|
-
const role = internetAccount ? internetAccount.role : 'admin'
|
|
275
|
-
const admin = role === 'admin'
|
|
276
|
-
const readOnly = !(role && ['admin', 'user'].includes(role))
|
|
277
|
-
const [region] = regions
|
|
278
|
-
const sourceAssemblyId = display.getAssemblyId(region.assemblyName)
|
|
279
|
-
const currentAssemblyId = display.getAssemblyId(region.assemblyName)
|
|
280
|
-
menuItems.push(
|
|
281
|
-
{
|
|
282
|
-
label: 'Add child feature',
|
|
283
|
-
disabled: readOnly,
|
|
284
|
-
onClick: () => {
|
|
285
|
-
;(session as unknown as AbstractSessionModel).queueDialog(
|
|
286
|
-
(doneCallback) => [
|
|
287
|
-
AddChildFeature,
|
|
288
|
-
{
|
|
289
|
-
session,
|
|
290
|
-
handleClose: () => {
|
|
291
|
-
doneCallback()
|
|
292
|
-
},
|
|
293
|
-
changeManager,
|
|
294
|
-
sourceFeature,
|
|
295
|
-
sourceAssemblyId,
|
|
296
|
-
internetAccount,
|
|
297
|
-
},
|
|
298
|
-
],
|
|
299
|
-
)
|
|
300
|
-
},
|
|
301
|
-
},
|
|
302
|
-
{
|
|
303
|
-
label: 'Copy features and annotations',
|
|
304
|
-
disabled: readOnly,
|
|
305
|
-
onClick: () => {
|
|
306
|
-
;(session as unknown as AbstractSessionModel).queueDialog(
|
|
307
|
-
(doneCallback) => [
|
|
308
|
-
CopyFeature,
|
|
309
|
-
{
|
|
310
|
-
session,
|
|
311
|
-
handleClose: () => {
|
|
312
|
-
doneCallback()
|
|
313
|
-
},
|
|
314
|
-
changeManager,
|
|
315
|
-
sourceFeature,
|
|
316
|
-
sourceAssemblyId: currentAssemblyId,
|
|
317
|
-
},
|
|
318
|
-
],
|
|
319
|
-
)
|
|
320
|
-
},
|
|
321
|
-
},
|
|
322
|
-
{
|
|
323
|
-
label: 'Delete feature',
|
|
324
|
-
disabled: !admin,
|
|
325
|
-
onClick: () => {
|
|
326
|
-
;(session as unknown as AbstractSessionModel).queueDialog(
|
|
327
|
-
(doneCallback) => [
|
|
328
|
-
DeleteFeature,
|
|
329
|
-
{
|
|
330
|
-
session,
|
|
331
|
-
handleClose: () => {
|
|
332
|
-
doneCallback()
|
|
333
|
-
},
|
|
334
|
-
changeManager,
|
|
335
|
-
sourceFeature,
|
|
336
|
-
sourceAssemblyId: currentAssemblyId,
|
|
337
|
-
selectedFeature,
|
|
338
|
-
setSelectedFeature: (feature?: AnnotationFeature) => {
|
|
339
|
-
display.setSelectedFeature(feature)
|
|
340
|
-
},
|
|
341
|
-
},
|
|
342
|
-
],
|
|
343
|
-
)
|
|
344
|
-
},
|
|
345
|
-
},
|
|
346
|
-
{
|
|
347
|
-
label: 'Edit feature details',
|
|
348
|
-
onClick: () => {
|
|
349
|
-
const apolloFeatureWidget = (
|
|
350
|
-
session as unknown as SessionWithWidgets
|
|
351
|
-
).addWidget(
|
|
352
|
-
'ApolloFeatureDetailsWidget',
|
|
353
|
-
'apolloFeatureDetailsWidget',
|
|
354
|
-
{
|
|
355
|
-
feature: sourceFeature,
|
|
356
|
-
assembly: currentAssemblyId,
|
|
357
|
-
refName: region.refName,
|
|
358
|
-
},
|
|
359
|
-
)
|
|
360
|
-
;(session as unknown as SessionWithWidgets).showWidget(
|
|
361
|
-
apolloFeatureWidget,
|
|
362
|
-
)
|
|
363
|
-
},
|
|
364
|
-
},
|
|
365
|
-
)
|
|
366
|
-
const { featureTypeOntology } = session.apolloDataStore.ontologyManager
|
|
367
|
-
if (!featureTypeOntology) {
|
|
368
|
-
throw new Error('featureTypeOntology is undefined')
|
|
234
|
+
const { hoveredFeature } = display
|
|
235
|
+
if (!hoveredFeature) {
|
|
236
|
+
return []
|
|
369
237
|
}
|
|
370
|
-
|
|
371
|
-
(featureTypeOntology.isTypeOf(sourceFeature.type, 'transcript') ||
|
|
372
|
-
featureTypeOntology.isTypeOf(
|
|
373
|
-
sourceFeature.type,
|
|
374
|
-
'pseudogenic_transcript',
|
|
375
|
-
)) &&
|
|
376
|
-
isSessionModelWithWidgets(session)
|
|
377
|
-
) {
|
|
378
|
-
menuItems.push({
|
|
379
|
-
label: 'Edit transcript details',
|
|
380
|
-
onClick: () => {
|
|
381
|
-
const apolloTranscriptWidget = session.addWidget(
|
|
382
|
-
'ApolloTranscriptDetails',
|
|
383
|
-
'apolloTranscriptDetails',
|
|
384
|
-
{
|
|
385
|
-
feature: sourceFeature,
|
|
386
|
-
assembly: currentAssemblyId,
|
|
387
|
-
changeManager,
|
|
388
|
-
refName: region.refName,
|
|
389
|
-
},
|
|
390
|
-
)
|
|
391
|
-
session.showWidget(apolloTranscriptWidget)
|
|
392
|
-
},
|
|
393
|
-
})
|
|
394
|
-
}
|
|
395
|
-
return menuItems
|
|
238
|
+
return getContextMenuItemsForFeature(display, hoveredFeature.feature)
|
|
396
239
|
}
|
|
397
240
|
|
|
398
241
|
function getFeatureFromLayout(
|
|
@@ -416,13 +259,12 @@ function getRowForFeature(
|
|
|
416
259
|
|
|
417
260
|
function onMouseDown(
|
|
418
261
|
stateModel: LinearApolloDisplay,
|
|
419
|
-
currentMousePosition:
|
|
262
|
+
currentMousePosition: MousePositionWithFeature,
|
|
420
263
|
event: CanvasMouseEvent,
|
|
421
264
|
) {
|
|
422
|
-
const {
|
|
265
|
+
const { feature } = currentMousePosition
|
|
423
266
|
// swallow the mouseDown if we are on the edge of the feature so that we
|
|
424
267
|
// don't start dragging the view if we try to drag the feature edge
|
|
425
|
-
const { feature } = featureAndGlyphUnderMouse
|
|
426
268
|
const edge = isMouseOnFeatureEdge(currentMousePosition, feature, stateModel)
|
|
427
269
|
if (edge) {
|
|
428
270
|
event.stopPropagation()
|
|
@@ -438,10 +280,9 @@ function onMouseMove(
|
|
|
438
280
|
stateModel: LinearApolloDisplay,
|
|
439
281
|
mousePosition: MousePosition,
|
|
440
282
|
) {
|
|
441
|
-
if (
|
|
442
|
-
const {
|
|
443
|
-
stateModel.
|
|
444
|
-
const { feature } = featureAndGlyphUnderMouse
|
|
283
|
+
if (isMousePositionWithFeature(mousePosition)) {
|
|
284
|
+
const { feature, bp } = mousePosition
|
|
285
|
+
stateModel.setHoveredFeature({ feature, bp })
|
|
445
286
|
const edge = isMouseOnFeatureEdge(mousePosition, feature, stateModel)
|
|
446
287
|
if (edge) {
|
|
447
288
|
stateModel.setCursor('col-resize')
|
|
@@ -458,10 +299,12 @@ function onMouseUp(
|
|
|
458
299
|
if (stateModel.apolloDragging) {
|
|
459
300
|
return
|
|
460
301
|
}
|
|
461
|
-
const {
|
|
462
|
-
if (
|
|
463
|
-
|
|
302
|
+
const { feature } = mousePosition
|
|
303
|
+
if (!feature) {
|
|
304
|
+
return
|
|
464
305
|
}
|
|
306
|
+
stateModel.setSelectedFeature(feature)
|
|
307
|
+
stateModel.showFeatureDetailsWidget(feature)
|
|
465
308
|
}
|
|
466
309
|
|
|
467
310
|
/** @returns undefined if mouse not on the edge of this feature, otherwise 'start' or 'end' depending on which edge */
|
|
@@ -496,6 +339,7 @@ export const boxGlyph: Glyph = {
|
|
|
496
339
|
drawDragPreview,
|
|
497
340
|
drawHover,
|
|
498
341
|
drawTooltip,
|
|
342
|
+
getContextMenuItemsForFeature,
|
|
499
343
|
getContextMenuItems,
|
|
500
344
|
getFeatureFromLayout,
|
|
501
345
|
getRowCount,
|