@apollo-annotation/jbrowse-plugin-apollo 0.3.7 → 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 +2371 -1642
- package/dist/index.esm.js.map +1 -1
- package/dist/jbrowse-plugin-apollo.cjs.development.js +2384 -1641
- 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 +4387 -2952
- 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 +33 -13
- package/src/FeatureDetailsWidget/ApolloTranscriptDetailsWidget.tsx +64 -5
- package/src/FeatureDetailsWidget/TranscriptSequence.tsx +70 -73
- package/src/FeatureDetailsWidget/TranscriptWidgetEditLocation.tsx +33 -31
- package/src/LinearApolloDisplay/components/LinearApolloDisplay.tsx +60 -72
- package/src/LinearApolloDisplay/glyphs/BoxGlyph.ts +50 -194
- package/src/LinearApolloDisplay/glyphs/GeneGlyph.ts +441 -180
- package/src/LinearApolloDisplay/glyphs/GenericChildGlyph.ts +53 -34
- package/src/LinearApolloDisplay/glyphs/Glyph.ts +7 -9
- package/src/LinearApolloDisplay/stateModel/base.ts +34 -43
- package/src/LinearApolloDisplay/stateModel/layouts.ts +3 -2
- package/src/LinearApolloDisplay/stateModel/mouseEvents.ts +32 -261
- package/src/LinearApolloDisplay/stateModel/rendering.ts +43 -343
- 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 +95 -38
- package/src/LinearApolloSixFrameDisplay/glyphs/GeneGlyph.ts +221 -201
- package/src/LinearApolloSixFrameDisplay/glyphs/Glyph.ts +12 -8
- package/src/LinearApolloSixFrameDisplay/stateModel/base.ts +42 -4
- package/src/LinearApolloSixFrameDisplay/stateModel/layouts.ts +4 -8
- package/src/LinearApolloSixFrameDisplay/stateModel/mouseEvents.ts +73 -97
- package/src/LinearApolloSixFrameDisplay/stateModel/rendering.ts +49 -61
- package/src/TabularEditor/HybridGrid/Feature.tsx +16 -14
- package/src/TabularEditor/HybridGrid/HybridGrid.tsx +7 -5
- package/src/components/AddAssembly.tsx +1 -1
- package/src/components/AddAssemblyAliases.tsx +1 -1
- package/src/components/AddChildFeature.tsx +5 -2
- package/src/components/AddFeature.tsx +9 -3
- package/src/components/AddRefSeqAliases.tsx +9 -9
- package/src/components/CopyFeature.tsx +3 -1
- package/src/components/CreateApolloAnnotation.tsx +1 -0
- package/src/components/DeleteAssembly.tsx +1 -1
- package/src/components/EditZoomThresholdDialog.tsx +69 -0
- package/src/components/FilterFeatures.tsx +7 -7
- package/src/components/FilterTranscripts.tsx +6 -6
- package/src/components/ImportFeatures.tsx +1 -1
- package/src/components/ManageChecks.tsx +1 -1
- package/src/components/MergeTranscripts.tsx +12 -15
- package/src/components/OntologyTermMultiSelect.tsx +11 -11
- package/src/components/OpenLocalFile.tsx +11 -7
- package/src/components/ViewCheckResults.tsx +1 -1
- package/src/components/index.ts +1 -0
- package/src/config.ts +6 -0
- 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} +56 -47
- package/src/menus/topLevelMenuAdmin.ts +154 -0
- package/src/session/session.ts +162 -116
- package/src/util/annotationFeatureUtils.ts +15 -21
- package/src/util/displayUtils.ts +149 -0
- package/src/util/glyphUtils.ts +152 -0
- package/src/util/mouseEventsUtils.ts +32 -0
|
@@ -1,25 +1,34 @@
|
|
|
1
1
|
import { type AnnotationFeature } from '@apollo-annotation/mst'
|
|
2
|
+
import { readConfObject } from '@jbrowse/core/configuration'
|
|
3
|
+
import { type BaseDisplayModel } from '@jbrowse/core/pluggableElementTypes'
|
|
2
4
|
import { type MenuItem } from '@jbrowse/core/ui'
|
|
3
5
|
import {
|
|
4
6
|
type AbstractSessionModel,
|
|
7
|
+
getContainingView,
|
|
5
8
|
getFrame,
|
|
6
9
|
intersection2,
|
|
7
10
|
isSessionModelWithWidgets,
|
|
8
11
|
} from '@jbrowse/core/util'
|
|
12
|
+
import { type LinearGenomeViewModel } from '@jbrowse/plugin-linear-genome-view'
|
|
13
|
+
import SkipNextRoundedIcon from '@mui/icons-material/SkipNextRounded'
|
|
14
|
+
import SkipPreviousRoundedIcon from '@mui/icons-material/SkipPreviousRounded'
|
|
9
15
|
import { alpha } from '@mui/material'
|
|
10
16
|
|
|
11
17
|
import { type OntologyRecord } from '../../OntologyManager'
|
|
12
18
|
import { MergeExons, MergeTranscripts, SplitExon } from '../../components'
|
|
13
19
|
import { type ApolloSessionModel } from '../../session'
|
|
14
|
-
import { getMinAndMaxPx, getOverlappingEdge } from '../../util'
|
|
15
|
-
import { getFeaturesUnderClick } from '../../util/annotationFeatureUtils'
|
|
16
|
-
import { type LinearApolloDisplay } from '../stateModel'
|
|
17
20
|
import {
|
|
18
|
-
type LinearApolloDisplayMouseEvents,
|
|
19
21
|
type MousePosition,
|
|
20
|
-
type
|
|
21
|
-
|
|
22
|
-
|
|
22
|
+
type MousePositionWithFeature,
|
|
23
|
+
containsSelectedFeature,
|
|
24
|
+
getMinAndMaxPx,
|
|
25
|
+
getOverlappingEdge,
|
|
26
|
+
isMousePositionWithFeature,
|
|
27
|
+
navToFeatureCenter,
|
|
28
|
+
} from '../../util'
|
|
29
|
+
import { getRelatedFeatures } from '../../util/annotationFeatureUtils'
|
|
30
|
+
import { type LinearApolloDisplay } from '../stateModel'
|
|
31
|
+
import { type LinearApolloDisplayMouseEvents } from '../stateModel/mouseEvents'
|
|
23
32
|
import { type LinearApolloDisplayRendering } from '../stateModel/rendering'
|
|
24
33
|
import { type CanvasMouseEvent } from '../types'
|
|
25
34
|
|
|
@@ -77,52 +86,102 @@ if (canvas?.getContext) {
|
|
|
77
86
|
}
|
|
78
87
|
}
|
|
79
88
|
|
|
80
|
-
function
|
|
89
|
+
function drawBackground(
|
|
81
90
|
ctx: CanvasRenderingContext2D,
|
|
82
91
|
feature: AnnotationFeature,
|
|
83
|
-
row: number,
|
|
84
92
|
stateModel: LinearApolloDisplayRendering,
|
|
85
93
|
displayedRegionIndex: number,
|
|
86
|
-
|
|
87
|
-
|
|
94
|
+
row: number,
|
|
95
|
+
color: string,
|
|
96
|
+
) {
|
|
97
|
+
const { apolloRowHeight, lgv, session } = stateModel
|
|
88
98
|
const { bpPerPx, displayedRegions, offsetPx } = lgv
|
|
89
99
|
const displayedRegion = displayedRegions[displayedRegionIndex]
|
|
90
100
|
const { refName, reversed } = displayedRegion
|
|
91
|
-
const rowHeight = apolloRowHeight
|
|
92
|
-
const cdsHeight = Math.round(0.9 * rowHeight)
|
|
93
|
-
const { children, min, strand } = feature
|
|
94
|
-
if (!children) {
|
|
95
|
-
return
|
|
96
|
-
}
|
|
97
|
-
const { apolloSelectedFeature } = session
|
|
98
101
|
const { apolloDataStore } = session
|
|
99
102
|
const { featureTypeOntology } = apolloDataStore.ontologyManager
|
|
100
103
|
if (!featureTypeOntology) {
|
|
101
104
|
throw new Error('featureTypeOntology is undefined')
|
|
102
105
|
}
|
|
103
106
|
|
|
104
|
-
// Draw background for gene
|
|
105
107
|
const topLevelFeatureMinX =
|
|
106
108
|
(lgv.bpToPx({
|
|
107
109
|
refName,
|
|
108
|
-
coord: min,
|
|
110
|
+
coord: feature.min,
|
|
109
111
|
regionNumber: displayedRegionIndex,
|
|
110
112
|
})?.offsetPx ?? 0) - offsetPx
|
|
111
113
|
const topLevelFeatureWidthPx = feature.length / bpPerPx
|
|
112
114
|
const topLevelFeatureStartPx = reversed
|
|
113
115
|
? topLevelFeatureMinX - topLevelFeatureWidthPx
|
|
114
116
|
: topLevelFeatureMinX
|
|
115
|
-
const topLevelFeatureTop = row *
|
|
117
|
+
const topLevelFeatureTop = row * apolloRowHeight
|
|
116
118
|
const topLevelFeatureHeight =
|
|
117
|
-
getRowCount(feature, featureTypeOntology) *
|
|
119
|
+
getRowCount(feature, featureTypeOntology) * apolloRowHeight
|
|
118
120
|
|
|
119
|
-
ctx.fillStyle =
|
|
121
|
+
ctx.fillStyle = color
|
|
120
122
|
ctx.fillRect(
|
|
121
123
|
topLevelFeatureStartPx,
|
|
122
124
|
topLevelFeatureTop,
|
|
123
125
|
topLevelFeatureWidthPx,
|
|
124
126
|
topLevelFeatureHeight,
|
|
125
127
|
)
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function backgroundColorForFeature(
|
|
131
|
+
session: ApolloSessionModel,
|
|
132
|
+
featureType: string,
|
|
133
|
+
): string {
|
|
134
|
+
const color = readConfObject(
|
|
135
|
+
session.getPluginConfiguration(),
|
|
136
|
+
'backgroundColorForFeature',
|
|
137
|
+
{ featureType },
|
|
138
|
+
) as string
|
|
139
|
+
return color
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function draw(
|
|
143
|
+
ctx: CanvasRenderingContext2D,
|
|
144
|
+
feature: AnnotationFeature,
|
|
145
|
+
row: number,
|
|
146
|
+
stateModel: LinearApolloDisplayRendering,
|
|
147
|
+
displayedRegionIndex: number,
|
|
148
|
+
): void {
|
|
149
|
+
const { apolloRowHeight, lgv, selectedFeature, session, theme } = stateModel
|
|
150
|
+
const { bpPerPx, displayedRegions, offsetPx } = lgv
|
|
151
|
+
const displayedRegion = displayedRegions[displayedRegionIndex]
|
|
152
|
+
const { refName, reversed } = displayedRegion
|
|
153
|
+
const rowHeight = apolloRowHeight
|
|
154
|
+
const cdsHeight = Math.round(0.9 * rowHeight)
|
|
155
|
+
const { children, strand } = feature
|
|
156
|
+
if (!children) {
|
|
157
|
+
return
|
|
158
|
+
}
|
|
159
|
+
const { apolloDataStore } = session
|
|
160
|
+
const { featureTypeOntology } = apolloDataStore.ontologyManager
|
|
161
|
+
if (!featureTypeOntology) {
|
|
162
|
+
throw new Error('featureTypeOntology is undefined')
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Draw background for gene
|
|
166
|
+
drawBackground(
|
|
167
|
+
ctx,
|
|
168
|
+
feature,
|
|
169
|
+
stateModel,
|
|
170
|
+
displayedRegionIndex,
|
|
171
|
+
row,
|
|
172
|
+
alpha(theme.palette.background.paper, 0.6),
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
if (featureTypeOntology.isTypeOf(feature.type, 'pseudogene')) {
|
|
176
|
+
drawBackground(
|
|
177
|
+
ctx,
|
|
178
|
+
feature,
|
|
179
|
+
stateModel,
|
|
180
|
+
displayedRegionIndex,
|
|
181
|
+
row,
|
|
182
|
+
backgroundColorForFeature(session, 'pseudogenic_transcript'),
|
|
183
|
+
)
|
|
184
|
+
}
|
|
126
185
|
|
|
127
186
|
// Draw lines on different rows for each transcript
|
|
128
187
|
let currentRow = 0
|
|
@@ -140,6 +199,29 @@ function draw(
|
|
|
140
199
|
}
|
|
141
200
|
|
|
142
201
|
const cdsCount = getCDSCount(transcript, featureTypeOntology)
|
|
202
|
+
if (cdsCount === 0) {
|
|
203
|
+
drawBackground(
|
|
204
|
+
ctx,
|
|
205
|
+
transcript,
|
|
206
|
+
stateModel,
|
|
207
|
+
displayedRegionIndex,
|
|
208
|
+
currentRow,
|
|
209
|
+
backgroundColorForFeature(session, 'nonCodingTranscript'),
|
|
210
|
+
)
|
|
211
|
+
}
|
|
212
|
+
if (
|
|
213
|
+
featureTypeOntology.isTypeOf(transcript.type, 'pseudogenic_transcript')
|
|
214
|
+
) {
|
|
215
|
+
drawBackground(
|
|
216
|
+
ctx,
|
|
217
|
+
transcript,
|
|
218
|
+
stateModel,
|
|
219
|
+
displayedRegionIndex,
|
|
220
|
+
currentRow,
|
|
221
|
+
backgroundColorForFeature(session, 'pseudogenic_transcript'),
|
|
222
|
+
)
|
|
223
|
+
}
|
|
224
|
+
|
|
143
225
|
for (const [, childFeature] of transcriptChildren) {
|
|
144
226
|
if (!featureTypeOntology.isTypeOf(childFeature.type, 'CDS')) {
|
|
145
227
|
continue
|
|
@@ -169,9 +251,9 @@ function draw(
|
|
|
169
251
|
}
|
|
170
252
|
|
|
171
253
|
const forwardFill =
|
|
172
|
-
theme
|
|
254
|
+
theme.palette.mode === 'dark' ? forwardFillDark : forwardFillLight
|
|
173
255
|
const backwardFill =
|
|
174
|
-
theme
|
|
256
|
+
theme.palette.mode === 'dark' ? backwardFillDark : backwardFillLight
|
|
175
257
|
// Draw exon and CDS for each transcript
|
|
176
258
|
currentRow = 0
|
|
177
259
|
for (const [, child] of children) {
|
|
@@ -188,7 +270,7 @@ function draw(
|
|
|
188
270
|
const cdsCount = getCDSCount(child, featureTypeOntology)
|
|
189
271
|
if (cdsCount != 0) {
|
|
190
272
|
for (const cdsRow of child.cdsLocations) {
|
|
191
|
-
const {
|
|
273
|
+
const { children: transcriptChildren } = child
|
|
192
274
|
if (!transcriptChildren) {
|
|
193
275
|
continue
|
|
194
276
|
}
|
|
@@ -217,7 +299,7 @@ function draw(
|
|
|
217
299
|
regionNumber: displayedRegionIndex,
|
|
218
300
|
})?.offsetPx ?? 0) - offsetPx
|
|
219
301
|
const cdsStartPx = reversed ? minX - cdsWidthPx : minX
|
|
220
|
-
ctx.fillStyle = theme
|
|
302
|
+
ctx.fillStyle = theme.palette.text.primary
|
|
221
303
|
const cdsTop =
|
|
222
304
|
(row + currentRow) * rowHeight + (rowHeight - cdsHeight) / 2
|
|
223
305
|
ctx.fillRect(cdsStartPx, cdsTop, cdsWidthPx, cdsHeight)
|
|
@@ -234,12 +316,8 @@ function draw(
|
|
|
234
316
|
child.strand ?? 1,
|
|
235
317
|
cds.phase,
|
|
236
318
|
)
|
|
237
|
-
const frameColor = theme
|
|
238
|
-
|
|
239
|
-
ctx.fillStyle =
|
|
240
|
-
apolloSelectedFeature && _id === apolloSelectedFeature._id
|
|
241
|
-
? 'rgb(0,0,0)'
|
|
242
|
-
: cdsColorCode
|
|
319
|
+
const frameColor = theme.palette.framesCDS.at(frame)?.main
|
|
320
|
+
ctx.fillStyle = frameColor ?? 'black'
|
|
243
321
|
ctx.fillRect(
|
|
244
322
|
cdsStartPx + 1,
|
|
245
323
|
cdsTop + 1,
|
|
@@ -295,6 +373,9 @@ function draw(
|
|
|
295
373
|
currentRow += 1
|
|
296
374
|
}
|
|
297
375
|
}
|
|
376
|
+
if (selectedFeature && containsSelectedFeature(feature, selectedFeature)) {
|
|
377
|
+
drawHighlight(stateModel, ctx, selectedFeature, true)
|
|
378
|
+
}
|
|
298
379
|
}
|
|
299
380
|
|
|
300
381
|
function drawExon(
|
|
@@ -308,11 +389,10 @@ function drawExon(
|
|
|
308
389
|
forwardFill: CanvasPattern | null,
|
|
309
390
|
backwardFill: CanvasPattern | null,
|
|
310
391
|
) {
|
|
311
|
-
const { apolloRowHeight, lgv,
|
|
392
|
+
const { apolloRowHeight, lgv, theme } = stateModel
|
|
312
393
|
const { bpPerPx, displayedRegions, offsetPx } = lgv
|
|
313
394
|
const displayedRegion = displayedRegions[displayedRegionIndex]
|
|
314
395
|
const { refName, reversed } = displayedRegion
|
|
315
|
-
const { apolloSelectedFeature } = session
|
|
316
396
|
|
|
317
397
|
const minX =
|
|
318
398
|
(lgv.bpToPx({
|
|
@@ -326,14 +406,11 @@ function drawExon(
|
|
|
326
406
|
const top = (row + currentRow) * apolloRowHeight
|
|
327
407
|
const exonHeight = Math.round(0.6 * apolloRowHeight)
|
|
328
408
|
const exonTop = top + (apolloRowHeight - exonHeight) / 2
|
|
329
|
-
ctx.fillStyle = theme
|
|
409
|
+
ctx.fillStyle = theme.palette.text.primary
|
|
330
410
|
ctx.fillRect(startPx, exonTop, widthPx, exonHeight)
|
|
331
411
|
if (widthPx > 2) {
|
|
332
412
|
ctx.clearRect(startPx + 1, exonTop + 1, widthPx - 2, exonHeight - 2)
|
|
333
|
-
ctx.fillStyle =
|
|
334
|
-
apolloSelectedFeature && exon._id === apolloSelectedFeature._id
|
|
335
|
-
? 'rgb(0,0,0)'
|
|
336
|
-
: 'rgb(211,211,211)'
|
|
413
|
+
ctx.fillStyle = 'rgb(211,211,211)'
|
|
337
414
|
ctx.fillRect(startPx + 1, exonTop + 1, widthPx - 2, exonHeight - 2)
|
|
338
415
|
if (forwardFill && backwardFill && strand) {
|
|
339
416
|
const reversal = reversed ? -1 : 1
|
|
@@ -354,6 +431,21 @@ function drawExon(
|
|
|
354
431
|
}
|
|
355
432
|
}
|
|
356
433
|
|
|
434
|
+
function* range(start: number, stop: number, step = 1): Generator<number> {
|
|
435
|
+
if (start === stop) {
|
|
436
|
+
return
|
|
437
|
+
}
|
|
438
|
+
if (start < stop) {
|
|
439
|
+
for (let i = start; i < stop; i += step) {
|
|
440
|
+
yield i
|
|
441
|
+
}
|
|
442
|
+
return
|
|
443
|
+
}
|
|
444
|
+
for (let i = start; i > stop; i -= step) {
|
|
445
|
+
yield i
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
|
|
357
449
|
function drawLine(
|
|
358
450
|
ctx: CanvasRenderingContext2D,
|
|
359
451
|
stateModel: LinearApolloDisplayRendering,
|
|
@@ -372,14 +464,33 @@ function drawLine(
|
|
|
372
464
|
coord: transcript.min,
|
|
373
465
|
regionNumber: displayedRegionIndex,
|
|
374
466
|
})?.offsetPx ?? 0) - offsetPx
|
|
375
|
-
const widthPx = transcript.length / bpPerPx
|
|
467
|
+
const widthPx = Math.round(transcript.length / bpPerPx)
|
|
376
468
|
const startPx = reversed ? minX - widthPx : minX
|
|
377
469
|
const height =
|
|
378
470
|
Math.round((currentRow + 1 / 2) * apolloRowHeight) + row * apolloRowHeight
|
|
379
|
-
ctx.strokeStyle = theme
|
|
471
|
+
ctx.strokeStyle = theme.palette.text.primary
|
|
472
|
+
const { strand = 1 } = transcript
|
|
380
473
|
ctx.beginPath()
|
|
381
|
-
|
|
382
|
-
|
|
474
|
+
// Draw the transcript line, and extend it out a bit on the 3` end
|
|
475
|
+
const lineStart = startPx - (strand === -1 ? 5 : 0)
|
|
476
|
+
const lineEnd = startPx + widthPx + (strand === -1 ? 0 : 5)
|
|
477
|
+
ctx.moveTo(lineStart, height)
|
|
478
|
+
ctx.lineTo(lineEnd, height)
|
|
479
|
+
// Now to draw arrows every 20 pixels along the line
|
|
480
|
+
// Make the arrow range a bit shorter to avoid an arrow hanging off the 5` end
|
|
481
|
+
const arrowsStart = lineStart + (strand === -1 ? 0 : 3)
|
|
482
|
+
const arrowsEnd = lineEnd - (strand === -1 ? 3 : 0)
|
|
483
|
+
// Offset determines if the arrows face left or right
|
|
484
|
+
const offset = strand === -1 ? 3 : -3
|
|
485
|
+
const arrowRange =
|
|
486
|
+
strand === -1
|
|
487
|
+
? range(arrowsStart, arrowsEnd, 20)
|
|
488
|
+
: range(arrowsEnd, arrowsStart, 20)
|
|
489
|
+
for (const arrowLocation of arrowRange) {
|
|
490
|
+
ctx.moveTo(arrowLocation + offset, height + offset)
|
|
491
|
+
ctx.lineTo(arrowLocation, height)
|
|
492
|
+
ctx.lineTo(arrowLocation + offset, height - offset)
|
|
493
|
+
}
|
|
383
494
|
ctx.stroke()
|
|
384
495
|
}
|
|
385
496
|
|
|
@@ -405,24 +516,22 @@ function drawDragPreview(
|
|
|
405
516
|
const rectY = row * apolloRowHeight
|
|
406
517
|
const rectWidth = Math.abs(current.x - featureEdgePx)
|
|
407
518
|
const rectHeight = apolloRowHeight * rowCount
|
|
408
|
-
overlayCtx.strokeStyle = theme
|
|
519
|
+
overlayCtx.strokeStyle = theme.palette.info.main
|
|
409
520
|
overlayCtx.setLineDash([6])
|
|
410
521
|
overlayCtx.strokeRect(rectX, rectY, rectWidth, rectHeight)
|
|
411
|
-
overlayCtx.fillStyle = alpha(theme
|
|
522
|
+
overlayCtx.fillStyle = alpha(theme.palette.info.main, 0.2)
|
|
412
523
|
overlayCtx.fillRect(rectX, rectY, rectWidth, rectHeight)
|
|
413
524
|
}
|
|
414
525
|
|
|
415
|
-
function
|
|
416
|
-
stateModel:
|
|
526
|
+
function drawHighlight(
|
|
527
|
+
stateModel: LinearApolloDisplayRendering,
|
|
417
528
|
ctx: CanvasRenderingContext2D,
|
|
529
|
+
feature: AnnotationFeature,
|
|
530
|
+
selected = false,
|
|
418
531
|
) {
|
|
419
|
-
const {
|
|
532
|
+
const { apolloRowHeight, lgv, session, theme } = stateModel
|
|
420
533
|
const { featureTypeOntology } = session.apolloDataStore.ontologyManager
|
|
421
534
|
|
|
422
|
-
if (!apolloHover) {
|
|
423
|
-
return
|
|
424
|
-
}
|
|
425
|
-
const { feature } = apolloHover
|
|
426
535
|
const position = stateModel.getFeatureLayoutPosition(feature)
|
|
427
536
|
if (!position) {
|
|
428
537
|
return
|
|
@@ -441,7 +550,9 @@ function drawHover(
|
|
|
441
550
|
const row = layoutRow + featureRow
|
|
442
551
|
const top = row * apolloRowHeight
|
|
443
552
|
const widthPx = length / bpPerPx
|
|
444
|
-
ctx.fillStyle =
|
|
553
|
+
ctx.fillStyle = selected
|
|
554
|
+
? theme.palette.action.disabled
|
|
555
|
+
: theme.palette.action.focus
|
|
445
556
|
|
|
446
557
|
if (!featureTypeOntology) {
|
|
447
558
|
throw new Error('featureTypeOntology is undefined')
|
|
@@ -454,6 +565,18 @@ function drawHover(
|
|
|
454
565
|
)
|
|
455
566
|
}
|
|
456
567
|
|
|
568
|
+
function drawHover(
|
|
569
|
+
stateModel: LinearApolloDisplay,
|
|
570
|
+
ctx: CanvasRenderingContext2D,
|
|
571
|
+
) {
|
|
572
|
+
const { hoveredFeature } = stateModel
|
|
573
|
+
|
|
574
|
+
if (!hoveredFeature) {
|
|
575
|
+
return
|
|
576
|
+
}
|
|
577
|
+
drawHighlight(stateModel, ctx, hoveredFeature.feature)
|
|
578
|
+
}
|
|
579
|
+
|
|
457
580
|
function getFeatureFromLayout(
|
|
458
581
|
feature: AnnotationFeature,
|
|
459
582
|
bp: number,
|
|
@@ -613,15 +736,53 @@ function getRowForFeature(
|
|
|
613
736
|
return
|
|
614
737
|
}
|
|
615
738
|
|
|
739
|
+
function selectFeatureAndOpenWidget(
|
|
740
|
+
stateModel: LinearApolloDisplayMouseEvents,
|
|
741
|
+
feature: AnnotationFeature,
|
|
742
|
+
) {
|
|
743
|
+
if (stateModel.apolloDragging) {
|
|
744
|
+
return
|
|
745
|
+
}
|
|
746
|
+
stateModel.setSelectedFeature(feature)
|
|
747
|
+
const { session } = stateModel
|
|
748
|
+
const { apolloDataStore } = session
|
|
749
|
+
const { featureTypeOntology } = apolloDataStore.ontologyManager
|
|
750
|
+
if (!featureTypeOntology) {
|
|
751
|
+
throw new Error('featureTypeOntology is undefined')
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
let containsCDSOrExon = false
|
|
755
|
+
for (const [, child] of feature.children ?? []) {
|
|
756
|
+
if (
|
|
757
|
+
featureTypeOntology.isTypeOf(child.type, 'CDS') ||
|
|
758
|
+
featureTypeOntology.isTypeOf(child.type, 'exon')
|
|
759
|
+
) {
|
|
760
|
+
containsCDSOrExon = true
|
|
761
|
+
break
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
if (
|
|
765
|
+
(featureTypeOntology.isTypeOf(feature.type, 'transcript') ||
|
|
766
|
+
featureTypeOntology.isTypeOf(feature.type, 'pseudogenic_transcript')) &&
|
|
767
|
+
containsCDSOrExon
|
|
768
|
+
) {
|
|
769
|
+
stateModel.showFeatureDetailsWidget(feature, [
|
|
770
|
+
'ApolloTranscriptDetails',
|
|
771
|
+
'apolloTranscriptDetails',
|
|
772
|
+
])
|
|
773
|
+
} else {
|
|
774
|
+
stateModel.showFeatureDetailsWidget(feature)
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
|
|
616
778
|
function onMouseDown(
|
|
617
779
|
stateModel: LinearApolloDisplay,
|
|
618
|
-
currentMousePosition:
|
|
780
|
+
currentMousePosition: MousePositionWithFeature,
|
|
619
781
|
event: CanvasMouseEvent,
|
|
620
782
|
) {
|
|
621
|
-
const {
|
|
783
|
+
const { feature } = currentMousePosition
|
|
622
784
|
// swallow the mouseDown if we are on the edge of the feature so that we
|
|
623
785
|
// don't start dragging the view if we try to drag the feature edge
|
|
624
|
-
const { feature } = featureAndGlyphUnderMouse
|
|
625
786
|
const draggableFeature = getDraggableFeatureInfo(
|
|
626
787
|
currentMousePosition,
|
|
627
788
|
feature,
|
|
@@ -642,10 +803,9 @@ function onMouseMove(
|
|
|
642
803
|
stateModel: LinearApolloDisplay,
|
|
643
804
|
mousePosition: MousePosition,
|
|
644
805
|
) {
|
|
645
|
-
if (
|
|
646
|
-
const {
|
|
647
|
-
stateModel.
|
|
648
|
-
const { feature } = featureAndGlyphUnderMouse
|
|
806
|
+
if (isMousePositionWithFeature(mousePosition)) {
|
|
807
|
+
const { feature, bp } = mousePosition
|
|
808
|
+
stateModel.setHoveredFeature({ feature, bp })
|
|
649
809
|
const draggableFeature = getDraggableFeatureInfo(
|
|
650
810
|
mousePosition,
|
|
651
811
|
feature,
|
|
@@ -666,41 +826,11 @@ function onMouseUp(
|
|
|
666
826
|
if (stateModel.apolloDragging) {
|
|
667
827
|
return
|
|
668
828
|
}
|
|
669
|
-
const {
|
|
670
|
-
if (!
|
|
829
|
+
const { feature } = mousePosition
|
|
830
|
+
if (!feature) {
|
|
671
831
|
return
|
|
672
832
|
}
|
|
673
|
-
|
|
674
|
-
stateModel.setSelectedFeature(feature)
|
|
675
|
-
const { session } = stateModel
|
|
676
|
-
const { apolloDataStore } = session
|
|
677
|
-
const { featureTypeOntology } = apolloDataStore.ontologyManager
|
|
678
|
-
if (!featureTypeOntology) {
|
|
679
|
-
throw new Error('featureTypeOntology is undefined')
|
|
680
|
-
}
|
|
681
|
-
|
|
682
|
-
let containsCDSOrExon = false
|
|
683
|
-
for (const [, child] of feature.children ?? []) {
|
|
684
|
-
if (
|
|
685
|
-
featureTypeOntology.isTypeOf(child.type, 'CDS') ||
|
|
686
|
-
featureTypeOntology.isTypeOf(child.type, 'exon')
|
|
687
|
-
) {
|
|
688
|
-
containsCDSOrExon = true
|
|
689
|
-
break
|
|
690
|
-
}
|
|
691
|
-
}
|
|
692
|
-
if (
|
|
693
|
-
(featureTypeOntology.isTypeOf(feature.type, 'transcript') ||
|
|
694
|
-
featureTypeOntology.isTypeOf(feature.type, 'pseudogenic_transcript')) &&
|
|
695
|
-
containsCDSOrExon
|
|
696
|
-
) {
|
|
697
|
-
stateModel.showFeatureDetailsWidget(feature, [
|
|
698
|
-
'ApolloTranscriptDetails',
|
|
699
|
-
'apolloTranscriptDetails',
|
|
700
|
-
])
|
|
701
|
-
} else {
|
|
702
|
-
stateModel.showFeatureDetailsWidget(feature)
|
|
703
|
-
}
|
|
833
|
+
selectFeatureAndOpenWidget(stateModel, feature)
|
|
704
834
|
}
|
|
705
835
|
|
|
706
836
|
function getDraggableFeatureInfo(
|
|
@@ -805,13 +935,102 @@ function isCDSFeature(
|
|
|
805
935
|
return featureTypeOntology.isTypeOf(feature.type, 'CDS')
|
|
806
936
|
}
|
|
807
937
|
|
|
938
|
+
interface AdjacentExons {
|
|
939
|
+
upstream: AnnotationFeature | undefined
|
|
940
|
+
downstream: AnnotationFeature | undefined
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
function getAdjacentExons(
|
|
944
|
+
currentExon: AnnotationFeature,
|
|
945
|
+
display: LinearApolloDisplayMouseEvents,
|
|
946
|
+
mousePosition: MousePositionWithFeature,
|
|
947
|
+
session: ApolloSessionModel,
|
|
948
|
+
): AdjacentExons {
|
|
949
|
+
const lgv = getContainingView(
|
|
950
|
+
display as BaseDisplayModel,
|
|
951
|
+
) as unknown as LinearGenomeViewModel
|
|
952
|
+
|
|
953
|
+
// Genomic coords of current view
|
|
954
|
+
const viewGenomicLeft = mousePosition.bp - lgv.bpPerPx * mousePosition.x
|
|
955
|
+
const viewGenomicRight = viewGenomicLeft + lgv.coarseTotalBp
|
|
956
|
+
if (!currentExon.parent) {
|
|
957
|
+
return { upstream: undefined, downstream: undefined }
|
|
958
|
+
}
|
|
959
|
+
const transcript = currentExon.parent
|
|
960
|
+
if (!transcript.children) {
|
|
961
|
+
throw new Error(`Error getting children of ${transcript._id}`)
|
|
962
|
+
}
|
|
963
|
+
const { featureTypeOntology } = session.apolloDataStore.ontologyManager
|
|
964
|
+
if (!featureTypeOntology) {
|
|
965
|
+
throw new Error('featureTypeOntology is undefined')
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
let exons = []
|
|
969
|
+
for (const [, child] of transcript.children) {
|
|
970
|
+
if (featureTypeOntology.isTypeOf(child.type, 'exon')) {
|
|
971
|
+
exons.push(child)
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
const adjacentExons: AdjacentExons = {
|
|
975
|
+
upstream: undefined,
|
|
976
|
+
downstream: undefined,
|
|
977
|
+
}
|
|
978
|
+
exons = exons.sort((a, b) => (a.min < b.min ? -1 : 1))
|
|
979
|
+
for (const exon of exons) {
|
|
980
|
+
if (exon.min > viewGenomicRight) {
|
|
981
|
+
adjacentExons.downstream = exon
|
|
982
|
+
break
|
|
983
|
+
}
|
|
984
|
+
}
|
|
985
|
+
exons = exons.sort((a, b) => (a.min > b.min ? -1 : 1))
|
|
986
|
+
for (const exon of exons) {
|
|
987
|
+
if (exon.max < viewGenomicLeft) {
|
|
988
|
+
adjacentExons.upstream = exon
|
|
989
|
+
break
|
|
990
|
+
}
|
|
991
|
+
}
|
|
992
|
+
if (transcript.strand === -1) {
|
|
993
|
+
const newUpstream = adjacentExons.downstream
|
|
994
|
+
adjacentExons.downstream = adjacentExons.upstream
|
|
995
|
+
adjacentExons.upstream = newUpstream
|
|
996
|
+
}
|
|
997
|
+
return adjacentExons
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
function getStreamIcon(
|
|
1001
|
+
strand: 1 | -1 | undefined,
|
|
1002
|
+
isUpstream: boolean,
|
|
1003
|
+
isFlipped: boolean | undefined,
|
|
1004
|
+
) {
|
|
1005
|
+
// This is the icon you would use for strand=1, downstream, straight
|
|
1006
|
+
// (non-flipped) view
|
|
1007
|
+
let icon = SkipNextRoundedIcon
|
|
1008
|
+
|
|
1009
|
+
if (strand === -1) {
|
|
1010
|
+
icon = SkipPreviousRoundedIcon
|
|
1011
|
+
}
|
|
1012
|
+
if (isUpstream) {
|
|
1013
|
+
icon =
|
|
1014
|
+
icon === SkipPreviousRoundedIcon
|
|
1015
|
+
? SkipNextRoundedIcon
|
|
1016
|
+
: SkipPreviousRoundedIcon
|
|
1017
|
+
}
|
|
1018
|
+
if (isFlipped) {
|
|
1019
|
+
icon =
|
|
1020
|
+
icon === SkipPreviousRoundedIcon
|
|
1021
|
+
? SkipNextRoundedIcon
|
|
1022
|
+
: SkipPreviousRoundedIcon
|
|
1023
|
+
}
|
|
1024
|
+
return icon
|
|
1025
|
+
}
|
|
1026
|
+
|
|
808
1027
|
function getContextMenuItems(
|
|
809
1028
|
display: LinearApolloDisplayMouseEvents,
|
|
810
|
-
mousePosition:
|
|
1029
|
+
mousePosition: MousePositionWithFeature,
|
|
811
1030
|
): MenuItem[] {
|
|
812
1031
|
const {
|
|
813
1032
|
apolloInternetAccount: internetAccount,
|
|
814
|
-
|
|
1033
|
+
hoveredFeature,
|
|
815
1034
|
changeManager,
|
|
816
1035
|
regions,
|
|
817
1036
|
selectedFeature,
|
|
@@ -822,53 +1041,120 @@ function getContextMenuItems(
|
|
|
822
1041
|
const menuItems: MenuItem[] = []
|
|
823
1042
|
const role = internetAccount ? internetAccount.role : 'admin'
|
|
824
1043
|
const admin = role === 'admin'
|
|
825
|
-
if (!
|
|
1044
|
+
if (!hoveredFeature) {
|
|
826
1045
|
return menuItems
|
|
827
1046
|
}
|
|
828
1047
|
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
featuresUnderClick =
|
|
832
|
-
|
|
1048
|
+
if (isMousePositionWithFeature(mousePosition)) {
|
|
1049
|
+
const { bp, feature } = mousePosition
|
|
1050
|
+
let featuresUnderClick = getRelatedFeatures(feature, bp)
|
|
1051
|
+
if (isCDSFeature(feature, session)) {
|
|
1052
|
+
featuresUnderClick = getRelatedFeatures(feature, bp, true)
|
|
1053
|
+
}
|
|
833
1054
|
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
1055
|
+
for (const feature of featuresUnderClick) {
|
|
1056
|
+
const contextMenuItemsForFeature = boxGlyph.getContextMenuItemsForFeature(
|
|
1057
|
+
display,
|
|
1058
|
+
feature,
|
|
1059
|
+
)
|
|
1060
|
+
if (isExonFeature(feature, session)) {
|
|
1061
|
+
const adjacentExons = getAdjacentExons(
|
|
1062
|
+
feature,
|
|
1063
|
+
display,
|
|
1064
|
+
mousePosition,
|
|
1065
|
+
session,
|
|
1066
|
+
)
|
|
1067
|
+
const lgv = getContainingView(
|
|
1068
|
+
display as BaseDisplayModel,
|
|
1069
|
+
) as unknown as LinearGenomeViewModel
|
|
1070
|
+
if (adjacentExons.upstream) {
|
|
1071
|
+
const exon = adjacentExons.upstream
|
|
1072
|
+
contextMenuItemsForFeature.push({
|
|
1073
|
+
label: 'Go to upstream exon',
|
|
1074
|
+
icon: getStreamIcon(
|
|
1075
|
+
feature.strand,
|
|
1076
|
+
true,
|
|
1077
|
+
lgv.displayedRegions.at(0)?.reversed,
|
|
1078
|
+
),
|
|
1079
|
+
onClick: () => {
|
|
1080
|
+
lgv.navTo(navToFeatureCenter(exon, 0.1, lgv.totalBp))
|
|
1081
|
+
selectFeatureAndOpenWidget(display, exon)
|
|
1082
|
+
},
|
|
1083
|
+
})
|
|
1084
|
+
}
|
|
1085
|
+
if (adjacentExons.downstream) {
|
|
1086
|
+
const exon = adjacentExons.downstream
|
|
1087
|
+
contextMenuItemsForFeature.push({
|
|
1088
|
+
label: 'Go to downstream exon',
|
|
1089
|
+
icon: getStreamIcon(
|
|
1090
|
+
feature.strand,
|
|
1091
|
+
false,
|
|
1092
|
+
lgv.displayedRegions.at(0)?.reversed,
|
|
1093
|
+
),
|
|
1094
|
+
onClick: () => {
|
|
1095
|
+
lgv.navTo(navToFeatureCenter(exon, 0.1, lgv.totalBp))
|
|
1096
|
+
selectFeatureAndOpenWidget(display, exon)
|
|
1097
|
+
},
|
|
1098
|
+
})
|
|
1099
|
+
}
|
|
1100
|
+
contextMenuItemsForFeature.push(
|
|
1101
|
+
{
|
|
1102
|
+
label: 'Merge exons',
|
|
1103
|
+
disabled: !admin,
|
|
1104
|
+
onClick: () => {
|
|
1105
|
+
;(session as unknown as AbstractSessionModel).queueDialog(
|
|
1106
|
+
(doneCallback) => [
|
|
1107
|
+
MergeExons,
|
|
1108
|
+
{
|
|
1109
|
+
session,
|
|
1110
|
+
handleClose: () => {
|
|
1111
|
+
doneCallback()
|
|
1112
|
+
},
|
|
1113
|
+
changeManager,
|
|
1114
|
+
sourceFeature: feature,
|
|
1115
|
+
sourceAssemblyId: currentAssemblyId,
|
|
1116
|
+
selectedFeature,
|
|
1117
|
+
setSelectedFeature: (feature?: AnnotationFeature) => {
|
|
1118
|
+
display.setSelectedFeature(feature)
|
|
1119
|
+
},
|
|
852
1120
|
},
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
1121
|
+
],
|
|
1122
|
+
)
|
|
1123
|
+
},
|
|
1124
|
+
},
|
|
1125
|
+
{
|
|
1126
|
+
label: 'Split exon',
|
|
1127
|
+
disabled: !admin,
|
|
1128
|
+
onClick: () => {
|
|
1129
|
+
;(session as unknown as AbstractSessionModel).queueDialog(
|
|
1130
|
+
(doneCallback) => [
|
|
1131
|
+
SplitExon,
|
|
1132
|
+
{
|
|
1133
|
+
session,
|
|
1134
|
+
handleClose: () => {
|
|
1135
|
+
doneCallback()
|
|
1136
|
+
},
|
|
1137
|
+
changeManager,
|
|
1138
|
+
sourceFeature: feature,
|
|
1139
|
+
sourceAssemblyId: currentAssemblyId,
|
|
1140
|
+
selectedFeature,
|
|
1141
|
+
setSelectedFeature: (feature?: AnnotationFeature) => {
|
|
1142
|
+
display.setSelectedFeature(feature)
|
|
1143
|
+
},
|
|
859
1144
|
},
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
1145
|
+
],
|
|
1146
|
+
)
|
|
1147
|
+
},
|
|
863
1148
|
},
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
1149
|
+
)
|
|
1150
|
+
}
|
|
1151
|
+
if (isTranscriptFeature(feature, session)) {
|
|
1152
|
+
contextMenuItemsForFeature.push({
|
|
1153
|
+
label: 'Merge transcript',
|
|
868
1154
|
onClick: () => {
|
|
869
1155
|
;(session as unknown as AbstractSessionModel).queueDialog(
|
|
870
1156
|
(doneCallback) => [
|
|
871
|
-
|
|
1157
|
+
MergeTranscripts,
|
|
872
1158
|
{
|
|
873
1159
|
session,
|
|
874
1160
|
handleClose: () => {
|
|
@@ -885,56 +1171,31 @@ function getContextMenuItems(
|
|
|
885
1171
|
],
|
|
886
1172
|
)
|
|
887
1173
|
},
|
|
888
|
-
},
|
|
889
|
-
)
|
|
890
|
-
}
|
|
891
|
-
if (isTranscriptFeature(feature, session)) {
|
|
892
|
-
contextMenuItemsForFeature.push({
|
|
893
|
-
label: 'Merge transcript',
|
|
894
|
-
onClick: () => {
|
|
895
|
-
;(session as unknown as AbstractSessionModel).queueDialog(
|
|
896
|
-
(doneCallback) => [
|
|
897
|
-
MergeTranscripts,
|
|
898
|
-
{
|
|
899
|
-
session,
|
|
900
|
-
handleClose: () => {
|
|
901
|
-
doneCallback()
|
|
902
|
-
},
|
|
903
|
-
changeManager,
|
|
904
|
-
sourceFeature: feature,
|
|
905
|
-
sourceAssemblyId: currentAssemblyId,
|
|
906
|
-
selectedFeature,
|
|
907
|
-
setSelectedFeature: (feature?: AnnotationFeature) => {
|
|
908
|
-
display.setSelectedFeature(feature)
|
|
909
|
-
},
|
|
910
|
-
},
|
|
911
|
-
],
|
|
912
|
-
)
|
|
913
|
-
},
|
|
914
|
-
})
|
|
915
|
-
if (isSessionModelWithWidgets(session)) {
|
|
916
|
-
contextMenuItemsForFeature.push({
|
|
917
|
-
label: 'Open transcript details',
|
|
918
|
-
onClick: () => {
|
|
919
|
-
const apolloTranscriptWidget = session.addWidget(
|
|
920
|
-
'ApolloTranscriptDetails',
|
|
921
|
-
'apolloTranscriptDetails',
|
|
922
|
-
{
|
|
923
|
-
feature,
|
|
924
|
-
assembly: currentAssemblyId,
|
|
925
|
-
changeManager,
|
|
926
|
-
refName: region.refName,
|
|
927
|
-
},
|
|
928
|
-
)
|
|
929
|
-
session.showWidget(apolloTranscriptWidget)
|
|
930
|
-
},
|
|
931
1174
|
})
|
|
1175
|
+
if (isSessionModelWithWidgets(session)) {
|
|
1176
|
+
contextMenuItemsForFeature.push({
|
|
1177
|
+
label: 'Open transcript details',
|
|
1178
|
+
onClick: () => {
|
|
1179
|
+
const apolloTranscriptWidget = session.addWidget(
|
|
1180
|
+
'ApolloTranscriptDetails',
|
|
1181
|
+
'apolloTranscriptDetails',
|
|
1182
|
+
{
|
|
1183
|
+
feature,
|
|
1184
|
+
assembly: currentAssemblyId,
|
|
1185
|
+
changeManager,
|
|
1186
|
+
refName: region.refName,
|
|
1187
|
+
},
|
|
1188
|
+
)
|
|
1189
|
+
session.showWidget(apolloTranscriptWidget)
|
|
1190
|
+
},
|
|
1191
|
+
})
|
|
1192
|
+
}
|
|
932
1193
|
}
|
|
1194
|
+
menuItems.push({
|
|
1195
|
+
label: feature.type,
|
|
1196
|
+
subMenu: contextMenuItemsForFeature,
|
|
1197
|
+
})
|
|
933
1198
|
}
|
|
934
|
-
menuItems.push({
|
|
935
|
-
label: feature.type,
|
|
936
|
-
subMenu: contextMenuItemsForFeature,
|
|
937
|
-
})
|
|
938
1199
|
}
|
|
939
1200
|
return menuItems
|
|
940
1201
|
}
|