@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,1191 @@
1
+ import { alpha } from '@mui/material'
2
+ import { AnnotationFeatureI } from 'apollo-mst'
3
+ import {
4
+ DiscontinuousLocationEndChange,
5
+ DiscontinuousLocationStartChange,
6
+ LocationEndChange,
7
+ LocationStartChange,
8
+ } from 'apollo-shared'
9
+
10
+ import { LinearApolloDisplay } from '../stateModel'
11
+ import {
12
+ CDSDiscontinuousLocation,
13
+ MousePosition,
14
+ } from '../stateModel/mouseEvents'
15
+ import { CanvasMouseEvent } from '../types'
16
+ import { Glyph } from './Glyph'
17
+
18
+ type LocationChange =
19
+ | DiscontinuousLocationEndChange
20
+ | DiscontinuousLocationStartChange
21
+ | LocationEndChange
22
+ | LocationStartChange
23
+
24
+ let forwardFill: CanvasPattern | null = null
25
+ let backwardFill: CanvasPattern | null = null
26
+ if ('document' in window) {
27
+ for (const direction of ['forward', 'backward']) {
28
+ const canvas = document.createElement('canvas')
29
+ const canvasSize = 10
30
+ canvas.width = canvas.height = canvasSize
31
+ const ctx = canvas.getContext('2d')
32
+ if (ctx) {
33
+ const stripeColor1 = 'rgba(0,0,0,0)'
34
+ const stripeColor2 = 'rgba(255,255,255,0.25)'
35
+ const gradient =
36
+ direction === 'forward'
37
+ ? ctx.createLinearGradient(0, canvasSize, canvasSize, 0)
38
+ : ctx.createLinearGradient(0, 0, canvasSize, canvasSize)
39
+ gradient.addColorStop(0, stripeColor1)
40
+ gradient.addColorStop(0.25, stripeColor1)
41
+ gradient.addColorStop(0.25, stripeColor2)
42
+ gradient.addColorStop(0.5, stripeColor2)
43
+ gradient.addColorStop(0.5, stripeColor1)
44
+ gradient.addColorStop(0.75, stripeColor1)
45
+ gradient.addColorStop(0.75, stripeColor2)
46
+ gradient.addColorStop(1, stripeColor2)
47
+ ctx.fillStyle = gradient
48
+ ctx.fillRect(0, 0, 10, 10)
49
+ if (direction === 'forward') {
50
+ forwardFill = ctx.createPattern(canvas, 'repeat')
51
+ } else {
52
+ backwardFill = ctx.createPattern(canvas, 'repeat')
53
+ }
54
+ }
55
+ }
56
+ }
57
+
58
+ interface AnnotationFeature {
59
+ parent?: AnnotationFeatureI
60
+ start?: number
61
+ end?: number
62
+ phase?: 0 | 1 | 2
63
+ annotationFeature: AnnotationFeatureI
64
+ }
65
+
66
+ interface CDSFeatures {
67
+ parent: AnnotationFeatureI
68
+ cds: AnnotationFeatureI
69
+ }
70
+
71
+ interface ExonCDSRelation {
72
+ exon: AnnotationFeatureI
73
+ cdsDL?: CDSDiscontinuousLocation
74
+ }
75
+
76
+ export class CanonicalGeneGlyph extends Glyph {
77
+ featuresForRow(feature: AnnotationFeatureI): AnnotationFeature[][] {
78
+ const cdsFeatures: CDSFeatures[] = []
79
+ for (const [, child] of feature.children ?? new Map()) {
80
+ for (const [, annotationFeature] of child.children ?? new Map()) {
81
+ if (annotationFeature.type === 'CDS') {
82
+ cdsFeatures.push({
83
+ parent: child,
84
+ cds: annotationFeature,
85
+ })
86
+ }
87
+ }
88
+ }
89
+
90
+ const features: AnnotationFeature[][] = []
91
+ for (const f of cdsFeatures) {
92
+ const childFeatures: AnnotationFeature[] = []
93
+ for (const [, cf] of f.parent.children ?? new Map()) {
94
+ if (cf.type === 'CDS' && cf._id !== f.cds._id) {
95
+ continue
96
+ }
97
+ if (cf.discontinuousLocations && cf.discontinuousLocations.length > 0) {
98
+ for (const dl of cf.discontinuousLocations) {
99
+ childFeatures.push({
100
+ annotationFeature: cf,
101
+ parent: f.parent,
102
+ start: dl.start,
103
+ end: dl.end,
104
+ phase: dl.phase,
105
+ })
106
+ }
107
+ } else {
108
+ childFeatures.push({
109
+ annotationFeature: cf,
110
+ parent: f.parent,
111
+ })
112
+ }
113
+ }
114
+ childFeatures.push({
115
+ annotationFeature: f.parent,
116
+ })
117
+ features.push(childFeatures)
118
+ }
119
+
120
+ return features
121
+ }
122
+
123
+ getRowCount(feature: AnnotationFeatureI, _bpPerPx?: number): number {
124
+ let cdsCount = 0
125
+ for (const [, child] of feature.children ?? new Map()) {
126
+ for (const [, grandchild] of child.children ?? new Map()) {
127
+ if (grandchild.type === 'CDS') {
128
+ cdsCount += 1
129
+ }
130
+ }
131
+ }
132
+ return cdsCount
133
+ }
134
+
135
+ draw(
136
+ stateModel: LinearApolloDisplay,
137
+ ctx: CanvasRenderingContext2D,
138
+ feature: AnnotationFeatureI,
139
+ xOffset: number,
140
+ row: number,
141
+ reversed: boolean,
142
+ ): void {
143
+ const { apolloRowHeight, lgv, session, theme } = stateModel
144
+ const { bpPerPx } = lgv
145
+ const rowHeight = apolloRowHeight
146
+ const utrHeight = Math.round(0.6 * rowHeight)
147
+ const cdsHeight = Math.round(0.9 * rowHeight)
148
+ const { _id, children, min, strand } = feature
149
+ const { apolloSelectedFeature } = session
150
+ let currentCDS = 0
151
+ for (const [, mrna] of children ?? new Map()) {
152
+ if (mrna.type !== 'mRNA') {
153
+ continue
154
+ }
155
+ for (const [, cds] of mrna.children ?? new Map()) {
156
+ if (cds.type !== 'CDS') {
157
+ continue
158
+ }
159
+ const offsetPx = (mrna.start - min) / bpPerPx
160
+ const widthPx = mrna.length / bpPerPx
161
+ const startPx = reversed
162
+ ? xOffset - offsetPx - widthPx
163
+ : xOffset + offsetPx
164
+ const height =
165
+ Math.round((currentCDS + 1 / 2) * rowHeight) + row * rowHeight
166
+ ctx.strokeStyle = theme?.palette.text.primary ?? 'black'
167
+ ctx.beginPath()
168
+ ctx.moveTo(startPx, height)
169
+ ctx.lineTo(startPx + widthPx, height)
170
+ ctx.stroke()
171
+ currentCDS += 1
172
+ }
173
+ }
174
+ currentCDS = 0
175
+ for (const [, mrna] of children ?? new Map()) {
176
+ if (mrna.type !== 'mRNA') {
177
+ continue
178
+ }
179
+ const cdsCount = [...(mrna.children ?? [])].filter(
180
+ ([, exonOrCDS]) => exonOrCDS.type === 'CDS',
181
+ ).length
182
+ for (let count = 0; count < cdsCount; count++) {
183
+ for (const [, exon] of mrna.children ?? new Map()) {
184
+ if (exon.type !== 'exon') {
185
+ continue
186
+ }
187
+ const offsetPx = (exon.start - min) / bpPerPx
188
+ const widthPx = exon.length / bpPerPx
189
+ const startPx = reversed
190
+ ? xOffset - offsetPx - widthPx
191
+ : xOffset + offsetPx
192
+ const top = (row + currentCDS) * rowHeight
193
+ const utrTop = top + (rowHeight - utrHeight) / 2
194
+ ctx.fillStyle = theme?.palette.text.primary ?? 'black'
195
+ ctx.fillRect(startPx, utrTop, widthPx, utrHeight)
196
+ if (widthPx > 2) {
197
+ ctx.clearRect(startPx + 1, utrTop + 1, widthPx - 2, utrHeight - 2)
198
+ ctx.fillStyle =
199
+ apolloSelectedFeature && exon._id === apolloSelectedFeature._id
200
+ ? 'rgb(0,0,0)'
201
+ : 'rgb(211,211,211)'
202
+ ctx.fillRect(startPx + 1, utrTop + 1, widthPx - 2, utrHeight - 2)
203
+ if (forwardFill && backwardFill && strand) {
204
+ const reversal = reversed ? -1 : 1
205
+ const [topFill, bottomFill] =
206
+ strand * reversal === 1
207
+ ? [forwardFill, backwardFill]
208
+ : [backwardFill, forwardFill]
209
+ ctx.fillStyle = topFill
210
+ ctx.fillRect(
211
+ startPx + 1,
212
+ utrTop + 1,
213
+ widthPx - 2,
214
+ (utrHeight - 2) / 2,
215
+ )
216
+ ctx.fillStyle = bottomFill
217
+ ctx.fillRect(
218
+ startPx + 1,
219
+ utrTop + 1 + (utrHeight - 2) / 2,
220
+ widthPx - 2,
221
+ (utrHeight - 2) / 2,
222
+ )
223
+ }
224
+ }
225
+ }
226
+ currentCDS += 1
227
+ }
228
+ }
229
+ currentCDS = 0
230
+ for (const [, mrna] of children ?? new Map()) {
231
+ if (mrna.type !== 'mRNA') {
232
+ continue
233
+ }
234
+ for (const [, cds] of mrna.children ?? new Map()) {
235
+ if (cds.type !== 'CDS') {
236
+ continue
237
+ }
238
+ if (cds.discontinuousLocations) {
239
+ for (const cdsLocation of cds.discontinuousLocations) {
240
+ const offsetPx = (cdsLocation.start - min) / bpPerPx
241
+ const widthPx = (cdsLocation.end - cdsLocation.start) / bpPerPx
242
+ const startPx = reversed
243
+ ? xOffset - offsetPx - widthPx
244
+ : xOffset + offsetPx
245
+ ctx.fillStyle = theme?.palette.text.primary ?? 'black'
246
+ const top = (row + currentCDS) * rowHeight
247
+ const cdsTop = top + (rowHeight - cdsHeight) / 2
248
+ ctx.fillRect(startPx, cdsTop, widthPx, cdsHeight)
249
+ if (widthPx > 2) {
250
+ ctx.clearRect(startPx + 1, cdsTop + 1, widthPx - 2, cdsHeight - 2)
251
+ ctx.fillStyle =
252
+ apolloSelectedFeature && cds._id === apolloSelectedFeature._id
253
+ ? 'rgb(0,0,0)'
254
+ : 'rgb(171,71,188)'
255
+ ctx.fillRect(startPx + 1, cdsTop + 1, widthPx - 2, cdsHeight - 2)
256
+ if (forwardFill && backwardFill && strand) {
257
+ const reversal = reversed ? -1 : 1
258
+ const [topFill, bottomFill] =
259
+ strand * reversal === 1
260
+ ? [forwardFill, backwardFill]
261
+ : [backwardFill, forwardFill]
262
+ ctx.fillStyle = topFill
263
+ ctx.fillRect(
264
+ startPx + 1,
265
+ cdsTop + 1,
266
+ widthPx - 2,
267
+ (cdsHeight - 2) / 2,
268
+ )
269
+ ctx.fillStyle = bottomFill
270
+ ctx.fillRect(
271
+ startPx + 1,
272
+ cdsTop + (cdsHeight - 2) / 2,
273
+ widthPx - 2,
274
+ (cdsHeight - 2) / 2,
275
+ )
276
+ }
277
+ }
278
+ }
279
+ }
280
+ currentCDS += 1
281
+ }
282
+ }
283
+ if (apolloSelectedFeature) {
284
+ if (_id === apolloSelectedFeature._id) {
285
+ const widthPx = feature.length / bpPerPx
286
+ const startPx = reversed ? xOffset - widthPx : xOffset
287
+ const top = row * rowHeight
288
+ const height = this.getRowCount(feature) * rowHeight
289
+ ctx.fillStyle = theme?.palette.action.selected ?? 'rgba(0,0,0,0.08)'
290
+ ctx.fillRect(startPx, top, widthPx, height)
291
+ } else {
292
+ let featureEntry: AnnotationFeatureI | undefined
293
+ let featureRow: number | undefined
294
+ let i = 0
295
+ for (const [, f] of children ?? new Map()) {
296
+ if (f._id === apolloSelectedFeature?._id) {
297
+ featureEntry = f
298
+ featureRow = i
299
+ }
300
+ i++
301
+ }
302
+
303
+ if (featureEntry === undefined || featureRow === undefined) {
304
+ return
305
+ }
306
+ const cdsCount = this.cdsCount(featureEntry)
307
+ let height = rowHeight
308
+ if (cdsCount > 1) {
309
+ height = height * cdsCount
310
+ }
311
+ const widthPx = featureEntry.length / bpPerPx
312
+ const offsetPx = (featureEntry.start - min) / bpPerPx
313
+ const startPx = reversed ? xOffset - widthPx : xOffset + offsetPx
314
+ const top = (row + featureRow) * rowHeight
315
+ ctx.fillStyle = theme?.palette.action.selected ?? 'rgba(0,0,0,08)'
316
+ ctx.fillRect(startPx, top, widthPx, height)
317
+ }
318
+ }
319
+ }
320
+
321
+ // CDS count with discontinuous locations
322
+ cdsCount(feature?: AnnotationFeatureI) {
323
+ let cdsCount = 0
324
+ for (const [, cf] of feature?.children ?? new Map()) {
325
+ if (
326
+ cf.type === 'CDS' &&
327
+ cf.discontinuousLocations &&
328
+ cf.discontinuousLocations.length > 0
329
+ ) {
330
+ cdsCount++
331
+ }
332
+ }
333
+ return cdsCount
334
+ }
335
+
336
+ drawHover(
337
+ stateModel: LinearApolloDisplay,
338
+ ctx: CanvasRenderingContext2D,
339
+ rowNum: number,
340
+ xOffset: number,
341
+ reversed: boolean,
342
+ ) {
343
+ const { apolloHover } = stateModel
344
+ if (!apolloHover) {
345
+ return
346
+ }
347
+ const { feature } = apolloHover
348
+ if (!feature) {
349
+ return
350
+ }
351
+
352
+ if (
353
+ feature.discontinuousLocations &&
354
+ feature.discontinuousLocations.length > 0
355
+ ) {
356
+ for (const dl of feature.discontinuousLocations) {
357
+ this.drawShadeForFeature(
358
+ stateModel,
359
+ ctx,
360
+ dl.start,
361
+ dl.end,
362
+ dl.end - dl.start,
363
+ )
364
+ }
365
+ } else {
366
+ this.drawShadeForFeature(
367
+ stateModel,
368
+ ctx,
369
+ feature.start,
370
+ feature.end,
371
+ feature.length,
372
+ rowNum,
373
+ xOffset,
374
+ reversed,
375
+ )
376
+ }
377
+ }
378
+
379
+ drawShadeForFeature(
380
+ stateModel: LinearApolloDisplay,
381
+ ctx: CanvasRenderingContext2D,
382
+ start: number,
383
+ end: number,
384
+ length: number,
385
+ rowNum?: number,
386
+ xOffset?: number,
387
+ reversed?: boolean,
388
+ ) {
389
+ const { apolloHover, apolloRowHeight, displayedRegions, lgv, theme } =
390
+ stateModel
391
+ const { bpPerPx, bpToPx, offsetPx } = lgv
392
+
393
+ if (!apolloHover) {
394
+ return
395
+ }
396
+ const { feature, topLevelFeature } = apolloHover
397
+
398
+ if (!feature || !topLevelFeature) {
399
+ return
400
+ }
401
+
402
+ let featureEntry: AnnotationFeatureI | undefined
403
+ let childFeature: AnnotationFeatureI | undefined
404
+ let featureRow: number | undefined
405
+ let i = 0
406
+ for (const [, f] of topLevelFeature.children ?? new Map()) {
407
+ if (f._id === feature?._id) {
408
+ featureEntry = f
409
+ featureRow = i
410
+ }
411
+ for (const [, cf] of f.children ?? new Map()) {
412
+ if (cf._id === feature._id) {
413
+ childFeature = cf
414
+ featureEntry = f
415
+ featureRow = i
416
+ }
417
+ }
418
+ i++
419
+ }
420
+
421
+ const cdsCount = this.cdsCount(featureEntry)
422
+
423
+ if (cdsCount > 1 && rowNum && xOffset) {
424
+ if (featureEntry === undefined || featureRow === undefined) {
425
+ return
426
+ }
427
+ const widthPx = childFeature
428
+ ? childFeature.length / bpPerPx
429
+ : featureEntry.length / bpPerPx
430
+ const offsetPx = childFeature
431
+ ? (childFeature.start - feature.min) / bpPerPx
432
+ : (featureEntry.start - feature.min) / bpPerPx
433
+ const startPx = reversed ? xOffset - widthPx : xOffset + offsetPx
434
+ const top = (rowNum + featureRow) * apolloRowHeight
435
+ ctx.fillStyle = theme?.palette.action.selected ?? 'rgba(0,0,0,04)'
436
+ ctx.fillRect(startPx, top, widthPx, apolloRowHeight * cdsCount)
437
+ } else {
438
+ const { mousePosition } = apolloHover
439
+ if (!mousePosition) {
440
+ return
441
+ }
442
+ const rowHeight = apolloRowHeight
443
+ const { regionNumber, y } = mousePosition
444
+ const rowNumber = Math.floor(y / rowHeight)
445
+
446
+ const displayedRegion = displayedRegions[regionNumber]
447
+ const { refName, reversed } = displayedRegion
448
+ const startPx =
449
+ (bpToPx({
450
+ refName,
451
+ coord: reversed ? end : start,
452
+ regionNumber,
453
+ })?.offsetPx ?? 0) - offsetPx
454
+ const top = rowNumber * rowHeight
455
+ const widthPx = length / bpPerPx
456
+ ctx.fillStyle = theme?.palette.action.focus ?? 'rgba(0,0,0,0.04)'
457
+ ctx.fillRect(startPx, top, widthPx, rowHeight)
458
+ }
459
+ }
460
+
461
+ drawDragPreview(
462
+ stateModel: LinearApolloDisplay,
463
+ overlayCtx: CanvasRenderingContext2D,
464
+ ) {
465
+ const { apolloDragging, apolloRowHeight, displayedRegions, lgv, theme } =
466
+ stateModel
467
+ const { bpPerPx, offsetPx } = lgv
468
+ if (!apolloDragging) {
469
+ return
470
+ }
471
+ const {
472
+ discontinuousLocation,
473
+ feature,
474
+ glyph,
475
+ mousePosition: startingMousePosition,
476
+ } = apolloDragging.start
477
+ if (!feature) {
478
+ throw new Error('no feature for drag preview??')
479
+ }
480
+ if (glyph !== this) {
481
+ throw new Error('drawDragPreview() called on wrong glyph?')
482
+ }
483
+ const { mousePosition: currentMousePosition } = apolloDragging.current
484
+ const edge = this.isMouseOnFeatureEdge(
485
+ startingMousePosition,
486
+ feature,
487
+ stateModel,
488
+ )
489
+ if (!edge) {
490
+ return
491
+ }
492
+
493
+ const row = Math.floor(startingMousePosition.y / apolloRowHeight)
494
+ const region = displayedRegions[startingMousePosition.regionNumber]
495
+ const rowCount = 1
496
+
497
+ let featureEdgeBp
498
+ if (discontinuousLocation) {
499
+ featureEdgeBp = region.reversed
500
+ ? region.end - discontinuousLocation[edge]
501
+ : discontinuousLocation[edge] - region.start
502
+ } else {
503
+ featureEdgeBp = region.reversed
504
+ ? region.end - feature[edge]
505
+ : feature[edge] - region.start
506
+ }
507
+ const featureEdgePx = featureEdgeBp / bpPerPx - offsetPx
508
+
509
+ const rectX = Math.min(currentMousePosition.x, featureEdgePx)
510
+ const rectY = row * apolloRowHeight
511
+ const rectWidth = Math.abs(currentMousePosition.x - featureEdgePx)
512
+ const rectHeight = apolloRowHeight * rowCount
513
+
514
+ overlayCtx.strokeStyle = theme?.palette.info.main ?? 'rgb(255,0,0)'
515
+ overlayCtx.setLineDash([6])
516
+ overlayCtx.strokeRect(rectX, rectY, rectWidth, rectHeight)
517
+ overlayCtx.fillStyle = alpha(
518
+ theme?.palette.info.main ?? 'rgb(255,0,0)',
519
+ 0.2,
520
+ )
521
+ overlayCtx.fillRect(rectX, rectY, rectWidth, rectHeight)
522
+ }
523
+
524
+ /**
525
+ * Check If the mouse position is on the edge of the selected feature
526
+ */
527
+ isMouseOnFeatureEdge(
528
+ mousePosition: MousePosition,
529
+ feature: AnnotationFeatureI,
530
+ stateModel: LinearApolloDisplay,
531
+ topLevelFeature?: AnnotationFeatureI,
532
+ ) {
533
+ if (!mousePosition) {
534
+ return
535
+ }
536
+
537
+ const { bp, refName, regionNumber, x } = mousePosition
538
+ const { lgv } = stateModel
539
+ const { bpToPx, offsetPx } = lgv
540
+ let startPxInfo
541
+ let endPxInfo
542
+ if (
543
+ feature.discontinuousLocations &&
544
+ feature.discontinuousLocations.length > 0
545
+ ) {
546
+ let discontinuousLocation
547
+ for (const dl of feature.discontinuousLocations) {
548
+ if (bp >= dl.start && bp <= dl.end) {
549
+ discontinuousLocation = dl
550
+ break
551
+ }
552
+ }
553
+ if (!discontinuousLocation) {
554
+ return
555
+ }
556
+ startPxInfo = bpToPx({
557
+ refName,
558
+ coord: discontinuousLocation.start,
559
+ regionNumber,
560
+ })
561
+ endPxInfo = bpToPx({
562
+ refName,
563
+ coord: discontinuousLocation.end,
564
+ regionNumber,
565
+ })
566
+ } else {
567
+ startPxInfo = bpToPx({
568
+ refName,
569
+ coord: feature.start,
570
+ regionNumber,
571
+ })
572
+ endPxInfo = bpToPx({ refName, coord: feature.end, regionNumber })
573
+ }
574
+
575
+ if (startPxInfo !== undefined && endPxInfo !== undefined) {
576
+ const startPx = startPxInfo.offsetPx - offsetPx
577
+ const endPx = endPxInfo.offsetPx - offsetPx
578
+ if (Math.abs(endPx - startPx) < 8) {
579
+ return
580
+ }
581
+ const parentFeature = this.getParentFeature(feature, topLevelFeature)
582
+ // Limit dragging till parent feature end
583
+ if (parentFeature) {
584
+ if (parentFeature.type === 'gene') {
585
+ return
586
+ }
587
+ if (feature.start <= parentFeature.start && Math.abs(startPx - x) < 4) {
588
+ return
589
+ }
590
+ if (feature.end >= parentFeature.end && Math.abs(endPx - x) < 4) {
591
+ return
592
+ }
593
+ }
594
+ if (Math.abs(startPx - x) < 4) {
595
+ return 'start'
596
+ }
597
+ if (Math.abs(endPx - x) < 4) {
598
+ return 'end'
599
+ }
600
+ }
601
+ return
602
+ }
603
+
604
+ onMouseMove(stateModel: LinearApolloDisplay, event: CanvasMouseEvent) {
605
+ const { feature, mousePosition, topLevelFeature } =
606
+ stateModel.getFeatureAndGlyphUnderMouse(event)
607
+ if (stateModel.apolloDragging) {
608
+ stateModel.setCursor('col-resize')
609
+ return
610
+ }
611
+ if (feature && mousePosition) {
612
+ const edge = this.isMouseOnFeatureEdge(
613
+ mousePosition,
614
+ feature,
615
+ stateModel,
616
+ topLevelFeature,
617
+ )
618
+ if (edge) {
619
+ stateModel.setCursor('col-resize')
620
+ } else {
621
+ stateModel.setCursor()
622
+ }
623
+ }
624
+ }
625
+
626
+ onMouseDown(stateModel: LinearApolloDisplay, event: CanvasMouseEvent) {
627
+ // swallow the mouseDown if we are on the edge of the feature
628
+ const { feature, mousePosition } =
629
+ stateModel.getFeatureAndGlyphUnderMouse(event)
630
+ if (feature && mousePosition) {
631
+ const edge = this.isMouseOnFeatureEdge(mousePosition, feature, stateModel)
632
+ if (edge) {
633
+ event.stopPropagation()
634
+ }
635
+ }
636
+ }
637
+
638
+ onMouseUp(stateModel: LinearApolloDisplay, event: CanvasMouseEvent) {
639
+ if (stateModel.apolloDragging ?? event.button !== 0) {
640
+ return
641
+ }
642
+ const { feature } = stateModel.getFeatureAndGlyphUnderMouse(event)
643
+ if (feature) {
644
+ stateModel.setSelectedFeature(feature)
645
+ }
646
+ }
647
+
648
+ startDrag(stateModel: LinearApolloDisplay): boolean {
649
+ // only accept the drag if we are on the edge of the feature
650
+ const { feature, mousePosition, topLevelFeature } =
651
+ stateModel.apolloDragging?.start ?? {}
652
+ const { mousePosition: currentMousePosition } =
653
+ stateModel.apolloDragging?.current ?? {}
654
+ if (feature && mousePosition && currentMousePosition) {
655
+ const edge = this.isMouseOnFeatureEdge(
656
+ mousePosition,
657
+ feature,
658
+ stateModel,
659
+ topLevelFeature,
660
+ )
661
+ if (edge) {
662
+ return true
663
+ }
664
+ }
665
+ return false
666
+ }
667
+
668
+ adjacentExonsOfCdsDL(
669
+ cdsDL: CDSDiscontinuousLocation,
670
+ exonCDSRelations: ExonCDSRelation[],
671
+ ) {
672
+ let prevExon, nextExon, matchingExon, idx
673
+ if (exonCDSRelations) {
674
+ for (const [i, exonCDSRelation] of exonCDSRelations.entries()) {
675
+ const dl = exonCDSRelation.cdsDL
676
+ if (
677
+ cdsDL.start === dl?.start &&
678
+ cdsDL.end === dl.end &&
679
+ cdsDL.phase === dl.phase
680
+ ) {
681
+ idx = i
682
+ break
683
+ }
684
+ }
685
+ if (idx !== undefined) {
686
+ const { exon } = exonCDSRelations[idx]
687
+ matchingExon = exon
688
+ }
689
+ if (idx !== undefined && idx > 0) {
690
+ prevExon = exonCDSRelations[idx - 1].exon
691
+ }
692
+ if (idx !== undefined && idx < exonCDSRelations.length - 1) {
693
+ nextExon = exonCDSRelations[idx + 1].exon
694
+ }
695
+ }
696
+ return { prevExon, matchingExon, nextExon }
697
+ }
698
+
699
+ exonCDSRelation(
700
+ cds?: AnnotationFeatureI,
701
+ topLevelFeature?: AnnotationFeatureI,
702
+ ): ExonCDSRelation[] {
703
+ const exonCDSRelations: ExonCDSRelation[] = []
704
+ if (!cds) {
705
+ return exonCDSRelations
706
+ }
707
+ const parentFeature = this.getParentFeature(cds, topLevelFeature)
708
+ if (!parentFeature?.children) {
709
+ return exonCDSRelations
710
+ }
711
+ for (const [, f] of parentFeature.children) {
712
+ if (f.type === 'exon') {
713
+ const cdsDLForExon = this.cdsDLForExon(f, cds)
714
+ exonCDSRelations.push({
715
+ exon: f,
716
+ cdsDL: cdsDLForExon
717
+ ? {
718
+ start: cdsDLForExon.start,
719
+ end: cdsDLForExon.end,
720
+ phase: cdsDLForExon.phase,
721
+ }
722
+ : undefined,
723
+ })
724
+ }
725
+ }
726
+ return exonCDSRelations
727
+ }
728
+
729
+ cdsDLForExon(exon: AnnotationFeatureI, cds: AnnotationFeatureI) {
730
+ let discontinuousLocation
731
+ if (cds.discontinuousLocations && cds.discontinuousLocations.length > 0) {
732
+ for (const dl of cds.discontinuousLocations) {
733
+ if (dl.start >= exon.start && dl.end <= exon.end) {
734
+ discontinuousLocation = dl
735
+ break
736
+ }
737
+ }
738
+ }
739
+ return discontinuousLocation
740
+ }
741
+
742
+ cdsDlsForExon(
743
+ exon: AnnotationFeatureI,
744
+ topLevelFeature?: AnnotationFeatureI,
745
+ ): CDSDiscontinuousLocation[] {
746
+ const dls: CDSDiscontinuousLocation[] = []
747
+ const parentFeature = this.getParentFeature(exon, topLevelFeature)
748
+ if (!parentFeature?.children || !topLevelFeature) {
749
+ return dls
750
+ }
751
+ const cdsFeatures: AnnotationFeatureI[] = []
752
+ for (const [, f] of parentFeature.children) {
753
+ if (f.type === 'CDS') {
754
+ cdsFeatures.push(f)
755
+ }
756
+ }
757
+
758
+ for (const cds of cdsFeatures) {
759
+ for (const [, f] of parentFeature.children) {
760
+ if (f.type === 'exon' && f._id === exon._id) {
761
+ const cdsDLForExon = this.cdsDLForExon(f, cds)
762
+ if (cdsDLForExon) {
763
+ dls.push(cdsDLForExon)
764
+ }
765
+ }
766
+ }
767
+ }
768
+ return dls
769
+ }
770
+
771
+ adjacentExonsOfExon(
772
+ exon: AnnotationFeatureI,
773
+ topLevelFeature?: AnnotationFeatureI,
774
+ ) {
775
+ const parentFeature: AnnotationFeatureI = this.getParentFeature(
776
+ exon,
777
+ topLevelFeature,
778
+ )
779
+ if (!(parentFeature && parentFeature.children)) {
780
+ return
781
+ }
782
+
783
+ let i = 0
784
+ for (const [, f] of parentFeature.children) {
785
+ if (f._id === exon._id) {
786
+ break
787
+ }
788
+ i++
789
+ }
790
+
791
+ let prevExon, nextExon
792
+ const keys = [...parentFeature.children.keys()]
793
+ if (i > 0) {
794
+ const f = parentFeature.children.get(keys[i - 1])
795
+ if (f && f.type === 'exon') {
796
+ prevExon = f
797
+ }
798
+ }
799
+ if (i < keys.length - 1) {
800
+ const f = parentFeature.children.get(keys[i + 1])
801
+ if (f && f.type === 'exon') {
802
+ nextExon = f
803
+ }
804
+ }
805
+ return { prevExon, nextExon }
806
+ }
807
+
808
+ continueDrag(
809
+ stateModel: LinearApolloDisplay,
810
+ currentMousePosition: MousePosition,
811
+ ): void {
812
+ const {
813
+ discontinuousLocation,
814
+ feature,
815
+ glyph,
816
+ mousePosition,
817
+ topLevelFeature,
818
+ } = stateModel.apolloDragging?.start ?? {}
819
+ if (!(currentMousePosition && mousePosition)) {
820
+ return
821
+ }
822
+ const { bp } = currentMousePosition
823
+ if (!feature || !currentMousePosition) {
824
+ return
825
+ }
826
+ const edge = this.isMouseOnFeatureEdge(
827
+ mousePosition,
828
+ feature,
829
+ stateModel,
830
+ topLevelFeature,
831
+ )
832
+
833
+ if (
834
+ feature.type === 'CDS' &&
835
+ feature.discontinuousLocations &&
836
+ feature.discontinuousLocations.length > 0 &&
837
+ discontinuousLocation
838
+ ) {
839
+ const exonCDSRelations = this.exonCDSRelation(feature, topLevelFeature)
840
+ const { matchingExon, nextExon, prevExon } = this.adjacentExonsOfCdsDL(
841
+ discontinuousLocation,
842
+ exonCDSRelations,
843
+ )
844
+
845
+ if (nextExon && bp >= nextExon.start - 1) {
846
+ return
847
+ }
848
+ if (prevExon && bp <= prevExon.end + 1) {
849
+ return
850
+ }
851
+ if (!prevExon && nextExon && matchingExon && bp < matchingExon.start) {
852
+ return
853
+ }
854
+ if (prevExon && !nextExon && matchingExon && bp > matchingExon.end) {
855
+ return
856
+ }
857
+
858
+ if (
859
+ edge &&
860
+ ((edge === 'start' && bp >= discontinuousLocation.end - 1) ||
861
+ (edge === 'end' && bp <= discontinuousLocation.start + 1))
862
+ ) {
863
+ return
864
+ }
865
+ }
866
+
867
+ if (feature.type !== 'CDS') {
868
+ const adjacentExons = this.adjacentExonsOfExon(feature, topLevelFeature)
869
+ if (adjacentExons?.nextExon && bp >= adjacentExons?.nextExon.start - 1) {
870
+ return
871
+ }
872
+ if (adjacentExons?.prevExon && bp <= adjacentExons?.prevExon.end + 1) {
873
+ return
874
+ }
875
+ const dls: CDSDiscontinuousLocation[] = this.cdsDlsForExon(
876
+ feature,
877
+ topLevelFeature,
878
+ )
879
+
880
+ if (dls && dls.length > 0) {
881
+ let stopDrag
882
+ for (const dl of dls) {
883
+ if (
884
+ edge &&
885
+ ((edge === 'start' && bp >= dl.start - 1) ||
886
+ (edge === 'end' && bp <= dl.end + 1))
887
+ ) {
888
+ stopDrag = true
889
+ break
890
+ }
891
+ }
892
+ if (stopDrag) {
893
+ return
894
+ }
895
+ } else {
896
+ if (
897
+ edge &&
898
+ ((edge === 'start' && bp >= feature.end - 1) ||
899
+ (edge === 'end' && bp <= feature.start + 1))
900
+ ) {
901
+ return
902
+ }
903
+ }
904
+ }
905
+
906
+ stateModel.setDragging({
907
+ start: {
908
+ feature,
909
+ topLevelFeature,
910
+ glyph,
911
+ discontinuousLocation,
912
+ mousePosition,
913
+ },
914
+ current: {
915
+ feature,
916
+ topLevelFeature,
917
+ glyph,
918
+ mousePosition: currentMousePosition,
919
+ },
920
+ })
921
+ }
922
+
923
+ getFeatureFromLayout(
924
+ feature: AnnotationFeatureI,
925
+ bp: number,
926
+ row: number,
927
+ ): AnnotationFeatureI | undefined {
928
+ const featuresForRow: AnnotationFeature[] =
929
+ this.featuresForRow(feature)[row]
930
+ let featureFromLayout: AnnotationFeatureI | undefined
931
+
932
+ for (const f of featuresForRow) {
933
+ if (
934
+ f.start !== undefined &&
935
+ f.end !== undefined &&
936
+ f.phase !== undefined
937
+ ) {
938
+ if (bp >= f.start && bp <= f.end && f.parent) {
939
+ featureFromLayout = f.annotationFeature
940
+ }
941
+ } else {
942
+ if (
943
+ bp >= f.annotationFeature.start &&
944
+ bp <= f.annotationFeature.end &&
945
+ f.parent
946
+ ) {
947
+ featureFromLayout = f.annotationFeature
948
+ }
949
+ }
950
+ }
951
+
952
+ if (!featureFromLayout) {
953
+ featureFromLayout = featuresForRow.at(-1)?.annotationFeature
954
+ }
955
+
956
+ return featureFromLayout
957
+ }
958
+
959
+ getRowForFeature(
960
+ feature: AnnotationFeatureI,
961
+ childFeature: AnnotationFeatureI,
962
+ ) {
963
+ const rows = this.featuresForRow(feature)
964
+ for (const [idx, row] of rows.entries()) {
965
+ if (
966
+ row.some(
967
+ (feature) => feature.annotationFeature._id === childFeature._id,
968
+ )
969
+ ) {
970
+ return idx
971
+ }
972
+ }
973
+ return
974
+ }
975
+
976
+ async executeDrag(stateModel: LinearApolloDisplay) {
977
+ const {
978
+ apolloDragging,
979
+ changeManager,
980
+ displayedRegions,
981
+ getAssemblyId,
982
+ setCursor,
983
+ } = stateModel
984
+ if (!apolloDragging) {
985
+ return
986
+ }
987
+ const {
988
+ discontinuousLocation,
989
+ feature,
990
+ glyph,
991
+ mousePosition: startingMousePosition,
992
+ topLevelFeature,
993
+ } = apolloDragging.start
994
+ if (!feature) {
995
+ throw new Error('no feature for drag preview??')
996
+ }
997
+ if (glyph !== this) {
998
+ throw new Error('drawDragPreview() called on wrong glyph?')
999
+ }
1000
+ const edge = this.isMouseOnFeatureEdge(
1001
+ startingMousePosition,
1002
+ feature,
1003
+ stateModel,
1004
+ )
1005
+ if (!edge) {
1006
+ return
1007
+ }
1008
+
1009
+ const { mousePosition: currentMousePosition } = apolloDragging.current
1010
+ const region = displayedRegions[startingMousePosition.regionNumber]
1011
+ const newBp = currentMousePosition.bp
1012
+ const assembly = getAssemblyId(region.assemblyName)
1013
+ const changes: (
1014
+ | LocationStartChange
1015
+ | LocationEndChange
1016
+ | DiscontinuousLocationEndChange
1017
+ | DiscontinuousLocationStartChange
1018
+ )[] = []
1019
+
1020
+ if (edge === 'start') {
1021
+ if (
1022
+ discontinuousLocation?.idx !== undefined &&
1023
+ feature.discontinuousLocations &&
1024
+ feature.discontinuousLocations.length > 0
1025
+ ) {
1026
+ const oldStart =
1027
+ feature.discontinuousLocations[discontinuousLocation.idx].start
1028
+ this.addDiscontinuousLocStartChange(
1029
+ changes,
1030
+ feature,
1031
+ newBp,
1032
+ oldStart,
1033
+ assembly,
1034
+ discontinuousLocation.idx,
1035
+ )
1036
+
1037
+ const exonCDSRelations = this.exonCDSRelation(feature, topLevelFeature)
1038
+ const exonForCds = this.adjacentExonsOfCdsDL(
1039
+ discontinuousLocation,
1040
+ exonCDSRelations,
1041
+ )
1042
+ if (
1043
+ exonForCds &&
1044
+ exonForCds.matchingExon &&
1045
+ newBp < exonForCds.matchingExon.start
1046
+ ) {
1047
+ this.addStartLocationChange(
1048
+ changes,
1049
+ exonForCds.matchingExon,
1050
+ newBp,
1051
+ assembly,
1052
+ )
1053
+ }
1054
+ } else {
1055
+ this.addStartLocationChange(changes, feature, newBp, assembly)
1056
+ }
1057
+ } else {
1058
+ if (
1059
+ discontinuousLocation?.idx !== undefined &&
1060
+ feature.discontinuousLocations &&
1061
+ feature.discontinuousLocations.length > 0
1062
+ ) {
1063
+ const oldEnd =
1064
+ feature.discontinuousLocations[discontinuousLocation.idx].end
1065
+ this.addDiscontinuousLocEndChange(
1066
+ changes,
1067
+ feature,
1068
+ newBp,
1069
+ oldEnd,
1070
+ assembly,
1071
+ discontinuousLocation.idx,
1072
+ )
1073
+
1074
+ const exonCDSRelations = this.exonCDSRelation(feature, topLevelFeature)
1075
+ const exonForCds = this.adjacentExonsOfCdsDL(
1076
+ discontinuousLocation,
1077
+ exonCDSRelations,
1078
+ )
1079
+ if (
1080
+ exonForCds &&
1081
+ exonForCds.matchingExon &&
1082
+ newBp > exonForCds.matchingExon.end
1083
+ ) {
1084
+ this.addEndLocationChange(
1085
+ changes,
1086
+ exonForCds.matchingExon,
1087
+ newBp,
1088
+ assembly,
1089
+ )
1090
+ }
1091
+ } else {
1092
+ this.addEndLocationChange(changes, feature, newBp, assembly)
1093
+ }
1094
+ }
1095
+
1096
+ if (!changeManager) {
1097
+ throw new Error('no change manager')
1098
+ }
1099
+ for (const change of changes) {
1100
+ await changeManager.submit(change)
1101
+ }
1102
+
1103
+ setCursor()
1104
+ }
1105
+
1106
+ addDiscontinuousLocStartChange(
1107
+ changes: LocationChange[],
1108
+ feature: AnnotationFeatureI, // cds
1109
+ newBp: number,
1110
+ oldStart: number,
1111
+ assembly: string,
1112
+ index: number,
1113
+ ) {
1114
+ const featureId = feature._id
1115
+ changes.push(
1116
+ new DiscontinuousLocationStartChange({
1117
+ typeName: 'DiscontinuousLocationStartChange',
1118
+ changedIds: [feature._id],
1119
+ featureId,
1120
+ newStart: newBp,
1121
+ oldStart,
1122
+ index,
1123
+ assembly,
1124
+ }),
1125
+ )
1126
+ }
1127
+
1128
+ addDiscontinuousLocEndChange(
1129
+ changes: LocationChange[],
1130
+ feature: AnnotationFeatureI, // cds
1131
+ newBp: number,
1132
+ oldEnd: number,
1133
+ assembly: string,
1134
+ index: number,
1135
+ ) {
1136
+ const featureId = feature._id
1137
+ changes.push(
1138
+ new DiscontinuousLocationEndChange({
1139
+ typeName: 'DiscontinuousLocationEndChange',
1140
+ changedIds: [feature._id],
1141
+ featureId,
1142
+ newEnd: newBp,
1143
+ oldEnd,
1144
+ index,
1145
+ assembly,
1146
+ }),
1147
+ )
1148
+ }
1149
+
1150
+ addEndLocationChange(
1151
+ changes: LocationChange[],
1152
+ feature: AnnotationFeatureI,
1153
+ newBp: number,
1154
+ assembly: string,
1155
+ ) {
1156
+ const featureId = feature._id
1157
+ const oldEnd = feature.end
1158
+ const newEnd = newBp
1159
+ changes.push(
1160
+ new LocationEndChange({
1161
+ typeName: 'LocationEndChange',
1162
+ changedIds: [featureId],
1163
+ featureId,
1164
+ oldEnd,
1165
+ newEnd,
1166
+ assembly,
1167
+ }),
1168
+ )
1169
+ }
1170
+
1171
+ addStartLocationChange(
1172
+ changes: LocationChange[],
1173
+ feature: AnnotationFeatureI,
1174
+ newBp: number,
1175
+ assembly: string,
1176
+ ) {
1177
+ const featureId = feature._id
1178
+ const oldStart = feature.start
1179
+ const newStart = newBp
1180
+ changes.push(
1181
+ new LocationStartChange({
1182
+ typeName: 'LocationStartChange',
1183
+ changedIds: [featureId],
1184
+ featureId,
1185
+ oldStart,
1186
+ newStart,
1187
+ assembly,
1188
+ }),
1189
+ )
1190
+ }
1191
+ }