@apollo-annotation/jbrowse-plugin-apollo 0.1.18 → 0.1.20
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 +3189 -3575
- package/dist/index.esm.js.map +1 -1
- package/dist/jbrowse-plugin-apollo.cjs.development.js +3185 -3570
- 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 +14884 -15905
- 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 +33 -33
- package/src/ApolloInternetAccount/addMenuItems.ts +18 -0
- package/src/ApolloInternetAccount/components/AuthTypeSelector.tsx +1 -0
- package/src/ApolloInternetAccount/configSchema.ts +5 -2
- package/src/ApolloInternetAccount/model.ts +14 -5
- package/src/ApolloRefNameAliasAdapter/ApolloRefNameAliasAdapter.ts +94 -0
- package/src/ApolloRefNameAliasAdapter/configSchema.ts +12 -0
- package/src/ApolloRefNameAliasAdapter/index.ts +21 -0
- package/src/ApolloSequenceAdapter/ApolloSequenceAdapter.ts +1 -0
- package/src/ApolloSixFrameRenderer/components/ApolloRendering.tsx +10 -10
- package/src/ApolloTextSearchAdapter/ApolloTextSearchAdapter.ts +35 -32
- package/src/BackendDrivers/BackendDriver.ts +8 -0
- package/src/BackendDrivers/CollaborationServerDriver.ts +49 -1
- package/src/BackendDrivers/DesktopFileDriver.ts +14 -1
- package/src/BackendDrivers/InMemoryFileDriver.ts +17 -1
- package/src/ChangeManager.ts +1 -1
- package/src/FeatureDetailsWidget/ApolloFeatureDetailsWidget.tsx +5 -25
- package/src/FeatureDetailsWidget/ApolloTranscriptDetailsWidget.tsx +82 -0
- package/src/FeatureDetailsWidget/Attributes.tsx +11 -3
- package/src/FeatureDetailsWidget/BasicInformation.tsx +38 -30
- package/src/FeatureDetailsWidget/Sequence.tsx +7 -7
- package/src/FeatureDetailsWidget/TranscriptBasic.tsx +446 -0
- package/src/FeatureDetailsWidget/TranscriptSequence.tsx +365 -0
- package/src/FeatureDetailsWidget/index.ts +2 -0
- package/src/FeatureDetailsWidget/model.ts +77 -9
- package/src/LinearApolloDisplay/components/LinearApolloDisplay.tsx +0 -2
- package/src/LinearApolloDisplay/glyphs/BoxGlyph.ts +453 -380
- package/src/LinearApolloDisplay/glyphs/GeneGlyph.ts +520 -0
- package/src/LinearApolloDisplay/glyphs/GenericChildGlyph.ts +138 -134
- package/src/LinearApolloDisplay/glyphs/Glyph.ts +38 -370
- package/src/LinearApolloDisplay/glyphs/index.ts +1 -2
- package/src/LinearApolloDisplay/stateModel/base.ts +3 -6
- package/src/LinearApolloDisplay/stateModel/getGlyph.ts +30 -30
- package/src/LinearApolloDisplay/stateModel/index.ts +5 -1
- package/src/LinearApolloDisplay/stateModel/layouts.ts +32 -24
- package/src/LinearApolloDisplay/stateModel/mouseEvents.ts +206 -217
- package/src/LinearApolloDisplay/stateModel/rendering.ts +43 -67
- package/src/OntologyManager/OntologyStore/fulltext.ts +1 -1
- package/src/OntologyManager/OntologyStore/index.ts +2 -1
- package/src/OntologyManager/index.ts +6 -2
- package/src/OntologyManager/util.ts +2 -2
- package/src/SixFrameFeatureDisplay/stateModel.ts +15 -10
- package/src/TabularEditor/HybridGrid/ChangeHandling.ts +21 -46
- package/src/TabularEditor/HybridGrid/Feature.tsx +31 -82
- package/src/TabularEditor/HybridGrid/FeatureAttributes.tsx +3 -2
- package/src/TabularEditor/HybridGrid/HybridGrid.tsx +2 -3
- package/src/TabularEditor/HybridGrid/NumberCell.tsx +1 -0
- package/src/TabularEditor/HybridGrid/featureContextMenuItems.ts +46 -5
- package/src/TabularEditor/model.ts +5 -3
- package/src/components/AddAssembly.tsx +15 -9
- package/src/components/AddChildFeature.tsx +7 -73
- package/src/components/AddFeature.tsx +2 -57
- package/src/components/AddRefSeqAliases.tsx +285 -0
- package/src/components/CopyFeature.tsx +16 -33
- package/src/components/DeleteFeature.tsx +4 -6
- package/src/components/ImportFeatures.tsx +6 -3
- package/src/components/LogOut.tsx +105 -0
- package/src/components/ManageChecks.tsx +1 -0
- package/src/components/ManageUsers.tsx +21 -1
- package/src/components/ModifyFeatureAttribute.tsx +2 -2
- package/src/components/OntologyTermAutocomplete.tsx +0 -2
- package/src/components/OntologyTermMultiSelect.tsx +1 -0
- package/src/components/OpenLocalFile.tsx +6 -5
- package/src/components/ViewChangeLog.tsx +1 -0
- package/src/components/ViewCheckResults.tsx +1 -0
- package/src/components/index.ts +4 -0
- package/src/extensions/annotationFromPileup.ts +10 -16
- package/src/index.ts +57 -3
- package/src/session/ClientDataStore.ts +49 -46
- package/src/session/session.ts +186 -114
- package/src/util/loadAssemblyIntoClient.ts +4 -210
- package/src/FeatureDetailsWidget/RelatedFeature.tsx +0 -97
- package/src/LinearApolloDisplay/glyphs/CanonicalGeneGlyph.ts +0 -1204
- package/src/LinearApolloDisplay/glyphs/ImplicitExonGeneGlyph.ts +0 -716
- package/src/LinearApolloDisplay/stateModel/glyphs.ts +0 -47
|
@@ -1,427 +1,500 @@
|
|
|
1
|
-
|
|
2
|
-
/* eslint-disable @typescript-eslint/unbound-method */
|
|
3
|
-
import { AnnotationFeatureI } from '@apollo-annotation/mst'
|
|
4
|
-
import {
|
|
5
|
-
LocationEndChange,
|
|
6
|
-
LocationStartChange,
|
|
7
|
-
} from '@apollo-annotation/shared'
|
|
1
|
+
import { AnnotationFeature } from '@apollo-annotation/mst'
|
|
8
2
|
import { Theme, alpha } from '@mui/material'
|
|
3
|
+
import { MenuItem } from '@jbrowse/core/ui'
|
|
4
|
+
|
|
5
|
+
import { AbstractSessionModel, SessionWithWidgets } from '@jbrowse/core/util'
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
AddChildFeature,
|
|
9
|
+
CopyFeature,
|
|
10
|
+
DeleteFeature,
|
|
11
|
+
ModifyFeatureAttribute,
|
|
12
|
+
} from '../../components'
|
|
9
13
|
|
|
10
14
|
import { LinearApolloDisplay } from '../stateModel'
|
|
11
|
-
import {
|
|
15
|
+
import {
|
|
16
|
+
isMousePositionWithFeatureAndGlyph,
|
|
17
|
+
LinearApolloDisplayMouseEvents,
|
|
18
|
+
MousePosition,
|
|
19
|
+
MousePositionWithFeatureAndGlyph,
|
|
20
|
+
} from '../stateModel/mouseEvents'
|
|
12
21
|
import { CanvasMouseEvent } from '../types'
|
|
13
22
|
import { Glyph } from './Glyph'
|
|
23
|
+
import { LinearApolloDisplayRendering } from '../stateModel/rendering'
|
|
14
24
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
25
|
+
function drawBoxOutline(
|
|
26
|
+
ctx: CanvasRenderingContext2D,
|
|
27
|
+
x: number,
|
|
28
|
+
y: number,
|
|
29
|
+
width: number,
|
|
30
|
+
height: number,
|
|
31
|
+
color: string,
|
|
32
|
+
) {
|
|
33
|
+
drawBox(ctx, x, y, width, height, color)
|
|
34
|
+
if (width <= 2) {
|
|
35
|
+
return
|
|
18
36
|
}
|
|
37
|
+
ctx.clearRect(x + 1, y + 1, width - 2, height - 2)
|
|
38
|
+
}
|
|
19
39
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
40
|
+
function drawBoxFill(
|
|
41
|
+
ctx: CanvasRenderingContext2D,
|
|
42
|
+
x: number,
|
|
43
|
+
y: number,
|
|
44
|
+
width: number,
|
|
45
|
+
height: number,
|
|
46
|
+
color: string,
|
|
47
|
+
) {
|
|
48
|
+
drawBox(ctx, x + 1, y + 1, width - 2, height - 2, color)
|
|
49
|
+
}
|
|
26
50
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
51
|
+
function drawBoxText(
|
|
52
|
+
ctx: CanvasRenderingContext2D,
|
|
53
|
+
x: number,
|
|
54
|
+
y: number,
|
|
55
|
+
width: number,
|
|
56
|
+
color: string,
|
|
57
|
+
text: string,
|
|
58
|
+
) {
|
|
59
|
+
ctx.fillStyle = color
|
|
60
|
+
const textStart = Math.max(x + 1, 0)
|
|
61
|
+
const textWidth = x - 1 + width - textStart
|
|
62
|
+
ctx.fillText(text, textStart, y + 11, textWidth)
|
|
63
|
+
}
|
|
32
64
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
65
|
+
function draw(
|
|
66
|
+
ctx: CanvasRenderingContext2D,
|
|
67
|
+
feature: AnnotationFeature,
|
|
68
|
+
row: number,
|
|
69
|
+
stateModel: LinearApolloDisplayRendering,
|
|
70
|
+
displayedRegionIndex: number,
|
|
71
|
+
) {
|
|
72
|
+
const { apolloRowHeight: heightPx, lgv, session, theme } = stateModel
|
|
73
|
+
const { bpPerPx, displayedRegions, offsetPx } = lgv
|
|
74
|
+
const displayedRegion = displayedRegions[displayedRegionIndex]
|
|
75
|
+
const minX =
|
|
76
|
+
(lgv.bpToPx({
|
|
77
|
+
refName: displayedRegion.refName,
|
|
78
|
+
coord: feature.min,
|
|
79
|
+
regionNumber: displayedRegionIndex,
|
|
80
|
+
})?.offsetPx ?? 0) - offsetPx
|
|
81
|
+
const { reversed } = displayedRegion
|
|
82
|
+
const { apolloSelectedFeature } = session
|
|
83
|
+
const widthPx = feature.length / bpPerPx
|
|
84
|
+
const startPx = reversed ? minX - widthPx : minX
|
|
85
|
+
const top = row * heightPx
|
|
86
|
+
const isSelected = isSelectedFeature(feature, apolloSelectedFeature)
|
|
87
|
+
const backgroundColor = getBackgroundColor(theme, isSelected)
|
|
88
|
+
const textColor = getTextColor(theme, isSelected)
|
|
89
|
+
const featureBox: [number, number, number, number] = [
|
|
90
|
+
startPx,
|
|
91
|
+
top,
|
|
92
|
+
widthPx,
|
|
93
|
+
heightPx,
|
|
94
|
+
]
|
|
95
|
+
drawBoxOutline(ctx, ...featureBox, textColor)
|
|
96
|
+
if (widthPx <= 2) {
|
|
97
|
+
// Don't need to add details if the feature is too small to see them
|
|
98
|
+
return
|
|
39
99
|
}
|
|
40
100
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
y: number,
|
|
45
|
-
width: number,
|
|
46
|
-
height: number,
|
|
47
|
-
color: string,
|
|
48
|
-
) {
|
|
49
|
-
ctx.fillStyle = color
|
|
50
|
-
ctx.fillRect(x, y, width, height)
|
|
51
|
-
}
|
|
101
|
+
drawBoxFill(ctx, startPx, top, widthPx, heightPx, backgroundColor)
|
|
102
|
+
drawBoxText(ctx, startPx, top, widthPx, textColor, feature.type)
|
|
103
|
+
}
|
|
52
104
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
this.drawBox(ctx, x, y, width, height, color)
|
|
62
|
-
ctx.clearRect(x + 1, y + 1, width - 2, height - 2)
|
|
105
|
+
function drawDragPreview(
|
|
106
|
+
stateModel: LinearApolloDisplay,
|
|
107
|
+
overlayCtx: CanvasRenderingContext2D,
|
|
108
|
+
) {
|
|
109
|
+
const { apolloDragging, apolloRowHeight, lgv, theme } = stateModel
|
|
110
|
+
const { bpPerPx, displayedRegions, offsetPx } = lgv
|
|
111
|
+
if (!apolloDragging) {
|
|
112
|
+
return
|
|
63
113
|
}
|
|
114
|
+
const { current, edge, feature, start } = apolloDragging
|
|
115
|
+
|
|
116
|
+
const row = Math.floor(start.y / apolloRowHeight)
|
|
117
|
+
const region = displayedRegions[start.regionNumber]
|
|
118
|
+
const rowCount = getRowCount(feature)
|
|
119
|
+
|
|
120
|
+
const featureEdgeBp = region.reversed
|
|
121
|
+
? region.end - feature[edge]
|
|
122
|
+
: feature[edge] - region.start
|
|
123
|
+
const featureEdgePx = featureEdgeBp / bpPerPx - offsetPx
|
|
124
|
+
const rectX = Math.min(current.x, featureEdgePx)
|
|
125
|
+
const rectY = row * apolloRowHeight
|
|
126
|
+
const rectWidth = Math.abs(current.x - featureEdgePx)
|
|
127
|
+
const rectHeight = apolloRowHeight * rowCount
|
|
128
|
+
overlayCtx.strokeStyle = theme?.palette.info.main ?? 'rgb(255,0,0)'
|
|
129
|
+
overlayCtx.setLineDash([6])
|
|
130
|
+
overlayCtx.strokeRect(rectX, rectY, rectWidth, rectHeight)
|
|
131
|
+
overlayCtx.fillStyle = alpha(theme?.palette.info.main ?? 'rgb(255,0,0)', 0.2)
|
|
132
|
+
overlayCtx.fillRect(rectX, rectY, rectWidth, rectHeight)
|
|
133
|
+
}
|
|
64
134
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
135
|
+
function drawHover(
|
|
136
|
+
stateModel: LinearApolloDisplay,
|
|
137
|
+
ctx: CanvasRenderingContext2D,
|
|
138
|
+
) {
|
|
139
|
+
const { apolloHover, apolloRowHeight, lgv, theme } = stateModel
|
|
140
|
+
if (!apolloHover) {
|
|
141
|
+
return
|
|
142
|
+
}
|
|
143
|
+
const { feature } = apolloHover
|
|
144
|
+
const position = stateModel.getFeatureLayoutPosition(feature)
|
|
145
|
+
if (!position) {
|
|
146
|
+
return
|
|
74
147
|
}
|
|
148
|
+
const { bpPerPx, displayedRegions, offsetPx } = lgv
|
|
149
|
+
const { layoutIndex, layoutRow } = position
|
|
150
|
+
const displayedRegion = displayedRegions[layoutIndex]
|
|
151
|
+
const { refName, reversed } = displayedRegion
|
|
152
|
+
const { length, max, min } = feature
|
|
153
|
+
const startPx =
|
|
154
|
+
(lgv.bpToPx({
|
|
155
|
+
refName,
|
|
156
|
+
coord: reversed ? max : min,
|
|
157
|
+
regionNumber: layoutIndex,
|
|
158
|
+
})?.offsetPx ?? 0) - offsetPx
|
|
159
|
+
const top = layoutRow * apolloRowHeight
|
|
160
|
+
const widthPx = length / bpPerPx
|
|
161
|
+
ctx.fillStyle = theme?.palette.action.focus ?? 'rgba(0,0,0,0.04)'
|
|
162
|
+
ctx.fillRect(startPx, top, widthPx, apolloRowHeight)
|
|
163
|
+
}
|
|
75
164
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
) {
|
|
84
|
-
ctx.fillStyle = color
|
|
85
|
-
const textStart = Math.max(x + 1, 0)
|
|
86
|
-
const textWidth = x - 1 + width - textStart
|
|
87
|
-
ctx.fillText(text, textStart, y + 11, textWidth)
|
|
165
|
+
function drawTooltip(
|
|
166
|
+
display: LinearApolloDisplayMouseEvents,
|
|
167
|
+
context: CanvasRenderingContext2D,
|
|
168
|
+
): void {
|
|
169
|
+
const { apolloHover, apolloRowHeight, lgv, theme } = display
|
|
170
|
+
if (!apolloHover) {
|
|
171
|
+
return
|
|
88
172
|
}
|
|
173
|
+
const { feature } = apolloHover
|
|
174
|
+
const position = display.getFeatureLayoutPosition(feature)
|
|
175
|
+
if (!position) {
|
|
176
|
+
return
|
|
177
|
+
}
|
|
178
|
+
const { layoutIndex, layoutRow } = position
|
|
179
|
+
const { bpPerPx, displayedRegions, offsetPx } = lgv
|
|
180
|
+
const displayedRegion = displayedRegions[layoutIndex]
|
|
181
|
+
const { refName, reversed } = displayedRegion
|
|
89
182
|
|
|
90
|
-
|
|
91
|
-
stateModel: LinearApolloDisplay,
|
|
92
|
-
ctx: CanvasRenderingContext2D,
|
|
93
|
-
feature: AnnotationFeatureI,
|
|
94
|
-
xOffset: number,
|
|
95
|
-
row: number,
|
|
96
|
-
reversed: boolean,
|
|
97
|
-
) {
|
|
98
|
-
const { apolloRowHeight: heightPx, lgv, session, theme } = stateModel
|
|
99
|
-
const { bpPerPx } = lgv
|
|
100
|
-
const { apolloSelectedFeature } = session
|
|
101
|
-
const offsetPx = (feature.start - feature.min) / bpPerPx
|
|
102
|
-
const widthPx = feature.length / bpPerPx
|
|
103
|
-
const startPx = reversed ? xOffset - offsetPx - widthPx : xOffset + offsetPx
|
|
104
|
-
const top = row * heightPx
|
|
105
|
-
const isSelected = this.getIsSelectedFeature(feature, apolloSelectedFeature)
|
|
106
|
-
const backgroundColor = this.getBackgroundColor(theme, isSelected)
|
|
107
|
-
const textColor = this.getTextColor(theme, isSelected)
|
|
108
|
-
const groupingColor = isSelected
|
|
109
|
-
? 'rgba(130,0,0,0.45)'
|
|
110
|
-
: 'rgba(255,0,0,0.25)'
|
|
111
|
-
const featureBox: [number, number, number, number] = [
|
|
112
|
-
startPx,
|
|
113
|
-
top,
|
|
114
|
-
widthPx,
|
|
115
|
-
heightPx,
|
|
116
|
-
]
|
|
117
|
-
this.drawBoxOutline(ctx, ...featureBox, textColor)
|
|
118
|
-
if (widthPx <= 2) {
|
|
119
|
-
// Don't need to add details if the feature is too small to see them
|
|
120
|
-
return
|
|
121
|
-
}
|
|
183
|
+
let location = 'Loc: '
|
|
122
184
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
]
|
|
126
|
-
if (
|
|
127
|
-
feature.discontinuousLocations &&
|
|
128
|
-
feature.discontinuousLocations.length > 0
|
|
129
|
-
) {
|
|
130
|
-
featureLocations = feature.discontinuousLocations.map((f) => ({
|
|
131
|
-
start: f.start,
|
|
132
|
-
end: f.end,
|
|
133
|
-
type: feature.type,
|
|
134
|
-
}))
|
|
135
|
-
}
|
|
136
|
-
if (featureLocations.length > 1) {
|
|
137
|
-
this.drawBoxFill(ctx, ...featureBox, groupingColor)
|
|
138
|
-
for (const location of featureLocations) {
|
|
139
|
-
const offsetPx = (location.start - feature.min) / bpPerPx
|
|
140
|
-
const widthPx = (location.end - location.start) / bpPerPx
|
|
141
|
-
const startPx = reversed
|
|
142
|
-
? xOffset - offsetPx - widthPx
|
|
143
|
-
: xOffset + offsetPx
|
|
144
|
-
this.drawBoxOutline(ctx, startPx, top, widthPx, heightPx, textColor)
|
|
145
|
-
}
|
|
146
|
-
}
|
|
185
|
+
const { length, max, min } = feature
|
|
186
|
+
location += `${min + 1}–${max}`
|
|
147
187
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
}
|
|
157
|
-
}
|
|
188
|
+
let startPx =
|
|
189
|
+
(lgv.bpToPx({
|
|
190
|
+
refName,
|
|
191
|
+
coord: reversed ? max : min,
|
|
192
|
+
regionNumber: layoutIndex,
|
|
193
|
+
})?.offsetPx ?? 0) - offsetPx
|
|
194
|
+
const top = layoutRow * apolloRowHeight
|
|
195
|
+
const widthPx = length / bpPerPx
|
|
158
196
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
197
|
+
const featureType = `Type: ${feature.type}`
|
|
198
|
+
const { attributes } = feature
|
|
199
|
+
const featureName = attributes.get('gff_name')?.find((name) => name !== '')
|
|
200
|
+
const textWidth = [
|
|
201
|
+
context.measureText(featureType).width,
|
|
202
|
+
context.measureText(location).width,
|
|
203
|
+
]
|
|
204
|
+
if (featureName) {
|
|
205
|
+
textWidth.push(context.measureText(`Name: ${featureName}`).width)
|
|
165
206
|
}
|
|
207
|
+
const maxWidth = Math.max(...textWidth)
|
|
166
208
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
)
|
|
171
|
-
|
|
209
|
+
startPx = startPx + widthPx + 5
|
|
210
|
+
context.fillStyle = alpha(theme?.palette.text.primary ?? 'rgb(1, 1, 1)', 0.7)
|
|
211
|
+
context.fillRect(startPx, top, maxWidth + 4, textWidth.length === 3 ? 45 : 35)
|
|
212
|
+
context.beginPath()
|
|
213
|
+
context.moveTo(startPx, top)
|
|
214
|
+
context.lineTo(startPx - 5, top + 5)
|
|
215
|
+
context.lineTo(startPx, top + 10)
|
|
216
|
+
context.fill()
|
|
217
|
+
context.fillStyle = theme?.palette.background.default ?? 'rgba(255, 255, 255)'
|
|
218
|
+
let textTop = top + 12
|
|
219
|
+
context.fillText(featureType, startPx + 2, textTop)
|
|
220
|
+
if (featureName) {
|
|
221
|
+
textTop = textTop + 12
|
|
222
|
+
context.fillText(`Name: ${featureName}`, startPx + 2, textTop)
|
|
172
223
|
}
|
|
224
|
+
textTop = textTop + 12
|
|
225
|
+
context.fillText(location, startPx + 2, textTop)
|
|
226
|
+
}
|
|
173
227
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
if (!mousePosition) {
|
|
181
|
-
return
|
|
182
|
-
}
|
|
183
|
-
const { refName, regionNumber, x } = mousePosition
|
|
184
|
-
const { lgv } = stateModel
|
|
185
|
-
const { bpToPx, offsetPx } = lgv
|
|
186
|
-
const startPxInfo = bpToPx({ refName, coord: feature.start, regionNumber })
|
|
187
|
-
const endPxInfo = bpToPx({ refName, coord: feature.end, regionNumber })
|
|
188
|
-
if (startPxInfo !== undefined && endPxInfo !== undefined) {
|
|
189
|
-
const startPx = startPxInfo.offsetPx - offsetPx
|
|
190
|
-
const endPx = endPxInfo.offsetPx - offsetPx
|
|
191
|
-
if (Math.abs(endPx - startPx) < 8) {
|
|
192
|
-
return
|
|
193
|
-
}
|
|
194
|
-
if (Math.abs(startPx - x) < 4) {
|
|
195
|
-
return 'start'
|
|
196
|
-
}
|
|
197
|
-
if (Math.abs(endPx - x) < 4) {
|
|
198
|
-
return 'end'
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
return
|
|
202
|
-
}
|
|
228
|
+
export function isSelectedFeature(
|
|
229
|
+
feature: AnnotationFeature,
|
|
230
|
+
selectedFeature: AnnotationFeature | undefined,
|
|
231
|
+
) {
|
|
232
|
+
return Boolean(selectedFeature && feature._id === selectedFeature._id)
|
|
233
|
+
}
|
|
203
234
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
}
|
|
210
|
-
const { feature, mousePosition } = apolloHover
|
|
211
|
-
if (!feature || !mousePosition) {
|
|
212
|
-
return
|
|
213
|
-
}
|
|
214
|
-
const { bpPerPx, bpToPx, offsetPx } = lgv
|
|
215
|
-
const displayedRegion = displayedRegions[mousePosition.regionNumber]
|
|
216
|
-
const { refName, reversed } = displayedRegion
|
|
217
|
-
const { end, length, start } = feature
|
|
218
|
-
const { regionNumber, y } = mousePosition
|
|
219
|
-
const startPx =
|
|
220
|
-
(bpToPx({ refName, coord: reversed ? end : start, regionNumber })
|
|
221
|
-
?.offsetPx ?? 0) - offsetPx
|
|
222
|
-
const row = Math.floor(y / apolloRowHeight)
|
|
223
|
-
const top = row * apolloRowHeight
|
|
224
|
-
const widthPx = length / bpPerPx
|
|
225
|
-
ctx.fillStyle = theme?.palette.action.focus ?? 'rgba(0,0,0,0.04)'
|
|
226
|
-
ctx.fillRect(startPx, top, widthPx, apolloRowHeight)
|
|
227
|
-
}
|
|
235
|
+
function getBackgroundColor(theme: Theme | undefined, selected: boolean) {
|
|
236
|
+
return selected
|
|
237
|
+
? theme?.palette.text.primary ?? 'black'
|
|
238
|
+
: theme?.palette.background.default ?? 'white'
|
|
239
|
+
}
|
|
228
240
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
const { bpPerPx, offsetPx } = lgv
|
|
236
|
-
if (!apolloDragging) {
|
|
237
|
-
return
|
|
238
|
-
}
|
|
239
|
-
const {
|
|
240
|
-
feature,
|
|
241
|
-
glyph,
|
|
242
|
-
mousePosition: startingMousePosition,
|
|
243
|
-
} = apolloDragging.start
|
|
244
|
-
if (!feature) {
|
|
245
|
-
throw new Error('no feature for drag preview??')
|
|
246
|
-
}
|
|
247
|
-
if (glyph !== this) {
|
|
248
|
-
throw new Error('drawDragPreview() called on wrong glyph?')
|
|
249
|
-
}
|
|
250
|
-
const { mousePosition: currentMousePosition } = apolloDragging.current
|
|
251
|
-
const edge = this.isMouseOnFeatureEdge(
|
|
252
|
-
startingMousePosition,
|
|
253
|
-
feature,
|
|
254
|
-
stateModel,
|
|
255
|
-
)
|
|
256
|
-
if (!edge) {
|
|
257
|
-
return
|
|
258
|
-
}
|
|
241
|
+
function getTextColor(theme: Theme | undefined, selected: boolean) {
|
|
242
|
+
return selected
|
|
243
|
+
? theme?.palette.getContrastText(getBackgroundColor(theme, selected)) ??
|
|
244
|
+
'white'
|
|
245
|
+
: theme?.palette.text.primary ?? 'black'
|
|
246
|
+
}
|
|
259
247
|
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
const rectWidth = Math.abs(currentMousePosition.x - featureEdgePx)
|
|
272
|
-
const rectHeight = apolloRowHeight * rowCount
|
|
273
|
-
|
|
274
|
-
overlayCtx.strokeStyle = theme?.palette.info.main ?? 'rgb(255,0,0)'
|
|
275
|
-
overlayCtx.setLineDash([6])
|
|
276
|
-
overlayCtx.strokeRect(rectX, rectY, rectWidth, rectHeight)
|
|
277
|
-
overlayCtx.fillStyle = alpha(
|
|
278
|
-
theme?.palette.info.main ?? 'rgb(255,0,0)',
|
|
279
|
-
0.2,
|
|
280
|
-
)
|
|
281
|
-
overlayCtx.fillRect(rectX, rectY, rectWidth, rectHeight)
|
|
282
|
-
}
|
|
248
|
+
export function drawBox(
|
|
249
|
+
ctx: CanvasRenderingContext2D,
|
|
250
|
+
x: number,
|
|
251
|
+
y: number,
|
|
252
|
+
width: number,
|
|
253
|
+
height: number,
|
|
254
|
+
color: string,
|
|
255
|
+
) {
|
|
256
|
+
ctx.fillStyle = color
|
|
257
|
+
ctx.fillRect(x, y, width, height)
|
|
258
|
+
}
|
|
283
259
|
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
}
|
|
260
|
+
function getContextMenuItems(
|
|
261
|
+
display: LinearApolloDisplayMouseEvents,
|
|
262
|
+
): MenuItem[] {
|
|
263
|
+
const {
|
|
264
|
+
apolloHover,
|
|
265
|
+
apolloInternetAccount: internetAccount,
|
|
266
|
+
changeManager,
|
|
267
|
+
regions,
|
|
268
|
+
selectedFeature,
|
|
269
|
+
session,
|
|
270
|
+
} = display
|
|
271
|
+
const menuItems: MenuItem[] = []
|
|
272
|
+
if (!apolloHover) {
|
|
273
|
+
return menuItems
|
|
299
274
|
}
|
|
275
|
+
const { feature: sourceFeature } = apolloHover
|
|
276
|
+
const role = internetAccount ? internetAccount.role : 'admin'
|
|
277
|
+
const admin = role === 'admin'
|
|
278
|
+
const readOnly = !(role && ['admin', 'user'].includes(role))
|
|
279
|
+
const [region] = regions
|
|
280
|
+
const sourceAssemblyId = display.getAssemblyId(region.assemblyName)
|
|
281
|
+
const currentAssemblyId = display.getAssemblyId(region.assemblyName)
|
|
282
|
+
menuItems.push(
|
|
283
|
+
{
|
|
284
|
+
label: 'Add child feature',
|
|
285
|
+
disabled: readOnly,
|
|
286
|
+
onClick: () => {
|
|
287
|
+
;(session as unknown as AbstractSessionModel).queueDialog(
|
|
288
|
+
(doneCallback) => [
|
|
289
|
+
AddChildFeature,
|
|
290
|
+
{
|
|
291
|
+
session,
|
|
292
|
+
handleClose: () => {
|
|
293
|
+
doneCallback()
|
|
294
|
+
},
|
|
295
|
+
changeManager,
|
|
296
|
+
sourceFeature,
|
|
297
|
+
sourceAssemblyId,
|
|
298
|
+
internetAccount,
|
|
299
|
+
},
|
|
300
|
+
],
|
|
301
|
+
)
|
|
302
|
+
},
|
|
303
|
+
},
|
|
304
|
+
{
|
|
305
|
+
label: 'Copy features and annotations',
|
|
306
|
+
disabled: readOnly,
|
|
307
|
+
onClick: () => {
|
|
308
|
+
;(session as unknown as AbstractSessionModel).queueDialog(
|
|
309
|
+
(doneCallback) => [
|
|
310
|
+
CopyFeature,
|
|
311
|
+
{
|
|
312
|
+
session,
|
|
313
|
+
handleClose: () => {
|
|
314
|
+
doneCallback()
|
|
315
|
+
},
|
|
316
|
+
changeManager,
|
|
317
|
+
sourceFeature,
|
|
318
|
+
sourceAssemblyId: currentAssemblyId,
|
|
319
|
+
},
|
|
320
|
+
],
|
|
321
|
+
)
|
|
322
|
+
},
|
|
323
|
+
},
|
|
324
|
+
{
|
|
325
|
+
label: 'Delete feature',
|
|
326
|
+
disabled: !admin,
|
|
327
|
+
onClick: () => {
|
|
328
|
+
;(session as unknown as AbstractSessionModel).queueDialog(
|
|
329
|
+
(doneCallback) => [
|
|
330
|
+
DeleteFeature,
|
|
331
|
+
{
|
|
332
|
+
session,
|
|
333
|
+
handleClose: () => {
|
|
334
|
+
doneCallback()
|
|
335
|
+
},
|
|
336
|
+
changeManager,
|
|
337
|
+
sourceFeature,
|
|
338
|
+
sourceAssemblyId: currentAssemblyId,
|
|
339
|
+
selectedFeature,
|
|
340
|
+
setSelectedFeature: (feature?: AnnotationFeature) => {
|
|
341
|
+
display.setSelectedFeature(feature)
|
|
342
|
+
},
|
|
343
|
+
},
|
|
344
|
+
],
|
|
345
|
+
)
|
|
346
|
+
},
|
|
347
|
+
},
|
|
348
|
+
{
|
|
349
|
+
label: 'Modify feature attribute',
|
|
350
|
+
disabled: readOnly,
|
|
351
|
+
onClick: () => {
|
|
352
|
+
;(session as unknown as AbstractSessionModel).queueDialog(
|
|
353
|
+
(doneCallback) => [
|
|
354
|
+
ModifyFeatureAttribute,
|
|
355
|
+
{
|
|
356
|
+
session,
|
|
357
|
+
handleClose: () => {
|
|
358
|
+
doneCallback()
|
|
359
|
+
},
|
|
360
|
+
changeManager,
|
|
361
|
+
sourceFeature,
|
|
362
|
+
sourceAssemblyId: currentAssemblyId,
|
|
363
|
+
},
|
|
364
|
+
],
|
|
365
|
+
)
|
|
366
|
+
},
|
|
367
|
+
},
|
|
368
|
+
{
|
|
369
|
+
label: 'Edit feature details',
|
|
370
|
+
onClick: () => {
|
|
371
|
+
const apolloFeatureWidget = (
|
|
372
|
+
session as unknown as SessionWithWidgets
|
|
373
|
+
).addWidget(
|
|
374
|
+
'ApolloFeatureDetailsWidget',
|
|
375
|
+
'apolloFeatureDetailsWidget',
|
|
376
|
+
{
|
|
377
|
+
feature: sourceFeature,
|
|
378
|
+
assembly: currentAssemblyId,
|
|
379
|
+
refName: region.refName,
|
|
380
|
+
},
|
|
381
|
+
)
|
|
382
|
+
;(session as unknown as SessionWithWidgets).showWidget(
|
|
383
|
+
apolloFeatureWidget,
|
|
384
|
+
)
|
|
385
|
+
},
|
|
386
|
+
},
|
|
387
|
+
)
|
|
388
|
+
return menuItems
|
|
389
|
+
}
|
|
300
390
|
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
391
|
+
function getFeatureFromLayout(
|
|
392
|
+
feature: AnnotationFeature,
|
|
393
|
+
_bp: number,
|
|
394
|
+
_row: number,
|
|
395
|
+
): AnnotationFeature {
|
|
396
|
+
return feature
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
function getRowCount(_feature: AnnotationFeature) {
|
|
400
|
+
return 1
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
function getRowForFeature(
|
|
404
|
+
_feature: AnnotationFeature,
|
|
405
|
+
_childFeature: AnnotationFeature,
|
|
406
|
+
): number | undefined {
|
|
407
|
+
return 0
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
function onMouseDown(
|
|
411
|
+
stateModel: LinearApolloDisplay,
|
|
412
|
+
currentMousePosition: MousePositionWithFeatureAndGlyph,
|
|
413
|
+
event: CanvasMouseEvent,
|
|
414
|
+
) {
|
|
415
|
+
const { featureAndGlyphUnderMouse } = currentMousePosition
|
|
416
|
+
// swallow the mouseDown if we are on the edge of the feature so that we
|
|
417
|
+
// don't start dragging the view if we try to drag the feature edge
|
|
418
|
+
const { feature } = featureAndGlyphUnderMouse
|
|
419
|
+
const edge = isMouseOnFeatureEdge(currentMousePosition, feature, stateModel)
|
|
420
|
+
if (edge) {
|
|
421
|
+
event.stopPropagation()
|
|
422
|
+
stateModel.startDrag(currentMousePosition, feature, edge)
|
|
311
423
|
}
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
function onMouseLeave(): void {
|
|
427
|
+
return
|
|
428
|
+
}
|
|
312
429
|
|
|
313
|
-
|
|
314
|
-
|
|
430
|
+
function onMouseMove(
|
|
431
|
+
stateModel: LinearApolloDisplay,
|
|
432
|
+
mousePosition: MousePosition,
|
|
433
|
+
) {
|
|
434
|
+
if (isMousePositionWithFeatureAndGlyph(mousePosition)) {
|
|
435
|
+
const { featureAndGlyphUnderMouse } = mousePosition
|
|
436
|
+
stateModel.setApolloHover(featureAndGlyphUnderMouse)
|
|
437
|
+
const { feature } = featureAndGlyphUnderMouse
|
|
438
|
+
const edge = isMouseOnFeatureEdge(mousePosition, feature, stateModel)
|
|
439
|
+
if (edge) {
|
|
440
|
+
stateModel.setCursor('col-resize')
|
|
315
441
|
return
|
|
316
442
|
}
|
|
317
|
-
const { feature } = stateModel.getFeatureAndGlyphUnderMouse(event)
|
|
318
|
-
if (feature) {
|
|
319
|
-
stateModel.setSelectedFeature(feature)
|
|
320
|
-
}
|
|
321
443
|
}
|
|
444
|
+
stateModel.setCursor()
|
|
445
|
+
}
|
|
322
446
|
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
return true
|
|
330
|
-
}
|
|
331
|
-
}
|
|
332
|
-
return false
|
|
447
|
+
function onMouseUp(
|
|
448
|
+
stateModel: LinearApolloDisplay,
|
|
449
|
+
mousePosition: MousePosition,
|
|
450
|
+
) {
|
|
451
|
+
if (stateModel.apolloDragging) {
|
|
452
|
+
return
|
|
333
453
|
}
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
stateModel
|
|
337
|
-
currentMousePosition: MousePosition,
|
|
338
|
-
) {
|
|
339
|
-
const { feature, glyph, mousePosition, topLevelFeature } =
|
|
340
|
-
stateModel.apolloDragging?.start ?? {}
|
|
341
|
-
if (!(currentMousePosition && mousePosition)) {
|
|
342
|
-
return
|
|
343
|
-
}
|
|
344
|
-
stateModel.setDragging({
|
|
345
|
-
start: {
|
|
346
|
-
feature,
|
|
347
|
-
topLevelFeature,
|
|
348
|
-
glyph,
|
|
349
|
-
mousePosition,
|
|
350
|
-
},
|
|
351
|
-
current: {
|
|
352
|
-
feature,
|
|
353
|
-
topLevelFeature,
|
|
354
|
-
glyph,
|
|
355
|
-
mousePosition: currentMousePosition,
|
|
356
|
-
},
|
|
357
|
-
})
|
|
454
|
+
const { featureAndGlyphUnderMouse } = mousePosition
|
|
455
|
+
if (featureAndGlyphUnderMouse?.feature) {
|
|
456
|
+
stateModel.setSelectedFeature(featureAndGlyphUnderMouse.feature)
|
|
358
457
|
}
|
|
458
|
+
}
|
|
359
459
|
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
460
|
+
/** @returns undefined if mouse not on the edge of this feature, otherwise 'start' or 'end' depending on which edge */
|
|
461
|
+
function isMouseOnFeatureEdge(
|
|
462
|
+
mousePosition: MousePosition,
|
|
463
|
+
feature: AnnotationFeature,
|
|
464
|
+
stateModel: LinearApolloDisplay,
|
|
465
|
+
) {
|
|
466
|
+
const { refName, regionNumber, x } = mousePosition
|
|
467
|
+
const { lgv } = stateModel
|
|
468
|
+
const { offsetPx } = lgv
|
|
469
|
+
const minPxInfo = lgv.bpToPx({ refName, coord: feature.min, regionNumber })
|
|
470
|
+
const maxPxInfo = lgv.bpToPx({ refName, coord: feature.max, regionNumber })
|
|
471
|
+
if (minPxInfo !== undefined && maxPxInfo !== undefined) {
|
|
472
|
+
const minPx = minPxInfo.offsetPx - offsetPx
|
|
473
|
+
const maxPx = maxPxInfo.offsetPx - offsetPx
|
|
474
|
+
if (Math.abs(maxPx - minPx) < 8) {
|
|
369
475
|
return
|
|
370
476
|
}
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
glyph,
|
|
374
|
-
mousePosition: startingMousePosition,
|
|
375
|
-
} = apolloDragging.start
|
|
376
|
-
if (!feature) {
|
|
377
|
-
throw new Error('no feature for drag preview??')
|
|
378
|
-
}
|
|
379
|
-
if (glyph !== this) {
|
|
380
|
-
throw new Error('drawDragPreview() called on wrong glyph?')
|
|
477
|
+
if (Math.abs(minPx - x) < 4) {
|
|
478
|
+
return 'min'
|
|
381
479
|
}
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
feature,
|
|
385
|
-
stateModel,
|
|
386
|
-
)
|
|
387
|
-
if (!edge) {
|
|
388
|
-
return
|
|
480
|
+
if (Math.abs(maxPx - x) < 4) {
|
|
481
|
+
return 'max'
|
|
389
482
|
}
|
|
390
|
-
|
|
391
|
-
const { mousePosition: currentMousePosition } = apolloDragging.current
|
|
392
|
-
const region = displayedRegions[startingMousePosition.regionNumber]
|
|
393
|
-
const newBp = currentMousePosition.bp
|
|
394
|
-
const assembly = getAssemblyId(region.assemblyName)
|
|
395
|
-
let change: LocationEndChange | LocationStartChange
|
|
396
|
-
if (edge === 'end') {
|
|
397
|
-
const featureId = feature._id
|
|
398
|
-
const oldEnd = feature.end
|
|
399
|
-
const newEnd = newBp
|
|
400
|
-
change = new LocationEndChange({
|
|
401
|
-
typeName: 'LocationEndChange',
|
|
402
|
-
changedIds: [featureId],
|
|
403
|
-
featureId,
|
|
404
|
-
oldEnd,
|
|
405
|
-
newEnd,
|
|
406
|
-
assembly,
|
|
407
|
-
})
|
|
408
|
-
} else {
|
|
409
|
-
const featureId = feature._id
|
|
410
|
-
const oldStart = feature.start
|
|
411
|
-
const newStart = newBp
|
|
412
|
-
change = new LocationStartChange({
|
|
413
|
-
typeName: 'LocationStartChange',
|
|
414
|
-
changedIds: [featureId],
|
|
415
|
-
featureId,
|
|
416
|
-
oldStart,
|
|
417
|
-
newStart,
|
|
418
|
-
assembly,
|
|
419
|
-
})
|
|
420
|
-
}
|
|
421
|
-
if (!changeManager) {
|
|
422
|
-
throw new Error('no change manager')
|
|
423
|
-
}
|
|
424
|
-
void changeManager.submit(change)
|
|
425
|
-
setCursor()
|
|
426
483
|
}
|
|
484
|
+
return
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
export const boxGlyph: Glyph = {
|
|
488
|
+
draw,
|
|
489
|
+
drawDragPreview,
|
|
490
|
+
drawHover,
|
|
491
|
+
drawTooltip,
|
|
492
|
+
getContextMenuItems,
|
|
493
|
+
getFeatureFromLayout,
|
|
494
|
+
getRowCount,
|
|
495
|
+
getRowForFeature,
|
|
496
|
+
onMouseDown,
|
|
497
|
+
onMouseLeave,
|
|
498
|
+
onMouseMove,
|
|
499
|
+
onMouseUp,
|
|
427
500
|
}
|