@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
package/src/index.ts ADDED
@@ -0,0 +1,394 @@
1
+ import { ConfigurationSchema } from '@jbrowse/core/configuration'
2
+ import {
3
+ DisplayType,
4
+ InternetAccountType,
5
+ PluggableElementType,
6
+ TrackType,
7
+ ViewType,
8
+ createBaseTrackConfig,
9
+ createBaseTrackModel,
10
+ } from '@jbrowse/core/pluggableElementTypes'
11
+ import Plugin from '@jbrowse/core/Plugin'
12
+ import PluginManager from '@jbrowse/core/PluginManager'
13
+ import {
14
+ AbstractSessionModel,
15
+ Region,
16
+ getSession,
17
+ isAbstractMenuManager,
18
+ } from '@jbrowse/core/util'
19
+ import { LinearGenomeViewStateModel } from '@jbrowse/plugin-linear-genome-view'
20
+ import AddIcon from '@mui/icons-material/Add'
21
+ import { changeRegistry, checkRegistry } from 'apollo-common'
22
+ import {
23
+ CDSCheck,
24
+ CoreValidation,
25
+ ParentChildValidation,
26
+ changes,
27
+ validationRegistry,
28
+ } from 'apollo-shared'
29
+
30
+ import { version } from '../package.json'
31
+ import {
32
+ configSchema as apolloInternetAccountConfigSchema,
33
+ modelFactory as apolloInternetAccountModelFactory,
34
+ } from './ApolloInternetAccount'
35
+ import { installApolloSequenceAdapter } from './ApolloSequenceAdapter'
36
+ import {
37
+ ApolloSixFrameRenderer,
38
+ ReactComponent as ApolloSixFrameRendererReactComponent,
39
+ configSchema as apolloSixFrameRendererConfigSchema,
40
+ } from './ApolloSixFrameRenderer'
41
+ import { installApolloTextSearchAdapter } from './ApolloTextSearchAdapter'
42
+ import { BackendDriver } from './BackendDrivers'
43
+ import {
44
+ DownloadGFF3,
45
+ ManageChecks,
46
+ OpenLocalFile,
47
+ ViewChangeLog,
48
+ } from './components'
49
+ import { AddFeature } from './components/AddFeature'
50
+ import { ViewCheckResults } from './components/ViewCheckResults'
51
+ import ApolloPluginConfigurationSchema from './config'
52
+ import { annotationFromPileup } from './extensions'
53
+ import {
54
+ stateModelFactory as LinearApolloDisplayStateModelFactory,
55
+ configSchemaFactory as linearApolloDisplayConfigSchemaFactory,
56
+ } from './LinearApolloDisplay'
57
+ import {
58
+ DisplayComponent,
59
+ makeSixFrameDisplayComponent,
60
+ } from './makeDisplayComponent'
61
+ import { ApolloSessionModel, extendSession } from './session'
62
+ import {
63
+ stateModelFactory as SixFrameFeatureDisplayStateModelFactory,
64
+ configSchemaFactory as sixFrameFeatureDisplayConfigSchemaFactory,
65
+ } from './SixFrameFeatureDisplay'
66
+
67
+ interface RpcHandle {
68
+ on(event: string, listener: (event: MessageEvent) => void): this
69
+ workers: Worker[]
70
+ }
71
+
72
+ interface ApolloMessageData {
73
+ apollo: true
74
+ messageId: string
75
+ method: string
76
+ region: Region
77
+ sequence: string
78
+ assembly: string
79
+ }
80
+
81
+ function isApolloMessageData(data?: unknown): data is ApolloMessageData {
82
+ return (
83
+ typeof data === 'object' &&
84
+ data !== null &&
85
+ 'apollo' in data &&
86
+ data.apollo === true
87
+ )
88
+ }
89
+
90
+ const inWebWorker = 'WorkerGlobalScope' in globalThis
91
+
92
+ for (const [changeName, change] of Object.entries(changes)) {
93
+ changeRegistry.registerChange(changeName, change)
94
+ }
95
+
96
+ const cdsCheck = new CDSCheck()
97
+ checkRegistry.registerCheck(cdsCheck.name, cdsCheck)
98
+ validationRegistry.registerValidation(new CoreValidation())
99
+ validationRegistry.registerValidation(new ParentChildValidation())
100
+
101
+ export default class ApolloPlugin extends Plugin {
102
+ name = 'ApolloPlugin'
103
+ version = version
104
+ configurationSchema = ApolloPluginConfigurationSchema
105
+
106
+ install(pluginManager: PluginManager) {
107
+ installApolloSequenceAdapter(pluginManager)
108
+ installApolloTextSearchAdapter(pluginManager)
109
+ pluginManager.addTrackType(() => {
110
+ const configSchema = ConfigurationSchema(
111
+ 'ApolloTrack',
112
+ { adapter: '' },
113
+ {
114
+ baseConfiguration: createBaseTrackConfig(pluginManager),
115
+ explicitIdentifier: 'trackId',
116
+ },
117
+ )
118
+ return new TrackType({
119
+ name: 'ApolloTrack',
120
+ configSchema,
121
+ stateModel: createBaseTrackModel(
122
+ pluginManager,
123
+ 'ApolloTrack',
124
+ configSchema,
125
+ ),
126
+ })
127
+ })
128
+
129
+ pluginManager.addInternetAccountType(() => {
130
+ return new InternetAccountType({
131
+ name: 'ApolloInternetAccount',
132
+ configSchema: apolloInternetAccountConfigSchema,
133
+ stateModel: apolloInternetAccountModelFactory(
134
+ apolloInternetAccountConfigSchema,
135
+ ),
136
+ })
137
+ })
138
+
139
+ pluginManager.addDisplayType(() => {
140
+ const configSchema = linearApolloDisplayConfigSchemaFactory(pluginManager)
141
+ return new DisplayType({
142
+ name: 'LinearApolloDisplay',
143
+ configSchema,
144
+ stateModel: LinearApolloDisplayStateModelFactory(
145
+ pluginManager,
146
+ configSchema,
147
+ ),
148
+ trackType: 'ApolloTrack',
149
+ viewType: 'LinearGenomeView',
150
+ ReactComponent: DisplayComponent,
151
+ })
152
+ })
153
+
154
+ pluginManager.addDisplayType(() => {
155
+ const configSchema =
156
+ sixFrameFeatureDisplayConfigSchemaFactory(pluginManager)
157
+ const SixFrameDisplayComponent =
158
+ makeSixFrameDisplayComponent(pluginManager)
159
+ return new DisplayType({
160
+ name: 'SixFrameFeatureDisplay',
161
+ configSchema,
162
+ stateModel: SixFrameFeatureDisplayStateModelFactory(
163
+ pluginManager,
164
+ configSchema,
165
+ ),
166
+ trackType: 'ApolloTrack',
167
+ viewType: 'LinearGenomeView',
168
+ ReactComponent: SixFrameDisplayComponent,
169
+ })
170
+ })
171
+
172
+ pluginManager.addRendererType(
173
+ () =>
174
+ new ApolloSixFrameRenderer({
175
+ name: 'ApolloSixFrameRenderer',
176
+ ReactComponent: ApolloSixFrameRendererReactComponent,
177
+ configSchema: apolloSixFrameRendererConfigSchema,
178
+ pluginManager,
179
+ }),
180
+ )
181
+
182
+ pluginManager.addToExtensionPoint(
183
+ 'Core-extendSession',
184
+ extendSession.bind(this, pluginManager),
185
+ )
186
+
187
+ pluginManager.addToExtensionPoint(
188
+ 'Core-extendPluggableElement',
189
+ (pluggableElement: PluggableElementType) => {
190
+ if (pluggableElement.name === 'LinearGenomeView') {
191
+ const { stateModel } = pluggableElement as ViewType
192
+ const lgv = stateModel as LinearGenomeViewStateModel
193
+ const newStateModel = lgv.views((self) => {
194
+ const superRubberBandMenuItems = self.rubberBandMenuItems
195
+ return {
196
+ rubberBandMenuItems() {
197
+ return [
198
+ ...superRubberBandMenuItems(),
199
+ {
200
+ label: 'Add new feature',
201
+ icon: AddIcon,
202
+ onClick: () => {
203
+ const session = getSession(
204
+ self,
205
+ ) as unknown as ApolloSessionModel
206
+ const { leftOffset, rightOffset } = self
207
+ const selectedRegions = self.getSelectedRegions(
208
+ leftOffset,
209
+ rightOffset,
210
+ )
211
+ ;(session as unknown as AbstractSessionModel).queueDialog(
212
+ (doneCallback) => [
213
+ AddFeature,
214
+ {
215
+ session,
216
+ handleClose: () => {
217
+ doneCallback()
218
+ },
219
+ region: selectedRegions[0],
220
+ changeManager:
221
+ session.apolloDataStore.changeManager,
222
+ },
223
+ ],
224
+ )
225
+ },
226
+ },
227
+ ]
228
+ },
229
+ }
230
+ })
231
+ ;(pluggableElement as ViewType).stateModel = newStateModel
232
+ }
233
+ return pluggableElement
234
+ },
235
+ )
236
+
237
+ pluginManager.addToExtensionPoint(
238
+ 'Core-extendPluggableElement',
239
+ annotationFromPileup,
240
+ )
241
+ if (!inWebWorker) {
242
+ pluginManager.addToExtensionPoint(
243
+ 'Core-extendWorker',
244
+ (handle: RpcHandle) => {
245
+ if (!('on' in handle && handle.on)) {
246
+ return handle
247
+ }
248
+ handle.on('apollo', async (event: MessageEvent) => {
249
+ if (!isApolloMessageData(event)) {
250
+ return
251
+ }
252
+ const { apollo, messageId, method } = event
253
+ switch (method) {
254
+ case 'getSequence': {
255
+ const { region } = event
256
+ const { assemblyName } = region
257
+ const dataStore = (
258
+ pluginManager.rootModel?.session as
259
+ | ApolloSessionModel
260
+ | undefined
261
+ )?.apolloDataStore
262
+ if (!dataStore) {
263
+ break
264
+ }
265
+ const backendDriver = dataStore.getBackendDriver(
266
+ assemblyName,
267
+ ) as BackendDriver
268
+ const { seq: sequence } =
269
+ await backendDriver.getSequence(region)
270
+ handle.workers[0].postMessage({
271
+ apollo,
272
+ messageId,
273
+ sequence,
274
+ })
275
+ break
276
+ }
277
+ case 'getRegions': {
278
+ const { assembly } = event
279
+ const dataStore = (
280
+ pluginManager.rootModel?.session as
281
+ | ApolloSessionModel
282
+ | undefined
283
+ )?.apolloDataStore
284
+ if (!dataStore) {
285
+ break
286
+ }
287
+ const backendDriver = dataStore.getBackendDriver(
288
+ assembly,
289
+ ) as BackendDriver
290
+ const regions = await backendDriver.getRegions(assembly)
291
+ handle.workers[0].postMessage({
292
+ apollo,
293
+ messageId,
294
+ regions,
295
+ })
296
+ break
297
+ }
298
+ default: {
299
+ break
300
+ }
301
+ }
302
+ })
303
+ return handle
304
+ },
305
+ )
306
+ }
307
+ }
308
+
309
+ configure(pluginManager: PluginManager) {
310
+ if (isAbstractMenuManager(pluginManager.rootModel)) {
311
+ pluginManager.rootModel.appendToMenu('Apollo', {
312
+ label: 'Download GFF3',
313
+ onClick: (session: ApolloSessionModel) => {
314
+ ;(session as unknown as AbstractSessionModel).queueDialog(
315
+ (doneCallback) => [
316
+ DownloadGFF3,
317
+ {
318
+ session,
319
+ handleClose: () => {
320
+ doneCallback()
321
+ },
322
+ },
323
+ ],
324
+ )
325
+ },
326
+ })
327
+ pluginManager.rootModel.appendToMenu('Apollo', {
328
+ label: 'Manage Checks',
329
+ onClick: (session: ApolloSessionModel) => {
330
+ ;(session as unknown as AbstractSessionModel).queueDialog(
331
+ (doneCallback) => [
332
+ ManageChecks,
333
+ {
334
+ session,
335
+ handleClose: () => {
336
+ doneCallback()
337
+ },
338
+ },
339
+ ],
340
+ )
341
+ },
342
+ })
343
+ pluginManager.rootModel.appendToMenu('Apollo', {
344
+ label: 'View Change Log',
345
+ onClick: (session: ApolloSessionModel) => {
346
+ ;(session as unknown as AbstractSessionModel).queueDialog(
347
+ (doneCallback) => [
348
+ ViewChangeLog,
349
+ {
350
+ session,
351
+ handleClose: () => {
352
+ doneCallback()
353
+ },
354
+ },
355
+ ],
356
+ )
357
+ },
358
+ })
359
+ pluginManager.rootModel.appendToMenu('Apollo', {
360
+ label: 'Open local GFF3 file',
361
+ onClick: (session: ApolloSessionModel) => {
362
+ ;(session as unknown as AbstractSessionModel).queueDialog(
363
+ (doneCallback) => [
364
+ OpenLocalFile,
365
+ {
366
+ session,
367
+ handleClose: () => {
368
+ doneCallback()
369
+ },
370
+ inMemoryFileDriver: session.apolloDataStore.inMemoryFileDriver,
371
+ },
372
+ ],
373
+ )
374
+ },
375
+ })
376
+ pluginManager.rootModel.appendToMenu('Apollo', {
377
+ label: 'View check results',
378
+ onClick: (session: ApolloSessionModel) => {
379
+ ;(session as unknown as AbstractSessionModel).queueDialog(
380
+ (doneCallback) => [
381
+ ViewCheckResults,
382
+ {
383
+ session,
384
+ handleClose: () => {
385
+ doneCallback()
386
+ },
387
+ },
388
+ ],
389
+ )
390
+ },
391
+ })
392
+ }
393
+ }
394
+ }
@@ -0,0 +1,244 @@
1
+ import PluginManager from '@jbrowse/core/PluginManager'
2
+ import type LinearGenomeViewPlugin from '@jbrowse/plugin-linear-genome-view'
3
+ import ExpandLessIcon from '@mui/icons-material/ExpandLess'
4
+ import ExpandMoreIcon from '@mui/icons-material/ExpandMore'
5
+ import { Typography } from '@mui/material'
6
+ import { alpha } from '@mui/material/styles'
7
+ import { observer } from 'mobx-react'
8
+ import React, { useCallback, useEffect, useRef } from 'react'
9
+ import { makeStyles } from 'tss-react/mui'
10
+
11
+ import { LinearApolloDisplay } from './LinearApolloDisplay/components'
12
+ import { LinearApolloDisplay as LinearApolloDisplayI } from './LinearApolloDisplay/stateModel'
13
+ import { TrackLines } from './SixFrameFeatureDisplay/components'
14
+ import { SixFrameFeatureDisplay } from './SixFrameFeatureDisplay/stateModel'
15
+ import { TabularEditorPane } from './TabularEditor'
16
+
17
+ const accordionControlHeight = 12
18
+
19
+ const useStyles = makeStyles()((theme) => ({
20
+ shading: {
21
+ background: alpha(theme.palette.primary.main, 0.2),
22
+ overflowY: 'scroll',
23
+ overflowX: 'hidden',
24
+ },
25
+ details: {
26
+ background: theme.palette.background.paper,
27
+ },
28
+ accordionControl: {
29
+ height: accordionControlHeight,
30
+ width: '100%',
31
+ '&:hover': {
32
+ background: theme.palette.action.hover,
33
+ },
34
+ display: 'flex',
35
+ alignItems: 'center',
36
+ justifyContent: 'center',
37
+ },
38
+ accordionRoot: {
39
+ background: theme.palette.divider,
40
+ },
41
+ resizeHandle: {
42
+ width: '100%',
43
+ height: 4,
44
+ position: 'absolute',
45
+ cursor: 'row-resize',
46
+ zIndex: 100,
47
+ },
48
+ expandIcon: {
49
+ // position: 'relative',
50
+ },
51
+ title: {
52
+ // position: 'relative',
53
+ userSelect: 'none',
54
+ },
55
+ }))
56
+
57
+ function scrollSelectedFeatureIntoView(
58
+ model: LinearApolloDisplayI,
59
+ scrollContainerRef: React.RefObject<HTMLDivElement>,
60
+ ) {
61
+ const { apolloRowHeight, selectedFeature } = model
62
+ if (scrollContainerRef.current && selectedFeature) {
63
+ const position = model.getFeatureLayoutPosition(selectedFeature)
64
+ if (position) {
65
+ const row = position.layoutRow + position.featureRow
66
+ const top = row * apolloRowHeight
67
+ scrollContainerRef.current.scroll({ top, behavior: 'smooth' })
68
+ }
69
+ }
70
+ }
71
+
72
+ const ResizeHandle = ({
73
+ onResize,
74
+ }: {
75
+ onResize: (sizeDelta: number) => void
76
+ }) => {
77
+ const { classes } = useStyles()
78
+ const mouseMove = useCallback(
79
+ (event: MouseEvent) => {
80
+ event.stopPropagation()
81
+ event.preventDefault()
82
+ onResize(event.movementY)
83
+ },
84
+ [onResize],
85
+ )
86
+ const cancelDrag: (event: MouseEvent) => void = useCallback(
87
+ (event: MouseEvent) => {
88
+ event.stopPropagation()
89
+ event.preventDefault()
90
+ window.removeEventListener('mousemove', mouseMove)
91
+ window.removeEventListener('mouseup', cancelDrag)
92
+ window.removeEventListener('mouseleave', cancelDrag)
93
+ },
94
+ [mouseMove],
95
+ )
96
+ return (
97
+ // TODO: a11y
98
+ // eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions
99
+ <div
100
+ onMouseDown={(event: React.MouseEvent) => {
101
+ event.stopPropagation()
102
+ window.addEventListener('mousemove', mouseMove)
103
+ window.addEventListener('mouseup', cancelDrag)
104
+ window.addEventListener('mouseleave', cancelDrag)
105
+ }}
106
+ onClick={(e) => {
107
+ e.stopPropagation()
108
+ e.preventDefault()
109
+ }}
110
+ className={classes.resizeHandle}
111
+ />
112
+ )
113
+ }
114
+
115
+ const AccordionControl = observer(function AccordionControl({
116
+ onClick,
117
+ onResize,
118
+ open,
119
+ title,
120
+ }: {
121
+ open: boolean
122
+ onClick: (e: React.MouseEvent) => void
123
+ onResize?: (sizeDelta: number) => void
124
+ title?: string
125
+ }) {
126
+ const { classes } = useStyles()
127
+ return (
128
+ <div className={classes.accordionRoot}>
129
+ {open && onResize ? <ResizeHandle onResize={onResize} /> : null}
130
+ {/* TODO: a11y */}
131
+ {/* eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions */}
132
+ <div className={classes.accordionControl} onClick={onClick}>
133
+ {open ? (
134
+ <ExpandLessIcon className={classes.expandIcon} />
135
+ ) : (
136
+ <ExpandMoreIcon className={classes.expandIcon} />
137
+ )}
138
+ {title ? (
139
+ <Typography
140
+ className={classes.title}
141
+ variant="caption"
142
+ component="span"
143
+ >
144
+ {title}
145
+ </Typography>
146
+ ) : null}
147
+ </div>
148
+ </div>
149
+ )
150
+ })
151
+
152
+ export const DisplayComponent = observer(function DisplayComponent({
153
+ model,
154
+ ...other
155
+ }: {
156
+ model: LinearApolloDisplayI
157
+ }) {
158
+ const { classes } = useStyles()
159
+
160
+ const {
161
+ height: overallHeight,
162
+ isShown,
163
+ selectedFeature,
164
+ tabularEditor,
165
+ toggleShown,
166
+ } = model
167
+ const detailsHeight = tabularEditor.isShown ? model.detailsHeight : 0
168
+ const featureAreaHeight = isShown
169
+ ? overallHeight - detailsHeight - accordionControlHeight * 2
170
+ : 0
171
+
172
+ const onDetailsResize = (delta: number) => {
173
+ model.setDetailsHeight(model.detailsHeight - delta)
174
+ }
175
+
176
+ const canvasScrollContainerRef = useRef<HTMLDivElement>(null)
177
+ useEffect(
178
+ () => scrollSelectedFeatureIntoView(model, canvasScrollContainerRef),
179
+ [model, selectedFeature],
180
+ )
181
+ return (
182
+ <div className={classes.details} style={{ height: overallHeight }}>
183
+ <AccordionControl
184
+ open={isShown}
185
+ title="Graphical"
186
+ onClick={toggleShown}
187
+ />
188
+ <div
189
+ className={classes.shading}
190
+ ref={canvasScrollContainerRef}
191
+ style={{ height: featureAreaHeight }}
192
+ >
193
+ <LinearApolloDisplay model={model} {...other} />
194
+ </div>
195
+ <AccordionControl
196
+ title="Table"
197
+ open={tabularEditor.isShown}
198
+ onClick={tabularEditor.togglePane}
199
+ onResize={onDetailsResize}
200
+ />
201
+ <div style={{ height: detailsHeight }}>
202
+ <TabularEditorPane model={model} />
203
+ </div>
204
+ </div>
205
+ )
206
+ })
207
+
208
+ export function makeSixFrameDisplayComponent(pluginManager: PluginManager) {
209
+ const LGVPlugin = pluginManager.getPlugin('LinearGenomeViewPlugin') as
210
+ | LinearGenomeViewPlugin
211
+ | undefined
212
+ if (!LGVPlugin) {
213
+ throw new Error('LinearGenomeView plugin not found')
214
+ }
215
+ const { BaseLinearDisplayComponent } = LGVPlugin.exports
216
+ function ApolloDisplayComponent({
217
+ model,
218
+ ...other
219
+ }: {
220
+ model: SixFrameFeatureDisplay
221
+ }) {
222
+ const { classes } = useStyles()
223
+ const { height, selectedFeature } = model
224
+ let { detailsHeight } = model
225
+ if (!selectedFeature) {
226
+ detailsHeight = 0
227
+ }
228
+ const featureAreaHeight = height - detailsHeight
229
+ return (
230
+ <div style={{ height: model.height }}>
231
+ <div className={classes.shading} style={{ height: featureAreaHeight }}>
232
+ <BaseLinearDisplayComponent model={model} {...other} />
233
+ </div>
234
+ {/* <div className={classes.details} style={{ height: detailsHeight }}>
235
+ <ApolloDetails model={model} />
236
+ </div> */}
237
+ <div className="testTrackLines">
238
+ <TrackLines model={model} />
239
+ </div>
240
+ </div>
241
+ )
242
+ }
243
+ return observer(ApolloDisplayComponent)
244
+ }