@apollo-annotation/jbrowse-plugin-apollo 0.3.6 → 0.3.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.esm.js +4603 -2045
- package/dist/index.esm.js.map +1 -1
- package/dist/jbrowse-plugin-apollo.cjs.development.js +4611 -2039
- package/dist/jbrowse-plugin-apollo.cjs.development.js.map +1 -1
- package/dist/jbrowse-plugin-apollo.cjs.production.min.js +1 -1
- package/dist/jbrowse-plugin-apollo.cjs.production.min.js.map +1 -1
- package/dist/jbrowse-plugin-apollo.umd.development.js +9387 -4016
- package/dist/jbrowse-plugin-apollo.umd.development.js.map +1 -1
- package/dist/jbrowse-plugin-apollo.umd.production.min.js +1 -1
- package/dist/jbrowse-plugin-apollo.umd.production.min.js.map +1 -1
- package/package.json +15 -15
- package/src/ApolloInternetAccount/model.ts +48 -13
- package/src/BackendDrivers/CollaborationServerDriver.ts +23 -2
- package/src/ChangeManager.ts +42 -18
- package/src/FeatureDetailsWidget/ApolloTranscriptDetailsWidget.tsx +64 -5
- package/src/FeatureDetailsWidget/Attributes.tsx +8 -3
- package/src/FeatureDetailsWidget/TranscriptSequence.tsx +70 -81
- package/src/FeatureDetailsWidget/TranscriptWidgetEditLocation.tsx +946 -190
- package/src/FeatureDetailsWidget/TranscriptWidgetSummary.tsx +4 -0
- package/src/LinearApolloDisplay/components/LinearApolloDisplay.tsx +61 -73
- package/src/LinearApolloDisplay/glyphs/BoxGlyph.ts +55 -211
- package/src/LinearApolloDisplay/glyphs/GeneGlyph.ts +562 -108
- package/src/LinearApolloDisplay/glyphs/GenericChildGlyph.ts +78 -14
- package/src/LinearApolloDisplay/glyphs/Glyph.ts +15 -9
- package/src/LinearApolloDisplay/stateModel/base.ts +63 -43
- package/src/LinearApolloDisplay/stateModel/layouts.ts +3 -2
- package/src/LinearApolloDisplay/stateModel/mouseEvents.ts +79 -292
- package/src/LinearApolloDisplay/stateModel/rendering.ts +45 -344
- package/src/LinearApolloReferenceSequenceDisplay/components/LinearApolloReferenceSequenceDisplay.tsx +87 -0
- package/src/LinearApolloReferenceSequenceDisplay/components/index.ts +1 -0
- package/src/LinearApolloReferenceSequenceDisplay/configSchema.ts +7 -0
- package/src/LinearApolloReferenceSequenceDisplay/index.ts +3 -0
- package/src/LinearApolloReferenceSequenceDisplay/stateModel/base.ts +227 -0
- package/src/LinearApolloReferenceSequenceDisplay/stateModel/index.ts +25 -0
- package/src/LinearApolloReferenceSequenceDisplay/stateModel/rendering.ts +481 -0
- package/src/LinearApolloSixFrameDisplay/components/LinearApolloSixFrameDisplay.tsx +102 -40
- package/src/LinearApolloSixFrameDisplay/components/TrackLines.tsx +12 -20
- package/src/LinearApolloSixFrameDisplay/glyphs/GeneGlyph.ts +382 -243
- package/src/LinearApolloSixFrameDisplay/glyphs/Glyph.ts +12 -8
- package/src/LinearApolloSixFrameDisplay/stateModel/base.ts +83 -4
- package/src/LinearApolloSixFrameDisplay/stateModel/layouts.ts +23 -11
- package/src/LinearApolloSixFrameDisplay/stateModel/mouseEvents.ts +118 -123
- package/src/LinearApolloSixFrameDisplay/stateModel/rendering.ts +53 -63
- package/src/OntologyManager/index.ts +4 -1
- package/src/TabularEditor/HybridGrid/Feature.tsx +20 -14
- package/src/TabularEditor/HybridGrid/HybridGrid.tsx +7 -5
- package/src/TabularEditor/HybridGrid/featureContextMenuItems.ts +108 -16
- package/src/components/AddAssembly.tsx +1 -1
- package/src/components/AddAssemblyAliases.tsx +114 -0
- package/src/components/AddChildFeature.tsx +7 -7
- package/src/components/AddFeature.tsx +20 -15
- package/src/components/AddRefSeqAliases.tsx +9 -9
- package/src/components/CopyFeature.tsx +4 -4
- package/src/components/CreateApolloAnnotation.tsx +335 -151
- package/src/components/DeleteAssembly.tsx +1 -1
- package/src/components/DeleteFeature.tsx +358 -11
- package/src/components/DownloadGFF3.tsx +20 -1
- package/src/components/EditZoomThresholdDialog.tsx +69 -0
- package/src/components/FilterFeatures.tsx +7 -7
- package/src/components/FilterTranscripts.tsx +86 -0
- package/src/components/ImportFeatures.tsx +1 -1
- package/src/components/ManageChecks.tsx +1 -1
- package/src/components/MergeExons.tsx +193 -0
- package/src/components/MergeTranscripts.tsx +182 -0
- package/src/components/OntologyTermMultiSelect.tsx +11 -11
- package/src/components/OpenLocalFile.tsx +11 -7
- package/src/components/SplitExon.tsx +134 -0
- package/src/components/ViewCheckResults.tsx +1 -1
- package/src/components/index.ts +4 -0
- package/src/config.ts +11 -0
- package/src/extensions/annotationFromJBrowseFeature.ts +2 -0
- package/src/extensions/annotationFromPileup.ts +99 -89
- package/src/index.ts +42 -105
- package/src/makeDisplayComponent.tsx +0 -1
- package/src/menus/index.ts +1 -0
- package/src/{ApolloInternetAccount/addMenuItems.ts → menus/topLevelMenu.ts} +60 -33
- package/src/menus/topLevelMenuAdmin.ts +154 -0
- package/src/session/session.ts +163 -104
- package/src/util/annotationFeatureUtils.ts +59 -0
- package/src/util/copyToClipboard.ts +21 -0
- package/src/util/displayUtils.ts +149 -0
- package/src/util/glyphUtils.ts +201 -0
- package/src/util/index.ts +2 -0
- package/src/util/mouseEventsUtils.ts +145 -0
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
import { type AnnotationFeature } from '@apollo-annotation/mst'
|
|
2
2
|
import { type MenuItem } from '@jbrowse/core/ui'
|
|
3
3
|
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
type MousePositionWithFeatureAndGlyph,
|
|
7
|
-
} from '../stateModel/mouseEvents'
|
|
4
|
+
import { type MousePositionWithFeature } from '../../util'
|
|
5
|
+
import { type LinearApolloSixFrameDisplayMouseEvents } from '../stateModel/mouseEvents'
|
|
8
6
|
import { type LinearApolloSixFrameDisplayRendering } from '../stateModel/rendering'
|
|
9
7
|
import { type CanvasMouseEvent } from '../types'
|
|
10
8
|
|
|
@@ -30,25 +28,25 @@ export interface Glyph {
|
|
|
30
28
|
|
|
31
29
|
onMouseDown(
|
|
32
30
|
display: LinearApolloSixFrameDisplayMouseEvents,
|
|
33
|
-
currentMousePosition:
|
|
31
|
+
currentMousePosition: MousePositionWithFeature,
|
|
34
32
|
event: CanvasMouseEvent,
|
|
35
33
|
): void
|
|
36
34
|
|
|
37
35
|
onMouseMove(
|
|
38
36
|
display: LinearApolloSixFrameDisplayMouseEvents,
|
|
39
|
-
currentMousePosition:
|
|
37
|
+
currentMousePosition: MousePositionWithFeature,
|
|
40
38
|
event: CanvasMouseEvent,
|
|
41
39
|
): void
|
|
42
40
|
|
|
43
41
|
onMouseLeave(
|
|
44
42
|
display: LinearApolloSixFrameDisplayMouseEvents,
|
|
45
|
-
currentMousePosition:
|
|
43
|
+
currentMousePosition: MousePositionWithFeature,
|
|
46
44
|
event: CanvasMouseEvent,
|
|
47
45
|
): void
|
|
48
46
|
|
|
49
47
|
onMouseUp(
|
|
50
48
|
display: LinearApolloSixFrameDisplayMouseEvents,
|
|
51
|
-
currentMousePosition:
|
|
49
|
+
currentMousePosition: MousePositionWithFeature,
|
|
52
50
|
event: CanvasMouseEvent,
|
|
53
51
|
): void
|
|
54
52
|
|
|
@@ -57,7 +55,13 @@ export interface Glyph {
|
|
|
57
55
|
context: CanvasRenderingContext2D,
|
|
58
56
|
): void
|
|
59
57
|
|
|
58
|
+
getContextMenuItemsForFeature(
|
|
59
|
+
display: LinearApolloSixFrameDisplayMouseEvents,
|
|
60
|
+
sourceFeature: AnnotationFeature,
|
|
61
|
+
): MenuItem[]
|
|
62
|
+
|
|
60
63
|
getContextMenuItems(
|
|
61
64
|
display: LinearApolloSixFrameDisplayMouseEvents,
|
|
65
|
+
currentMousePosition: MousePositionWithFeature,
|
|
62
66
|
): MenuItem[]
|
|
63
67
|
}
|
|
@@ -10,6 +10,7 @@ import { type AnyConfigurationSchemaType } from '@jbrowse/core/configuration/con
|
|
|
10
10
|
import { BaseDisplay } from '@jbrowse/core/pluggableElementTypes'
|
|
11
11
|
import {
|
|
12
12
|
type AbstractSessionModel,
|
|
13
|
+
type SessionWithWidgets,
|
|
13
14
|
getContainingView,
|
|
14
15
|
getSession,
|
|
15
16
|
} from '@jbrowse/core/util'
|
|
@@ -21,8 +22,9 @@ import { addDisposer, cast, getRoot, getSnapshot, types } from 'mobx-state-tree'
|
|
|
21
22
|
|
|
22
23
|
import { type ApolloInternetAccountModel } from '../../ApolloInternetAccount/model'
|
|
23
24
|
import { FilterFeatures } from '../../components/FilterFeatures'
|
|
24
|
-
import { type ApolloSessionModel } from '../../session'
|
|
25
|
+
import { type ApolloSessionModel, type HoveredFeature } from '../../session'
|
|
25
26
|
import { type ApolloRootModel } from '../../types'
|
|
27
|
+
import { EditZoomThresholdDialog } from '../../util/displayUtils'
|
|
26
28
|
|
|
27
29
|
const minDisplayHeight = 20
|
|
28
30
|
|
|
@@ -36,6 +38,9 @@ export function baseModelFactory(
|
|
|
36
38
|
configuration: ConfigurationReference(configSchema),
|
|
37
39
|
graphical: true,
|
|
38
40
|
table: false,
|
|
41
|
+
showFeatureLabels: true,
|
|
42
|
+
showCheckResults: true,
|
|
43
|
+
zoomThreshold: 200,
|
|
39
44
|
heightPreConfig: types.maybe(
|
|
40
45
|
types.refinement(
|
|
41
46
|
'displayHeight',
|
|
@@ -72,10 +77,13 @@ export function baseModelFactory(
|
|
|
72
77
|
return 500
|
|
73
78
|
}
|
|
74
79
|
if (self.graphical) {
|
|
75
|
-
return 200
|
|
80
|
+
return self.showFeatureLabels ? 400 : 200
|
|
76
81
|
}
|
|
77
82
|
return 300
|
|
78
83
|
},
|
|
84
|
+
get zoomThresholdSetting() {
|
|
85
|
+
return self.zoomThreshold ?? getConf(self, 'zoomThreshold')
|
|
86
|
+
},
|
|
79
87
|
}))
|
|
80
88
|
.views((self) => ({
|
|
81
89
|
get rendererTypeName() {
|
|
@@ -96,7 +104,7 @@ export function baseModelFactory(
|
|
|
96
104
|
return regions
|
|
97
105
|
},
|
|
98
106
|
regionCannotBeRendered(/* region */) {
|
|
99
|
-
if (self.lgv && self.lgv.bpPerPx >=
|
|
107
|
+
if (self.lgv && self.lgv.bpPerPx >= self.zoomThreshold) {
|
|
100
108
|
return 'Zoom in to see annotations'
|
|
101
109
|
}
|
|
102
110
|
return
|
|
@@ -138,6 +146,10 @@ export function baseModelFactory(
|
|
|
138
146
|
return (self.session as unknown as ApolloSessionModel)
|
|
139
147
|
.apolloSelectedFeature
|
|
140
148
|
},
|
|
149
|
+
get hoveredFeature(): HoveredFeature | undefined {
|
|
150
|
+
return (self.session as unknown as ApolloSessionModel)
|
|
151
|
+
.apolloHoveredFeature
|
|
152
|
+
},
|
|
141
153
|
}))
|
|
142
154
|
.actions((self) => ({
|
|
143
155
|
setScrollTop(scrollTop: number) {
|
|
@@ -164,15 +176,24 @@ export function baseModelFactory(
|
|
|
164
176
|
self.graphical = true
|
|
165
177
|
self.table = true
|
|
166
178
|
},
|
|
179
|
+
toggleShowFeatureLabels() {
|
|
180
|
+
self.showFeatureLabels = !self.showFeatureLabels
|
|
181
|
+
},
|
|
182
|
+
toggleShowCheckResults() {
|
|
183
|
+
self.showCheckResults = !self.showCheckResults
|
|
184
|
+
},
|
|
167
185
|
updateFilteredFeatureTypes(types: string[]) {
|
|
168
186
|
self.filteredFeatureTypes = cast(types)
|
|
169
187
|
},
|
|
188
|
+
setZoomThresholdSetting({ zoomThreshold }: { zoomThreshold: number }) {
|
|
189
|
+
self.zoomThreshold = zoomThreshold
|
|
190
|
+
},
|
|
170
191
|
}))
|
|
171
192
|
.views((self) => {
|
|
172
193
|
const { filteredFeatureTypes, trackMenuItems: superTrackMenuItems } = self
|
|
173
194
|
return {
|
|
174
195
|
trackMenuItems() {
|
|
175
|
-
const { graphical, table } = self
|
|
196
|
+
const { graphical, table, showFeatureLabels, showCheckResults } = self
|
|
176
197
|
return [
|
|
177
198
|
...superTrackMenuItems(),
|
|
178
199
|
{
|
|
@@ -203,6 +224,31 @@ export function baseModelFactory(
|
|
|
203
224
|
self.showGraphicalAndTable()
|
|
204
225
|
},
|
|
205
226
|
},
|
|
227
|
+
{
|
|
228
|
+
label: 'Feature Labels',
|
|
229
|
+
type: 'checkbox',
|
|
230
|
+
checked: showFeatureLabels,
|
|
231
|
+
onClick: () => {
|
|
232
|
+
self.toggleShowFeatureLabels()
|
|
233
|
+
},
|
|
234
|
+
},
|
|
235
|
+
{
|
|
236
|
+
label: 'Check Results',
|
|
237
|
+
type: 'checkbox',
|
|
238
|
+
checked: showCheckResults,
|
|
239
|
+
onClick: () => {
|
|
240
|
+
self.toggleShowCheckResults()
|
|
241
|
+
},
|
|
242
|
+
},
|
|
243
|
+
{
|
|
244
|
+
label: 'Change zoom threshold',
|
|
245
|
+
onClick: () => {
|
|
246
|
+
getSession(self).queueDialog((handleClose) => [
|
|
247
|
+
EditZoomThresholdDialog,
|
|
248
|
+
{ model: self, handleClose },
|
|
249
|
+
])
|
|
250
|
+
},
|
|
251
|
+
},
|
|
206
252
|
],
|
|
207
253
|
},
|
|
208
254
|
{
|
|
@@ -236,6 +282,39 @@ export function baseModelFactory(
|
|
|
236
282
|
self.session as unknown as ApolloSessionModel
|
|
237
283
|
).apolloSetSelectedFeature(feature)
|
|
238
284
|
},
|
|
285
|
+
setHoveredFeature(hoveredFeature?: HoveredFeature) {
|
|
286
|
+
;(
|
|
287
|
+
self.session as unknown as ApolloSessionModel
|
|
288
|
+
).apolloSetHoveredFeature(hoveredFeature)
|
|
289
|
+
},
|
|
290
|
+
showFeatureDetailsWidget(
|
|
291
|
+
feature: AnnotationFeature,
|
|
292
|
+
customWidgetNameAndId?: [string, string],
|
|
293
|
+
) {
|
|
294
|
+
const [region] = self.regions
|
|
295
|
+
const { assemblyName, refName } = region
|
|
296
|
+
const assembly = self.getAssemblyId(assemblyName)
|
|
297
|
+
if (!assembly) {
|
|
298
|
+
return
|
|
299
|
+
}
|
|
300
|
+
const { session } = self
|
|
301
|
+
const { changeManager } = session.apolloDataStore
|
|
302
|
+
const [widgetName, widgetId] = customWidgetNameAndId ?? [
|
|
303
|
+
'ApolloFeatureDetailsWidget',
|
|
304
|
+
'apolloFeatureDetailsWidget',
|
|
305
|
+
]
|
|
306
|
+
const apolloFeatureWidget = (
|
|
307
|
+
session as unknown as SessionWithWidgets
|
|
308
|
+
).addWidget(widgetName, widgetId, {
|
|
309
|
+
feature,
|
|
310
|
+
assembly,
|
|
311
|
+
refName,
|
|
312
|
+
changeManager,
|
|
313
|
+
})
|
|
314
|
+
;(session as unknown as SessionWithWidgets).showWidget(
|
|
315
|
+
apolloFeatureWidget,
|
|
316
|
+
)
|
|
317
|
+
},
|
|
239
318
|
afterAttach() {
|
|
240
319
|
addDisposer(
|
|
241
320
|
self,
|
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
/* eslint-disable @typescript-eslint/no-unnecessary-condition */
|
|
2
2
|
|
|
3
|
-
import {
|
|
4
|
-
type AnnotationFeature,
|
|
5
|
-
type TranscriptPartCoding,
|
|
6
|
-
} from '@apollo-annotation/mst'
|
|
3
|
+
import { type AnnotationFeature } from '@apollo-annotation/mst'
|
|
7
4
|
import type PluginManager from '@jbrowse/core/PluginManager'
|
|
8
5
|
import { type AnyConfigurationSchemaType } from '@jbrowse/core/configuration/configurationSchema'
|
|
9
6
|
import {
|
|
@@ -22,7 +19,6 @@ import { baseModelFactory } from './base'
|
|
|
22
19
|
export interface LayoutRow {
|
|
23
20
|
rowNum: number
|
|
24
21
|
feature: AnnotationFeature
|
|
25
|
-
cds: TranscriptPartCoding | null
|
|
26
22
|
}
|
|
27
23
|
|
|
28
24
|
export function layoutsModelFactory(
|
|
@@ -82,6 +78,9 @@ export function layoutsModelFactory(
|
|
|
82
78
|
getGlyph(_feature: AnnotationFeature) {
|
|
83
79
|
return geneGlyph
|
|
84
80
|
},
|
|
81
|
+
featureLabelSpacer(elem: number): number {
|
|
82
|
+
return self.showFeatureLabels ? elem * 2 - 1 : elem
|
|
83
|
+
},
|
|
85
84
|
}))
|
|
86
85
|
.actions((self) => ({
|
|
87
86
|
addSeenFeature(feature: AnnotationFeature) {
|
|
@@ -91,6 +90,11 @@ export function layoutsModelFactory(
|
|
|
91
90
|
self.seenFeatures.delete(featureId)
|
|
92
91
|
},
|
|
93
92
|
}))
|
|
93
|
+
.views((self) => ({
|
|
94
|
+
get geneTrackRowNums() {
|
|
95
|
+
return [4, 5].map((elem) => self.featureLabelSpacer(elem))
|
|
96
|
+
},
|
|
97
|
+
}))
|
|
94
98
|
.views((self) => ({
|
|
95
99
|
get featureLayouts() {
|
|
96
100
|
const { assemblyManager } =
|
|
@@ -120,12 +124,15 @@ export function layoutsModelFactory(
|
|
|
120
124
|
throw new Error('featureTypeOntology is undefined')
|
|
121
125
|
}
|
|
122
126
|
if (feature.looksLikeGene) {
|
|
123
|
-
const rowNum =
|
|
127
|
+
const rowNum =
|
|
128
|
+
feature.strand == 1
|
|
129
|
+
? self.geneTrackRowNums[0]
|
|
130
|
+
: self.geneTrackRowNums[1]
|
|
124
131
|
if (!featureLayout.get(rowNum)) {
|
|
125
132
|
featureLayout.set(rowNum, [])
|
|
126
133
|
}
|
|
127
134
|
const layoutRow = featureLayout.get(rowNum)
|
|
128
|
-
layoutRow?.push({ rowNum, feature
|
|
135
|
+
layoutRow?.push({ rowNum, feature })
|
|
129
136
|
const { children } = feature
|
|
130
137
|
if (!children) {
|
|
131
138
|
continue
|
|
@@ -142,9 +149,12 @@ export function layoutsModelFactory(
|
|
|
142
149
|
if (!featureTypeOntology.isTypeOf(exon.type, 'exon')) {
|
|
143
150
|
continue
|
|
144
151
|
}
|
|
145
|
-
const rowNum =
|
|
152
|
+
const rowNum =
|
|
153
|
+
exon.strand == 1
|
|
154
|
+
? self.geneTrackRowNums[0]
|
|
155
|
+
: self.geneTrackRowNums[1]
|
|
146
156
|
const layoutRow = featureLayout.get(rowNum)
|
|
147
|
-
layoutRow?.push({ rowNum, feature: exon
|
|
157
|
+
layoutRow?.push({ rowNum, feature: exon })
|
|
148
158
|
}
|
|
149
159
|
}
|
|
150
160
|
for (const cdsRow of cdsLocations) {
|
|
@@ -155,12 +165,14 @@ export function layoutsModelFactory(
|
|
|
155
165
|
strand ?? 1,
|
|
156
166
|
cds.phase,
|
|
157
167
|
)
|
|
158
|
-
rowNum =
|
|
168
|
+
rowNum = self.featureLabelSpacer(
|
|
169
|
+
rowNum < 0 ? -1 * rowNum + 5 : rowNum,
|
|
170
|
+
)
|
|
159
171
|
if (!featureLayout.get(rowNum)) {
|
|
160
172
|
featureLayout.set(rowNum, [])
|
|
161
173
|
}
|
|
162
174
|
const layoutRow = featureLayout.get(rowNum)
|
|
163
|
-
layoutRow?.push({ rowNum, feature: child
|
|
175
|
+
layoutRow?.push({ rowNum, feature: child })
|
|
164
176
|
}
|
|
165
177
|
}
|
|
166
178
|
}
|
|
@@ -1,7 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
type AnnotationFeature,
|
|
3
|
-
type TranscriptPartCoding,
|
|
4
|
-
} from '@apollo-annotation/mst'
|
|
1
|
+
import { type AnnotationFeature } from '@apollo-annotation/mst'
|
|
5
2
|
import {
|
|
6
3
|
LocationEndChange,
|
|
7
4
|
LocationStartChange,
|
|
@@ -9,55 +6,23 @@ import {
|
|
|
9
6
|
import type PluginManager from '@jbrowse/core/PluginManager'
|
|
10
7
|
import { type AnyConfigurationSchemaType } from '@jbrowse/core/configuration/configurationSchema'
|
|
11
8
|
import { type MenuItem } from '@jbrowse/core/ui'
|
|
12
|
-
import {
|
|
9
|
+
import { getFrame } from '@jbrowse/core/util'
|
|
13
10
|
import { autorun } from 'mobx'
|
|
14
|
-
import { type Instance, addDisposer } from 'mobx-state-tree'
|
|
11
|
+
import { type Instance, addDisposer, cast } from 'mobx-state-tree'
|
|
15
12
|
import { type CSSProperties } from 'react'
|
|
16
13
|
|
|
17
|
-
import {
|
|
18
|
-
|
|
14
|
+
import {
|
|
15
|
+
type Edge,
|
|
16
|
+
type MousePosition,
|
|
17
|
+
type MousePositionWithFeature,
|
|
18
|
+
getMousePosition,
|
|
19
|
+
getPropagatedLocationChanges,
|
|
20
|
+
isMousePositionWithFeature,
|
|
21
|
+
} from '../../util'
|
|
19
22
|
import { type CanvasMouseEvent } from '../types'
|
|
20
23
|
|
|
21
24
|
import { renderingModelFactory } from './rendering'
|
|
22
25
|
|
|
23
|
-
export interface FeatureAndGlyphUnderMouse {
|
|
24
|
-
cds: TranscriptPartCoding | null
|
|
25
|
-
feature: AnnotationFeature
|
|
26
|
-
topLevelFeature: AnnotationFeature
|
|
27
|
-
glyph: Glyph
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
/** extended information about the position of the mouse on the canvas, including the refName, bp, and displayedRegion number */
|
|
31
|
-
export interface MousePosition {
|
|
32
|
-
x: number
|
|
33
|
-
y: number
|
|
34
|
-
refName: string
|
|
35
|
-
bp: number
|
|
36
|
-
regionNumber: number
|
|
37
|
-
featureAndGlyphUnderMouse?: FeatureAndGlyphUnderMouse
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
export type MousePositionWithFeatureAndGlyph = Required<MousePosition>
|
|
41
|
-
|
|
42
|
-
export function isMousePositionWithFeatureAndGlyph(
|
|
43
|
-
mousePosition: MousePosition,
|
|
44
|
-
): mousePosition is MousePositionWithFeatureAndGlyph {
|
|
45
|
-
return 'featureAndGlyphUnderMouse' in mousePosition
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
function getMousePosition(
|
|
49
|
-
event: CanvasMouseEvent,
|
|
50
|
-
lgv: LinearGenomeViewModel,
|
|
51
|
-
): MousePosition {
|
|
52
|
-
const canvas = event.currentTarget
|
|
53
|
-
const { clientX, clientY } = event
|
|
54
|
-
const { left, top } = canvas.getBoundingClientRect()
|
|
55
|
-
const x = clientX - left
|
|
56
|
-
const y = clientY - top
|
|
57
|
-
const { coord: bp, index: regionNumber, refName } = lgv.pxToBp(x)
|
|
58
|
-
return { x, y, refName, bp, regionNumber }
|
|
59
|
-
}
|
|
60
|
-
|
|
61
26
|
export function mouseEventsModelIntermediateFactory(
|
|
62
27
|
pluginManager: PluginManager,
|
|
63
28
|
configSchema: AnyConfigurationSchemaType,
|
|
@@ -75,13 +40,13 @@ export function mouseEventsModelIntermediateFactory(
|
|
|
75
40
|
start: MousePosition
|
|
76
41
|
current: MousePosition
|
|
77
42
|
feature: AnnotationFeature
|
|
78
|
-
edge:
|
|
43
|
+
edge: Edge
|
|
44
|
+
shrinkParent: boolean
|
|
79
45
|
} | null,
|
|
80
46
|
cursor: undefined as CSSProperties['cursor'] | undefined,
|
|
81
|
-
apolloHover: undefined as FeatureAndGlyphUnderMouse | undefined,
|
|
82
47
|
}))
|
|
83
48
|
.views((self) => ({
|
|
84
|
-
getMousePosition(event:
|
|
49
|
+
getMousePosition(event: React.MouseEvent): MousePosition {
|
|
85
50
|
const mousePosition = getMousePosition(event, self.lgv)
|
|
86
51
|
const { bp, regionNumber, y } = mousePosition
|
|
87
52
|
const row = Math.floor(y / self.apolloRowHeight) + 1
|
|
@@ -90,8 +55,13 @@ export function mouseEventsModelIntermediateFactory(
|
|
|
90
55
|
if (!layoutRow) {
|
|
91
56
|
return mousePosition
|
|
92
57
|
}
|
|
58
|
+
const { featureTypeOntology } =
|
|
59
|
+
self.session.apolloDataStore.ontologyManager
|
|
60
|
+
if (!featureTypeOntology) {
|
|
61
|
+
throw new Error('featureTypeOntology is undefined')
|
|
62
|
+
}
|
|
93
63
|
let foundFeature
|
|
94
|
-
if (
|
|
64
|
+
if (self.geneTrackRowNums.includes(row)) {
|
|
95
65
|
foundFeature = layoutRow.find(
|
|
96
66
|
(f) =>
|
|
97
67
|
f.feature.type == 'exon' &&
|
|
@@ -104,19 +74,45 @@ export function mouseEventsModelIntermediateFactory(
|
|
|
104
74
|
)
|
|
105
75
|
}
|
|
106
76
|
} else {
|
|
107
|
-
foundFeature = layoutRow.find(
|
|
108
|
-
|
|
109
|
-
|
|
77
|
+
foundFeature = layoutRow.find((f) => {
|
|
78
|
+
const { feature } = f
|
|
79
|
+
const featureID = feature.attributes.get('gff_id')?.toString()
|
|
80
|
+
const isTranscript = featureTypeOntology.isTypeOf(
|
|
81
|
+
feature.type,
|
|
82
|
+
'transcript',
|
|
83
|
+
)
|
|
84
|
+
if (!isTranscript) {
|
|
85
|
+
return false
|
|
86
|
+
}
|
|
87
|
+
for (const loc of feature.cdsLocations) {
|
|
88
|
+
for (const cds of loc) {
|
|
89
|
+
let rowNum: number = getFrame(
|
|
90
|
+
cds.min,
|
|
91
|
+
cds.max,
|
|
92
|
+
feature.strand ?? 1,
|
|
93
|
+
cds.phase,
|
|
94
|
+
)
|
|
95
|
+
rowNum = self.featureLabelSpacer(
|
|
96
|
+
rowNum < 0 ? -1 * rowNum + 5 : rowNum,
|
|
97
|
+
)
|
|
98
|
+
if (row === rowNum && bp >= cds.min && bp <= cds.max) {
|
|
99
|
+
return (
|
|
100
|
+
featureID === undefined ||
|
|
101
|
+
!self.filteredTranscripts.includes(featureID)
|
|
102
|
+
)
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return false
|
|
107
|
+
})
|
|
110
108
|
}
|
|
111
109
|
if (!foundFeature) {
|
|
112
110
|
return mousePosition
|
|
113
111
|
}
|
|
114
|
-
const { feature
|
|
115
|
-
const { topLevelFeature } = feature
|
|
116
|
-
const glyph = self.getGlyph(feature)
|
|
112
|
+
const { feature } = foundFeature
|
|
117
113
|
return {
|
|
118
114
|
...mousePosition,
|
|
119
|
-
|
|
115
|
+
feature,
|
|
120
116
|
}
|
|
121
117
|
},
|
|
122
118
|
}))
|
|
@@ -135,14 +131,14 @@ export function mouseEventsModelIntermediateFactory(
|
|
|
135
131
|
},
|
|
136
132
|
}))
|
|
137
133
|
.actions((self) => ({
|
|
138
|
-
setApolloHover(n?: (typeof self)['apolloHover']) {
|
|
139
|
-
self.apolloHover = n
|
|
140
|
-
},
|
|
141
134
|
setCursor(cursor?: CSSProperties['cursor']) {
|
|
142
135
|
if (self.cursor !== cursor) {
|
|
143
136
|
self.cursor = cursor
|
|
144
137
|
}
|
|
145
138
|
},
|
|
139
|
+
updateFilteredTranscripts(forms: string[]): void {
|
|
140
|
+
self.filteredTranscripts = cast(forms)
|
|
141
|
+
},
|
|
146
142
|
}))
|
|
147
143
|
.actions(() => ({
|
|
148
144
|
// onClick(event: CanvasMouseEvent) {
|
|
@@ -160,36 +156,43 @@ export function mouseEventsModelFactory(
|
|
|
160
156
|
mouseEventsModelIntermediateFactory(pluginManager, configSchema)
|
|
161
157
|
|
|
162
158
|
return LinearApolloSixFrameDisplayMouseEvents.views((self) => ({
|
|
163
|
-
contextMenuItems(
|
|
164
|
-
const {
|
|
165
|
-
if (!
|
|
159
|
+
contextMenuItems(event: React.MouseEvent<HTMLDivElement>): MenuItem[] {
|
|
160
|
+
const { hoveredFeature } = self
|
|
161
|
+
if (!hoveredFeature) {
|
|
166
162
|
return []
|
|
167
163
|
}
|
|
168
|
-
const
|
|
164
|
+
const mousePosition = self.getMousePosition(event)
|
|
165
|
+
const { topLevelFeature } = hoveredFeature.feature
|
|
169
166
|
const glyph = self.getGlyph(topLevelFeature)
|
|
170
|
-
|
|
167
|
+
if (isMousePositionWithFeature(mousePosition)) {
|
|
168
|
+
return glyph.getContextMenuItems(self, mousePosition)
|
|
169
|
+
}
|
|
170
|
+
return []
|
|
171
171
|
},
|
|
172
172
|
}))
|
|
173
173
|
.actions((self) => ({
|
|
174
174
|
// explicitly pass in a feature in case it's not the same as the one in
|
|
175
175
|
// mousePosition (e.g. if features are drawn overlapping).
|
|
176
176
|
startDrag(
|
|
177
|
-
mousePosition:
|
|
177
|
+
mousePosition: MousePositionWithFeature,
|
|
178
178
|
feature: AnnotationFeature,
|
|
179
|
-
edge:
|
|
179
|
+
edge: Edge,
|
|
180
|
+
shrinkParent = false,
|
|
180
181
|
) {
|
|
181
182
|
self.apolloDragging = {
|
|
182
183
|
start: mousePosition,
|
|
183
184
|
current: mousePosition,
|
|
184
185
|
feature,
|
|
185
186
|
edge,
|
|
187
|
+
shrinkParent,
|
|
186
188
|
}
|
|
187
189
|
},
|
|
188
190
|
endDrag() {
|
|
189
191
|
if (!self.apolloDragging) {
|
|
190
192
|
throw new Error('endDrag() called with no current drag in progress')
|
|
191
193
|
}
|
|
192
|
-
const { current, edge, feature, start } =
|
|
194
|
+
const { current, edge, feature, start, shrinkParent } =
|
|
195
|
+
self.apolloDragging
|
|
193
196
|
// don't do anything if it was only dragged a tiny bit
|
|
194
197
|
if (Math.abs(current.x - start.x) <= 4) {
|
|
195
198
|
self.setDragging()
|
|
@@ -199,33 +202,35 @@ export function mouseEventsModelFactory(
|
|
|
199
202
|
const { displayedRegions } = self.lgv
|
|
200
203
|
const region = displayedRegions[start.regionNumber]
|
|
201
204
|
const assembly = self.getAssemblyId(region.assemblyName)
|
|
205
|
+
const changes = getPropagatedLocationChanges(
|
|
206
|
+
feature,
|
|
207
|
+
current.bp,
|
|
208
|
+
edge,
|
|
209
|
+
shrinkParent,
|
|
210
|
+
)
|
|
202
211
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
newStart,
|
|
226
|
-
assembly,
|
|
227
|
-
})
|
|
228
|
-
}
|
|
212
|
+
const change: LocationEndChange | LocationStartChange =
|
|
213
|
+
edge === 'max'
|
|
214
|
+
? new LocationEndChange({
|
|
215
|
+
typeName: 'LocationEndChange',
|
|
216
|
+
changedIds: changes.map((c) => c.featureId),
|
|
217
|
+
changes: changes.map((c) => ({
|
|
218
|
+
featureId: c.featureId,
|
|
219
|
+
oldEnd: c.oldLocation,
|
|
220
|
+
newEnd: c.newLocation,
|
|
221
|
+
})),
|
|
222
|
+
assembly,
|
|
223
|
+
})
|
|
224
|
+
: new LocationStartChange({
|
|
225
|
+
typeName: 'LocationStartChange',
|
|
226
|
+
changedIds: changes.map((c) => c.featureId),
|
|
227
|
+
changes: changes.map((c) => ({
|
|
228
|
+
featureId: c.featureId,
|
|
229
|
+
oldStart: c.oldLocation,
|
|
230
|
+
newStart: c.newLocation,
|
|
231
|
+
})),
|
|
232
|
+
assembly,
|
|
233
|
+
})
|
|
229
234
|
void self.changeManager.submit(change)
|
|
230
235
|
self.setDragging()
|
|
231
236
|
self.setCursor()
|
|
@@ -234,12 +239,9 @@ export function mouseEventsModelFactory(
|
|
|
234
239
|
.actions((self) => ({
|
|
235
240
|
onMouseDown(event: CanvasMouseEvent) {
|
|
236
241
|
const mousePosition = self.getMousePosition(event)
|
|
237
|
-
if (
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
mousePosition,
|
|
241
|
-
event,
|
|
242
|
-
)
|
|
242
|
+
if (isMousePositionWithFeature(mousePosition)) {
|
|
243
|
+
const glyph = self.getGlyph(mousePosition.feature)
|
|
244
|
+
glyph.onMouseDown(self, mousePosition, event)
|
|
243
245
|
}
|
|
244
246
|
},
|
|
245
247
|
onMouseMove(event: CanvasMouseEvent) {
|
|
@@ -249,38 +251,31 @@ export function mouseEventsModelFactory(
|
|
|
249
251
|
self.continueDrag(mousePosition, event)
|
|
250
252
|
return
|
|
251
253
|
}
|
|
252
|
-
if (
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
mousePosition,
|
|
256
|
-
event,
|
|
257
|
-
)
|
|
254
|
+
if (isMousePositionWithFeature(mousePosition)) {
|
|
255
|
+
const glyph = self.getGlyph(mousePosition.feature)
|
|
256
|
+
glyph.onMouseMove(self, mousePosition, event)
|
|
258
257
|
} else {
|
|
259
|
-
self.
|
|
258
|
+
self.setHoveredFeature()
|
|
260
259
|
self.setCursor()
|
|
261
260
|
}
|
|
262
261
|
},
|
|
263
262
|
onMouseLeave(event: CanvasMouseEvent) {
|
|
264
263
|
self.setDragging()
|
|
265
|
-
self.
|
|
264
|
+
self.setHoveredFeature()
|
|
266
265
|
|
|
267
266
|
const mousePosition = self.getMousePosition(event)
|
|
268
|
-
if (
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
mousePosition,
|
|
272
|
-
event,
|
|
273
|
-
)
|
|
267
|
+
if (isMousePositionWithFeature(mousePosition)) {
|
|
268
|
+
const glyph = self.getGlyph(mousePosition.feature)
|
|
269
|
+
glyph.onMouseLeave(self, mousePosition, event)
|
|
274
270
|
}
|
|
275
271
|
},
|
|
276
272
|
onMouseUp(event: CanvasMouseEvent) {
|
|
277
273
|
const mousePosition = self.getMousePosition(event)
|
|
278
|
-
if (
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
)
|
|
274
|
+
if (isMousePositionWithFeature(mousePosition)) {
|
|
275
|
+
const glyph = self.getGlyph(mousePosition.feature)
|
|
276
|
+
glyph.onMouseUp(self, mousePosition, event)
|
|
277
|
+
} else {
|
|
278
|
+
self.setSelectedFeature()
|
|
284
279
|
}
|
|
285
280
|
|
|
286
281
|
if (self.apolloDragging) {
|
|
@@ -310,11 +305,11 @@ export function mouseEventsModelFactory(
|
|
|
310
305
|
self.featuresHeight,
|
|
311
306
|
)
|
|
312
307
|
|
|
313
|
-
const { apolloDragging,
|
|
314
|
-
if (!
|
|
308
|
+
const { apolloDragging, hoveredFeature } = self
|
|
309
|
+
if (!hoveredFeature) {
|
|
315
310
|
return
|
|
316
311
|
}
|
|
317
|
-
const
|
|
312
|
+
const glyph = self.getGlyph(hoveredFeature.feature)
|
|
318
313
|
|
|
319
314
|
// draw mouseover hovers
|
|
320
315
|
glyph.drawHover(self, ctx)
|