@apollo-annotation/jbrowse-plugin-apollo 0.3.12 → 1.0.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 (192) hide show
  1. package/dist/ApolloRefNameAliasAdapter/ApolloRefNameAliasAdapter.d.ts +1 -1
  2. package/dist/ApolloRefNameAliasAdapter/ApolloRefNameAliasAdapter.d.ts.map +1 -1
  3. package/dist/BackendDrivers/BackendDriver.d.ts +29 -4
  4. package/dist/BackendDrivers/BackendDriver.d.ts.map +1 -1
  5. package/dist/BackendDrivers/CollaborationServerDriver.d.ts +3 -1
  6. package/dist/BackendDrivers/CollaborationServerDriver.d.ts.map +1 -1
  7. package/dist/BackendDrivers/LocalDriver/LocalDriver.d.ts +22 -0
  8. package/dist/BackendDrivers/LocalDriver/LocalDriver.d.ts.map +1 -0
  9. package/dist/BackendDrivers/LocalDriver/db.d.ts +4 -0
  10. package/dist/BackendDrivers/LocalDriver/db.d.ts.map +1 -0
  11. package/dist/BackendDrivers/index.d.ts +1 -2
  12. package/dist/BackendDrivers/index.d.ts.map +1 -1
  13. package/dist/ChangeManager.d.ts +3 -3
  14. package/dist/ChangeManager.d.ts.map +1 -1
  15. package/dist/FeatureDetailsWidget/ApolloTranscriptDetailsWidget.d.ts +0 -6
  16. package/dist/FeatureDetailsWidget/ApolloTranscriptDetailsWidget.d.ts.map +1 -1
  17. package/dist/FeatureDetailsWidget/TranscriptWidgetEditLocation.d.ts.map +1 -1
  18. package/dist/FeatureDetailsWidget/model.d.ts +0 -2
  19. package/dist/FeatureDetailsWidget/model.d.ts.map +1 -1
  20. package/dist/LinearApolloDisplay/components/CheckResultWarnings.d.ts.map +1 -1
  21. package/dist/LinearApolloDisplay/components/LinearApolloDisplay.d.ts.map +1 -1
  22. package/dist/LinearApolloDisplay/components/OverlayCanvas.d.ts +7 -0
  23. package/dist/LinearApolloDisplay/components/OverlayCanvas.d.ts.map +1 -0
  24. package/dist/LinearApolloDisplay/components/Tooltip.d.ts +10 -0
  25. package/dist/LinearApolloDisplay/components/Tooltip.d.ts.map +1 -0
  26. package/dist/LinearApolloDisplay/glyphs/BoxGlyph.d.ts +0 -1
  27. package/dist/LinearApolloDisplay/glyphs/BoxGlyph.d.ts.map +1 -1
  28. package/dist/LinearApolloDisplay/glyphs/CDSGlyph.d.ts +3 -0
  29. package/dist/LinearApolloDisplay/glyphs/CDSGlyph.d.ts.map +1 -0
  30. package/dist/LinearApolloDisplay/glyphs/ExonGlyph.d.ts +3 -0
  31. package/dist/LinearApolloDisplay/glyphs/ExonGlyph.d.ts.map +1 -0
  32. package/dist/LinearApolloDisplay/glyphs/GeneGlyph.d.ts.map +1 -1
  33. package/dist/LinearApolloDisplay/glyphs/GenericChildGlyph.d.ts.map +1 -1
  34. package/dist/LinearApolloDisplay/glyphs/Glyph.d.ts +26 -20
  35. package/dist/LinearApolloDisplay/glyphs/Glyph.d.ts.map +1 -1
  36. package/dist/LinearApolloDisplay/glyphs/TranscriptGlyph.d.ts +3 -0
  37. package/dist/LinearApolloDisplay/glyphs/TranscriptGlyph.d.ts.map +1 -0
  38. package/dist/LinearApolloDisplay/glyphs/util.d.ts +13 -0
  39. package/dist/LinearApolloDisplay/glyphs/util.d.ts.map +1 -1
  40. package/dist/LinearApolloDisplay/stateModel/base.d.ts +17 -0
  41. package/dist/LinearApolloDisplay/stateModel/base.d.ts.map +1 -1
  42. package/dist/LinearApolloDisplay/stateModel/index.d.ts +35 -17
  43. package/dist/LinearApolloDisplay/stateModel/index.d.ts.map +1 -1
  44. package/dist/LinearApolloDisplay/stateModel/layouts.d.ts +29 -7
  45. package/dist/LinearApolloDisplay/stateModel/layouts.d.ts.map +1 -1
  46. package/dist/LinearApolloDisplay/stateModel/mouseEvents.d.ts +69 -23
  47. package/dist/LinearApolloDisplay/stateModel/mouseEvents.d.ts.map +1 -1
  48. package/dist/LinearApolloDisplay/stateModel/rendering.d.ts +26 -9
  49. package/dist/LinearApolloDisplay/stateModel/rendering.d.ts.map +1 -1
  50. package/dist/LinearApolloReferenceSequenceDisplay/stateModel/base.d.ts +6 -0
  51. package/dist/LinearApolloReferenceSequenceDisplay/stateModel/base.d.ts.map +1 -1
  52. package/dist/LinearApolloReferenceSequenceDisplay/stateModel/index.d.ts +6 -0
  53. package/dist/LinearApolloReferenceSequenceDisplay/stateModel/index.d.ts.map +1 -1
  54. package/dist/LinearApolloReferenceSequenceDisplay/stateModel/rendering.d.ts +6 -0
  55. package/dist/LinearApolloReferenceSequenceDisplay/stateModel/rendering.d.ts.map +1 -1
  56. package/dist/LinearApolloSixFrameDisplay/components/LinearApolloSixFrameDisplay.d.ts.map +1 -1
  57. package/dist/LinearApolloSixFrameDisplay/glyphs/GeneGlyph.d.ts.map +1 -1
  58. package/dist/LinearApolloSixFrameDisplay/glyphs/Glyph.d.ts +1 -1
  59. package/dist/LinearApolloSixFrameDisplay/glyphs/Glyph.d.ts.map +1 -1
  60. package/dist/LinearApolloSixFrameDisplay/stateModel/layouts.d.ts.map +1 -1
  61. package/dist/LinearApolloSixFrameDisplay/stateModel/rendering.d.ts.map +1 -1
  62. package/dist/OntologyManager/OntologyStore/fulltext.d.ts +1 -1
  63. package/dist/OntologyManager/OntologyStore/fulltext.d.ts.map +1 -1
  64. package/dist/OntologyManager/OntologyStore/index.d.ts +2 -2
  65. package/dist/OntologyManager/OntologyStore/index.d.ts.map +1 -1
  66. package/dist/OntologyManager/OntologyStore/indexeddb-storage.d.ts +1 -1
  67. package/dist/OntologyManager/OntologyStore/indexeddb-storage.d.ts.map +1 -1
  68. package/dist/OntologyManager/OntologyStore/types.d.ts +18 -0
  69. package/dist/OntologyManager/OntologyStore/types.d.ts.map +1 -0
  70. package/dist/TabularEditor/HybridGrid/featureContextMenuItems.d.ts.map +1 -1
  71. package/dist/components/AddChildFeature.d.ts.map +1 -1
  72. package/dist/components/ColorFeature.d.ts +13 -0
  73. package/dist/components/ColorFeature.d.ts.map +1 -0
  74. package/dist/components/CreateApolloAnnotation.d.ts.map +1 -1
  75. package/dist/components/DownloadGFF3.d.ts +4 -1
  76. package/dist/components/DownloadGFF3.d.ts.map +1 -1
  77. package/dist/components/DuplicateTranscript.d.ts.map +1 -1
  78. package/dist/components/ViewChangeLog.d.ts +2 -1
  79. package/dist/components/ViewChangeLog.d.ts.map +1 -1
  80. package/dist/components/ViewCheckResults.d.ts +2 -1
  81. package/dist/components/ViewCheckResults.d.ts.map +1 -1
  82. package/dist/components/index.d.ts +1 -1
  83. package/dist/components/index.d.ts.map +1 -1
  84. package/dist/config.d.ts +4 -0
  85. package/dist/config.d.ts.map +1 -0
  86. package/dist/extensions/annotationFromJBrowseFeature.d.ts.map +1 -1
  87. package/dist/extensions/annotationFromPileup.d.ts.map +1 -1
  88. package/dist/index.d.ts +11 -0
  89. package/dist/index.d.ts.map +1 -0
  90. package/dist/index.esm.js +6325 -5997
  91. package/dist/index.esm.js.map +1 -1
  92. package/dist/jbrowse-plugin-apollo.cjs.development.js +5869 -5541
  93. package/dist/jbrowse-plugin-apollo.cjs.development.js.map +1 -1
  94. package/dist/jbrowse-plugin-apollo.cjs.production.min.js +1 -1
  95. package/dist/jbrowse-plugin-apollo.cjs.production.min.js.map +1 -1
  96. package/dist/jbrowse-plugin-apollo.umd.development.js +16782 -25897
  97. package/dist/jbrowse-plugin-apollo.umd.development.js.map +1 -1
  98. package/dist/jbrowse-plugin-apollo.umd.production.min.js +1 -1
  99. package/dist/jbrowse-plugin-apollo.umd.production.min.js.map +1 -1
  100. package/dist/makeDisplayComponent.d.ts.map +1 -1
  101. package/dist/menus/Icons.d.ts +3 -0
  102. package/dist/menus/Icons.d.ts.map +1 -0
  103. package/dist/menus/topLevelMenu.d.ts.map +1 -1
  104. package/dist/session/changeHandlers.d.ts +9 -0
  105. package/dist/session/changeHandlers.d.ts.map +1 -0
  106. package/dist/util/annotationFeatureUtils.d.ts +2 -1
  107. package/dist/util/annotationFeatureUtils.d.ts.map +1 -1
  108. package/dist/util/glyphUtils.d.ts +3 -3
  109. package/dist/util/glyphUtils.d.ts.map +1 -1
  110. package/dist/util/index.d.ts +0 -1
  111. package/dist/util/index.d.ts.map +1 -1
  112. package/package.json +4 -4
  113. package/src/ApolloInternetAccount/model.ts +68 -4
  114. package/src/ApolloRefNameAliasAdapter/ApolloRefNameAliasAdapter.ts +6 -3
  115. package/src/ApolloTextSearchAdapter/ApolloTextSearchAdapter.ts +1 -1
  116. package/src/BackendDrivers/BackendDriver.ts +36 -3
  117. package/src/BackendDrivers/CollaborationServerDriver.ts +78 -23
  118. package/src/BackendDrivers/LocalDriver/LocalDriver.ts +367 -0
  119. package/src/BackendDrivers/LocalDriver/db.ts +37 -0
  120. package/src/BackendDrivers/index.ts +1 -2
  121. package/src/ChangeManager.ts +27 -25
  122. package/src/FeatureDetailsWidget/ApolloTranscriptDetailsWidget.tsx +1 -1
  123. package/src/FeatureDetailsWidget/TranscriptWidgetEditLocation.tsx +69 -53
  124. package/src/LinearApolloDisplay/components/CheckResultWarnings.tsx +1 -5
  125. package/src/LinearApolloDisplay/components/LinearApolloDisplay.tsx +95 -115
  126. package/src/LinearApolloDisplay/components/OverlayCanvas.tsx +76 -0
  127. package/src/LinearApolloDisplay/components/Tooltip.tsx +42 -0
  128. package/src/LinearApolloDisplay/glyphs/BoxGlyph.ts +60 -302
  129. package/src/LinearApolloDisplay/glyphs/CDSGlyph.ts +145 -0
  130. package/src/LinearApolloDisplay/glyphs/ExonGlyph.ts +212 -0
  131. package/src/LinearApolloDisplay/glyphs/GeneGlyph.ts +65 -999
  132. package/src/LinearApolloDisplay/glyphs/GenericChildGlyph.ts +71 -181
  133. package/src/LinearApolloDisplay/glyphs/Glyph.ts +42 -66
  134. package/src/LinearApolloDisplay/glyphs/TranscriptGlyph.ts +291 -0
  135. package/src/LinearApolloDisplay/glyphs/util.ts +87 -0
  136. package/src/LinearApolloDisplay/stateModel/base.ts +83 -0
  137. package/src/LinearApolloDisplay/stateModel/layouts.ts +198 -138
  138. package/src/LinearApolloDisplay/stateModel/mouseEvents.ts +252 -158
  139. package/src/LinearApolloDisplay/stateModel/rendering.ts +103 -21
  140. package/src/LinearApolloReferenceSequenceDisplay/drawSequenceOverlay.ts +3 -3
  141. package/src/LinearApolloReferenceSequenceDisplay/stateModel/base.ts +20 -2
  142. package/src/LinearApolloSixFrameDisplay/components/LinearApolloSixFrameDisplay.tsx +7 -2
  143. package/src/LinearApolloSixFrameDisplay/glyphs/GeneGlyph.ts +8 -13
  144. package/src/LinearApolloSixFrameDisplay/glyphs/Glyph.ts +1 -1
  145. package/src/LinearApolloSixFrameDisplay/stateModel/layouts.ts +4 -3
  146. package/src/LinearApolloSixFrameDisplay/stateModel/mouseEvents.ts +1 -1
  147. package/src/LinearApolloSixFrameDisplay/stateModel/rendering.ts +2 -1
  148. package/src/OntologyManager/OntologyStore/__snapshots__/index.test.ts.snap +18262 -8519
  149. package/src/OntologyManager/OntologyStore/fulltext.ts +1 -2
  150. package/src/OntologyManager/OntologyStore/index.test.ts +5 -2
  151. package/src/OntologyManager/OntologyStore/index.ts +7 -8
  152. package/src/OntologyManager/OntologyStore/indexeddb-storage.ts +2 -2
  153. package/src/OntologyManager/OntologyStore/types.ts +27 -0
  154. package/src/OntologyManager/index.ts +15 -26
  155. package/src/TabularEditor/HybridGrid/featureContextMenuItems.ts +4 -5
  156. package/src/components/AddChildFeature.tsx +15 -8
  157. package/src/components/ColorFeature.tsx +167 -0
  158. package/src/components/CreateApolloAnnotation.tsx +35 -9
  159. package/src/components/DownloadGFF3.tsx +92 -121
  160. package/src/components/DuplicateTranscript.tsx +10 -0
  161. package/src/components/ViewChangeLog.tsx +123 -83
  162. package/src/components/ViewCheckResults.tsx +15 -73
  163. package/src/components/index.ts +1 -1
  164. package/src/config.ts +37 -19
  165. package/src/extensions/annotationFromJBrowseFeature.test.ts +1 -1
  166. package/src/extensions/annotationFromJBrowseFeature.ts +91 -63
  167. package/src/extensions/annotationFromPileup.ts +40 -40
  168. package/src/index.ts +45 -1
  169. package/src/makeDisplayComponent.tsx +10 -3
  170. package/src/menus/Icons.tsx +49 -0
  171. package/src/menus/topLevelMenu.ts +24 -96
  172. package/src/session/ClientDataStore.ts +16 -17
  173. package/src/session/changeHandlers.ts +261 -0
  174. package/src/session/session.ts +77 -46
  175. package/src/util/annotationFeatureUtils.ts +29 -1
  176. package/src/util/glyphUtils.ts +74 -31
  177. package/src/util/index.ts +0 -1
  178. package/dist/BackendDrivers/DesktopFileDriver.d.ts +0 -160
  179. package/dist/BackendDrivers/DesktopFileDriver.d.ts.map +0 -1
  180. package/dist/BackendDrivers/InMemoryFileDriver.d.ts +0 -162
  181. package/dist/BackendDrivers/InMemoryFileDriver.d.ts.map +0 -1
  182. package/dist/LinearApolloDisplay/glyphs/index.d.ts +0 -4
  183. package/dist/LinearApolloDisplay/glyphs/index.d.ts.map +0 -1
  184. package/dist/components/OpenLocalFile.d.ts +0 -15
  185. package/dist/components/OpenLocalFile.d.ts.map +0 -1
  186. package/dist/util/loadAssemblyIntoClient.d.ts +0 -5
  187. package/dist/util/loadAssemblyIntoClient.d.ts.map +0 -1
  188. package/src/BackendDrivers/DesktopFileDriver.ts +0 -184
  189. package/src/BackendDrivers/InMemoryFileDriver.ts +0 -107
  190. package/src/LinearApolloDisplay/glyphs/index.ts +0 -3
  191. package/src/components/OpenLocalFile.tsx +0 -189
  192. package/src/util/loadAssemblyIntoClient.ts +0 -94
@@ -0,0 +1,367 @@
1
+ /* eslint-disable @typescript-eslint/no-unused-vars */
2
+ /* eslint-disable @typescript-eslint/require-await */
3
+ import {
4
+ type Change,
5
+ type SerializedChange,
6
+ checkRegistry,
7
+ isFeatureChange,
8
+ } from '@apollo-annotation/common'
9
+ import type {
10
+ AnnotationFeature,
11
+ AnnotationFeatureSnapshot,
12
+ CheckResultSnapshot,
13
+ } from '@apollo-annotation/mst'
14
+ import {
15
+ ValidationResultSet,
16
+ isDeleteFeatureChange,
17
+ } from '@apollo-annotation/shared'
18
+ import type { Assembly } from '@jbrowse/core/assemblyManager/assembly'
19
+ import type {
20
+ BaseRefNameAliasAdapter,
21
+ BaseSequenceAdapter,
22
+ } from '@jbrowse/core/data_adapters/BaseAdapter'
23
+ import {
24
+ type AbstractRootModel,
25
+ type Region,
26
+ getEnv,
27
+ getSession,
28
+ } from '@jbrowse/core/util'
29
+ import { getSnapshot } from '@jbrowse/mobx-state-tree'
30
+
31
+ import type { SubmitOpts } from '../../ChangeManager'
32
+ import {
33
+ BackendDriver,
34
+ type ChangeDocument,
35
+ type GetChangesOpts,
36
+ type GetChangesResult,
37
+ type RefNameAliases,
38
+ } from '../BackendDriver'
39
+
40
+ import { type FeatureDatabase, openDb } from './db'
41
+
42
+ export class LocalDriver extends BackendDriver {
43
+ async getFeatures(
44
+ region: Region,
45
+ ): Promise<[AnnotationFeatureSnapshot[], CheckResultSnapshot[]]> {
46
+ const { assemblyName, end, refName, start } = region
47
+ const regions = await this.getRegions(assemblyName)
48
+ const refNames = regions.map((r) => r.refName)
49
+ const db = await openDb(assemblyName, refNames)
50
+ const featureStoreName = `features-${refName}`
51
+ const checkStoreName = `checkresults-${refName}`
52
+ const features: AnnotationFeatureSnapshot[] = []
53
+ const checkResults: CheckResultSnapshot[] = []
54
+ const tx = db.transaction([featureStoreName, checkStoreName])
55
+ for await (const cursor of tx
56
+ .objectStore(featureStoreName)
57
+ .index('min')
58
+ .iterate(IDBKeyRange.upperBound(end, true))) {
59
+ if ((cursor.value as { max: number }).max > start) {
60
+ features.push(cursor.value as AnnotationFeatureSnapshot)
61
+ }
62
+ }
63
+ for await (const cursor of tx
64
+ .objectStore(checkStoreName)
65
+ .index('min')
66
+ .iterate(IDBKeyRange.upperBound(end, true))) {
67
+ if ((cursor.value as { end: number }).end > start) {
68
+ checkResults.push(cursor.value as CheckResultSnapshot)
69
+ }
70
+ }
71
+ return [features, checkResults]
72
+ }
73
+
74
+ async getSequence(region: Region): Promise<{ seq: string; refSeq: string }> {
75
+ const session = getSession(this.clientStore)
76
+ const { assemblyManager } = session
77
+ const assembly = await assemblyManager.waitForAssembly(region.assemblyName)
78
+ if (!assembly) {
79
+ throw new Error(`Assembly not found: "${region.assemblyName}"`)
80
+ }
81
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
82
+ const { configuration } = assembly
83
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access
84
+ const { adapter: adapterConf } = configuration.sequence
85
+ const { pluginManager } = getEnv(this.clientStore)
86
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-member-access
87
+ const type = pluginManager.getAdapterType(adapterConf.type)
88
+ if (!type) {
89
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
90
+ throw new Error(`No adapter found for "${adapterConf.type}"`)
91
+ }
92
+ const CLASS = await type.getAdapterClass()
93
+ const adapter = new CLASS(
94
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
95
+ adapterConf,
96
+ undefined,
97
+ pluginManager,
98
+ ) as BaseSequenceAdapter
99
+ const seq = await adapter.getSequence(region)
100
+ if (!seq) {
101
+ throw new Error(`Sequence not found: ${JSON.stringify(region)}`)
102
+ }
103
+ return { seq, refSeq: region.refName }
104
+ }
105
+
106
+ async getRegions(assemblyName: string): Promise<Region[]> {
107
+ const session = getSession(this.clientStore)
108
+ const { assemblyManager } = session
109
+ const assembly = await assemblyManager.waitForAssembly(assemblyName)
110
+ if (!assembly) {
111
+ throw new Error(`Assembly not found: "${assemblyName}"`)
112
+ }
113
+ const { regions } = assembly
114
+ if (!regions) {
115
+ throw new Error(`Assembly not found: "${assemblyName}"`)
116
+ }
117
+ return regions
118
+ }
119
+
120
+ getAssemblies(internetAccountConfigId?: string): Assembly[] {
121
+ return []
122
+ }
123
+
124
+ async getRefNameAliases(assemblyName: string): Promise<RefNameAliases[]> {
125
+ const session = getSession(this.clientStore)
126
+ const { assemblyManager } = session
127
+ const assembly = await assemblyManager.waitForAssembly(assemblyName)
128
+ if (!assembly) {
129
+ throw new Error(`Assembly not found: "${assemblyName}"`)
130
+ }
131
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
132
+ const { configuration } = assembly
133
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
134
+ const { refNameAliases } = configuration
135
+ if (!refNameAliases) {
136
+ return []
137
+ }
138
+ const { pluginManager } = getEnv(this.clientStore)
139
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-member-access
140
+ const type = pluginManager.getAdapterType(refNameAliases.adapter.type)
141
+ if (!type) {
142
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
143
+ throw new Error(`No adapter found for "${refNameAliases.adapter.type}"`)
144
+ }
145
+ const CLASS = await type.getAdapterClass()
146
+ const adapter = new CLASS(
147
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-member-access
148
+ refNameAliases.adapter,
149
+ undefined,
150
+ pluginManager,
151
+ ) as BaseRefNameAliasAdapter
152
+ return adapter.getRefNameAliases({})
153
+ }
154
+
155
+ async submitChange(
156
+ change: Change,
157
+ opts: SubmitOpts,
158
+ ): Promise<ValidationResultSet> {
159
+ if (!isFeatureChange(change)) {
160
+ return new ValidationResultSet()
161
+ }
162
+ const { assembly, changedIds } = change
163
+ const regions = await this.getRegions(assembly)
164
+ const refNames = regions.map((r) => r.refName)
165
+ const db = await openDb(assembly, refNames)
166
+ const topLevelFeatures = new Set<AnnotationFeature>()
167
+ const deletedFeatureIds: { refSeq: string; featureId: string }[] = []
168
+ const neededRefNames = new Set<string>()
169
+ if (isDeleteFeatureChange(change)) {
170
+ for (const c of change.changes) {
171
+ if (c.parentFeatureId) {
172
+ const feature = this.clientStore.getFeature(c.parentFeatureId)
173
+ if (feature) {
174
+ topLevelFeatures.add(feature.topLevelFeature)
175
+ neededRefNames.add(feature.topLevelFeature.refSeq)
176
+ }
177
+ } else {
178
+ const { refSeq, _id } = c.deletedFeature
179
+ deletedFeatureIds.push({ refSeq, featureId: _id })
180
+ neededRefNames.add(refSeq)
181
+ }
182
+ }
183
+ } else {
184
+ for (const changedId of changedIds) {
185
+ const feature = this.clientStore.getFeature(changedId)
186
+ if (feature) {
187
+ topLevelFeatures.add(feature.topLevelFeature)
188
+ neededRefNames.add(feature.refSeq)
189
+ }
190
+ }
191
+ }
192
+ const storeNames = [...neededRefNames].flatMap((r) => [
193
+ `features-${r}`,
194
+ `checkresults-${r}`,
195
+ ])
196
+ storeNames.push('changes')
197
+ const tx = db.transaction(storeNames, 'readwrite')
198
+ for (const { refSeq, featureId } of deletedFeatureIds) {
199
+ void tx.objectStore(`features-${refSeq}`).delete(featureId)
200
+ }
201
+ for (const feature of topLevelFeatures) {
202
+ const snapshot = getSnapshot<AnnotationFeatureSnapshot>(feature)
203
+ void tx
204
+ .objectStore(`features-${feature.refSeq}`)
205
+ .put(snapshot, feature._id)
206
+ }
207
+ // Delete old check results for deleted features
208
+ for (const { featureId, refSeq } of deletedFeatureIds) {
209
+ const checkStore = tx.objectStore(`checkresults-${refSeq}`)
210
+ for await (const cursor of checkStore
211
+ .index('featureId')
212
+ .iterate(featureId)) {
213
+ this.clientStore.deleteCheckResult(
214
+ (cursor.value as CheckResultSnapshot)._id,
215
+ )
216
+ void cursor.delete()
217
+ }
218
+ }
219
+ // Delete old check results for modified features
220
+ for (const feature of topLevelFeatures) {
221
+ const checkStore = tx.objectStore(`checkresults-${feature.refSeq}`)
222
+ for await (const cursor of checkStore
223
+ .index('featureId')
224
+ .iterate(feature._id)) {
225
+ this.clientStore.deleteCheckResult(
226
+ (cursor.value as CheckResultSnapshot)._id,
227
+ )
228
+ void cursor.delete()
229
+ }
230
+ }
231
+ void tx
232
+ .objectStore('changes')
233
+ .put({ ...change.toJSON(), createdAt: new Date() })
234
+ await tx.done
235
+
236
+ // Run checks on modified features. Collect all results first since checks
237
+ // are async (need sequence data) and would cause the transaction to auto-commit.
238
+ if (topLevelFeatures.size > 0) {
239
+ const checks = [...checkRegistry.getChecks().values()]
240
+ const allResults: {
241
+ refSeq: string
242
+ result: CheckResultSnapshot
243
+ topLevelFeatureId: string
244
+ }[] = []
245
+ for (const feature of topLevelFeatures) {
246
+ const snapshot = getSnapshot<AnnotationFeatureSnapshot>(feature)
247
+ const getSequence = async (start: number, end: number) => {
248
+ const result = await this.getSequence({
249
+ assemblyName: assembly,
250
+ refName: feature.refSeq,
251
+ start,
252
+ end,
253
+ })
254
+ return result.seq
255
+ }
256
+ for (const check of checks) {
257
+ const results = await check.checkFeature(snapshot, getSequence)
258
+ for (const result of results) {
259
+ allResults.push({
260
+ refSeq: feature.refSeq,
261
+ result,
262
+ topLevelFeatureId: feature._id,
263
+ })
264
+ }
265
+ }
266
+ }
267
+ if (allResults.length > 0) {
268
+ const checkStoreNames = refNames.map((r) => `checkresults-${r}`)
269
+ const checkTx = db.transaction(checkStoreNames, 'readwrite')
270
+ for (const { refSeq, result, topLevelFeatureId } of allResults) {
271
+ void checkTx
272
+ .objectStore(`checkresults-${refSeq}`)
273
+ .put({ ...result, featureId: topLevelFeatureId })
274
+ }
275
+ await checkTx.done
276
+ // Add new check results to client store
277
+ for (const { result } of allResults) {
278
+ this.clientStore.addCheckResult(result)
279
+ }
280
+ }
281
+ }
282
+
283
+ return new ValidationResultSet()
284
+ }
285
+
286
+ async searchFeatures(
287
+ term: string,
288
+ assemblies: string[],
289
+ ): Promise<AnnotationFeatureSnapshot[]> {
290
+ return []
291
+ }
292
+
293
+ async getCheckResults(assemblyName: string): Promise<CheckResultSnapshot[]> {
294
+ const regions = await this.getRegions(assemblyName)
295
+ const refNames = regions.map((r) => r.refName)
296
+ const db = await openDb(assemblyName, refNames)
297
+ const checkResults: CheckResultSnapshot[] = []
298
+ const storeNames = refNames.map((r) => `checkresults-${r}`)
299
+ const tx = db.transaction(storeNames)
300
+ for (const storeName of storeNames) {
301
+ for await (const cursor of tx.objectStore(storeName).iterate()) {
302
+ checkResults.push(cursor.value as CheckResultSnapshot)
303
+ }
304
+ }
305
+ return checkResults
306
+ }
307
+
308
+ async getChanges(
309
+ assemblyName: string,
310
+ opts: GetChangesOpts = {},
311
+ ): Promise<GetChangesResult> {
312
+ const regions = await this.getRegions(assemblyName)
313
+ const refNames = regions.map((r) => r.refName)
314
+ const db = await openDb(assemblyName, refNames)
315
+ let changes: ChangeDocument[] = []
316
+ for await (const cursor of db.transaction('changes').store.iterate()) {
317
+ changes.push({
318
+ sequence: cursor.key,
319
+ ...(cursor.value as SerializedChange & { createdAt: string }),
320
+ })
321
+ }
322
+
323
+ const { filters } = opts
324
+ if (filters?.user) {
325
+ const re = new RegExp(filters.user, 'i')
326
+ changes = changes.filter((c) => c.user !== undefined && re.test(c.user))
327
+ }
328
+ if (filters?.typeName) {
329
+ changes = changes.filter((c) => c.typeName === filters.typeName)
330
+ }
331
+ if (filters?.startTime) {
332
+ const start = new Date(filters.startTime).getTime()
333
+ changes = changes.filter((c) => new Date(c.createdAt).getTime() >= start)
334
+ }
335
+ if (filters?.endTime) {
336
+ const end = new Date(filters.endTime).getTime()
337
+ changes = changes.filter((c) => new Date(c.createdAt).getTime() <= end)
338
+ }
339
+
340
+ const totalCount = changes.length
341
+
342
+ const sortField = opts.sortField ?? 'sequence'
343
+ const sortOrder = opts.sortOrder ?? 'desc'
344
+ const direction = sortOrder === 'asc' ? 1 : -1
345
+ changes.sort((a, b) => {
346
+ const aVal = a[sortField as keyof ChangeDocument]
347
+ const bVal = b[sortField as keyof ChangeDocument]
348
+ if (aVal === bVal) {
349
+ return 0
350
+ }
351
+ if (aVal === undefined) {
352
+ return 1
353
+ }
354
+ if (bVal === undefined) {
355
+ return -1
356
+ }
357
+ return aVal < bVal ? -direction : direction
358
+ })
359
+
360
+ if (opts.page !== undefined && opts.pageSize !== undefined) {
361
+ const start = opts.page * opts.pageSize
362
+ changes = changes.slice(start, start + opts.pageSize)
363
+ }
364
+
365
+ return { changes, totalCount }
366
+ }
367
+ }
@@ -0,0 +1,37 @@
1
+ import { type IDBPDatabase, openDB } from 'idb/with-async-ittr'
2
+
3
+ export type FeatureDatabase = IDBPDatabase
4
+
5
+ export async function openDb(
6
+ assemblyName: string,
7
+ refNames: string[],
8
+ ): Promise<FeatureDatabase> {
9
+ const dbName = `Apollo-${assemblyName}`
10
+ return openDB(dbName, 1, {
11
+ upgrade(db) {
12
+ const changesStoreName = 'changes'
13
+ if (!db.objectStoreNames.contains(changesStoreName)) {
14
+ db.createObjectStore(changesStoreName, { autoIncrement: true })
15
+ }
16
+ for (const refName of refNames) {
17
+ const storeName = `features-${refName}`
18
+ if (!db.objectStoreNames.contains(storeName)) {
19
+ const store = db.createObjectStore(storeName)
20
+ store.createIndex('min', 'min', { unique: false })
21
+ store.createIndex('max', 'max', { unique: false })
22
+ }
23
+ const checkStoreName = `checkresults-${refName}`
24
+ if (!db.objectStoreNames.contains(checkStoreName)) {
25
+ const store = db.createObjectStore(checkStoreName, {
26
+ keyPath: '_id',
27
+ })
28
+ store.createIndex('min', 'start', { unique: false })
29
+ store.createIndex('max', 'end', { unique: false })
30
+ store.createIndex('featureId', 'featureId', {
31
+ unique: false,
32
+ })
33
+ }
34
+ }
35
+ },
36
+ })
37
+ }
@@ -1,4 +1,3 @@
1
1
  export * from './BackendDriver'
2
+ export * from './LocalDriver/LocalDriver'
2
3
  export * from './CollaborationServerDriver'
3
- export * from './InMemoryFileDriver'
4
- export * from './DesktopFileDriver'
@@ -1,9 +1,5 @@
1
- /* eslint-disable @typescript-eslint/no-unsafe-assignment */
2
- /* eslint-disable @typescript-eslint/no-unsafe-call */
3
- /* eslint-disable @typescript-eslint/no-unsafe-member-access */
4
1
  import {
5
2
  type Change,
6
- type ClientDataStore,
7
3
  isAssemblySpecificChange,
8
4
  } from '@apollo-annotation/common'
9
5
  import {
@@ -11,9 +7,10 @@ import {
11
7
  validationRegistry,
12
8
  } from '@apollo-annotation/shared'
13
9
  import { getSession } from '@jbrowse/core/util'
14
- import type { IAnyStateTreeNode } from '@jbrowse/mobx-state-tree'
15
10
 
16
11
  import type { ApolloSessionModel } from './session'
12
+ import type { ClientDataStoreModel } from './session/ClientDataStore'
13
+ import { changeHandlers, isLocalChange } from './session/changeHandlers'
17
14
 
18
15
  export interface SubmitOpts {
19
16
  /** defaults to true */
@@ -27,7 +24,7 @@ export interface SubmitOpts {
27
24
  }
28
25
 
29
26
  export class ChangeManager {
30
- constructor(private dataStore: ClientDataStore & IAnyStateTreeNode) {}
27
+ constructor(private dataStore: ClientDataStoreModel) {}
31
28
 
32
29
  recentChanges: Change[] = []
33
30
  undoneChanges: Change[] = []
@@ -90,27 +87,31 @@ export class ChangeManager {
90
87
  return
91
88
  }
92
89
 
93
- try {
94
- // submit to client data store
95
- await change.execute(this.dataStore)
96
- } catch (error) {
97
- if (updateJobsManager) {
98
- jobsManager.abortJob(job.name, String(error))
90
+ const changeName = change.typeName
91
+ const handler = isLocalChange(changeName)
92
+ ? changeHandlers[changeName]
93
+ : undefined
94
+ if (handler) {
95
+ try {
96
+ // submit to client data store
97
+ // @ts-expect-error change not narrowing
98
+ await handler(this.dataStore, change)
99
+ } catch (error) {
100
+ if (updateJobsManager) {
101
+ jobsManager.abortJob(job.name, String(error))
102
+ }
103
+ console.error(error)
104
+ session.notify(
105
+ `Error encountered in client: ${String(error)}. Data may be out of sync, please refresh the page`,
106
+ 'error',
107
+ )
108
+ setChangeInProgress(false)
109
+ return
99
110
  }
100
- console.error(error)
101
- session.notify(
102
- `Error encountered in client: ${String(error)}. Data may be out of sync, please refresh the page`,
103
- 'error',
104
- )
105
- setChangeInProgress(false)
106
- return
107
111
  }
108
112
 
109
113
  // post-validate
110
- const results2 = await validationRegistry.frontendPostValidate(
111
- change,
112
- this.dataStore,
113
- )
114
+ const results2 = await validationRegistry.frontendPostValidate(change)
114
115
  if (!results2.ok) {
115
116
  // notify of invalid change and revert
116
117
  await this.undo(change)
@@ -121,6 +122,7 @@ export class ChangeManager {
121
122
  jobsManager.update(job.name, 'Submitting to driver')
122
123
  }
123
124
  // submit to driver
125
+ // eslint-disable-next-line @typescript-eslint/unbound-method
124
126
  const { collaborationServerDriver, getBackendDriver } = this.dataStore
125
127
  const backendDriver = isAssemblySpecificChange(change)
126
128
  ? // for assembly-specific change, fall back in case it's an
@@ -136,8 +138,8 @@ export class ChangeManager {
136
138
  }
137
139
  console.error(error)
138
140
  session.notify(String(error), 'error')
139
- await this.undo(change, false)
140
141
  setChangeInProgress(false)
142
+ await this.undo(change, false)
141
143
  return
142
144
  }
143
145
  if (!backendResult.ok) {
@@ -146,8 +148,8 @@ export class ChangeManager {
146
148
  jobsManager.abortJob(job.name, msg)
147
149
  }
148
150
  session.notify(msg, 'error')
149
- await this.undo(change, false)
150
151
  setChangeInProgress(false)
152
+ await this.undo(change, false)
151
153
  return
152
154
  }
153
155
  if (change.notification) {
@@ -40,7 +40,7 @@ const useStyles = makeStyles()((theme) => ({
40
40
  },
41
41
  }))
42
42
 
43
- export const StyledAccordionSummary = styled(AccordionSummary)(() => ({
43
+ const StyledAccordionSummary = styled(AccordionSummary)(() => ({
44
44
  minHeight: 30,
45
45
  maxHeight: 30,
46
46
  '&.Mui-expanded': {
@@ -22,6 +22,7 @@ import RemoveIcon from '@mui/icons-material/Remove'
22
22
  import {
23
23
  Accordion,
24
24
  AccordionDetails,
25
+ AccordionSummary,
25
26
  Grid,
26
27
  Tooltip,
27
28
  Typography,
@@ -33,7 +34,6 @@ import type { OntologyRecord } from '../OntologyManager'
33
34
  import type { ApolloSessionModel } from '../session'
34
35
  import { copyToClipboard } from '../util/copyToClipboard'
35
36
 
36
- import { StyledAccordionSummary } from './ApolloTranscriptDetailsWidget'
37
37
  import { NumberTextField } from './NumberTextField'
38
38
 
39
39
  const StyledTextField = styled(NumberTextField)(() => ({
@@ -64,6 +64,15 @@ const SequenceContainer = styled('div')({
64
64
  },
65
65
  })
66
66
 
67
+ const StyledAccordionSummary = styled(AccordionSummary)(() => ({
68
+ minHeight: 30,
69
+ maxHeight: 30,
70
+ '&.Mui-expanded': {
71
+ minHeight: 30,
72
+ maxHeight: 30,
73
+ },
74
+ }))
75
+
67
76
  const Strand = (props: { strand: 1 | -1 | undefined }) => {
68
77
  const { strand } = props
69
78
 
@@ -951,61 +960,11 @@ export const TranscriptWidgetEditLocation = observer(
951
960
  <div>
952
961
  {cdsPresent && (
953
962
  <div>
954
- <Accordion>
955
- <StyledAccordionSummary
956
- expandIcon={<ExpandMoreIcon style={{ color: 'white' }} />}
957
- aria-controls="panel1-content"
958
- id="panel1-header"
959
- >
960
- <Typography component="span" fontWeight={'bold'}>
961
- Translation
962
- </Typography>
963
- </StyledAccordionSummary>
964
- <AccordionDetails>
965
- <SequenceContainer>
966
- <Typography
967
- component={'span'}
968
- ref={seqRef}
969
- style={{ maxHeight: 120, overflowY: 'scroll' }}
970
- >
971
- {getTranslationSequence()}
972
- </Typography>
973
- </SequenceContainer>
974
- <div
975
- style={{
976
- marginTop: 10,
977
- display: 'flex',
978
- flexDirection: 'row',
979
- alignItems: 'center',
980
- gap: 10,
981
- }}
982
- >
983
- <Tooltip title="Copy">
984
- <button
985
- onClick={onCopyClick}
986
- style={{ border: 'none', background: 'none', padding: 0 }}
987
- disabled={changeInProgress}
988
- >
989
- <ContentCopyIcon style={{ fontSize: 15 }} />
990
- </button>
991
- </Tooltip>
992
- <Tooltip title="Trim">
993
- <button
994
- onClick={trimTranslationSequence}
995
- style={{ border: 'none', background: 'none', padding: 0 }}
996
- disabled={changeInProgress}
997
- >
998
- <ContentCutIcon style={{ fontSize: 15 }} />
999
- </button>
1000
- </Tooltip>
1001
- </div>
1002
- </AccordionDetails>
1003
- </Accordion>
1004
963
  <Grid
1005
964
  container
1006
965
  justifyContent="center"
1007
966
  alignItems="center"
1008
- style={{ textAlign: 'center', marginTop: 10 }}
967
+ style={{ textAlign: 'center' }}
1009
968
  >
1010
969
  <Grid size={1} />
1011
970
  {strand === 1 ? (
@@ -1089,7 +1048,10 @@ export const TranscriptWidgetEditLocation = observer(
1089
1048
  </Grid>
1090
1049
  </div>
1091
1050
  )}
1092
- <div style={{ marginTop: 5 }}>
1051
+ <div style={{ marginTop: 5, marginBottom: 10 }}>
1052
+ <div style={{ textAlign: 'center' }}>
1053
+ <Typography>Exons</Typography>
1054
+ </div>
1093
1055
  {transcriptExonParts.map((loc, index) => {
1094
1056
  return (
1095
1057
  <div key={index}>
@@ -1203,6 +1165,60 @@ export const TranscriptWidgetEditLocation = observer(
1203
1165
  )
1204
1166
  })}
1205
1167
  </div>
1168
+ {cdsPresent && (
1169
+ <div>
1170
+ <Accordion>
1171
+ <StyledAccordionSummary
1172
+ expandIcon={<ExpandMoreIcon style={{ color: 'white' }} />}
1173
+ aria-controls="panel1-content"
1174
+ id="panel1-header"
1175
+ >
1176
+ <Typography component="span" fontWeight={'bold'}>
1177
+ Translation
1178
+ </Typography>
1179
+ </StyledAccordionSummary>
1180
+ <AccordionDetails>
1181
+ <SequenceContainer>
1182
+ <Typography
1183
+ component={'span'}
1184
+ ref={seqRef}
1185
+ style={{ maxHeight: 120, overflowY: 'scroll' }}
1186
+ >
1187
+ {getTranslationSequence()}
1188
+ </Typography>
1189
+ </SequenceContainer>
1190
+ <div
1191
+ style={{
1192
+ marginTop: 10,
1193
+ display: 'flex',
1194
+ flexDirection: 'row',
1195
+ alignItems: 'center',
1196
+ gap: 10,
1197
+ }}
1198
+ >
1199
+ <Tooltip title="Copy">
1200
+ <button
1201
+ onClick={onCopyClick}
1202
+ style={{ border: 'none', background: 'none', padding: 0 }}
1203
+ disabled={changeInProgress}
1204
+ >
1205
+ <ContentCopyIcon style={{ fontSize: 15 }} />
1206
+ </button>
1207
+ </Tooltip>
1208
+ <Tooltip title="Trim">
1209
+ <button
1210
+ onClick={trimTranslationSequence}
1211
+ style={{ border: 'none', background: 'none', padding: 0 }}
1212
+ disabled={changeInProgress}
1213
+ >
1214
+ <ContentCutIcon style={{ fontSize: 15 }} />
1215
+ </button>
1216
+ </Tooltip>
1217
+ </div>
1218
+ </AccordionDetails>
1219
+ </Accordion>
1220
+ </div>
1221
+ )}
1206
1222
  </div>
1207
1223
  )
1208
1224
  },