@apollo-annotation/jbrowse-plugin-apollo 0.3.13 → 1.0.1

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 +6329 -5998
  91. package/dist/index.esm.js.map +1 -1
  92. package/dist/jbrowse-plugin-apollo.cjs.development.js +5872 -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 +16789 -25901
  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 +370 -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,370 @@
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({
100
+ ...region,
101
+ refName: assembly.getSeqAdapterRefName(region.refName),
102
+ })
103
+ if (!seq) {
104
+ throw new Error(`Sequence not found: ${JSON.stringify(region)}`)
105
+ }
106
+ return { seq, refSeq: region.refName }
107
+ }
108
+
109
+ async getRegions(assemblyName: string): Promise<Region[]> {
110
+ const session = getSession(this.clientStore)
111
+ const { assemblyManager } = session
112
+ const assembly = await assemblyManager.waitForAssembly(assemblyName)
113
+ if (!assembly) {
114
+ throw new Error(`Assembly not found: "${assemblyName}"`)
115
+ }
116
+ const { regions } = assembly
117
+ if (!regions) {
118
+ throw new Error(`Assembly not found: "${assemblyName}"`)
119
+ }
120
+ return regions
121
+ }
122
+
123
+ getAssemblies(internetAccountConfigId?: string): Assembly[] {
124
+ return []
125
+ }
126
+
127
+ async getRefNameAliases(assemblyName: string): Promise<RefNameAliases[]> {
128
+ const session = getSession(this.clientStore)
129
+ const { assemblyManager } = session
130
+ const assembly = await assemblyManager.waitForAssembly(assemblyName)
131
+ if (!assembly) {
132
+ throw new Error(`Assembly not found: "${assemblyName}"`)
133
+ }
134
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
135
+ const { configuration } = assembly
136
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
137
+ const { refNameAliases } = configuration
138
+ if (!refNameAliases) {
139
+ return []
140
+ }
141
+ const { pluginManager } = getEnv(this.clientStore)
142
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-member-access
143
+ const type = pluginManager.getAdapterType(refNameAliases.adapter.type)
144
+ if (!type) {
145
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
146
+ throw new Error(`No adapter found for "${refNameAliases.adapter.type}"`)
147
+ }
148
+ const CLASS = await type.getAdapterClass()
149
+ const adapter = new CLASS(
150
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-member-access
151
+ refNameAliases.adapter,
152
+ undefined,
153
+ pluginManager,
154
+ ) as BaseRefNameAliasAdapter
155
+ return adapter.getRefNameAliases({})
156
+ }
157
+
158
+ async submitChange(
159
+ change: Change,
160
+ opts: SubmitOpts,
161
+ ): Promise<ValidationResultSet> {
162
+ if (!isFeatureChange(change)) {
163
+ return new ValidationResultSet()
164
+ }
165
+ const { assembly, changedIds } = change
166
+ const regions = await this.getRegions(assembly)
167
+ const refNames = regions.map((r) => r.refName)
168
+ const db = await openDb(assembly, refNames)
169
+ const topLevelFeatures = new Set<AnnotationFeature>()
170
+ const deletedFeatureIds: { refSeq: string; featureId: string }[] = []
171
+ const neededRefNames = new Set<string>()
172
+ if (isDeleteFeatureChange(change)) {
173
+ for (const c of change.changes) {
174
+ if (c.parentFeatureId) {
175
+ const feature = this.clientStore.getFeature(c.parentFeatureId)
176
+ if (feature) {
177
+ topLevelFeatures.add(feature.topLevelFeature)
178
+ neededRefNames.add(feature.topLevelFeature.refSeq)
179
+ }
180
+ } else {
181
+ const { refSeq, _id } = c.deletedFeature
182
+ deletedFeatureIds.push({ refSeq, featureId: _id })
183
+ neededRefNames.add(refSeq)
184
+ }
185
+ }
186
+ } else {
187
+ for (const changedId of changedIds) {
188
+ const feature = this.clientStore.getFeature(changedId)
189
+ if (feature) {
190
+ topLevelFeatures.add(feature.topLevelFeature)
191
+ neededRefNames.add(feature.refSeq)
192
+ }
193
+ }
194
+ }
195
+ const storeNames = [...neededRefNames].flatMap((r) => [
196
+ `features-${r}`,
197
+ `checkresults-${r}`,
198
+ ])
199
+ storeNames.push('changes')
200
+ const tx = db.transaction(storeNames, 'readwrite')
201
+ for (const { refSeq, featureId } of deletedFeatureIds) {
202
+ void tx.objectStore(`features-${refSeq}`).delete(featureId)
203
+ }
204
+ for (const feature of topLevelFeatures) {
205
+ const snapshot = getSnapshot<AnnotationFeatureSnapshot>(feature)
206
+ void tx
207
+ .objectStore(`features-${feature.refSeq}`)
208
+ .put(snapshot, feature._id)
209
+ }
210
+ // Delete old check results for deleted features
211
+ for (const { featureId, refSeq } of deletedFeatureIds) {
212
+ const checkStore = tx.objectStore(`checkresults-${refSeq}`)
213
+ for await (const cursor of checkStore
214
+ .index('featureId')
215
+ .iterate(featureId)) {
216
+ this.clientStore.deleteCheckResult(
217
+ (cursor.value as CheckResultSnapshot)._id,
218
+ )
219
+ void cursor.delete()
220
+ }
221
+ }
222
+ // Delete old check results for modified features
223
+ for (const feature of topLevelFeatures) {
224
+ const checkStore = tx.objectStore(`checkresults-${feature.refSeq}`)
225
+ for await (const cursor of checkStore
226
+ .index('featureId')
227
+ .iterate(feature._id)) {
228
+ this.clientStore.deleteCheckResult(
229
+ (cursor.value as CheckResultSnapshot)._id,
230
+ )
231
+ void cursor.delete()
232
+ }
233
+ }
234
+ void tx
235
+ .objectStore('changes')
236
+ .put({ ...change.toJSON(), createdAt: new Date() })
237
+ await tx.done
238
+
239
+ // Run checks on modified features. Collect all results first since checks
240
+ // are async (need sequence data) and would cause the transaction to auto-commit.
241
+ if (topLevelFeatures.size > 0) {
242
+ const checks = [...checkRegistry.getChecks().values()]
243
+ const allResults: {
244
+ refSeq: string
245
+ result: CheckResultSnapshot
246
+ topLevelFeatureId: string
247
+ }[] = []
248
+ for (const feature of topLevelFeatures) {
249
+ const snapshot = getSnapshot<AnnotationFeatureSnapshot>(feature)
250
+ const getSequence = async (start: number, end: number) => {
251
+ const result = await this.getSequence({
252
+ assemblyName: assembly,
253
+ refName: feature.refSeq,
254
+ start,
255
+ end,
256
+ })
257
+ return result.seq
258
+ }
259
+ for (const check of checks) {
260
+ const results = await check.checkFeature(snapshot, getSequence)
261
+ for (const result of results) {
262
+ allResults.push({
263
+ refSeq: feature.refSeq,
264
+ result,
265
+ topLevelFeatureId: feature._id,
266
+ })
267
+ }
268
+ }
269
+ }
270
+ if (allResults.length > 0) {
271
+ const checkStoreNames = refNames.map((r) => `checkresults-${r}`)
272
+ const checkTx = db.transaction(checkStoreNames, 'readwrite')
273
+ for (const { refSeq, result, topLevelFeatureId } of allResults) {
274
+ void checkTx
275
+ .objectStore(`checkresults-${refSeq}`)
276
+ .put({ ...result, featureId: topLevelFeatureId })
277
+ }
278
+ await checkTx.done
279
+ // Add new check results to client store
280
+ for (const { result } of allResults) {
281
+ this.clientStore.addCheckResult(result)
282
+ }
283
+ }
284
+ }
285
+
286
+ return new ValidationResultSet()
287
+ }
288
+
289
+ async searchFeatures(
290
+ term: string,
291
+ assemblies: string[],
292
+ ): Promise<AnnotationFeatureSnapshot[]> {
293
+ return []
294
+ }
295
+
296
+ async getCheckResults(assemblyName: string): Promise<CheckResultSnapshot[]> {
297
+ const regions = await this.getRegions(assemblyName)
298
+ const refNames = regions.map((r) => r.refName)
299
+ const db = await openDb(assemblyName, refNames)
300
+ const checkResults: CheckResultSnapshot[] = []
301
+ const storeNames = refNames.map((r) => `checkresults-${r}`)
302
+ const tx = db.transaction(storeNames)
303
+ for (const storeName of storeNames) {
304
+ for await (const cursor of tx.objectStore(storeName).iterate()) {
305
+ checkResults.push(cursor.value as CheckResultSnapshot)
306
+ }
307
+ }
308
+ return checkResults
309
+ }
310
+
311
+ async getChanges(
312
+ assemblyName: string,
313
+ opts: GetChangesOpts = {},
314
+ ): Promise<GetChangesResult> {
315
+ const regions = await this.getRegions(assemblyName)
316
+ const refNames = regions.map((r) => r.refName)
317
+ const db = await openDb(assemblyName, refNames)
318
+ let changes: ChangeDocument[] = []
319
+ for await (const cursor of db.transaction('changes').store.iterate()) {
320
+ changes.push({
321
+ sequence: cursor.key,
322
+ ...(cursor.value as SerializedChange & { createdAt: string }),
323
+ })
324
+ }
325
+
326
+ const { filters } = opts
327
+ if (filters?.user) {
328
+ const re = new RegExp(filters.user, 'i')
329
+ changes = changes.filter((c) => c.user !== undefined && re.test(c.user))
330
+ }
331
+ if (filters?.typeName) {
332
+ changes = changes.filter((c) => c.typeName === filters.typeName)
333
+ }
334
+ if (filters?.startTime) {
335
+ const start = new Date(filters.startTime).getTime()
336
+ changes = changes.filter((c) => new Date(c.createdAt).getTime() >= start)
337
+ }
338
+ if (filters?.endTime) {
339
+ const end = new Date(filters.endTime).getTime()
340
+ changes = changes.filter((c) => new Date(c.createdAt).getTime() <= end)
341
+ }
342
+
343
+ const totalCount = changes.length
344
+
345
+ const sortField = opts.sortField ?? 'sequence'
346
+ const sortOrder = opts.sortOrder ?? 'desc'
347
+ const direction = sortOrder === 'asc' ? 1 : -1
348
+ changes.sort((a, b) => {
349
+ const aVal = a[sortField as keyof ChangeDocument]
350
+ const bVal = b[sortField as keyof ChangeDocument]
351
+ if (aVal === bVal) {
352
+ return 0
353
+ }
354
+ if (aVal === undefined) {
355
+ return 1
356
+ }
357
+ if (bVal === undefined) {
358
+ return -1
359
+ }
360
+ return aVal < bVal ? -direction : direction
361
+ })
362
+
363
+ if (opts.page !== undefined && opts.pageSize !== undefined) {
364
+ const start = opts.page * opts.pageSize
365
+ changes = changes.slice(start, start + opts.pageSize)
366
+ }
367
+
368
+ return { changes, totalCount }
369
+ }
370
+ }
@@ -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
  },