@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,692 @@
1
+ import { getConf } from '@jbrowse/core/configuration'
2
+ import { AbstractSessionModel, Region, getSession } from '@jbrowse/core/util'
3
+ import { Menu, MenuItem } from '@mui/material'
4
+ import { AnnotationFeatureI } from 'apollo-mst'
5
+ import { LocationEndChange, LocationStartChange } from 'apollo-shared'
6
+ import { autorun, toJS } from 'mobx'
7
+ import { observer } from 'mobx-react'
8
+ import { getRoot, getSnapshot } from 'mobx-state-tree'
9
+ import React, { useEffect, useMemo, useRef, useState } from 'react'
10
+
11
+ import { ApolloInternetAccountModel } from '../../ApolloInternetAccount/model'
12
+ import { AddChildFeature } from '../../components/AddChildFeature'
13
+ import { CopyFeature } from '../../components/CopyFeature'
14
+ import { DeleteFeature } from '../../components/DeleteFeature'
15
+ import { Collaborator } from '../../session'
16
+ import { SixFrameFeatureDisplay } from '../../SixFrameFeatureDisplay/stateModel'
17
+ import { ApolloRootModel } from '../../types'
18
+
19
+ interface ApolloRenderingProps {
20
+ assemblyName: string
21
+ regions: Region[]
22
+ bpPerPx: number
23
+ displayModel: SixFrameFeatureDisplay
24
+ blockKey: string
25
+ }
26
+
27
+ function draw(
28
+ ctx: CanvasRenderingContext2D,
29
+ xOffset: number,
30
+ yOffset: number,
31
+ width: number,
32
+ bpPerPx: number,
33
+ rowHeight: number,
34
+ ) {
35
+ const widthPx = width / bpPerPx
36
+ ctx.fillStyle = 'black'
37
+ ctx.fillRect(xOffset, yOffset, widthPx, rowHeight)
38
+ if (widthPx > 2) {
39
+ ctx.clearRect(xOffset + 1, yOffset + 1, widthPx - 2, rowHeight - 2)
40
+ ctx.fillStyle = 'rgba(255,255,255,0.75)'
41
+ ctx.fillRect(xOffset + 1, yOffset + 1, widthPx - 2, rowHeight - 2)
42
+ // ctx.fillStyle = 'black'
43
+ // ctx.fillText(
44
+ // 'CDS',
45
+ // xOffset + startPx + 1,
46
+ // yOffset + 11,
47
+ // widthPx - 2,
48
+ // )
49
+ }
50
+ }
51
+
52
+ type Coord = [number, number]
53
+
54
+ /**
55
+ * Use the golden ratio to generate distinct colors for a given integer
56
+ * See https://martin.ankerl.com/2009/12/09/how-to-create-random-colors-programmatically/
57
+ * @param number -
58
+ * @returns HSL string
59
+ */
60
+ function selectColor(number: number) {
61
+ const goldenAngle = 180 * (3 - Math.sqrt(5))
62
+ const hue = number * goldenAngle + 60
63
+ return `hsl(${hue},100%,50%)`
64
+ }
65
+
66
+ function ApolloRendering(props: ApolloRenderingProps) {
67
+ const [contextCoord, setContextCoord] = useState<Coord>()
68
+ const [contextMenuFeature, setContextMenuFeature] =
69
+ useState<AnnotationFeatureI>()
70
+
71
+ const canvasRef = useRef<HTMLCanvasElement>(null)
72
+ const overlayCanvasRef = useRef<HTMLCanvasElement>(null)
73
+ const codonCanvasRef = useRef<HTMLCanvasElement>(null)
74
+ const [isAdmin, setIsAdmin] = useState<boolean>(false)
75
+ const [isReadOnly, setIsReadOnly] = useState<boolean>(true)
76
+ // const [overEdge, setOverEdge] = useState<'start' | 'end'>()
77
+ const [dragging, setDragging] = useState<{
78
+ edge: 'start' | 'end'
79
+ feature: AnnotationFeatureI
80
+ row: number
81
+ bp: number
82
+ px: number
83
+ }>()
84
+ const [movedDuringLastMouseDown, setMovedDuringLastMouseDown] =
85
+ useState(false)
86
+ const [collaborators, setCollaborators] = useState<Collaborator[]>([])
87
+
88
+ const { bpPerPx, displayModel, regions } = props
89
+ const { session } = displayModel
90
+ const { collaborators: collabs } = session
91
+
92
+ // bridging mobx observability and React useEffect observability
93
+ // eslint-disable-next-line react-hooks/exhaustive-deps
94
+ useEffect(() => autorun(() => setCollaborators(toJS(collabs))), [])
95
+
96
+ const [region] = regions
97
+ const totalWidth = (region.end - region.start) / bpPerPx
98
+ const {
99
+ apolloFeatureUnderMouse,
100
+ apolloRowHeight: height,
101
+ apolloRowUnderMouse,
102
+ changeManager,
103
+ codonLayout,
104
+ featureLayout,
105
+ features,
106
+ featuresHeight: totalHeight,
107
+ getAssemblyId,
108
+ selectedFeature,
109
+ setApolloFeatureUnderMouse,
110
+ setApolloRowUnderMouse,
111
+ setSelectedFeature,
112
+ showIntronLines: showLines,
113
+ showStartCodons: showStarts,
114
+ showStopCodons: showStops,
115
+ } = displayModel
116
+ // use this to convince useEffect that the features really did change
117
+ const featureSnap = [...features.values()].map((a) =>
118
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
119
+ // @ts-expect-error
120
+ [...a.values()].map((f) => getSnapshot(f)),
121
+ )
122
+
123
+ const apolloInternetAccount = useMemo(() => {
124
+ const { internetAccounts } = getRoot<ApolloRootModel>(session)
125
+ const { assemblyName } = region
126
+ const { assemblyManager } = getSession(displayModel)
127
+ const assembly = assemblyManager.get(assemblyName)
128
+ if (!assembly) {
129
+ throw new Error(`No assembly found with name ${assemblyName}`)
130
+ }
131
+ const { internetAccountConfigId } = getConf(assembly, [
132
+ 'sequence',
133
+ 'metadata',
134
+ ]) as { internetAccountConfigId: string }
135
+ const matchingAccount = internetAccounts.find(
136
+ (ia) => getConf(ia, 'internetAccountId') === internetAccountConfigId,
137
+ ) as ApolloInternetAccountModel | undefined
138
+ if (!matchingAccount) {
139
+ throw new Error(
140
+ `No InternetAccount found with config id ${internetAccountConfigId}`,
141
+ )
142
+ }
143
+ return matchingAccount
144
+ }, [displayModel, region, session])
145
+
146
+ const { role } = apolloInternetAccount
147
+
148
+ useEffect(() => {
149
+ if (role?.includes('admin')) {
150
+ setIsAdmin(true)
151
+ }
152
+ if (role?.includes('admin') ?? role?.includes('user')) {
153
+ setIsReadOnly(false)
154
+ }
155
+ }, [role])
156
+
157
+ useEffect(() => {
158
+ // if (!isAlive(region)) {
159
+ // return
160
+ // }
161
+ const canvas = canvasRef.current
162
+ if (!canvas) {
163
+ return
164
+ }
165
+ const ctx = canvas.getContext('2d')
166
+ if (!ctx) {
167
+ return
168
+ }
169
+ const transcript: Record<string, [number, number][]> = {}
170
+ ctx.clearRect(0, 0, totalWidth, totalHeight)
171
+ for (const [row, featureInfos] of featureLayout) {
172
+ for (const [parentID, feature] of featureInfos) {
173
+ const start = region.reversed
174
+ ? region.end - feature.end
175
+ : feature.start - region.start - 1
176
+ const end = feature.end - region.start - 1
177
+ const startPx = start / bpPerPx
178
+ const endPx = end / bpPerPx
179
+ const width = end - start
180
+ draw(ctx, startPx, row * height, width, bpPerPx, height)
181
+ const lineY = row * height + height / 2
182
+ if (!transcript[parentID]) {
183
+ transcript[parentID] = []
184
+ }
185
+ if (
186
+ !transcript[parentID].some(
187
+ (el) => el[0] === startPx && el[1] === lineY,
188
+ )
189
+ ) {
190
+ transcript[parentID].push([startPx, lineY])
191
+ }
192
+ if (
193
+ !transcript[parentID].some((el) => el[0] === endPx && el[1] === lineY)
194
+ ) {
195
+ transcript[parentID].push([endPx, lineY])
196
+ }
197
+ }
198
+ }
199
+ if (showLines) {
200
+ let offset = -Math.floor(Object.keys(transcript).length / 2)
201
+ for (const pid in transcript) {
202
+ ctx.strokeStyle = selectColor(offset)
203
+ const sortedCoords = transcript[pid].sort((a, b) => {
204
+ return a[0] - b[0]
205
+ })
206
+ let [prevCoords] = sortedCoords
207
+ for (const [index, coords] of sortedCoords.entries()) {
208
+ if (index === 0) {
209
+ continue
210
+ }
211
+ if (index % 2 === 0) {
212
+ /** Mid-point for intron line "hat" */
213
+ const midPoint: [number, number] = [
214
+ (coords[0] - prevCoords[0]) / 2 + prevCoords[0],
215
+ Math.max(
216
+ 1, // Avoid render ceiling
217
+ Math.min(prevCoords[1], coords[1]) - height / 2 + offset * 2,
218
+ ),
219
+ ]
220
+ ctx.beginPath()
221
+ ctx.moveTo(prevCoords[0], prevCoords[1] + offset * 2)
222
+ ctx.lineTo(...midPoint)
223
+ ctx.stroke()
224
+ ctx.moveTo(...midPoint)
225
+ ctx.lineTo(coords[0], coords[1] + offset * 2)
226
+ ctx.stroke()
227
+ }
228
+ prevCoords = coords
229
+ }
230
+ offset += 1
231
+ }
232
+ }
233
+ }, [
234
+ showLines,
235
+ region,
236
+ bpPerPx,
237
+ region.start,
238
+ region.end,
239
+ region.reversed,
240
+ totalWidth,
241
+ featureLayout,
242
+ totalHeight,
243
+ features,
244
+ height,
245
+ featureSnap,
246
+ ])
247
+ useEffect(() => {
248
+ // if (!isAlive(region)) {
249
+ // return
250
+ // }
251
+ const canvas = codonCanvasRef.current
252
+ if (!canvas) {
253
+ return
254
+ }
255
+ const ctx = canvas.getContext('2d')
256
+ if (!ctx) {
257
+ return
258
+ }
259
+
260
+ ctx.clearRect(0, 0, totalWidth, totalHeight)
261
+ for (const [row, { starts, stops }] of codonLayout) {
262
+ const scale = bpPerPx
263
+ for (const start of starts) {
264
+ const x = start / scale
265
+ if (region.start / scale <= x && x <= region.end / scale) {
266
+ ctx.fillStyle = 'rgba(255,0,255,1)'
267
+ if (showStarts) {
268
+ ctx.fillRect(
269
+ Math.round(x - 0.5 - region.start / scale),
270
+ row * height,
271
+ 1,
272
+ height,
273
+ )
274
+ } else {
275
+ ctx.clearRect(
276
+ Math.round(x - 0.5 - region.start / scale),
277
+ row * height,
278
+ 1,
279
+ height,
280
+ )
281
+ }
282
+ }
283
+ }
284
+ for (const start of stops) {
285
+ const x = start / scale
286
+ if (region.start / scale <= x && x <= region.end / scale) {
287
+ ctx.fillStyle = 'black'
288
+ if (showStops) {
289
+ ctx.fillRect(
290
+ Math.round(x - 0.5 - region.start / scale),
291
+ row * height,
292
+ 1,
293
+ height,
294
+ )
295
+ } else {
296
+ ctx.clearRect(
297
+ Math.round(x - 0.5 - region.start / scale),
298
+ row * height,
299
+ 1,
300
+ height,
301
+ )
302
+ }
303
+ }
304
+ }
305
+ }
306
+ }, [
307
+ showStarts,
308
+ showStops,
309
+ codonLayout,
310
+ totalWidth,
311
+ totalHeight,
312
+ bpPerPx,
313
+ height,
314
+ region,
315
+ region.start,
316
+ region.end,
317
+ ])
318
+
319
+ useEffect(() => {
320
+ // if (!isAlive(region)) {
321
+ // return
322
+ // }
323
+ const canvas = overlayCanvasRef.current
324
+ if (!canvas) {
325
+ return
326
+ }
327
+ const ctx = canvas.getContext('2d')
328
+ if (!ctx) {
329
+ return
330
+ }
331
+ ctx.clearRect(0, 0, totalWidth, totalHeight)
332
+ // if (dragging) {
333
+ // const { feature, row, edge, px } = dragging
334
+ // const featureEdge = region.reversed
335
+ // ? region.end - feature[edge]
336
+ // : feature[edge] - region.start
337
+ // const featureEdgePx = featureEdge / bpPerPx
338
+ // const startPx = Math.min(px, featureEdgePx)
339
+ // const widthPx = Math.abs(px - featureEdgePx)
340
+ // ctx.strokeStyle = 'red'
341
+ // ctx.setLineDash([6])
342
+ // ctx.strokeRect(startPx, row * height, widthPx, height * feature.rowCount)
343
+ // ctx.fillStyle = 'rgba(255,0,0,.2)'
344
+ // ctx.fillRect(startPx, row * height, widthPx, height * feature.rowCount)
345
+ // }
346
+ // const feature = dragging?.feature || apolloFeatureUnderMouse
347
+ // const row = dragging?.row || apolloRowUnderMouse
348
+ // if (feature && row !== undefined) {
349
+ // const start = region.reversed
350
+ // ? region.end - feature.end
351
+ // : feature.start - region.start - 1
352
+ // const width = feature.length
353
+ // const startPx = start / bpPerPx
354
+ // const widthPx = width / bpPerPx
355
+ // ctx.fillStyle = 'rgba(0,0,0,0.2)'
356
+ // ctx.fillRect(startPx, row * height, widthPx, height * feature.rowCount)
357
+ // }
358
+ for (const collaborator of collaborators) {
359
+ const { locations } = collaborator
360
+ if (locations.length === 0) {
361
+ return
362
+ }
363
+ for (const location of locations) {
364
+ const { end, start } = location
365
+ const locationStart = region.reversed
366
+ ? region.end - start
367
+ : start - region.start
368
+ const locationStartPx = locationStart / bpPerPx
369
+ const locationWidthPx = (end - start) / bpPerPx
370
+ ctx.fillStyle = 'rgba(0,255,0,.2)'
371
+ ctx.fillRect(locationStartPx, 1, locationWidthPx, 100)
372
+ ctx.fillStyle = 'black'
373
+ ctx.fillText(
374
+ collaborator.name,
375
+ locationStartPx + 1,
376
+ 11,
377
+ locationWidthPx - 2,
378
+ )
379
+ }
380
+ }
381
+ }, [
382
+ apolloFeatureUnderMouse,
383
+ apolloRowUnderMouse,
384
+ bpPerPx,
385
+ totalHeight,
386
+ totalWidth,
387
+ region,
388
+ region.start,
389
+ region.end,
390
+ region.reversed,
391
+ dragging,
392
+ height,
393
+ collaborators,
394
+ ])
395
+
396
+ // function onMouseMove(event: React.MouseEvent<HTMLCanvasElement, MouseEvent>) {
397
+ // // if (!isAlive(region)) {
398
+ // // return
399
+ // // }
400
+ // const { clientX, clientY, buttons } = event
401
+ // if (!movedDuringLastMouseDown && buttons === 1) {
402
+ // setMovedDuringLastMouseDown(true)
403
+ // }
404
+ // const { left, top } = canvasRef.current?.getBoundingClientRect() || {
405
+ // left: 0,
406
+ // top: 0,
407
+ // }
408
+ // // get pixel coordinates within the whole canvas
409
+ // let x = clientX - left
410
+ // x = region.reversed ? totalWidth - x : x
411
+ // const y = clientY - top
412
+
413
+ // if (dragging) {
414
+ // const { edge, feature, row } = dragging
415
+ // let px = region.reversed ? totalWidth - x : x
416
+ // let bp = region.start + x * bpPerPx
417
+ // if (edge === 'start' && bp > feature.end - 1) {
418
+ // bp = feature.end - 1
419
+ // px = (region.reversed ? region.end - bp : bp - region.start) / bpPerPx
420
+ // } else if (edge === 'end' && bp < feature.start + 1) {
421
+ // bp = feature.start + 1
422
+ // px = (region.reversed ? region.end - bp : bp - region.start) / bpPerPx
423
+ // }
424
+ // setDragging({
425
+ // edge,
426
+ // feature,
427
+ // row,
428
+ // px,
429
+ // bp,
430
+ // })
431
+ // return
432
+ // }
433
+
434
+ // const row = Math.floor(y / height)
435
+ // if (row === undefined) {
436
+ // setApolloFeatureUnderMouse(undefined)
437
+ // setApolloRowUnderMouse(undefined)
438
+ // return
439
+ // }
440
+ // const layoutRow = featureLayout.get(row)
441
+ // if (!layoutRow) {
442
+ // setApolloFeatureUnderMouse(undefined)
443
+ // setApolloRowUnderMouse(undefined)
444
+ // return
445
+ // }
446
+ // const bp = region.start + bpPerPx * x
447
+ // // eslint-disable-next-line @typescript-eslint/no-unused-vars
448
+ // const [parentID, feat] =
449
+ // layoutRow.find((f) => bp >= f[1].min && bp <= f[1].max) || []
450
+ // let feature: AnnotationFeatureI | undefined = feat
451
+ // if (feature) {
452
+ // const topRow = row
453
+ // const startPx = (feature.start - region.start) / bpPerPx
454
+ // const thisX = x - startPx
455
+ // feature = feature.getFeatureFromLayout(
456
+ // thisX,
457
+ // y - topRow * height,
458
+ // bpPerPx,
459
+ // height,
460
+ // ) as AnnotationFeatureI
461
+ // }
462
+ // if (feature) {
463
+ // // TODO: check reversed
464
+ // // TODO: ensure feature is in interbase
465
+ // const startPx = (feature.start - region.start) / bpPerPx
466
+ // const endPx = (feature.end - region.start) / bpPerPx
467
+ // if (endPx - startPx < 8) {
468
+ // setOverEdge(undefined)
469
+ // } else if (Math.abs(startPx - x) < 4) {
470
+ // setOverEdge('start')
471
+ // } else if (Math.abs(endPx - x) < 4) {
472
+ // setOverEdge('end')
473
+ // } else {
474
+ // setOverEdge(undefined)
475
+ // }
476
+ // }
477
+ // setApolloFeatureUnderMouse(feature)
478
+ // setApolloRowUnderMouse(row)
479
+ // }
480
+ function onMouseLeave() {
481
+ setApolloFeatureUnderMouse()
482
+ setApolloRowUnderMouse()
483
+ }
484
+ // function onMouseDown(event: React.MouseEvent) {
485
+ // if (apolloFeatureUnderMouse && overEdge) {
486
+ // const { clientX } = event
487
+ // const { left } = canvasRef.current?.getBoundingClientRect() || {
488
+ // left: 0,
489
+ // top: 0,
490
+ // }
491
+ // const px = clientX - left
492
+ // event.stopPropagation()
493
+ // setDragging({
494
+ // edge: overEdge,
495
+ // feature: apolloFeatureUnderMouse,
496
+ // row: apolloRowUnderMouse || 0,
497
+ // px,
498
+ // bp: apolloFeatureUnderMouse[overEdge],
499
+ // })
500
+ // }
501
+ // }
502
+ async function onMouseUp() {
503
+ if (!movedDuringLastMouseDown) {
504
+ if (apolloFeatureUnderMouse) {
505
+ setSelectedFeature(apolloFeatureUnderMouse)
506
+ }
507
+ } else if (dragging) {
508
+ const assembly = getAssemblyId(region.assemblyName)
509
+ const { bp, edge, feature } = dragging
510
+ let change: LocationEndChange | LocationStartChange
511
+ if (edge === 'end') {
512
+ const featureId = feature._id
513
+ const oldEnd = feature.end
514
+ const newEnd = Math.round(bp)
515
+ change = new LocationEndChange({
516
+ typeName: 'LocationEndChange',
517
+ changedIds: [featureId],
518
+ featureId,
519
+ oldEnd,
520
+ newEnd,
521
+ assembly,
522
+ })
523
+ } else {
524
+ const featureId = feature._id
525
+ const oldStart = feature.start
526
+ const newStart = Math.round(bp)
527
+ change = new LocationStartChange({
528
+ typeName: 'LocationStartChange',
529
+ changedIds: [featureId],
530
+ featureId,
531
+ oldStart,
532
+ newStart,
533
+ assembly,
534
+ })
535
+ }
536
+ await changeManager?.submit(change)
537
+ }
538
+ // eslint-disable-next-line unicorn/no-useless-undefined
539
+ setDragging(undefined)
540
+ setMovedDuringLastMouseDown(false)
541
+ }
542
+ function onContextMenu(event: React.MouseEvent) {
543
+ event.preventDefault()
544
+ setContextMenuFeature(apolloFeatureUnderMouse)
545
+ setContextCoord([event.pageX, event.pageY])
546
+ }
547
+
548
+ return (
549
+ <div
550
+ style={{ position: 'relative', width: totalWidth, height: totalHeight }}
551
+ >
552
+ <Menu
553
+ open={Boolean(contextMenuFeature)}
554
+ anchorReference="anchorPosition"
555
+ anchorPosition={
556
+ contextCoord
557
+ ? { left: contextCoord[0], top: contextCoord[1] }
558
+ : undefined
559
+ }
560
+ data-testid="base_linear_display_context_menu"
561
+ onClose={() => {
562
+ // eslint-disable-next-line unicorn/no-useless-undefined
563
+ setContextMenuFeature(undefined)
564
+ }}
565
+ >
566
+ <MenuItem
567
+ disabled={isReadOnly}
568
+ key={1}
569
+ value={1}
570
+ onClick={() => {
571
+ if (!contextMenuFeature) {
572
+ return
573
+ }
574
+ const currentAssemblyId = getAssemblyId(region.assemblyName)
575
+ ;(session as unknown as AbstractSessionModel).queueDialog(
576
+ (doneCallback) => [
577
+ AddChildFeature,
578
+ {
579
+ session,
580
+ handleClose: () => {
581
+ doneCallback()
582
+ // eslint-disable-next-line unicorn/no-useless-undefined
583
+ setContextMenuFeature(undefined)
584
+ },
585
+ changeManager,
586
+ sourceFeature: contextMenuFeature,
587
+ sourceAssemblyId: currentAssemblyId,
588
+ internetAccount: apolloInternetAccount,
589
+ },
590
+ ],
591
+ )
592
+ }}
593
+ >
594
+ Add child feature
595
+ </MenuItem>
596
+ <MenuItem
597
+ disabled={isReadOnly}
598
+ key={2}
599
+ value={2}
600
+ onClick={() => {
601
+ if (!contextMenuFeature) {
602
+ return
603
+ }
604
+ const currentAssemblyId = getAssemblyId(region.assemblyName)
605
+ ;(session as unknown as AbstractSessionModel).queueDialog(
606
+ (doneCallback) => [
607
+ CopyFeature,
608
+ {
609
+ session,
610
+ handleClose: () => {
611
+ doneCallback()
612
+ // eslint-disable-next-line unicorn/no-useless-undefined
613
+ setContextMenuFeature(undefined)
614
+ },
615
+ changeManager,
616
+ sourceFeature: contextMenuFeature,
617
+ sourceAssemblyId: currentAssemblyId,
618
+ },
619
+ ],
620
+ )
621
+ }}
622
+ >
623
+ Copy features and annotations
624
+ </MenuItem>
625
+ <MenuItem
626
+ disabled={!isAdmin}
627
+ key={3}
628
+ value={3}
629
+ onClick={() => {
630
+ if (!contextMenuFeature) {
631
+ return
632
+ }
633
+ const currentAssemblyId = getAssemblyId(region.assemblyName)
634
+ ;(session as unknown as AbstractSessionModel).queueDialog(
635
+ (doneCallback) => [
636
+ DeleteFeature,
637
+ {
638
+ session,
639
+ handleClose: () => {
640
+ doneCallback()
641
+ // eslint-disable-next-line unicorn/no-useless-undefined
642
+ setContextMenuFeature(undefined)
643
+ },
644
+ changeManager,
645
+ sourceFeature: contextMenuFeature,
646
+ sourceAssemblyId: currentAssemblyId,
647
+ selectedFeature,
648
+ setSelectedFeature,
649
+ },
650
+ ],
651
+ )
652
+ }}
653
+ >
654
+ Delete feature
655
+ </MenuItem>
656
+ </Menu>
657
+ <canvas
658
+ ref={canvasRef}
659
+ width={totalWidth}
660
+ height={totalHeight}
661
+ style={{ position: 'absolute', left: 0, top: 0 }}
662
+ />
663
+ <canvas
664
+ ref={overlayCanvasRef}
665
+ width={totalWidth}
666
+ height={totalHeight}
667
+ // onMouseMove={onMouseMove}
668
+ onMouseLeave={onMouseLeave}
669
+ // onMouseDown={onMouseDown}
670
+ onMouseUp={onMouseUp}
671
+ onContextMenu={onContextMenu}
672
+ style={{
673
+ position: 'absolute',
674
+ left: 0,
675
+ top: 0,
676
+ // cursor:
677
+ // dragging || (apolloFeatureUnderMouse && overEdge)
678
+ // ? 'col-resize'
679
+ // : 'default',
680
+ }}
681
+ />
682
+ <canvas
683
+ ref={codonCanvasRef}
684
+ width={totalWidth}
685
+ height={height * 6}
686
+ style={{ position: 'absolute', left: 0, top: 0 }}
687
+ />
688
+ </div>
689
+ )
690
+ }
691
+
692
+ export default observer(ApolloRendering)
@@ -0,0 +1,7 @@
1
+ import { ConfigurationSchema } from '@jbrowse/core/configuration'
2
+
3
+ export default ConfigurationSchema(
4
+ 'ApolloSixFrameRenderer',
5
+ {},
6
+ { explicitlyTyped: true },
7
+ )
@@ -0,0 +1,3 @@
1
+ export { default as ReactComponent } from './components/ApolloRendering'
2
+ export { default as configSchema } from './configSchema'
3
+ export { default as ApolloSixFrameRenderer } from './ApolloSixFrameRenderer'