@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,208 @@
1
+ import {
2
+ Button,
3
+ DialogActions,
4
+ DialogContent,
5
+ DialogContentText,
6
+ MenuItem,
7
+ Select,
8
+ SelectChangeEvent,
9
+ } from '@mui/material'
10
+ import {
11
+ DataGrid,
12
+ GridColDef,
13
+ GridRowsProp,
14
+ GridToolbar,
15
+ } from '@mui/x-data-grid'
16
+ import { changeRegistry } from 'apollo-common'
17
+ import { getRoot } from 'mobx-state-tree'
18
+ import React, { useEffect, useState } from 'react'
19
+ import { makeStyles } from 'tss-react/mui'
20
+
21
+ import { ApolloInternetAccountModel } from '../ApolloInternetAccount/model'
22
+ import { ApolloSessionModel } from '../session'
23
+ import { ApolloRootModel } from '../types'
24
+ import { createFetchErrorMessage } from '../util'
25
+ import { Dialog } from './Dialog'
26
+
27
+ interface ViewChangeLogProps {
28
+ session: ApolloSessionModel
29
+ handleClose(): void
30
+ }
31
+
32
+ interface AssemblyDocument {
33
+ _id: string
34
+ name: string
35
+ }
36
+
37
+ const useStyles = makeStyles()((theme) => ({
38
+ changeTextarea: {
39
+ fontFamily: 'monospace',
40
+ width: 600,
41
+ resize: 'none',
42
+ border: `1px solid ${theme.palette.divider}`,
43
+ borderRadius: theme.shape.borderRadius,
44
+ },
45
+ }))
46
+
47
+ export function ViewChangeLog({ handleClose, session }: ViewChangeLogProps) {
48
+ const { internetAccounts } = getRoot<ApolloRootModel>(session)
49
+ const apolloInternetAccount = internetAccounts.find(
50
+ (ia) => ia.type === 'ApolloInternetAccount',
51
+ ) as ApolloInternetAccountModel | undefined
52
+ if (!apolloInternetAccount) {
53
+ throw new Error('No Apollo internet account found')
54
+ }
55
+ const { baseURL } = apolloInternetAccount
56
+ const { classes } = useStyles()
57
+ const [errorMessage, setErrorMessage] = useState<string>()
58
+ const [assemblyCollection, setAssemblyCollection] = useState<
59
+ AssemblyDocument[]
60
+ >([])
61
+ const [assemblyId, setAssemblyId] = useState<string>('')
62
+ const [displayGridData, setDisplayGridData] = useState<GridRowsProp[]>([])
63
+
64
+ const gridColumns: GridColDef[] = [
65
+ { field: 'sequence' },
66
+ {
67
+ field: 'typeName',
68
+ headerName: 'Change type',
69
+ width: 200,
70
+ type: 'singleSelect',
71
+ // TODO: Get these from change manager once it's on the session
72
+ valueOptions: [...changeRegistry.changes.keys()],
73
+ },
74
+ {
75
+ field: 'changes',
76
+ headerName: 'Change JSON',
77
+ width: 600,
78
+ renderCell: ({ value }) => (
79
+ <textarea className={classes.changeTextarea} readOnly>
80
+ {JSON.stringify(value)}
81
+ </textarea>
82
+ ),
83
+ valueFormatter: ({ value }) => JSON.stringify(value),
84
+ },
85
+ { field: 'user', headerName: 'User', width: 140 },
86
+ {
87
+ field: 'createdAt',
88
+ headerName: 'Time',
89
+ width: 160,
90
+ type: 'dateTime',
91
+ valueGetter: ({ value }) => value && new Date(value),
92
+ },
93
+ ]
94
+
95
+ useEffect(() => {
96
+ async function getAssemblies() {
97
+ const uri = new URL('/assemblies', baseURL).href
98
+ const apolloFetch = apolloInternetAccount?.getFetcher({
99
+ locationType: 'UriLocation',
100
+ uri,
101
+ })
102
+ if (apolloFetch) {
103
+ const response = await apolloFetch(uri, { method: 'GET' })
104
+ if (!response.ok) {
105
+ const newErrorMessage = await createFetchErrorMessage(
106
+ response,
107
+ 'Error when retrieving assemblies from server',
108
+ )
109
+ setErrorMessage(newErrorMessage)
110
+ return
111
+ }
112
+ const data = (await response.json()) as AssemblyDocument[]
113
+ setAssemblyCollection(data)
114
+ }
115
+ }
116
+ getAssemblies().catch((error) => setErrorMessage(String(error)))
117
+ }, [apolloInternetAccount, baseURL])
118
+
119
+ useEffect(() => {
120
+ if (!assemblyId && assemblyCollection.length > 0) {
121
+ setAssemblyId(assemblyCollection[0]._id)
122
+ }
123
+ }, [assemblyId, assemblyCollection])
124
+
125
+ useEffect(() => {
126
+ async function getGridData() {
127
+ if (!assemblyId) {
128
+ return
129
+ }
130
+
131
+ // Get changes
132
+ const url = new URL('changes', baseURL)
133
+ const searchParams = new URLSearchParams({ assembly: assemblyId })
134
+ url.search = searchParams.toString()
135
+ const uri = url.toString()
136
+ const apolloFetch = apolloInternetAccount?.getFetcher({
137
+ locationType: 'UriLocation',
138
+ uri,
139
+ })
140
+ if (apolloFetch) {
141
+ const response = await apolloFetch(uri, {
142
+ headers: new Headers({ 'Content-Type': 'application/json' }),
143
+ })
144
+ if (!response.ok) {
145
+ const newErrorMessage = await createFetchErrorMessage(
146
+ response,
147
+ 'Error when retrieving changes',
148
+ )
149
+ setErrorMessage(newErrorMessage)
150
+ return
151
+ }
152
+ const data = await response.json()
153
+ setDisplayGridData(data)
154
+ }
155
+ }
156
+ getGridData().catch((error) => setErrorMessage(String(error)))
157
+ }, [assemblyId, apolloInternetAccount, baseURL])
158
+
159
+ async function handleChangeAssembly(e: SelectChangeEvent<string>) {
160
+ setAssemblyId(e.target.value as string)
161
+ }
162
+
163
+ return (
164
+ <Dialog
165
+ open
166
+ fullScreen
167
+ title="View change log"
168
+ handleClose={handleClose}
169
+ data-testid="view-changelog"
170
+ >
171
+ <Select
172
+ style={{ width: 200, marginLeft: 40 }}
173
+ value={assemblyId}
174
+ onChange={handleChangeAssembly}
175
+ >
176
+ {assemblyCollection.map((option) => (
177
+ <MenuItem key={option._id} value={option._id}>
178
+ {option.name}
179
+ </MenuItem>
180
+ ))}
181
+ </Select>
182
+
183
+ <DialogContent>
184
+ <DataGrid
185
+ pagination
186
+ rows={displayGridData}
187
+ columns={gridColumns}
188
+ getRowId={(row) => row._id}
189
+ slots={{ toolbar: GridToolbar }}
190
+ initialState={{
191
+ sorting: { sortModel: [{ field: 'sequence', sort: 'desc' }] },
192
+ columns: { columnVisibilityModel: { sequence: false } },
193
+ }}
194
+ />
195
+ </DialogContent>
196
+ <DialogActions>
197
+ <Button variant="outlined" type="submit" onClick={handleClose}>
198
+ Close
199
+ </Button>
200
+ </DialogActions>
201
+ {errorMessage ? (
202
+ <DialogContent>
203
+ <DialogContentText color="error">{errorMessage}</DialogContentText>
204
+ </DialogContent>
205
+ ) : null}
206
+ </Dialog>
207
+ )
208
+ }
@@ -0,0 +1,151 @@
1
+ import { Assembly } from '@jbrowse/core/assemblyManager/assembly'
2
+ import {
3
+ Button,
4
+ DialogActions,
5
+ DialogContent,
6
+ DialogContentText,
7
+ MenuItem,
8
+ Select,
9
+ SelectChangeEvent,
10
+ } from '@mui/material'
11
+ import {
12
+ DataGrid,
13
+ GridColDef,
14
+ GridRowsProp,
15
+ GridToolbar,
16
+ } from '@mui/x-data-grid'
17
+ import { getRoot } from 'mobx-state-tree'
18
+ import React, { useEffect, useState } from 'react'
19
+
20
+ import { ApolloInternetAccountModel } from '../ApolloInternetAccount/model'
21
+ import { ApolloSessionModel } from '../session'
22
+ import { ApolloRootModel } from '../types'
23
+ import { createFetchErrorMessage } from '../util'
24
+ import { Dialog } from './Dialog'
25
+
26
+ interface ViewCheckResultsProps {
27
+ session: ApolloSessionModel
28
+ handleClose(): void
29
+ }
30
+
31
+ export function ViewCheckResults({
32
+ handleClose,
33
+ session,
34
+ }: ViewCheckResultsProps) {
35
+ const { internetAccounts } = getRoot<ApolloRootModel>(session)
36
+ const { collaborationServerDriver } = session.apolloDataStore
37
+ const apolloInternetAccount = internetAccounts.find(
38
+ (ia) => ia.type === 'ApolloInternetAccount',
39
+ ) as ApolloInternetAccountModel | undefined
40
+ if (!apolloInternetAccount) {
41
+ throw new Error('No Apollo internet account found')
42
+ }
43
+ const { baseURL } = apolloInternetAccount
44
+ const [errorMessage, setErrorMessage] = useState<string>()
45
+ const [selectedAssembly, setSelectedAssembly] = useState<Assembly>()
46
+ const [displayGridData, setDisplayGridData] = useState<GridRowsProp[]>([])
47
+
48
+ const gridColumns: GridColDef[] = [
49
+ { field: '_id', headerName: 'id', width: 50 },
50
+ {
51
+ field: 'name',
52
+ headerName: 'Check name',
53
+ width: 200,
54
+ },
55
+ { field: 'refSeq', headerName: 'Reference sequence ID', width: 200 },
56
+ { field: 'ids', headerName: 'Feature IDs', width: 200 },
57
+ { field: 'message', headerName: 'Message', flex: 1 },
58
+ ]
59
+
60
+ const assemblies = collaborationServerDriver.getAssemblies()
61
+ useEffect(() => {
62
+ if (!selectedAssembly && assemblies.length > 0) {
63
+ setSelectedAssembly(assemblies[0])
64
+ }
65
+ }, [assemblies, selectedAssembly])
66
+
67
+ useEffect(() => {
68
+ async function getGridData() {
69
+ const assemblyId: string | undefined = selectedAssembly?.name
70
+ if (!assemblyId) {
71
+ return
72
+ }
73
+ const url = new URL('checks', baseURL)
74
+ const searchParams = new URLSearchParams({ assembly: assemblyId })
75
+ url.search = searchParams.toString()
76
+ const uri = url.toString()
77
+ const apolloFetch = apolloInternetAccount?.getFetcher({
78
+ locationType: 'UriLocation',
79
+ uri,
80
+ })
81
+ if (apolloFetch) {
82
+ const response = await apolloFetch(uri, {
83
+ headers: new Headers({ 'Content-Type': 'application/json' }),
84
+ })
85
+ if (!response.ok) {
86
+ const newErrorMessage = await createFetchErrorMessage(
87
+ response,
88
+ 'Error when retrieving checks',
89
+ )
90
+ setErrorMessage(newErrorMessage)
91
+ return
92
+ }
93
+ const data = await response.json()
94
+ setDisplayGridData(data)
95
+ }
96
+ }
97
+ getGridData().catch((error) => setErrorMessage(String(error)))
98
+ }, [selectedAssembly, apolloInternetAccount, baseURL])
99
+
100
+ function handleChangeAssembly(e: SelectChangeEvent<string>) {
101
+ const newAssembly = assemblies.find((asm) => asm.name === e.target.value)
102
+ setSelectedAssembly(newAssembly)
103
+ }
104
+
105
+ return (
106
+ <Dialog
107
+ open
108
+ fullScreen
109
+ title="View check results"
110
+ handleClose={handleClose}
111
+ data-testid="view-check-results"
112
+ >
113
+ <Select
114
+ style={{ width: 200, marginLeft: 40 }}
115
+ value={selectedAssembly?.name ?? ''}
116
+ onChange={handleChangeAssembly}
117
+ disabled={assemblies.length === 0}
118
+ >
119
+ {assemblies.map((option) => (
120
+ <MenuItem key={option.name} value={option.name}>
121
+ {option.displayName ?? option.name}
122
+ </MenuItem>
123
+ ))}
124
+ </Select>
125
+
126
+ <DialogContent>
127
+ <DataGrid
128
+ pagination
129
+ rows={displayGridData}
130
+ columns={gridColumns}
131
+ getRowId={(row) => row._id}
132
+ slots={{ toolbar: GridToolbar }}
133
+ initialState={{
134
+ sorting: { sortModel: [{ field: 'name', sort: 'asc' }] },
135
+ columns: { columnVisibilityModel: { name: true } },
136
+ }}
137
+ />
138
+ </DialogContent>
139
+ <DialogActions>
140
+ <Button variant="outlined" type="submit" onClick={handleClose}>
141
+ Close
142
+ </Button>
143
+ </DialogActions>
144
+ {errorMessage ? (
145
+ <DialogContent>
146
+ <DialogContentText color="error">{errorMessage}</DialogContentText>
147
+ </DialogContent>
148
+ ) : null}
149
+ </Dialog>
150
+ )
151
+ }
@@ -0,0 +1,12 @@
1
+ export * from './AddAssembly'
2
+ export * from './AddChildFeature'
3
+ export * from './CopyFeature'
4
+ export * from './DeleteAssembly'
5
+ export * from './DeleteFeature'
6
+ export * from './DownloadGFF3'
7
+ export * from './ImportFeatures'
8
+ export * from './ManageChecks'
9
+ export * from './ManageUsers'
10
+ export * from './ModifyFeatureAttribute'
11
+ export * from './OpenLocalFile'
12
+ export * from './ViewChangeLog'
package/src/config.ts ADDED
@@ -0,0 +1,10 @@
1
+ import { ConfigurationSchema } from '@jbrowse/core/configuration'
2
+ import { types } from 'mobx-state-tree'
3
+
4
+ import { OntologyRecordConfiguration } from './OntologyManager'
5
+
6
+ const ApolloPluginConfigurationSchema = ConfigurationSchema('ApolloPlugin', {
7
+ ontologies: types.array(OntologyRecordConfiguration),
8
+ })
9
+
10
+ export default ApolloPluginConfigurationSchema
@@ -0,0 +1,3 @@
1
+ declare module '*.json'
2
+
3
+ declare module 'librpc-web-mod'
@@ -0,0 +1,208 @@
1
+ import { Assembly } from '@jbrowse/core/assemblyManager/assembly'
2
+ import { DisplayType } from '@jbrowse/core/pluggableElementTypes'
3
+ import PluggableElementBase from '@jbrowse/core/pluggableElementTypes/PluggableElementBase'
4
+ import { getContainingView, getSession } from '@jbrowse/core/util'
5
+ import { LinearGenomeViewModel } from '@jbrowse/plugin-linear-genome-view'
6
+ import AddIcon from '@mui/icons-material/Add'
7
+ import { AnnotationFeatureSnapshot } from 'apollo-mst'
8
+ import { AddFeatureChange } from 'apollo-shared'
9
+ import ObjectID from 'bson-objectid'
10
+
11
+ import { ApolloSessionModel } from '../session'
12
+
13
+ function parseCigar(cigar: string): [string | undefined, number][] {
14
+ return (cigar.toUpperCase().match(/\d+\D/g) ?? []).map((op) => {
15
+ return [(op.match(/\D/) ?? [])[0], Number.parseInt(op, 10)]
16
+ })
17
+ }
18
+
19
+ export function annotationFromPileup(pluggableElement: PluggableElementBase) {
20
+ if (pluggableElement.name !== 'LinearPileupDisplay') {
21
+ return pluggableElement
22
+ }
23
+ const { stateModel } = pluggableElement as DisplayType
24
+ const newStateModel = stateModel
25
+ .views((self) => ({
26
+ getFirstRegion() {
27
+ const lgv = getContainingView(self) as unknown as LinearGenomeViewModel
28
+ return lgv.dynamicBlocks.contentBlocks[0]
29
+ },
30
+ getAssembly() {
31
+ const firstRegion = self.getFirstRegion()
32
+ const session = getSession(self)
33
+ const { assemblyManager } = session
34
+ const { assemblyName } = firstRegion
35
+ const assembly = assemblyManager.get(assemblyName)
36
+ if (!assembly) {
37
+ throw new Error(`Could not find assembly named ${assemblyName}`)
38
+ }
39
+ return assembly
40
+ },
41
+ getRefSeqId(assembly: Assembly) {
42
+ const firstRegion = self.getFirstRegion()
43
+ const { refName } = firstRegion
44
+ const { refNameAliases } = assembly
45
+ if (!refNameAliases) {
46
+ throw new Error(`Could not find aliases for ${assembly.name}`)
47
+ }
48
+ const newRefNames = [...Object.entries(refNameAliases)]
49
+ .filter(([id, refName]) => id !== refName)
50
+ .map(([id, refName]) => ({
51
+ _id: id,
52
+ name: refName ?? '',
53
+ }))
54
+ const refSeqId = newRefNames.find((item) => item.name === refName)?._id
55
+ if (!refSeqId) {
56
+ throw new Error(`Could not find refSeqId named ${refName}`)
57
+ }
58
+ return refSeqId
59
+ },
60
+ createFeature() {
61
+ const feature = self.contextMenuFeature
62
+ const assembly = self.getAssembly()
63
+ const refSeqId = self.getRefSeqId(assembly)
64
+ const cigarData: string = feature.get('CIGAR')
65
+ const ops = parseCigar(cigarData)
66
+ let currOffset = 0
67
+ const start: number = feature.get('start')
68
+ let openStart: number | undefined
69
+ const exons: {
70
+ start: number
71
+ end: number
72
+ }[] = []
73
+ for (const [op, len] of ops) {
74
+ // open or continue open
75
+ if (op === 'M' || op === '=') {
76
+ // if it was closed, then open with start, strand, type
77
+ if (openStart === undefined) {
78
+ // add subfeature
79
+ openStart = currOffset + start
80
+ }
81
+ } else if (op === 'N' && openStart !== undefined) {
82
+ // if it was open, then close and add the subfeature
83
+ exons.push({
84
+ start: openStart,
85
+ end: currOffset + openStart,
86
+ })
87
+ openStart = undefined
88
+ }
89
+ if (op !== 'I') {
90
+ // we ignore insertions when calculating potential exon length
91
+ currOffset += len
92
+ }
93
+ }
94
+ // if we are still open, then close with the final length and add subfeature
95
+ if (openStart !== undefined) {
96
+ exons.push({
97
+ start: openStart,
98
+ end: currOffset + start,
99
+ })
100
+ }
101
+
102
+ const newFeature: AnnotationFeatureSnapshot = {
103
+ _id: ObjectID().toHexString(),
104
+ gffId: '',
105
+ refSeq: refSeqId,
106
+ start: feature.get('start'),
107
+ end: feature.get('end'),
108
+ type: 'mRNA',
109
+ strand: feature.get('strand'),
110
+ }
111
+ if (exons.length === 0) {
112
+ return newFeature
113
+ }
114
+
115
+ const children: Record<string, AnnotationFeatureSnapshot> = {}
116
+ newFeature.children = children
117
+ const [firstExon] = exons
118
+ const cdsFeature: AnnotationFeatureSnapshot = {
119
+ _id: ObjectID().toHexString(),
120
+ gffId: '',
121
+ refSeq: refSeqId,
122
+ start: firstExon.start,
123
+ end: firstExon.end,
124
+ type: 'CDS',
125
+ strand: feature.get('strand'),
126
+ phase: 0,
127
+ }
128
+ newFeature.children[cdsFeature._id] = cdsFeature
129
+ if (exons.length === 1) {
130
+ const exon: AnnotationFeatureSnapshot = {
131
+ _id: ObjectID().toHexString(),
132
+ gffId: '',
133
+ refSeq: refSeqId,
134
+ start: firstExon.start,
135
+ end: firstExon.end,
136
+ type: 'exon',
137
+ strand: feature.get('strand'),
138
+ }
139
+ newFeature.children[exon._id] = exon
140
+ return newFeature
141
+ }
142
+
143
+ const discontinuousLocations: {
144
+ start: number
145
+ end: number
146
+ phase: 0 | 1 | 2
147
+ }[] = []
148
+ let phase: 0 | 1 | 2 = 0
149
+ for (const exon of exons) {
150
+ cdsFeature.start = Math.min(cdsFeature.start, exon.start)
151
+ cdsFeature.end = Math.max(cdsFeature.end, exon.end)
152
+ const { end, start } = exon
153
+ discontinuousLocations?.push({ start, end, phase })
154
+ const localPhase = (end - start) % 3
155
+ phase = ((phase + localPhase) % 3) as 0 | 1 | 2
156
+ const newExon: AnnotationFeatureSnapshot = {
157
+ _id: ObjectID().toHexString(),
158
+ gffId: '',
159
+ refSeq: refSeqId,
160
+ start,
161
+ end,
162
+ type: 'exon',
163
+ strand: feature.get('strand'),
164
+ }
165
+ newFeature.children[newExon._id] = newExon
166
+ }
167
+ cdsFeature.discontinuousLocations = discontinuousLocations
168
+ return newFeature
169
+ },
170
+ async onPileupFeatureContext() {
171
+ const newFeature = self.createFeature()
172
+ const assembly = self.getAssembly()
173
+ const assemblyId = assembly.name
174
+ const change = new AddFeatureChange({
175
+ changedIds: [newFeature._id],
176
+ typeName: 'AddFeatureChange',
177
+ assembly: assemblyId,
178
+ addedFeature: newFeature,
179
+ })
180
+ const session = getSession(self)
181
+ await (
182
+ session as unknown as ApolloSessionModel
183
+ ).apolloDataStore.changeManager.submit?.(change)
184
+ session.notify('Annotation added successfully', 'success')
185
+ },
186
+ }))
187
+ .views((self) => {
188
+ const superContextMenuItems = self.contextMenuItems
189
+ return {
190
+ contextMenuItems() {
191
+ const feature = self.contextMenuFeature
192
+ if (!feature) {
193
+ return superContextMenuItems()
194
+ }
195
+ return [
196
+ ...superContextMenuItems(),
197
+ {
198
+ label: 'Create Apollo annotation',
199
+ icon: AddIcon,
200
+ onClick: self.onPileupFeatureContext,
201
+ },
202
+ ]
203
+ },
204
+ }
205
+ })
206
+ ;(pluggableElement as DisplayType).stateModel = newStateModel
207
+ return pluggableElement
208
+ }
@@ -0,0 +1 @@
1
+ export * from './annotationFromPileup'