@apollo-annotation/jbrowse-plugin-apollo 0.1.0

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.
Files changed (116) hide show
  1. package/README.md +76 -0
  2. package/dist/index.esm.js +10248 -0
  3. package/dist/index.esm.js.map +1 -0
  4. package/dist/index.js +7 -0
  5. package/dist/jbrowse-plugin-apollo.cjs.development.js +10298 -0
  6. package/dist/jbrowse-plugin-apollo.cjs.development.js.map +1 -0
  7. package/dist/jbrowse-plugin-apollo.cjs.production.min.js +2 -0
  8. package/dist/jbrowse-plugin-apollo.cjs.production.min.js.map +1 -0
  9. package/dist/jbrowse-plugin-apollo.umd.development.js +46957 -0
  10. package/dist/jbrowse-plugin-apollo.umd.development.js.map +1 -0
  11. package/dist/jbrowse-plugin-apollo.umd.production.min.js +2 -0
  12. package/dist/jbrowse-plugin-apollo.umd.production.min.js.map +1 -0
  13. package/package.json +130 -0
  14. package/src/ApolloInternetAccount/addMenuItems.ts +94 -0
  15. package/src/ApolloInternetAccount/components/AuthTypeSelector.tsx +121 -0
  16. package/src/ApolloInternetAccount/components/LoginButtons.tsx +62 -0
  17. package/src/ApolloInternetAccount/components/LoginIcons.tsx +74 -0
  18. package/src/ApolloInternetAccount/configSchema.ts +26 -0
  19. package/src/ApolloInternetAccount/index.ts +2 -0
  20. package/src/ApolloInternetAccount/model.ts +448 -0
  21. package/src/ApolloJobModel.ts +117 -0
  22. package/src/ApolloSequenceAdapter/ApolloSequenceAdapter.ts +186 -0
  23. package/src/ApolloSequenceAdapter/configSchema.ts +12 -0
  24. package/src/ApolloSequenceAdapter/index.ts +21 -0
  25. package/src/ApolloSixFrameRenderer/ApolloSixFrameRenderer.tsx +12 -0
  26. package/src/ApolloSixFrameRenderer/components/ApolloRendering.tsx +692 -0
  27. package/src/ApolloSixFrameRenderer/configSchema.ts +7 -0
  28. package/src/ApolloSixFrameRenderer/index.ts +3 -0
  29. package/src/ApolloTextSearchAdapter/ApolloTextSearchAdapter.ts +64 -0
  30. package/src/ApolloTextSearchAdapter/configSchema.ts +24 -0
  31. package/src/ApolloTextSearchAdapter/index.ts +18 -0
  32. package/src/BackendDrivers/BackendDriver.ts +31 -0
  33. package/src/BackendDrivers/CollaborationServerDriver.ts +318 -0
  34. package/src/BackendDrivers/DesktopFileDriver.ts +170 -0
  35. package/src/BackendDrivers/InMemoryFileDriver.ts +76 -0
  36. package/src/BackendDrivers/index.ts +4 -0
  37. package/src/ChangeManager.ts +148 -0
  38. package/src/LinearApolloDisplay/components/LinearApolloDisplay.tsx +248 -0
  39. package/src/LinearApolloDisplay/components/index.ts +1 -0
  40. package/src/LinearApolloDisplay/configSchema.ts +16 -0
  41. package/src/LinearApolloDisplay/glyphs/BoxGlyph.ts +422 -0
  42. package/src/LinearApolloDisplay/glyphs/CanonicalGeneGlyph.ts +1191 -0
  43. package/src/LinearApolloDisplay/glyphs/GenericChildGlyph.ts +151 -0
  44. package/src/LinearApolloDisplay/glyphs/Glyph.ts +382 -0
  45. package/src/LinearApolloDisplay/glyphs/ImplicitExonGeneGlyph.ts +697 -0
  46. package/src/LinearApolloDisplay/glyphs/index.ts +4 -0
  47. package/src/LinearApolloDisplay/index.ts +2 -0
  48. package/src/LinearApolloDisplay/stateModel/base.ts +146 -0
  49. package/src/LinearApolloDisplay/stateModel/getGlyph.ts +39 -0
  50. package/src/LinearApolloDisplay/stateModel/glyphs.ts +45 -0
  51. package/src/LinearApolloDisplay/stateModel/index.ts +20 -0
  52. package/src/LinearApolloDisplay/stateModel/layouts.ts +230 -0
  53. package/src/LinearApolloDisplay/stateModel/mouseEvents.ts +513 -0
  54. package/src/LinearApolloDisplay/stateModel/rendering.ts +441 -0
  55. package/src/LinearApolloDisplay/stateModel/trackHeightMixin.ts +43 -0
  56. package/src/LinearApolloDisplay/types.ts +1 -0
  57. package/src/OntologyManager/OntologyStore/__snapshots__/fulltext.test.ts.snap +208 -0
  58. package/src/OntologyManager/OntologyStore/__snapshots__/index.test.ts.snap +18846 -0
  59. package/src/OntologyManager/OntologyStore/fulltext-stopwords.ts +137 -0
  60. package/src/OntologyManager/OntologyStore/fulltext.test.ts +94 -0
  61. package/src/OntologyManager/OntologyStore/fulltext.ts +264 -0
  62. package/src/OntologyManager/OntologyStore/index.test.ts +130 -0
  63. package/src/OntologyManager/OntologyStore/index.ts +526 -0
  64. package/src/OntologyManager/OntologyStore/indexeddb-schema.ts +89 -0
  65. package/src/OntologyManager/OntologyStore/indexeddb-storage.ts +180 -0
  66. package/src/OntologyManager/OntologyStore/obo-graph-json-schema.ts +110 -0
  67. package/src/OntologyManager/OntologyStore/prefixes.ts +35 -0
  68. package/src/OntologyManager/index.ts +173 -0
  69. package/src/SixFrameFeatureDisplay/components/TrackLines.tsx +19 -0
  70. package/src/SixFrameFeatureDisplay/components/index.ts +1 -0
  71. package/src/SixFrameFeatureDisplay/configSchema.ts +21 -0
  72. package/src/SixFrameFeatureDisplay/index.ts +2 -0
  73. package/src/SixFrameFeatureDisplay/stateModel.ts +413 -0
  74. package/src/TabularEditor/HybridGrid/ChangeHandling.ts +88 -0
  75. package/src/TabularEditor/HybridGrid/Feature.tsx +346 -0
  76. package/src/TabularEditor/HybridGrid/FeatureAttributes.tsx +34 -0
  77. package/src/TabularEditor/HybridGrid/Highlight.tsx +40 -0
  78. package/src/TabularEditor/HybridGrid/HybridGrid.tsx +138 -0
  79. package/src/TabularEditor/HybridGrid/NumberCell.tsx +77 -0
  80. package/src/TabularEditor/HybridGrid/ToolBar.tsx +59 -0
  81. package/src/TabularEditor/HybridGrid/featureContextMenuItems.ts +119 -0
  82. package/src/TabularEditor/HybridGrid/index.ts +1 -0
  83. package/src/TabularEditor/TabularEditorPane.tsx +34 -0
  84. package/src/TabularEditor/index.ts +3 -0
  85. package/src/TabularEditor/model.ts +44 -0
  86. package/src/TabularEditor/types.ts +3 -0
  87. package/src/components/AddAssembly.tsx +464 -0
  88. package/src/components/AddChildFeature.tsx +247 -0
  89. package/src/components/AddFeature.tsx +252 -0
  90. package/src/components/CopyFeature.tsx +328 -0
  91. package/src/components/DeleteAssembly.tsx +185 -0
  92. package/src/components/DeleteFeature.tsx +90 -0
  93. package/src/components/Dialog.tsx +47 -0
  94. package/src/components/DownloadGFF3.tsx +213 -0
  95. package/src/components/ImportFeatures.tsx +295 -0
  96. package/src/components/ManageChecks.tsx +280 -0
  97. package/src/components/ManageUsers.tsx +218 -0
  98. package/src/components/ModifyFeatureAttribute.tsx +457 -0
  99. package/src/components/OntologyTermAutocomplete.tsx +240 -0
  100. package/src/components/OntologyTermMultiSelect.tsx +349 -0
  101. package/src/components/OpenLocalFile.tsx +178 -0
  102. package/src/components/ViewChangeLog.tsx +208 -0
  103. package/src/components/ViewCheckResults.tsx +151 -0
  104. package/src/components/index.ts +12 -0
  105. package/src/config.ts +10 -0
  106. package/src/declare.d.ts +3 -0
  107. package/src/extensions/annotationFromPileup.ts +208 -0
  108. package/src/extensions/index.ts +1 -0
  109. package/src/index.ts +394 -0
  110. package/src/makeDisplayComponent.tsx +244 -0
  111. package/src/session/ClientDataStore.ts +282 -0
  112. package/src/session/index.ts +1 -0
  113. package/src/session/session.ts +373 -0
  114. package/src/types.ts +10 -0
  115. package/src/util/index.ts +31 -0
  116. package/src/util/loadAssemblyIntoClient.ts +291 -0
@@ -0,0 +1,697 @@
1
+ import { alpha } from '@mui/material'
2
+ import { AnnotationFeatureI } from 'apollo-mst'
3
+ import { LocationEndChange, LocationStartChange } from 'apollo-shared'
4
+
5
+ import { LinearApolloDisplay } from '../stateModel'
6
+ import { MousePosition } from '../stateModel/mouseEvents'
7
+ import { CanvasMouseEvent } from '../types'
8
+ import { Glyph } from './Glyph'
9
+
10
+ let forwardFill: CanvasPattern | null = null
11
+ let backwardFill: CanvasPattern | null = null
12
+ if ('document' in window) {
13
+ for (const direction of ['forward', 'backward']) {
14
+ const canvas = document.createElement('canvas')
15
+ const canvasSize = 10
16
+ canvas.width = canvas.height = canvasSize
17
+ const ctx = canvas.getContext('2d')
18
+ if (ctx) {
19
+ const stripeColor1 = 'rgba(0,0,0,0)'
20
+ const stripeColor2 = 'rgba(255,255,255,0.25)'
21
+ const gradient =
22
+ direction === 'forward'
23
+ ? ctx.createLinearGradient(0, canvasSize, canvasSize, 0)
24
+ : ctx.createLinearGradient(0, 0, canvasSize, canvasSize)
25
+ gradient.addColorStop(0, stripeColor1)
26
+ gradient.addColorStop(0.25, stripeColor1)
27
+ gradient.addColorStop(0.25, stripeColor2)
28
+ gradient.addColorStop(0.5, stripeColor2)
29
+ gradient.addColorStop(0.5, stripeColor1)
30
+ gradient.addColorStop(0.75, stripeColor1)
31
+ gradient.addColorStop(0.75, stripeColor2)
32
+ gradient.addColorStop(1, stripeColor2)
33
+ ctx.fillStyle = gradient
34
+ ctx.fillRect(0, 0, 10, 10)
35
+ if (direction === 'forward') {
36
+ forwardFill = ctx.createPattern(canvas, 'repeat')
37
+ } else {
38
+ backwardFill = ctx.createPattern(canvas, 'repeat')
39
+ }
40
+ }
41
+ }
42
+ }
43
+
44
+ export class ImplicitExonGeneGlyph extends Glyph {
45
+ featuresForRow(feature: AnnotationFeatureI): AnnotationFeatureI[][] {
46
+ const features: AnnotationFeatureI[][] = []
47
+ for (const [, child] of feature.children ?? new Map()) {
48
+ const childFeatures: AnnotationFeatureI[] = []
49
+ for (const [, annotationFeature] of child.children ?? new Map()) {
50
+ childFeatures.push(annotationFeature)
51
+ }
52
+ childFeatures.push(child)
53
+ features.push(childFeatures)
54
+ }
55
+ return features
56
+ }
57
+
58
+ getRowCount(feature: AnnotationFeatureI): number {
59
+ let mrnaCount = 0
60
+ for (const [, child] of feature.children ?? new Map()) {
61
+ if (child.type === 'mRNA') {
62
+ mrnaCount += 1
63
+ }
64
+ }
65
+ return mrnaCount
66
+ }
67
+
68
+ draw(
69
+ stateModel: LinearApolloDisplay,
70
+ ctx: CanvasRenderingContext2D,
71
+ feature: AnnotationFeatureI,
72
+ xOffset: number,
73
+ row: number,
74
+ reversed?: boolean,
75
+ ): void {
76
+ const { apolloRowHeight, lgv, session, theme } = stateModel
77
+ const { bpPerPx } = lgv
78
+ const rowHeight = apolloRowHeight
79
+ const utrHeight = Math.round(0.6 * rowHeight)
80
+ const cdsHeight = Math.round(0.9 * rowHeight)
81
+ const { _id, children, min, strand } = feature
82
+ const { apolloSelectedFeature } = session
83
+ let currentMRNA = 0
84
+ for (const [, mrna] of children ?? new Map()) {
85
+ if (mrna.type !== 'mRNA') {
86
+ continue
87
+ }
88
+ const offsetPx = (mrna.start - min) / bpPerPx
89
+ const widthPx = mrna.length / bpPerPx
90
+ const startPx = reversed
91
+ ? xOffset - offsetPx - widthPx
92
+ : xOffset + offsetPx
93
+ const height =
94
+ Math.round((currentMRNA + 1 / 2) * rowHeight) + row * rowHeight
95
+ ctx.strokeStyle = theme?.palette.text.primary ?? 'black'
96
+ ctx.beginPath()
97
+ ctx.moveTo(startPx, height)
98
+ ctx.lineTo(startPx + widthPx, height)
99
+ ctx.stroke()
100
+ currentMRNA += 1
101
+ }
102
+ currentMRNA = 0
103
+ for (const [, mrna] of children ?? new Map()) {
104
+ if (mrna.type !== 'mRNA') {
105
+ continue
106
+ }
107
+ const cdsCount = [...(mrna.children ?? [])].filter(
108
+ ([, exonOrCDS]) => exonOrCDS.type === 'CDS',
109
+ ).length
110
+ for (let count = 0; count < cdsCount; count++) {
111
+ for (const [, cdsOrUTR] of mrna.children ?? new Map()) {
112
+ const isCDS = cdsOrUTR.type === 'CDS'
113
+ const isUTR = cdsOrUTR.type.endsWith('UTR')
114
+ if (!(isCDS || isUTR)) {
115
+ continue
116
+ }
117
+ const offsetPx = (cdsOrUTR.start - min) / bpPerPx
118
+ const widthPx = cdsOrUTR.length / bpPerPx
119
+ const startPx = reversed
120
+ ? xOffset - offsetPx - widthPx
121
+ : xOffset + offsetPx
122
+ ctx.fillStyle = theme?.palette.text.primary ?? 'black'
123
+ const top = (row + currentMRNA) * rowHeight
124
+ const height = isCDS ? cdsHeight : utrHeight
125
+ const cdsOrUTRTop = top + (rowHeight - height) / 2
126
+ ctx.fillRect(startPx, cdsOrUTRTop, widthPx, height)
127
+ if (widthPx > 2) {
128
+ ctx.clearRect(startPx + 1, cdsOrUTRTop + 1, widthPx - 2, height - 2)
129
+ ctx.fillStyle =
130
+ apolloSelectedFeature &&
131
+ cdsOrUTR._id === apolloSelectedFeature._id
132
+ ? 'rgb(0,0,0)'
133
+ : isCDS
134
+ ? 'rgb(171,71,188)'
135
+ : 'rgb(211,211,211)'
136
+ ctx.fillRect(startPx + 1, cdsOrUTRTop + 1, widthPx - 2, height - 2)
137
+ if (forwardFill && backwardFill && strand) {
138
+ const reversal = reversed ? -1 : 1
139
+ const [topFill, bottomFill] =
140
+ strand * reversal === 1
141
+ ? [forwardFill, backwardFill]
142
+ : [backwardFill, forwardFill]
143
+ ctx.fillStyle = topFill
144
+ ctx.fillRect(
145
+ startPx + 1,
146
+ cdsOrUTRTop + 1,
147
+ widthPx - 2,
148
+ (height - 2) / 2,
149
+ )
150
+ ctx.fillStyle = bottomFill
151
+ ctx.fillRect(
152
+ startPx + 1,
153
+ cdsOrUTRTop + 1 + (height - 2) / 2,
154
+ widthPx - 2,
155
+ (height - 2) / 2,
156
+ )
157
+ }
158
+ }
159
+ }
160
+ }
161
+ currentMRNA += 1
162
+ }
163
+
164
+ if (apolloSelectedFeature) {
165
+ if (_id === apolloSelectedFeature._id) {
166
+ const widthPx = feature.length / bpPerPx
167
+ const startPx = reversed ? xOffset - widthPx : xOffset
168
+ const top = row * rowHeight
169
+ const height = this.getRowCount(feature) * rowHeight
170
+ ctx.fillStyle = theme?.palette.action.selected ?? 'rgba(0,0,0,0.08)'
171
+ ctx.fillRect(startPx, top, widthPx, height)
172
+ } else {
173
+ let featureEntry: AnnotationFeatureI | undefined
174
+ let featureRow: number | undefined
175
+ let i = 0
176
+ for (const [, f] of children ?? new Map()) {
177
+ if (f._id === apolloSelectedFeature?._id) {
178
+ featureEntry = f
179
+ featureRow = i
180
+ }
181
+ i++
182
+ }
183
+
184
+ if (featureEntry === undefined || featureRow === undefined) {
185
+ return
186
+ }
187
+ const widthPx = featureEntry.length / bpPerPx
188
+ const offsetPx = (featureEntry.start - min) / bpPerPx
189
+ const startPx = reversed ? xOffset - widthPx : xOffset + offsetPx
190
+ const top = (row + featureRow) * rowHeight
191
+ ctx.fillStyle = theme?.palette.action.selected ?? 'rgba(0,0,0,08)'
192
+ ctx.fillRect(startPx, top, widthPx, rowHeight)
193
+ }
194
+ }
195
+ }
196
+
197
+ drawHover(stateModel: LinearApolloDisplay, ctx: CanvasRenderingContext2D) {
198
+ const { apolloHover, apolloRowHeight, displayedRegions, lgv, theme } =
199
+ stateModel
200
+ if (!apolloHover) {
201
+ return
202
+ }
203
+ const { feature, mousePosition } = apolloHover
204
+ if (!(feature && mousePosition)) {
205
+ return
206
+ }
207
+ const { regionNumber, y } = mousePosition
208
+ const { bpPerPx, bpToPx, offsetPx } = lgv
209
+ const rowHeight = apolloRowHeight
210
+ const rowNumber = Math.floor(y / rowHeight)
211
+ const displayedRegion = displayedRegions[regionNumber]
212
+ const { refName, reversed } = displayedRegion
213
+ const startPx =
214
+ (bpToPx({
215
+ refName,
216
+ coord: reversed ? feature.end : feature.start,
217
+ regionNumber,
218
+ })?.offsetPx ?? 0) - offsetPx
219
+ const top = rowNumber * rowHeight
220
+ const widthPx = feature.length / bpPerPx
221
+ ctx.fillStyle = theme?.palette.action.focus ?? 'rgba(0,0,0,0.04)'
222
+ ctx.fillRect(startPx, top, widthPx, rowHeight)
223
+ }
224
+
225
+ drawDragPreview(
226
+ stateModel: LinearApolloDisplay,
227
+ overlayCtx: CanvasRenderingContext2D,
228
+ ) {
229
+ const { apolloDragging, apolloRowHeight, displayedRegions, lgv, theme } =
230
+ stateModel
231
+ const { bpPerPx, offsetPx } = lgv
232
+ if (!apolloDragging) {
233
+ return
234
+ }
235
+ const {
236
+ feature,
237
+ glyph,
238
+ mousePosition: startingMousePosition,
239
+ } = apolloDragging.start
240
+ if (!feature) {
241
+ throw new Error('no feature for drag preview??')
242
+ }
243
+ if (glyph !== this) {
244
+ throw new Error('drawDragPreview() called on wrong glyph?')
245
+ }
246
+ const { mousePosition: currentMousePosition } = apolloDragging.current
247
+ const edge = this.isMouseOnFeatureEdge(
248
+ startingMousePosition,
249
+ feature,
250
+ stateModel,
251
+ )
252
+ if (!edge) {
253
+ return
254
+ }
255
+
256
+ const row = Math.floor(startingMousePosition.y / apolloRowHeight)
257
+ const region = displayedRegions[startingMousePosition.regionNumber]
258
+ const rowCount = 1
259
+
260
+ const featureEdgeBp = region.reversed
261
+ ? region.end - feature[edge]
262
+ : feature[edge] - region.start
263
+ const featureEdgePx = featureEdgeBp / bpPerPx - offsetPx
264
+
265
+ const rectX = Math.min(currentMousePosition.x, featureEdgePx)
266
+ const rectY = row * apolloRowHeight
267
+ const rectWidth = Math.abs(currentMousePosition.x - featureEdgePx)
268
+ const rectHeight = apolloRowHeight * rowCount
269
+
270
+ overlayCtx.strokeStyle = theme?.palette.info.main ?? 'rgb(255,0,0)'
271
+ overlayCtx.setLineDash([6])
272
+ overlayCtx.strokeRect(rectX, rectY, rectWidth, rectHeight)
273
+ overlayCtx.fillStyle = alpha(
274
+ theme?.palette.info.main ?? 'rgb(255,0,0)',
275
+ 0.2,
276
+ )
277
+ overlayCtx.fillRect(rectX, rectY, rectWidth, rectHeight)
278
+ }
279
+
280
+ /**
281
+ * Check If the mouse position is on the edge of the selected feature
282
+ */
283
+ isMouseOnFeatureEdge(
284
+ mousePosition: MousePosition,
285
+ feature: AnnotationFeatureI,
286
+ stateModel: LinearApolloDisplay,
287
+ topLevelFeature?: AnnotationFeatureI,
288
+ ) {
289
+ if (!mousePosition) {
290
+ return
291
+ }
292
+
293
+ const { refName, regionNumber, x } = mousePosition
294
+ const { lgv } = stateModel
295
+ const { bpToPx, offsetPx } = lgv
296
+ const startPxInfo = bpToPx({ refName, coord: feature.start, regionNumber })
297
+ const endPxInfo = bpToPx({ refName, coord: feature.end, regionNumber })
298
+ if (startPxInfo !== undefined && endPxInfo !== undefined) {
299
+ const startPx = startPxInfo.offsetPx - offsetPx
300
+ const endPx = endPxInfo.offsetPx - offsetPx
301
+ if (Math.abs(endPx - startPx) < 8) {
302
+ return
303
+ }
304
+ const parentFeature = this.getParentFeature(feature, topLevelFeature)
305
+ // Limit dragging till parent feature end
306
+ if (
307
+ parentFeature &&
308
+ feature.start <= parentFeature.start &&
309
+ Math.abs(startPx - x) < 4
310
+ ) {
311
+ return
312
+ }
313
+ if (
314
+ parentFeature &&
315
+ feature.end >= parentFeature.end &&
316
+ Math.abs(endPx - x) < 4
317
+ ) {
318
+ return
319
+ }
320
+ if (Math.abs(startPx - x) < 4) {
321
+ return 'start'
322
+ }
323
+ if (Math.abs(endPx - x) < 4) {
324
+ return 'end'
325
+ }
326
+ }
327
+ return
328
+ }
329
+
330
+ onMouseMove(stateModel: LinearApolloDisplay, event: CanvasMouseEvent) {
331
+ const { feature, mousePosition, topLevelFeature } =
332
+ stateModel.getFeatureAndGlyphUnderMouse(event)
333
+ if (stateModel.apolloDragging) {
334
+ stateModel.setCursor('col-resize')
335
+ return
336
+ }
337
+ if (feature && mousePosition) {
338
+ const edge = this.isMouseOnFeatureEdge(
339
+ mousePosition,
340
+ feature,
341
+ stateModel,
342
+ topLevelFeature,
343
+ )
344
+ if (edge) {
345
+ stateModel.setCursor('col-resize')
346
+ } else {
347
+ stateModel.setCursor()
348
+ }
349
+ }
350
+ }
351
+
352
+ onMouseDown(stateModel: LinearApolloDisplay, event: CanvasMouseEvent) {
353
+ // swallow the mouseDown if we are on the edge of the feature
354
+ const { feature, mousePosition } =
355
+ stateModel.getFeatureAndGlyphUnderMouse(event)
356
+ if (feature && mousePosition) {
357
+ const edge = this.isMouseOnFeatureEdge(mousePosition, feature, stateModel)
358
+ if (edge) {
359
+ event.stopPropagation()
360
+ }
361
+ }
362
+ }
363
+
364
+ onMouseUp(stateModel: LinearApolloDisplay, event: CanvasMouseEvent) {
365
+ if (stateModel.apolloDragging ?? event.button !== 0) {
366
+ return
367
+ }
368
+ const { feature } = stateModel.getFeatureAndGlyphUnderMouse(event)
369
+ if (feature) {
370
+ stateModel.setSelectedFeature(feature)
371
+ }
372
+ }
373
+
374
+ startDrag(stateModel: LinearApolloDisplay): boolean {
375
+ // only accept the drag if we are on the edge of the feature
376
+ const { feature, mousePosition, topLevelFeature } =
377
+ stateModel.apolloDragging?.start ?? {}
378
+ const { mousePosition: currentMousePosition } =
379
+ stateModel.apolloDragging?.current ?? {}
380
+ if (feature && mousePosition && currentMousePosition) {
381
+ const edge = this.isMouseOnFeatureEdge(
382
+ mousePosition,
383
+ feature,
384
+ stateModel,
385
+ topLevelFeature,
386
+ )
387
+ if (edge) {
388
+ return true
389
+ }
390
+ }
391
+ return false
392
+ }
393
+
394
+ continueDrag(
395
+ stateModel: LinearApolloDisplay,
396
+ currentMousePosition: MousePosition,
397
+ ): void {
398
+ const { feature, glyph, mousePosition, topLevelFeature } =
399
+ stateModel.apolloDragging?.start ?? {}
400
+ if (!(currentMousePosition && mousePosition)) {
401
+ return
402
+ }
403
+ const parentFeature = this.getParentFeature(feature, topLevelFeature)
404
+ const adjacentFeatures: {
405
+ prevFeature?: AnnotationFeatureI
406
+ nextFeature?: AnnotationFeatureI
407
+ } = this.getAdjacentFeatures(feature, parentFeature)
408
+ if (!feature || !currentMousePosition) {
409
+ return
410
+ }
411
+ const { bp } = currentMousePosition
412
+ const edge = this.isMouseOnFeatureEdge(
413
+ mousePosition,
414
+ feature,
415
+ stateModel,
416
+ topLevelFeature,
417
+ )
418
+ if (
419
+ edge &&
420
+ ((edge === 'start' && bp >= feature.end - 1) ||
421
+ (edge === 'end' && bp <= feature.start + 1))
422
+ ) {
423
+ return
424
+ }
425
+ if (feature.type !== 'CDS') {
426
+ if (adjacentFeatures.prevFeature && !adjacentFeatures.nextFeature) {
427
+ if (
428
+ adjacentFeatures.prevFeature.type === 'CDS' &&
429
+ bp <= adjacentFeatures.prevFeature.start + 1
430
+ ) {
431
+ return
432
+ }
433
+ if (
434
+ adjacentFeatures.prevFeature.type !== 'CDS' &&
435
+ bp <= adjacentFeatures.prevFeature.end + 1
436
+ ) {
437
+ return
438
+ }
439
+ }
440
+ if (!adjacentFeatures.prevFeature && adjacentFeatures.nextFeature) {
441
+ if (
442
+ adjacentFeatures.nextFeature.type === 'CDS' &&
443
+ bp >= adjacentFeatures.nextFeature.end - 1
444
+ ) {
445
+ return
446
+ }
447
+ if (
448
+ adjacentFeatures.nextFeature.type !== 'CDS' &&
449
+ bp >= adjacentFeatures.nextFeature.start - 1
450
+ ) {
451
+ return
452
+ }
453
+ }
454
+ }
455
+
456
+ if (adjacentFeatures.prevFeature && adjacentFeatures.nextFeature) {
457
+ if (feature.type === 'CDS') {
458
+ if (
459
+ adjacentFeatures.nextFeature.type !== 'CDS' &&
460
+ bp >= adjacentFeatures.nextFeature.end - 1
461
+ ) {
462
+ return
463
+ }
464
+ if (
465
+ adjacentFeatures.nextFeature.type === 'CDS' &&
466
+ bp >= adjacentFeatures.nextFeature.start - 1
467
+ ) {
468
+ return
469
+ }
470
+ if (
471
+ adjacentFeatures.prevFeature.type !== 'CDS' &&
472
+ bp <= adjacentFeatures.prevFeature.start + 1
473
+ ) {
474
+ return
475
+ }
476
+ if (
477
+ adjacentFeatures.prevFeature.type === 'CDS' &&
478
+ bp <= adjacentFeatures.prevFeature.end + 1
479
+ ) {
480
+ return
481
+ }
482
+ } else {
483
+ if (
484
+ adjacentFeatures.prevFeature.type === 'CDS' &&
485
+ bp <= adjacentFeatures.prevFeature.start + 1
486
+ ) {
487
+ return
488
+ }
489
+ if (
490
+ adjacentFeatures.prevFeature.type !== 'CDS' &&
491
+ bp <= adjacentFeatures.prevFeature.end + 1
492
+ ) {
493
+ return
494
+ }
495
+ if (
496
+ adjacentFeatures.nextFeature.type !== 'CDS' &&
497
+ bp >= adjacentFeatures.nextFeature.start - 1
498
+ ) {
499
+ return
500
+ }
501
+ if (
502
+ adjacentFeatures.nextFeature.type === 'CDS' &&
503
+ bp >= adjacentFeatures.nextFeature.end - 1
504
+ ) {
505
+ return
506
+ }
507
+ }
508
+ }
509
+ stateModel.setDragging({
510
+ start: {
511
+ feature,
512
+ topLevelFeature,
513
+ glyph,
514
+ mousePosition,
515
+ },
516
+ current: {
517
+ feature,
518
+ topLevelFeature,
519
+ glyph,
520
+ mousePosition: currentMousePosition,
521
+ },
522
+ })
523
+ }
524
+
525
+ getFeatureFromLayout(
526
+ feature: AnnotationFeatureI,
527
+ bp: number,
528
+ row: number,
529
+ ): AnnotationFeatureI | undefined {
530
+ const layoutRow = this.featuresForRow(feature)[row]
531
+ return layoutRow?.find((f) => bp >= f.start && bp <= f.end)
532
+ }
533
+
534
+ getRowForFeature(
535
+ feature: AnnotationFeatureI,
536
+ childFeature: AnnotationFeatureI,
537
+ ) {
538
+ const rows = this.featuresForRow(feature)
539
+ for (const [idx, row] of rows.entries()) {
540
+ if (row.some((feature) => feature._id === childFeature._id)) {
541
+ return idx
542
+ }
543
+ }
544
+ return
545
+ }
546
+
547
+ async executeDrag(stateModel: LinearApolloDisplay) {
548
+ const {
549
+ apolloDragging,
550
+ changeManager,
551
+ displayedRegions,
552
+ getAssemblyId,
553
+ setCursor,
554
+ } = stateModel
555
+ if (!apolloDragging) {
556
+ return
557
+ }
558
+ const {
559
+ feature,
560
+ glyph,
561
+ mousePosition: startingMousePosition,
562
+ topLevelFeature,
563
+ } = apolloDragging.start
564
+ if (!feature) {
565
+ throw new Error('no feature for drag preview??')
566
+ }
567
+ if (glyph !== this) {
568
+ throw new Error('drawDragPreview() called on wrong glyph?')
569
+ }
570
+ const edge = this.isMouseOnFeatureEdge(
571
+ startingMousePosition,
572
+ feature,
573
+ stateModel,
574
+ )
575
+ if (!edge) {
576
+ return
577
+ }
578
+
579
+ const { mousePosition: currentMousePosition } = apolloDragging.current
580
+ const region = displayedRegions[startingMousePosition.regionNumber]
581
+ const newBp = currentMousePosition.bp
582
+ const assembly = getAssemblyId(region.assemblyName)
583
+
584
+ const parentFeature = this.getParentFeature(feature, topLevelFeature)
585
+ const adjacentFeatures: {
586
+ prevFeature?: AnnotationFeatureI
587
+ nextFeature?: AnnotationFeatureI
588
+ } = this.getAdjacentFeatures(feature, parentFeature)
589
+ const changes: (LocationStartChange | LocationEndChange)[] = []
590
+
591
+ if (edge === 'end') {
592
+ this.addEndLocation(changes, feature, newBp, assembly)
593
+ const { nextFeature } = adjacentFeatures
594
+ if (!nextFeature) {
595
+ return
596
+ }
597
+ if (
598
+ (feature.type !== 'CDS' && nextFeature.type === 'CDS') ||
599
+ (feature.type === 'CDS' && nextFeature.type !== 'CDS')
600
+ ) {
601
+ this.addStartLocation(changes, nextFeature, newBp + 1, assembly)
602
+ }
603
+ } else {
604
+ this.addStartLocation(changes, feature, newBp, assembly)
605
+ const { prevFeature } = adjacentFeatures
606
+ if (!prevFeature) {
607
+ return
608
+ }
609
+ if (
610
+ (feature.type !== 'CDS' && prevFeature.type === 'CDS') ||
611
+ (feature.type === 'CDS' && prevFeature.type !== 'CDS')
612
+ ) {
613
+ this.addEndLocation(changes, prevFeature, newBp - 1, assembly)
614
+ }
615
+ }
616
+ if (!changeManager) {
617
+ throw new Error('no change manager')
618
+ }
619
+ for (const change of changes) {
620
+ await changeManager.submit(change)
621
+ }
622
+ setCursor()
623
+ }
624
+
625
+ getAdjacentFeatures(
626
+ feature?: AnnotationFeatureI,
627
+ parentFeature?: AnnotationFeatureI,
628
+ ): {
629
+ prevFeature?: AnnotationFeatureI
630
+ nextFeature?: AnnotationFeatureI
631
+ } {
632
+ let prevFeature: AnnotationFeatureI | undefined
633
+ let nextFeature: AnnotationFeatureI | undefined
634
+ let i = 0
635
+ if (!feature || !(parentFeature && parentFeature.children)) {
636
+ return { prevFeature, nextFeature }
637
+ }
638
+ for (const [, f] of parentFeature.children) {
639
+ if (f._id === feature._id) {
640
+ break
641
+ }
642
+ i++
643
+ }
644
+ const keys = [...parentFeature.children.keys()]
645
+ if (i > 0) {
646
+ const key = keys[i - 1]
647
+ prevFeature = parentFeature.children.get(key)
648
+ }
649
+ if (i < keys.length - 1) {
650
+ const key = keys[i + 1]
651
+ nextFeature = parentFeature.children.get(key)
652
+ }
653
+ return { prevFeature, nextFeature }
654
+ }
655
+
656
+ addEndLocation(
657
+ changes: (LocationStartChange | LocationEndChange)[] = [],
658
+ feature: AnnotationFeatureI,
659
+ newBp: number,
660
+ assembly: string,
661
+ ) {
662
+ const featureId = feature._id
663
+ const oldEnd = feature.end
664
+ const newEnd = newBp
665
+ changes.push(
666
+ new LocationEndChange({
667
+ typeName: 'LocationEndChange',
668
+ changedIds: [featureId],
669
+ featureId,
670
+ oldEnd,
671
+ newEnd,
672
+ assembly,
673
+ }),
674
+ )
675
+ }
676
+
677
+ addStartLocation(
678
+ changes: (LocationStartChange | LocationEndChange)[] = [],
679
+ feature: AnnotationFeatureI,
680
+ newBp: number,
681
+ assembly: string,
682
+ ) {
683
+ const featureId = feature._id
684
+ const oldStart = feature.start
685
+ const newStart = newBp
686
+ changes.push(
687
+ new LocationStartChange({
688
+ typeName: 'LocationStartChange',
689
+ changedIds: [featureId],
690
+ featureId,
691
+ oldStart,
692
+ newStart,
693
+ assembly,
694
+ }),
695
+ )
696
+ }
697
+ }
@@ -0,0 +1,4 @@
1
+ export * from './BoxGlyph'
2
+ export * from './CanonicalGeneGlyph'
3
+ export * from './GenericChildGlyph'
4
+ export * from './ImplicitExonGeneGlyph'
@@ -0,0 +1,2 @@
1
+ export { configSchemaFactory } from './configSchema'
2
+ export { stateModelFactory } from './stateModel'