@apollo-annotation/jbrowse-plugin-apollo 0.3.5 → 0.3.6
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 +5474 -4937
- package/dist/index.esm.js.map +1 -1
- package/dist/jbrowse-plugin-apollo.cjs.development.js +4609 -4089
- 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 +3634 -3500
- 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 +4 -4
- package/src/ApolloInternetAccount/addMenuItems.ts +5 -2
- package/src/ApolloInternetAccount/components/AuthTypeSelector.tsx +1 -0
- package/src/ApolloInternetAccount/components/LoginButtons.tsx +1 -1
- package/src/ApolloInternetAccount/components/LoginIcons.tsx +1 -1
- package/src/ApolloInternetAccount/configSchema.ts +1 -1
- package/src/ApolloInternetAccount/model.ts +11 -10
- package/src/ApolloJobModel.ts +1 -1
- package/src/ApolloRefNameAliasAdapter/ApolloRefNameAliasAdapter.ts +8 -6
- package/src/ApolloRefNameAliasAdapter/index.ts +2 -2
- package/src/ApolloSequenceAdapter/ApolloSequenceAdapter.ts +4 -4
- package/src/ApolloSequenceAdapter/index.ts +1 -1
- package/src/ApolloTextSearchAdapter/ApolloTextSearchAdapter.ts +8 -7
- package/src/ApolloTextSearchAdapter/index.ts +1 -1
- package/src/BackendDrivers/BackendDriver.ts +7 -7
- package/src/BackendDrivers/CollaborationServerDriver.ts +14 -10
- package/src/BackendDrivers/DesktopFileDriver.ts +11 -10
- package/src/BackendDrivers/InMemoryFileDriver.ts +10 -6
- package/src/ChangeManager.ts +5 -5
- package/src/FeatureDetailsWidget/ApolloFeatureDetailsWidget.tsx +8 -7
- package/src/FeatureDetailsWidget/ApolloTranscriptDetailsWidget.tsx +35 -14
- package/src/FeatureDetailsWidget/AttributeKey.tsx +50 -0
- package/src/FeatureDetailsWidget/AttributeKeySelector.tsx +104 -0
- package/src/FeatureDetailsWidget/Attributes.tsx +210 -367
- package/src/FeatureDetailsWidget/BasicInformation.tsx +6 -5
- package/src/FeatureDetailsWidget/DefaultAttributeEditor.tsx +104 -0
- package/src/FeatureDetailsWidget/DefaultAttributeViewer.tsx +22 -0
- package/src/FeatureDetailsWidget/FeatureDetailsNavigation.tsx +4 -4
- package/src/FeatureDetailsWidget/NumberTextField.tsx +1 -1
- package/src/FeatureDetailsWidget/Sequence.tsx +2 -2
- package/src/FeatureDetailsWidget/StringTextField.tsx +1 -1
- package/src/FeatureDetailsWidget/TranscriptSequence.tsx +3 -3
- package/src/FeatureDetailsWidget/TranscriptWidgetEditLocation.tsx +21 -21
- package/src/FeatureDetailsWidget/TranscriptWidgetSummary.tsx +4 -4
- package/src/FeatureDetailsWidget/model.ts +8 -3
- package/src/LinearApolloDisplay/components/LinearApolloDisplay.tsx +6 -6
- package/src/LinearApolloDisplay/glyphs/BoxGlyph.ts +13 -14
- package/src/LinearApolloDisplay/glyphs/GeneGlyph.ts +9 -9
- package/src/LinearApolloDisplay/glyphs/GenericChildGlyph.ts +6 -5
- package/src/LinearApolloDisplay/glyphs/Glyph.ts +7 -7
- package/src/LinearApolloDisplay/stateModel/base.ts +52 -10
- package/src/LinearApolloDisplay/stateModel/index.ts +4 -3
- package/src/LinearApolloDisplay/stateModel/layouts.ts +8 -39
- package/src/LinearApolloDisplay/stateModel/mouseEvents.ts +13 -12
- package/src/LinearApolloDisplay/stateModel/rendering.ts +59 -31
- package/src/LinearApolloSixFrameDisplay/components/LinearApolloSixFrameDisplay.tsx +221 -0
- package/src/LinearApolloSixFrameDisplay/components/TrackLines.tsx +40 -0
- package/src/LinearApolloSixFrameDisplay/components/index.ts +2 -0
- package/src/LinearApolloSixFrameDisplay/configSchema.ts +7 -0
- package/src/LinearApolloSixFrameDisplay/glyphs/GeneGlyph.ts +821 -0
- package/src/LinearApolloSixFrameDisplay/glyphs/Glyph.ts +63 -0
- package/src/LinearApolloSixFrameDisplay/glyphs/index.ts +1 -0
- package/src/LinearApolloSixFrameDisplay/index.ts +2 -0
- package/src/LinearApolloSixFrameDisplay/stateModel/base.ts +261 -0
- package/src/LinearApolloSixFrameDisplay/stateModel/index.ts +27 -0
- package/src/LinearApolloSixFrameDisplay/stateModel/layouts.ts +236 -0
- package/src/LinearApolloSixFrameDisplay/stateModel/mouseEvents.ts +349 -0
- package/src/LinearApolloSixFrameDisplay/stateModel/rendering.ts +199 -0
- package/src/LinearApolloSixFrameDisplay/types.ts +1 -0
- package/src/OntologyManager/OntologyStore/fulltext.test.ts +1 -1
- package/src/OntologyManager/OntologyStore/fulltext.ts +8 -3
- package/src/OntologyManager/OntologyStore/index.test.ts +3 -1
- package/src/OntologyManager/OntologyStore/index.ts +19 -14
- package/src/OntologyManager/OntologyStore/indexeddb-schema.ts +6 -5
- package/src/OntologyManager/OntologyStore/indexeddb-storage.ts +11 -5
- package/src/OntologyManager/index.ts +8 -6
- package/src/OntologyManager/util.ts +3 -2
- package/src/TabularEditor/HybridGrid/ChangeHandling.ts +2 -2
- package/src/TabularEditor/HybridGrid/Feature.tsx +9 -7
- package/src/TabularEditor/HybridGrid/FeatureAttributes.tsx +1 -1
- package/src/TabularEditor/HybridGrid/HybridGrid.tsx +3 -2
- package/src/TabularEditor/HybridGrid/ToolBar.tsx +1 -1
- package/src/TabularEditor/HybridGrid/featureContextMenuItems.ts +6 -6
- package/src/TabularEditor/TabularEditorPane.tsx +1 -1
- package/src/TabularEditor/model.ts +2 -2
- package/src/TabularEditor/types.ts +5 -2
- package/src/components/AddAssembly.tsx +182 -179
- package/src/components/AddChildFeature.tsx +6 -5
- package/src/components/AddFeature.tsx +211 -38
- package/src/components/AddRefSeqAliases.tsx +14 -12
- package/src/components/CopyFeature.tsx +8 -7
- package/src/components/CreateApolloAnnotation.tsx +9 -8
- package/src/components/DeleteAssembly.tsx +9 -8
- package/src/components/DeleteFeature.tsx +5 -4
- package/src/components/Dialog.tsx +1 -1
- package/src/components/DownloadGFF3.tsx +11 -10
- package/src/components/FilterFeatures.tsx +6 -4
- package/src/components/ImportFeatures.tsx +7 -6
- package/src/components/LogOut.tsx +5 -4
- package/src/components/ManageChecks.tsx +9 -8
- package/src/components/ManageUsers.tsx +11 -10
- package/src/components/OntologyTermAutocomplete.tsx +5 -5
- package/src/components/OntologyTermMultiSelect.tsx +6 -6
- package/src/components/OpenLocalFile.tsx +4 -3
- package/src/components/ViewChangeLog.tsx +7 -6
- package/src/components/ViewCheckResults.tsx +8 -7
- package/src/extensions/annotationFromJBrowseFeature.test.ts +1 -0
- package/src/extensions/annotationFromJBrowseFeature.ts +11 -10
- package/src/extensions/annotationFromPileup.ts +6 -6
- package/src/index.ts +33 -50
- package/src/makeDisplayComponent.tsx +90 -37
- package/src/session/ClientDataStore.ts +21 -17
- package/src/session/session.ts +20 -26
- package/src/types.ts +4 -4
- package/src/util/annotationFeatureUtils.ts +1 -1
- package/src/util/index.ts +3 -3
- package/src/util/loadAssemblyIntoClient.ts +10 -3
- package/src/ApolloSixFrameRenderer/ApolloSixFrameRenderer.tsx +0 -13
- package/src/ApolloSixFrameRenderer/components/ApolloRendering.tsx +0 -707
- package/src/ApolloSixFrameRenderer/configSchema.ts +0 -7
- package/src/ApolloSixFrameRenderer/index.ts +0 -3
- package/src/SixFrameFeatureDisplay/components/TrackLines.tsx +0 -19
- package/src/SixFrameFeatureDisplay/components/index.ts +0 -1
- package/src/SixFrameFeatureDisplay/configSchema.ts +0 -21
- package/src/SixFrameFeatureDisplay/index.ts +0 -2
- package/src/SixFrameFeatureDisplay/stateModel.ts +0 -443
|
@@ -0,0 +1,821 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type AnnotationFeature,
|
|
3
|
+
type TranscriptPartCoding,
|
|
4
|
+
} from '@apollo-annotation/mst'
|
|
5
|
+
import { type MenuItem } from '@jbrowse/core/ui'
|
|
6
|
+
import {
|
|
7
|
+
type AbstractSessionModel,
|
|
8
|
+
type SessionWithWidgets,
|
|
9
|
+
getFrame,
|
|
10
|
+
intersection2,
|
|
11
|
+
isSessionModelWithWidgets,
|
|
12
|
+
} from '@jbrowse/core/util'
|
|
13
|
+
import { alpha } from '@mui/material'
|
|
14
|
+
import equal from 'fast-deep-equal/es6'
|
|
15
|
+
|
|
16
|
+
import { AddChildFeature, CopyFeature, DeleteFeature } from '../../components'
|
|
17
|
+
import { type LinearApolloSixFrameDisplay } from '../stateModel'
|
|
18
|
+
import {
|
|
19
|
+
type LinearApolloSixFrameDisplayMouseEvents,
|
|
20
|
+
type MousePosition,
|
|
21
|
+
type MousePositionWithFeatureAndGlyph,
|
|
22
|
+
isMousePositionWithFeatureAndGlyph,
|
|
23
|
+
} from '../stateModel/mouseEvents'
|
|
24
|
+
import { type LinearApolloSixFrameDisplayRendering } from '../stateModel/rendering'
|
|
25
|
+
import { type CanvasMouseEvent } from '../types'
|
|
26
|
+
|
|
27
|
+
import { type Glyph } from './Glyph'
|
|
28
|
+
|
|
29
|
+
let forwardFillLight: CanvasPattern | null = null
|
|
30
|
+
let backwardFillLight: CanvasPattern | null = null
|
|
31
|
+
let forwardFillDark: CanvasPattern | null = null
|
|
32
|
+
let backwardFillDark: CanvasPattern | null = null
|
|
33
|
+
if ('document' in globalThis) {
|
|
34
|
+
for (const direction of ['forward', 'backward']) {
|
|
35
|
+
for (const themeMode of ['light', 'dark']) {
|
|
36
|
+
const canvas = document.createElement('canvas')
|
|
37
|
+
const canvasSize = 10
|
|
38
|
+
canvas.width = canvas.height = canvasSize
|
|
39
|
+
const ctx = canvas.getContext('2d')
|
|
40
|
+
if (ctx) {
|
|
41
|
+
const stripeColor1 =
|
|
42
|
+
themeMode === 'light' ? 'rgba(0,0,0,0)' : 'rgba(0,0,0,0.75)'
|
|
43
|
+
const stripeColor2 =
|
|
44
|
+
themeMode === 'light' ? 'rgba(255,255,255,0.25)' : 'rgba(0,0,0,0.50)'
|
|
45
|
+
const gradient =
|
|
46
|
+
direction === 'forward'
|
|
47
|
+
? ctx.createLinearGradient(0, canvasSize, canvasSize, 0)
|
|
48
|
+
: ctx.createLinearGradient(0, 0, canvasSize, canvasSize)
|
|
49
|
+
gradient.addColorStop(0, stripeColor1)
|
|
50
|
+
gradient.addColorStop(0.25, stripeColor1)
|
|
51
|
+
gradient.addColorStop(0.25, stripeColor2)
|
|
52
|
+
gradient.addColorStop(0.5, stripeColor2)
|
|
53
|
+
gradient.addColorStop(0.5, stripeColor1)
|
|
54
|
+
gradient.addColorStop(0.75, stripeColor1)
|
|
55
|
+
gradient.addColorStop(0.75, stripeColor2)
|
|
56
|
+
gradient.addColorStop(1, stripeColor2)
|
|
57
|
+
ctx.fillStyle = gradient
|
|
58
|
+
ctx.fillRect(0, 0, 10, 10)
|
|
59
|
+
if (direction === 'forward') {
|
|
60
|
+
if (themeMode === 'light') {
|
|
61
|
+
forwardFillLight = ctx.createPattern(canvas, 'repeat')
|
|
62
|
+
} else {
|
|
63
|
+
forwardFillDark = ctx.createPattern(canvas, 'repeat')
|
|
64
|
+
}
|
|
65
|
+
} else {
|
|
66
|
+
if (themeMode === 'light') {
|
|
67
|
+
backwardFillLight = ctx.createPattern(canvas, 'repeat')
|
|
68
|
+
} else {
|
|
69
|
+
backwardFillDark = ctx.createPattern(canvas, 'repeat')
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function deepSetHas<T>(set: Set<T>, item: T): boolean {
|
|
78
|
+
for (const elem of set) {
|
|
79
|
+
if (equal(elem, item)) {
|
|
80
|
+
return true
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return false
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function draw(
|
|
87
|
+
ctx: CanvasRenderingContext2D,
|
|
88
|
+
topLevelFeature: AnnotationFeature,
|
|
89
|
+
_row: number,
|
|
90
|
+
stateModel: LinearApolloSixFrameDisplayRendering,
|
|
91
|
+
displayedRegionIndex: number,
|
|
92
|
+
): void {
|
|
93
|
+
const { apolloRowHeight, lgv, session, theme, highestRow } = stateModel
|
|
94
|
+
const { bpPerPx, displayedRegions, offsetPx } = lgv
|
|
95
|
+
const displayedRegion = displayedRegions[displayedRegionIndex]
|
|
96
|
+
const { refName, reversed } = displayedRegion
|
|
97
|
+
const rowHeight = apolloRowHeight
|
|
98
|
+
const exonHeight = Math.round(0.6 * rowHeight)
|
|
99
|
+
const cdsHeight = Math.round(0.7 * rowHeight)
|
|
100
|
+
const { children, min, strand, _id } = topLevelFeature
|
|
101
|
+
if (!children) {
|
|
102
|
+
return
|
|
103
|
+
}
|
|
104
|
+
const { apolloSelectedFeature } = session
|
|
105
|
+
const { apolloDataStore } = session
|
|
106
|
+
const { featureTypeOntology } = apolloDataStore.ontologyManager
|
|
107
|
+
if (!featureTypeOntology) {
|
|
108
|
+
throw new Error('featureTypeOntology is undefined')
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Draw background for gene
|
|
112
|
+
const topLevelFeatureMinX =
|
|
113
|
+
(lgv.bpToPx({
|
|
114
|
+
refName,
|
|
115
|
+
coord: min,
|
|
116
|
+
regionNumber: displayedRegionIndex,
|
|
117
|
+
})?.offsetPx ?? 0) - offsetPx
|
|
118
|
+
const topLevelFeatureWidthPx = topLevelFeature.length / bpPerPx
|
|
119
|
+
const topLevelFeatureStartPx = reversed
|
|
120
|
+
? topLevelFeatureMinX - topLevelFeatureWidthPx
|
|
121
|
+
: topLevelFeatureMinX
|
|
122
|
+
const topLevelRow = strand == 1 ? 3 : 4
|
|
123
|
+
const topLevelFeatureTop = topLevelRow * rowHeight
|
|
124
|
+
const topLevelFeatureHeight = Math.round(0.7 * rowHeight)
|
|
125
|
+
|
|
126
|
+
ctx.fillStyle = theme?.palette.text.primary ?? 'black'
|
|
127
|
+
ctx.fillRect(
|
|
128
|
+
topLevelFeatureStartPx,
|
|
129
|
+
topLevelFeatureTop,
|
|
130
|
+
topLevelFeatureWidthPx,
|
|
131
|
+
topLevelFeatureHeight,
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
ctx.fillStyle =
|
|
135
|
+
apolloSelectedFeature && _id === apolloSelectedFeature._id
|
|
136
|
+
? alpha('rgb(0,0,0)', 0.7)
|
|
137
|
+
: alpha(theme?.palette.background.paper ?? '#ffffff', 0.7)
|
|
138
|
+
ctx.fillRect(
|
|
139
|
+
topLevelFeatureStartPx + 1,
|
|
140
|
+
topLevelFeatureTop + 1,
|
|
141
|
+
topLevelFeatureWidthPx - 2,
|
|
142
|
+
topLevelFeatureHeight - 2,
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
const forwardFill =
|
|
146
|
+
theme?.palette.mode === 'dark' ? forwardFillDark : forwardFillLight
|
|
147
|
+
const backwardFill =
|
|
148
|
+
theme?.palette.mode === 'dark' ? backwardFillDark : backwardFillLight
|
|
149
|
+
const reversal = reversed ? -1 : 1
|
|
150
|
+
let topFill: CanvasPattern | null = null,
|
|
151
|
+
bottomFill: CanvasPattern | null = null
|
|
152
|
+
if (strand) {
|
|
153
|
+
;[topFill, bottomFill] =
|
|
154
|
+
strand * reversal === 1
|
|
155
|
+
? [forwardFill, backwardFill]
|
|
156
|
+
: [backwardFill, forwardFill]
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (topFill && bottomFill) {
|
|
160
|
+
ctx.fillStyle = topFill
|
|
161
|
+
ctx.fillRect(
|
|
162
|
+
topLevelFeatureStartPx + 1,
|
|
163
|
+
topLevelFeatureTop + 1,
|
|
164
|
+
topLevelFeatureWidthPx - 2,
|
|
165
|
+
(topLevelFeatureHeight - 2) / 2,
|
|
166
|
+
)
|
|
167
|
+
ctx.fillStyle = bottomFill
|
|
168
|
+
ctx.fillRect(
|
|
169
|
+
topLevelFeatureStartPx + 1,
|
|
170
|
+
topLevelFeatureTop + (topLevelFeatureHeight - 2) / 2,
|
|
171
|
+
topLevelFeatureWidthPx - 2,
|
|
172
|
+
(topLevelFeatureHeight - 2) / 2,
|
|
173
|
+
)
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const renderedCDS = new Set<TranscriptPartCoding>()
|
|
177
|
+
// Draw exon and CDS for each mRNA
|
|
178
|
+
for (const [, child] of children) {
|
|
179
|
+
if (
|
|
180
|
+
!(
|
|
181
|
+
featureTypeOntology.isTypeOf(child.type, 'transcript') ||
|
|
182
|
+
featureTypeOntology.isTypeOf(child.type, 'pseudogenic_transcript')
|
|
183
|
+
)
|
|
184
|
+
) {
|
|
185
|
+
continue
|
|
186
|
+
}
|
|
187
|
+
const { children: childrenOfmRNA, cdsLocations, _id } = child
|
|
188
|
+
if (!childrenOfmRNA) {
|
|
189
|
+
continue
|
|
190
|
+
}
|
|
191
|
+
for (const [, exon] of childrenOfmRNA) {
|
|
192
|
+
if (!featureTypeOntology.isTypeOf(exon.type, 'exon')) {
|
|
193
|
+
continue
|
|
194
|
+
}
|
|
195
|
+
const minX =
|
|
196
|
+
(lgv.bpToPx({
|
|
197
|
+
refName,
|
|
198
|
+
coord: exon.min,
|
|
199
|
+
regionNumber: displayedRegionIndex,
|
|
200
|
+
})?.offsetPx ?? 0) - offsetPx
|
|
201
|
+
const widthPx = exon.length / bpPerPx
|
|
202
|
+
const startPx = reversed ? minX - widthPx : minX
|
|
203
|
+
|
|
204
|
+
const exonTop =
|
|
205
|
+
topLevelFeatureTop + (topLevelFeatureHeight - exonHeight) / 2
|
|
206
|
+
ctx.fillStyle = theme?.palette.text.primary ?? 'black'
|
|
207
|
+
ctx.fillRect(startPx, exonTop, widthPx, exonHeight)
|
|
208
|
+
if (widthPx > 2) {
|
|
209
|
+
ctx.clearRect(startPx + 1, exonTop + 1, widthPx - 2, exonHeight - 2)
|
|
210
|
+
ctx.fillStyle =
|
|
211
|
+
apolloSelectedFeature && exon._id === apolloSelectedFeature._id
|
|
212
|
+
? 'rgb(0,0,0)'
|
|
213
|
+
: alpha('#f5f500', 0.6)
|
|
214
|
+
ctx.fillRect(startPx + 1, exonTop + 1, widthPx - 2, exonHeight - 2)
|
|
215
|
+
if (topFill && bottomFill) {
|
|
216
|
+
ctx.fillStyle = topFill
|
|
217
|
+
ctx.fillRect(
|
|
218
|
+
startPx + 1,
|
|
219
|
+
exonTop + 1,
|
|
220
|
+
widthPx - 2,
|
|
221
|
+
(exonHeight - 2) / 2,
|
|
222
|
+
)
|
|
223
|
+
ctx.fillStyle = bottomFill
|
|
224
|
+
ctx.fillRect(
|
|
225
|
+
startPx + 1,
|
|
226
|
+
exonTop + 1 + (exonHeight - 2) / 2,
|
|
227
|
+
widthPx - 2,
|
|
228
|
+
(exonHeight - 2) / 2,
|
|
229
|
+
)
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
for (const cdsRow of cdsLocations) {
|
|
235
|
+
let prevCDSTop = 0
|
|
236
|
+
let prevCDSEndPx = 0
|
|
237
|
+
let counter = 1
|
|
238
|
+
for (const cds of cdsRow.sort((a, b) => a.max - b.max)) {
|
|
239
|
+
if (
|
|
240
|
+
(apolloSelectedFeature &&
|
|
241
|
+
featureTypeOntology.isTypeOf(apolloSelectedFeature.type, 'CDS') &&
|
|
242
|
+
_id === apolloSelectedFeature.parent?._id) ||
|
|
243
|
+
!deepSetHas(renderedCDS, cds)
|
|
244
|
+
) {
|
|
245
|
+
const cdsWidthPx = (cds.max - cds.min) / bpPerPx
|
|
246
|
+
const minX =
|
|
247
|
+
(lgv.bpToPx({
|
|
248
|
+
refName,
|
|
249
|
+
coord: cds.min,
|
|
250
|
+
regionNumber: displayedRegionIndex,
|
|
251
|
+
})?.offsetPx ?? 0) - offsetPx
|
|
252
|
+
const cdsStartPx = reversed ? minX - cdsWidthPx : minX
|
|
253
|
+
ctx.fillStyle = theme?.palette.text.primary ?? 'black'
|
|
254
|
+
const frame = getFrame(cds.min, cds.max, child.strand ?? 1, cds.phase)
|
|
255
|
+
const frameAdjust = frame < 0 ? -1 * frame + 5 : frame
|
|
256
|
+
const cdsTop =
|
|
257
|
+
(frameAdjust - 1) * rowHeight + (rowHeight - cdsHeight) / 2
|
|
258
|
+
ctx.fillRect(cdsStartPx, cdsTop, cdsWidthPx, cdsHeight)
|
|
259
|
+
if (cdsWidthPx > 2) {
|
|
260
|
+
ctx.clearRect(
|
|
261
|
+
cdsStartPx + 1,
|
|
262
|
+
cdsTop + 1,
|
|
263
|
+
cdsWidthPx - 2,
|
|
264
|
+
cdsHeight - 2,
|
|
265
|
+
)
|
|
266
|
+
|
|
267
|
+
const frameColor = theme?.palette.framesCDS.at(frame)?.main
|
|
268
|
+
const cdsColorCode = frameColor ?? 'rgb(171,71,188)'
|
|
269
|
+
ctx.fillStyle = cdsColorCode
|
|
270
|
+
ctx.fillStyle =
|
|
271
|
+
apolloSelectedFeature &&
|
|
272
|
+
featureTypeOntology.isTypeOf(apolloSelectedFeature.type, 'CDS') &&
|
|
273
|
+
_id === apolloSelectedFeature.parent?._id
|
|
274
|
+
? 'rgb(0,0,0)'
|
|
275
|
+
: cdsColorCode
|
|
276
|
+
ctx.fillRect(
|
|
277
|
+
cdsStartPx + 1,
|
|
278
|
+
cdsTop + 1,
|
|
279
|
+
cdsWidthPx - 2,
|
|
280
|
+
cdsHeight - 2,
|
|
281
|
+
)
|
|
282
|
+
|
|
283
|
+
// Draw lines to connect CDS features with shared mRNA parent
|
|
284
|
+
if (counter > 1) {
|
|
285
|
+
// Mid-point for intron line "hat"
|
|
286
|
+
const midPoint: [number, number] = [
|
|
287
|
+
(cdsStartPx - prevCDSEndPx) / 2 + prevCDSEndPx,
|
|
288
|
+
Math.max(
|
|
289
|
+
frame < 0 ? rowHeight * highestRow + 1 : 1, // Avoid render ceiling
|
|
290
|
+
Math.min(prevCDSTop, cdsTop) - rowHeight / 2,
|
|
291
|
+
),
|
|
292
|
+
]
|
|
293
|
+
ctx.strokeStyle = 'rgb(0, 128, 128)'
|
|
294
|
+
ctx.beginPath()
|
|
295
|
+
ctx.moveTo(prevCDSEndPx, prevCDSTop)
|
|
296
|
+
ctx.lineTo(...midPoint)
|
|
297
|
+
ctx.stroke()
|
|
298
|
+
ctx.moveTo(...midPoint)
|
|
299
|
+
ctx.lineTo(cdsStartPx, cdsTop + rowHeight / 2)
|
|
300
|
+
ctx.stroke()
|
|
301
|
+
}
|
|
302
|
+
prevCDSEndPx = cdsStartPx + cdsWidthPx
|
|
303
|
+
prevCDSTop = cdsTop + rowHeight / 2
|
|
304
|
+
counter += 1
|
|
305
|
+
|
|
306
|
+
if (topFill && bottomFill) {
|
|
307
|
+
ctx.fillStyle = topFill
|
|
308
|
+
ctx.fillRect(
|
|
309
|
+
cdsStartPx + 1,
|
|
310
|
+
cdsTop + 1,
|
|
311
|
+
cdsWidthPx - 2,
|
|
312
|
+
(cdsHeight - 2) / 2,
|
|
313
|
+
)
|
|
314
|
+
ctx.fillStyle = bottomFill
|
|
315
|
+
ctx.fillRect(
|
|
316
|
+
cdsStartPx + 1,
|
|
317
|
+
cdsTop + (cdsHeight - 2) / 2,
|
|
318
|
+
cdsWidthPx - 2,
|
|
319
|
+
(cdsHeight - 2) / 2,
|
|
320
|
+
)
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
renderedCDS.add(cds)
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
function drawDragPreview(
|
|
331
|
+
stateModel: LinearApolloSixFrameDisplay,
|
|
332
|
+
overlayCtx: CanvasRenderingContext2D,
|
|
333
|
+
) {
|
|
334
|
+
const { apolloDragging, apolloRowHeight, lgv, theme } = stateModel
|
|
335
|
+
const { bpPerPx, displayedRegions, offsetPx } = lgv
|
|
336
|
+
if (!apolloDragging) {
|
|
337
|
+
return
|
|
338
|
+
}
|
|
339
|
+
const { current, edge, feature, start } = apolloDragging
|
|
340
|
+
|
|
341
|
+
const row = Math.floor(start.y / apolloRowHeight)
|
|
342
|
+
const region = displayedRegions[start.regionNumber]
|
|
343
|
+
const rowCount = 1
|
|
344
|
+
const featureEdgeBp = region.reversed
|
|
345
|
+
? region.end - feature[edge]
|
|
346
|
+
: feature[edge] - region.start
|
|
347
|
+
const featureEdgePx = featureEdgeBp / bpPerPx - offsetPx
|
|
348
|
+
const rectX = Math.min(current.x, featureEdgePx)
|
|
349
|
+
const rectY = row * apolloRowHeight
|
|
350
|
+
const rectWidth = Math.abs(current.x - featureEdgePx)
|
|
351
|
+
const rectHeight = apolloRowHeight * rowCount
|
|
352
|
+
overlayCtx.strokeStyle = theme?.palette.info.main ?? 'rgb(255,0,0)'
|
|
353
|
+
overlayCtx.setLineDash([6])
|
|
354
|
+
overlayCtx.strokeRect(rectX, rectY, rectWidth, rectHeight)
|
|
355
|
+
overlayCtx.fillStyle = alpha(theme?.palette.info.main ?? 'rgb(255,0,0)', 0.2)
|
|
356
|
+
overlayCtx.fillRect(rectX, rectY, rectWidth, rectHeight)
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
function drawHover(
|
|
360
|
+
stateModel: LinearApolloSixFrameDisplay,
|
|
361
|
+
ctx: CanvasRenderingContext2D,
|
|
362
|
+
) {
|
|
363
|
+
const { apolloHover, apolloRowHeight, lgv, highestRow, session } = stateModel
|
|
364
|
+
if (!apolloHover) {
|
|
365
|
+
return
|
|
366
|
+
}
|
|
367
|
+
const { apolloDataStore } = session
|
|
368
|
+
const { featureTypeOntology } = apolloDataStore.ontologyManager
|
|
369
|
+
if (!featureTypeOntology) {
|
|
370
|
+
throw new Error('featureTypeOntology is undefined')
|
|
371
|
+
}
|
|
372
|
+
const { feature } = apolloHover
|
|
373
|
+
if (!featureTypeOntology.isTypeOf(feature.type, 'transcript')) {
|
|
374
|
+
return
|
|
375
|
+
}
|
|
376
|
+
const position = stateModel.getFeatureLayoutPosition(feature)
|
|
377
|
+
if (!position) {
|
|
378
|
+
return
|
|
379
|
+
}
|
|
380
|
+
const { bpPerPx, displayedRegions, offsetPx } = lgv
|
|
381
|
+
const { layoutIndex } = position
|
|
382
|
+
const displayedRegion = displayedRegions[layoutIndex]
|
|
383
|
+
const { refName, reversed } = displayedRegion
|
|
384
|
+
const rowHeight = apolloRowHeight
|
|
385
|
+
const cdsHeight = Math.round(0.7 * rowHeight)
|
|
386
|
+
const { cdsLocations, strand } = feature
|
|
387
|
+
for (const cdsRow of cdsLocations) {
|
|
388
|
+
let prevCDSTop = 0
|
|
389
|
+
let prevCDSEndPx = 0
|
|
390
|
+
let counter = 1
|
|
391
|
+
for (const cds of cdsRow.sort((a, b) => a.max - b.max)) {
|
|
392
|
+
const cdsWidthPx = (cds.max - cds.min) / bpPerPx
|
|
393
|
+
if (cdsWidthPx > 2) {
|
|
394
|
+
const minX =
|
|
395
|
+
(lgv.bpToPx({
|
|
396
|
+
refName,
|
|
397
|
+
coord: cds.min,
|
|
398
|
+
regionNumber: layoutIndex,
|
|
399
|
+
})?.offsetPx ?? 0) - offsetPx
|
|
400
|
+
const cdsStartPx = reversed ? minX - cdsWidthPx : minX
|
|
401
|
+
const frame = getFrame(cds.min, cds.max, strand ?? 1, cds.phase)
|
|
402
|
+
const frameAdjust = frame < 0 ? -1 * frame + 5 : frame
|
|
403
|
+
const cdsTop =
|
|
404
|
+
(frameAdjust - 1) * rowHeight + (rowHeight - cdsHeight) / 2
|
|
405
|
+
ctx.fillStyle = 'rgba(255,0,0,0.6)'
|
|
406
|
+
ctx.fillRect(cdsStartPx, cdsTop, cdsWidthPx, cdsHeight)
|
|
407
|
+
|
|
408
|
+
if (counter > 1) {
|
|
409
|
+
// Mid-point for intron line "hat"
|
|
410
|
+
const midPoint: [number, number] = [
|
|
411
|
+
(cdsStartPx - prevCDSEndPx) / 2 + prevCDSEndPx,
|
|
412
|
+
Math.max(
|
|
413
|
+
frame < 0 ? rowHeight * highestRow + 1 : 1, // Avoid render ceiling
|
|
414
|
+
Math.min(prevCDSTop, cdsTop) - rowHeight / 2,
|
|
415
|
+
),
|
|
416
|
+
]
|
|
417
|
+
ctx.strokeStyle = 'rgb(0, 0, 0)'
|
|
418
|
+
ctx.lineWidth = 2
|
|
419
|
+
ctx.beginPath()
|
|
420
|
+
ctx.moveTo(prevCDSEndPx, prevCDSTop)
|
|
421
|
+
ctx.lineTo(...midPoint)
|
|
422
|
+
ctx.stroke()
|
|
423
|
+
ctx.moveTo(...midPoint)
|
|
424
|
+
ctx.lineTo(cdsStartPx, cdsTop + rowHeight / 2)
|
|
425
|
+
ctx.stroke()
|
|
426
|
+
}
|
|
427
|
+
prevCDSEndPx = cdsStartPx + cdsWidthPx
|
|
428
|
+
prevCDSTop = cdsTop + rowHeight / 2
|
|
429
|
+
counter += 1
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
function onMouseDown(
|
|
436
|
+
stateModel: LinearApolloSixFrameDisplay,
|
|
437
|
+
currentMousePosition: MousePositionWithFeatureAndGlyph,
|
|
438
|
+
event: CanvasMouseEvent,
|
|
439
|
+
) {
|
|
440
|
+
const { featureAndGlyphUnderMouse } = currentMousePosition
|
|
441
|
+
// swallow the mouseDown if we are on the edge of the feature so that we
|
|
442
|
+
// don't start dragging the view if we try to drag the feature edge
|
|
443
|
+
const { cds, feature } = featureAndGlyphUnderMouse
|
|
444
|
+
const draggableFeature = getDraggableFeatureInfo(
|
|
445
|
+
currentMousePosition,
|
|
446
|
+
cds,
|
|
447
|
+
feature,
|
|
448
|
+
stateModel,
|
|
449
|
+
)
|
|
450
|
+
if (draggableFeature) {
|
|
451
|
+
event.stopPropagation()
|
|
452
|
+
stateModel.startDrag(
|
|
453
|
+
currentMousePosition,
|
|
454
|
+
draggableFeature.feature,
|
|
455
|
+
draggableFeature.edge,
|
|
456
|
+
)
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
function onMouseMove(
|
|
461
|
+
stateModel: LinearApolloSixFrameDisplay,
|
|
462
|
+
mousePosition: MousePosition,
|
|
463
|
+
) {
|
|
464
|
+
if (isMousePositionWithFeatureAndGlyph(mousePosition)) {
|
|
465
|
+
const { featureAndGlyphUnderMouse } = mousePosition
|
|
466
|
+
stateModel.setApolloHover(featureAndGlyphUnderMouse)
|
|
467
|
+
const { cds, feature } = featureAndGlyphUnderMouse
|
|
468
|
+
const draggableFeature = getDraggableFeatureInfo(
|
|
469
|
+
mousePosition,
|
|
470
|
+
cds,
|
|
471
|
+
feature,
|
|
472
|
+
stateModel,
|
|
473
|
+
)
|
|
474
|
+
if (draggableFeature) {
|
|
475
|
+
stateModel.setCursor('col-resize')
|
|
476
|
+
return
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
stateModel.setCursor()
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
function onMouseUp(
|
|
483
|
+
stateModel: LinearApolloSixFrameDisplay,
|
|
484
|
+
mousePosition: MousePosition,
|
|
485
|
+
) {
|
|
486
|
+
if (stateModel.apolloDragging) {
|
|
487
|
+
return
|
|
488
|
+
}
|
|
489
|
+
const { featureAndGlyphUnderMouse } = mousePosition
|
|
490
|
+
const { session } = stateModel
|
|
491
|
+
const { apolloDataStore } = session
|
|
492
|
+
const { featureTypeOntology } = apolloDataStore.ontologyManager
|
|
493
|
+
if (featureAndGlyphUnderMouse?.cds) {
|
|
494
|
+
const { cds, feature } = featureAndGlyphUnderMouse
|
|
495
|
+
if (!featureTypeOntology) {
|
|
496
|
+
throw new Error('featureTypeOntology is undefined')
|
|
497
|
+
}
|
|
498
|
+
if (!feature.children) {
|
|
499
|
+
return
|
|
500
|
+
}
|
|
501
|
+
for (const child of feature.children.values()) {
|
|
502
|
+
const childIsCDS = featureTypeOntology.isTypeOf(child.type, 'CDS')
|
|
503
|
+
if (childIsCDS && cds.max <= child.max && cds.min >= child.min) {
|
|
504
|
+
stateModel.setSelectedFeature(child)
|
|
505
|
+
break
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
} else if (featureAndGlyphUnderMouse?.feature) {
|
|
509
|
+
stateModel.setSelectedFeature(featureAndGlyphUnderMouse.feature)
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
function getDraggableFeatureInfo(
|
|
514
|
+
mousePosition: MousePosition,
|
|
515
|
+
cds: TranscriptPartCoding | null,
|
|
516
|
+
feature: AnnotationFeature,
|
|
517
|
+
stateModel: LinearApolloSixFrameDisplay,
|
|
518
|
+
): { feature: AnnotationFeature; edge: 'min' | 'max' } | undefined {
|
|
519
|
+
const { session } = stateModel
|
|
520
|
+
const { apolloDataStore } = session
|
|
521
|
+
const { featureTypeOntology } = apolloDataStore.ontologyManager
|
|
522
|
+
if (!featureTypeOntology) {
|
|
523
|
+
throw new Error('featureTypeOntology is undefined')
|
|
524
|
+
}
|
|
525
|
+
const isTranscript = featureTypeOntology.isTypeOf(feature.type, 'transcript')
|
|
526
|
+
if (cds === null) {
|
|
527
|
+
return
|
|
528
|
+
}
|
|
529
|
+
const { bp, refName, regionNumber, x } = mousePosition
|
|
530
|
+
const { lgv } = stateModel
|
|
531
|
+
const { offsetPx } = lgv
|
|
532
|
+
|
|
533
|
+
const minPxInfo = lgv.bpToPx({ refName, coord: cds.min, regionNumber })
|
|
534
|
+
const maxPxInfo = lgv.bpToPx({ refName, coord: cds.max, regionNumber })
|
|
535
|
+
if (minPxInfo === undefined || maxPxInfo === undefined) {
|
|
536
|
+
return
|
|
537
|
+
}
|
|
538
|
+
const minPx = minPxInfo.offsetPx - offsetPx
|
|
539
|
+
const maxPx = maxPxInfo.offsetPx - offsetPx
|
|
540
|
+
if (Math.abs(maxPx - minPx) < 8) {
|
|
541
|
+
return
|
|
542
|
+
}
|
|
543
|
+
if (isTranscript) {
|
|
544
|
+
const transcript = feature
|
|
545
|
+
if (!transcript.children) {
|
|
546
|
+
return
|
|
547
|
+
}
|
|
548
|
+
const exonChildren: AnnotationFeature[] = []
|
|
549
|
+
for (const child of transcript.children.values()) {
|
|
550
|
+
const childIsExon = featureTypeOntology.isTypeOf(child.type, 'exon')
|
|
551
|
+
if (childIsExon) {
|
|
552
|
+
exonChildren.push(child)
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
const overlappingExon = exonChildren.find((child) => {
|
|
557
|
+
const [start, end] = intersection2(bp, bp + 1, child.min, child.max)
|
|
558
|
+
return start !== undefined && end !== undefined
|
|
559
|
+
})
|
|
560
|
+
if (!overlappingExon) {
|
|
561
|
+
return
|
|
562
|
+
}
|
|
563
|
+
const minPxInfo = lgv.bpToPx({
|
|
564
|
+
refName,
|
|
565
|
+
coord: overlappingExon.min,
|
|
566
|
+
regionNumber,
|
|
567
|
+
})
|
|
568
|
+
const maxPxInfo = lgv.bpToPx({
|
|
569
|
+
refName,
|
|
570
|
+
coord: overlappingExon.max,
|
|
571
|
+
regionNumber,
|
|
572
|
+
})
|
|
573
|
+
if (minPxInfo === undefined || maxPxInfo === undefined) {
|
|
574
|
+
return
|
|
575
|
+
}
|
|
576
|
+
const minPx = minPxInfo.offsetPx - offsetPx
|
|
577
|
+
const maxPx = maxPxInfo.offsetPx - offsetPx
|
|
578
|
+
if (Math.abs(maxPx - minPx) < 8) {
|
|
579
|
+
return
|
|
580
|
+
}
|
|
581
|
+
if (Math.abs(minPx - x) < 4) {
|
|
582
|
+
return { feature: overlappingExon, edge: 'min' }
|
|
583
|
+
}
|
|
584
|
+
if (Math.abs(maxPx - x) < 4) {
|
|
585
|
+
return { feature: overlappingExon, edge: 'max' }
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
return
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
function drawTooltip(
|
|
592
|
+
display: LinearApolloSixFrameDisplayMouseEvents,
|
|
593
|
+
context: CanvasRenderingContext2D,
|
|
594
|
+
): void {
|
|
595
|
+
const { apolloHover, apolloRowHeight, lgv, theme } = display
|
|
596
|
+
if (!apolloHover) {
|
|
597
|
+
return
|
|
598
|
+
}
|
|
599
|
+
const { cds, feature } = apolloHover
|
|
600
|
+
if (!cds) {
|
|
601
|
+
return
|
|
602
|
+
}
|
|
603
|
+
const position = display.getFeatureLayoutPosition(feature)
|
|
604
|
+
if (!position) {
|
|
605
|
+
return
|
|
606
|
+
}
|
|
607
|
+
const { layoutIndex } = position
|
|
608
|
+
const { bpPerPx, displayedRegions, offsetPx } = lgv
|
|
609
|
+
const displayedRegion = displayedRegions[layoutIndex]
|
|
610
|
+
const { refName, reversed } = displayedRegion
|
|
611
|
+
const rowHeight = apolloRowHeight
|
|
612
|
+
const cdsHeight = Math.round(0.7 * rowHeight)
|
|
613
|
+
let location = 'Loc: '
|
|
614
|
+
|
|
615
|
+
const { strand } = feature
|
|
616
|
+
const { max, min, phase } = cds
|
|
617
|
+
location += `${min + 1}–${max}`
|
|
618
|
+
|
|
619
|
+
let startPx =
|
|
620
|
+
(lgv.bpToPx({
|
|
621
|
+
refName,
|
|
622
|
+
coord: reversed ? max : min,
|
|
623
|
+
regionNumber: layoutIndex,
|
|
624
|
+
})?.offsetPx ?? 0) - offsetPx
|
|
625
|
+
const frame = getFrame(min, max, strand ?? 1, phase)
|
|
626
|
+
const frameAdjust = frame < 0 ? -1 * frame + 5 : frame
|
|
627
|
+
const cdsTop = (frameAdjust - 1) * rowHeight + (rowHeight - cdsHeight) / 2
|
|
628
|
+
const cdsWidthPx = (max - min) / bpPerPx
|
|
629
|
+
|
|
630
|
+
const featureType = `Type: ${cds.type}`
|
|
631
|
+
const { attributes } = feature
|
|
632
|
+
const featureName = attributes.get('gff_name')?.find((name) => name !== '')
|
|
633
|
+
const textWidth = [
|
|
634
|
+
context.measureText(featureType).width,
|
|
635
|
+
context.measureText(location).width,
|
|
636
|
+
]
|
|
637
|
+
if (featureName) {
|
|
638
|
+
textWidth.push(
|
|
639
|
+
context.measureText(`Parent Type: ${feature.type}`).width,
|
|
640
|
+
context.measureText(`Parent Name: ${featureName}`).width,
|
|
641
|
+
)
|
|
642
|
+
}
|
|
643
|
+
const maxWidth = Math.max(...textWidth)
|
|
644
|
+
|
|
645
|
+
startPx = startPx + cdsWidthPx + 5
|
|
646
|
+
context.fillStyle = alpha(theme?.palette.text.primary ?? 'rgb(1, 1, 1)', 0.7)
|
|
647
|
+
context.fillRect(
|
|
648
|
+
startPx,
|
|
649
|
+
cdsTop,
|
|
650
|
+
maxWidth + 4,
|
|
651
|
+
textWidth.length === 4 ? 55 : 35,
|
|
652
|
+
)
|
|
653
|
+
context.beginPath()
|
|
654
|
+
context.moveTo(startPx, cdsTop)
|
|
655
|
+
context.lineTo(startPx - 5, cdsTop + 5)
|
|
656
|
+
context.lineTo(startPx, cdsTop + 10)
|
|
657
|
+
context.fill()
|
|
658
|
+
context.fillStyle = theme?.palette.background.default ?? 'rgba(255, 255, 255)'
|
|
659
|
+
let textTop = cdsTop + 12
|
|
660
|
+
context.fillText(featureType, startPx + 2, textTop)
|
|
661
|
+
if (featureName) {
|
|
662
|
+
textTop = textTop + 12
|
|
663
|
+
context.fillText(`Parent Type: ${feature.type}`, startPx + 2, textTop)
|
|
664
|
+
textTop = textTop + 12
|
|
665
|
+
context.fillText(`Parent Name: ${featureName}`, startPx + 2, textTop)
|
|
666
|
+
}
|
|
667
|
+
textTop = textTop + 12
|
|
668
|
+
context.fillText(location, startPx + 2, textTop)
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
function getContextMenuItems(
|
|
672
|
+
display: LinearApolloSixFrameDisplayMouseEvents,
|
|
673
|
+
): MenuItem[] {
|
|
674
|
+
const {
|
|
675
|
+
apolloHover,
|
|
676
|
+
apolloInternetAccount: internetAccount,
|
|
677
|
+
changeManager,
|
|
678
|
+
regions,
|
|
679
|
+
selectedFeature,
|
|
680
|
+
session,
|
|
681
|
+
} = display
|
|
682
|
+
const menuItems: MenuItem[] = []
|
|
683
|
+
if (!apolloHover) {
|
|
684
|
+
return menuItems
|
|
685
|
+
}
|
|
686
|
+
const { feature: sourceFeature } = apolloHover
|
|
687
|
+
const role = internetAccount ? internetAccount.role : 'admin'
|
|
688
|
+
const admin = role === 'admin'
|
|
689
|
+
const readOnly = !(role && ['admin', 'user'].includes(role))
|
|
690
|
+
const [region] = regions
|
|
691
|
+
const sourceAssemblyId = display.getAssemblyId(region.assemblyName)
|
|
692
|
+
const currentAssemblyId = display.getAssemblyId(region.assemblyName)
|
|
693
|
+
menuItems.push(
|
|
694
|
+
{
|
|
695
|
+
label: 'Add child feature',
|
|
696
|
+
disabled: readOnly,
|
|
697
|
+
onClick: () => {
|
|
698
|
+
;(session as unknown as AbstractSessionModel).queueDialog(
|
|
699
|
+
(doneCallback) => [
|
|
700
|
+
AddChildFeature,
|
|
701
|
+
{
|
|
702
|
+
session,
|
|
703
|
+
handleClose: () => {
|
|
704
|
+
doneCallback()
|
|
705
|
+
},
|
|
706
|
+
changeManager,
|
|
707
|
+
sourceFeature,
|
|
708
|
+
sourceAssemblyId,
|
|
709
|
+
internetAccount,
|
|
710
|
+
},
|
|
711
|
+
],
|
|
712
|
+
)
|
|
713
|
+
},
|
|
714
|
+
},
|
|
715
|
+
{
|
|
716
|
+
label: 'Copy features and annotations',
|
|
717
|
+
disabled: readOnly,
|
|
718
|
+
onClick: () => {
|
|
719
|
+
;(session as unknown as AbstractSessionModel).queueDialog(
|
|
720
|
+
(doneCallback) => [
|
|
721
|
+
CopyFeature,
|
|
722
|
+
{
|
|
723
|
+
session,
|
|
724
|
+
handleClose: () => {
|
|
725
|
+
doneCallback()
|
|
726
|
+
},
|
|
727
|
+
changeManager,
|
|
728
|
+
sourceFeature,
|
|
729
|
+
sourceAssemblyId: currentAssemblyId,
|
|
730
|
+
},
|
|
731
|
+
],
|
|
732
|
+
)
|
|
733
|
+
},
|
|
734
|
+
},
|
|
735
|
+
{
|
|
736
|
+
label: 'Delete feature',
|
|
737
|
+
disabled: !admin,
|
|
738
|
+
onClick: () => {
|
|
739
|
+
;(session as unknown as AbstractSessionModel).queueDialog(
|
|
740
|
+
(doneCallback) => [
|
|
741
|
+
DeleteFeature,
|
|
742
|
+
{
|
|
743
|
+
session,
|
|
744
|
+
handleClose: () => {
|
|
745
|
+
doneCallback()
|
|
746
|
+
},
|
|
747
|
+
changeManager,
|
|
748
|
+
sourceFeature,
|
|
749
|
+
sourceAssemblyId: currentAssemblyId,
|
|
750
|
+
selectedFeature,
|
|
751
|
+
setSelectedFeature: (feature?: AnnotationFeature) => {
|
|
752
|
+
display.setSelectedFeature(feature)
|
|
753
|
+
},
|
|
754
|
+
},
|
|
755
|
+
],
|
|
756
|
+
)
|
|
757
|
+
},
|
|
758
|
+
},
|
|
759
|
+
{
|
|
760
|
+
label: 'Edit feature details',
|
|
761
|
+
onClick: () => {
|
|
762
|
+
const apolloFeatureWidget = (
|
|
763
|
+
session as unknown as SessionWithWidgets
|
|
764
|
+
).addWidget(
|
|
765
|
+
'ApolloFeatureDetailsWidget',
|
|
766
|
+
'apolloFeatureDetailsWidget',
|
|
767
|
+
{
|
|
768
|
+
feature: sourceFeature,
|
|
769
|
+
assembly: currentAssemblyId,
|
|
770
|
+
refName: region.refName,
|
|
771
|
+
},
|
|
772
|
+
)
|
|
773
|
+
;(session as unknown as SessionWithWidgets).showWidget(
|
|
774
|
+
apolloFeatureWidget,
|
|
775
|
+
)
|
|
776
|
+
},
|
|
777
|
+
},
|
|
778
|
+
)
|
|
779
|
+
const { featureTypeOntology } = session.apolloDataStore.ontologyManager
|
|
780
|
+
if (!featureTypeOntology) {
|
|
781
|
+
throw new Error('featureTypeOntology is undefined')
|
|
782
|
+
}
|
|
783
|
+
if (
|
|
784
|
+
featureTypeOntology.isTypeOf(sourceFeature.type, 'transcript') &&
|
|
785
|
+
isSessionModelWithWidgets(session)
|
|
786
|
+
) {
|
|
787
|
+
menuItems.push({
|
|
788
|
+
label: 'Edit transcript details',
|
|
789
|
+
onClick: () => {
|
|
790
|
+
const apolloTranscriptWidget = session.addWidget(
|
|
791
|
+
'ApolloTranscriptDetails',
|
|
792
|
+
'apolloTranscriptDetails',
|
|
793
|
+
{
|
|
794
|
+
feature: sourceFeature,
|
|
795
|
+
assembly: currentAssemblyId,
|
|
796
|
+
changeManager,
|
|
797
|
+
refName: region.refName,
|
|
798
|
+
},
|
|
799
|
+
)
|
|
800
|
+
session.showWidget(apolloTranscriptWidget)
|
|
801
|
+
},
|
|
802
|
+
})
|
|
803
|
+
}
|
|
804
|
+
return menuItems
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
function onMouseLeave(): void {
|
|
808
|
+
return
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
export const geneGlyph: Glyph = {
|
|
812
|
+
draw,
|
|
813
|
+
drawDragPreview,
|
|
814
|
+
drawHover,
|
|
815
|
+
drawTooltip,
|
|
816
|
+
getContextMenuItems,
|
|
817
|
+
onMouseDown,
|
|
818
|
+
onMouseLeave,
|
|
819
|
+
onMouseMove,
|
|
820
|
+
onMouseUp,
|
|
821
|
+
}
|