@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,448 @@
1
+ import { ConfigurationReference, getConf } from '@jbrowse/core/configuration'
2
+ import { InternetAccount } from '@jbrowse/core/pluggableElementTypes'
3
+ import {
4
+ AbstractSessionModel,
5
+ isAbstractMenuManager,
6
+ isElectron,
7
+ } from '@jbrowse/core/util'
8
+ import { Change } from 'apollo-common'
9
+ import {
10
+ ChangeMessage,
11
+ CheckResultUpdate,
12
+ RequestUserInformationMessage,
13
+ UserLocation,
14
+ UserLocationMessage,
15
+ getDecodedToken,
16
+ makeUserSessionId,
17
+ } from 'apollo-shared'
18
+ import { autorun } from 'mobx'
19
+ import { Instance, flow, getRoot, types } from 'mobx-state-tree'
20
+ import { io } from 'socket.io-client'
21
+
22
+ import { ApolloSessionModel, Collaborator } from '../session'
23
+ import { ApolloRootModel } from '../types'
24
+ import { createFetchErrorMessage } from '../util'
25
+ import { addMenuItems } from './addMenuItems'
26
+ import { AuthTypeSelector } from './components/AuthTypeSelector'
27
+ import { ApolloInternetAccountConfigModel } from './configSchema'
28
+
29
+ type AuthType = 'google' | 'microsoft' | 'guest'
30
+
31
+ type Role = 'admin' | 'user' | 'readOnly'
32
+
33
+ const inWebWorker = typeof sessionStorage === 'undefined'
34
+
35
+ const stateModelFactory = (configSchema: ApolloInternetAccountConfigModel) => {
36
+ return InternetAccount.named('ApolloInternetAccount')
37
+ .props({
38
+ type: types.literal('ApolloInternetAccount'),
39
+ configuration: ConfigurationReference(configSchema),
40
+ })
41
+ .views((self) => ({
42
+ get baseURL(): string {
43
+ return getConf(self, 'baseURL')
44
+ },
45
+ getUserId() {
46
+ const token = self.retrieveToken()
47
+ if (!token) {
48
+ return
49
+ }
50
+ const dec = getDecodedToken(token)
51
+ return dec.id
52
+ },
53
+ }))
54
+ .volatile(() => ({
55
+ role: undefined as Role | undefined,
56
+ }))
57
+ .actions((self) => {
58
+ let roleNotificationSent = false
59
+ return {
60
+ setRole() {
61
+ const token = self.retrieveToken()
62
+ if (!token) {
63
+ self.role = undefined
64
+ return
65
+ }
66
+ const dec = getDecodedToken(token)
67
+ const { role } = dec
68
+ if (!role && !roleNotificationSent) {
69
+ const { session } = getRoot<ApolloRootModel>(self)
70
+ ;(session as unknown as AbstractSessionModel).notify(
71
+ 'You have registered as a user but have not been given access. Ask your administrator to enable access for your account.',
72
+ 'warning',
73
+ )
74
+ // notify
75
+ roleNotificationSent = true
76
+ }
77
+ if (self.role !== role) {
78
+ self.role = role
79
+ }
80
+ },
81
+ }
82
+ })
83
+ .actions((self) => {
84
+ let listener: (event: MessageEvent) => void
85
+ return {
86
+ addMessageChannel(
87
+ resolve: (token: string) => void,
88
+ reject: (error: Error) => void,
89
+ ) {
90
+ listener = (event) => {
91
+ // this should probably get better handling, but ignored for now
92
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
93
+ this.finishOAuthWindow(event, resolve, reject)
94
+ }
95
+ window.addEventListener('message', listener)
96
+ },
97
+ deleteMessageChannel() {
98
+ window.removeEventListener('message', listener)
99
+ },
100
+ async finishOAuthWindow(
101
+ event: MessageEvent,
102
+ resolve: (token: string) => void,
103
+ reject: (error: Error) => void,
104
+ ) {
105
+ if (
106
+ event.data.name !== `JBrowseAuthWindow-${self.internetAccountId}`
107
+ ) {
108
+ return this.deleteMessageChannel()
109
+ }
110
+ const redirectUriWithInfo = event.data.redirectUri
111
+ const fixedQueryString = redirectUriWithInfo.replace('#', '?')
112
+ const redirectUrl = new URL(fixedQueryString)
113
+ const queryStringSearch = redirectUrl.search
114
+ const urlParams = new URLSearchParams(queryStringSearch)
115
+ const token = urlParams.get('access_token')
116
+ this.deleteMessageChannel()
117
+ if (!token) {
118
+ return reject(new Error('Error with token endpoint'))
119
+ }
120
+ self.storeToken(token)
121
+ self.setRole()
122
+ return resolve(token)
123
+ },
124
+ async openAuthWindow(
125
+ type: string,
126
+ resolve: (token: string) => void,
127
+ reject: (error: Error) => void,
128
+ ) {
129
+ const redirectUri = isElectron
130
+ ? 'http://localhost/auth'
131
+ : window.location.origin + window.location.pathname
132
+ const url = new URL('auth/login', self.baseURL)
133
+ const params = new URLSearchParams({
134
+ type,
135
+ redirect_uri: redirectUri,
136
+ })
137
+ url.search = params.toString()
138
+ const eventName = `JBrowseAuthWindow-${self.internetAccountId}`
139
+ if (isElectron) {
140
+ const { ipcRenderer } = window.require('electron')
141
+ const redirectUriFromElectron = await ipcRenderer.invoke(
142
+ 'openAuthWindow',
143
+ {
144
+ internetAccountId: self.internetAccountId,
145
+ data: { redirect_uri: redirectUri },
146
+ url: url.toString(),
147
+ },
148
+ )
149
+ const eventFromDesktop = new MessageEvent('message', {
150
+ data: { name: eventName, redirectUri: redirectUriFromElectron },
151
+ })
152
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
153
+ this.finishOAuthWindow(eventFromDesktop, resolve, reject)
154
+ } else {
155
+ this.addMessageChannel(resolve, reject)
156
+ window.open(url, eventName, 'width=500,height=600')
157
+ }
158
+ },
159
+ }
160
+ })
161
+ .actions((self) => ({
162
+ async getTokenFromUser(
163
+ resolve: (token: string) => void,
164
+ reject: (error: Error) => void,
165
+ ): Promise<void> {
166
+ const { baseURL } = self
167
+ const authType = await new Promise(
168
+ (resolve: (authType: AuthType) => void, reject) => {
169
+ const { session } = getRoot<ApolloRootModel>(self)
170
+ const { baseURL, name } = self
171
+ ;(session as unknown as AbstractSessionModel).queueDialog(
172
+ (doneCallback: () => void) => [
173
+ AuthTypeSelector,
174
+ {
175
+ name,
176
+ handleClose: (newAuthType?: AuthType | Error) => {
177
+ if (!newAuthType) {
178
+ reject(new Error('user cancelled entry'))
179
+ } else if (newAuthType instanceof Error) {
180
+ reject(newAuthType)
181
+ } else {
182
+ resolve(newAuthType)
183
+ }
184
+ doneCallback()
185
+ },
186
+ baseURL,
187
+ },
188
+ ],
189
+ )
190
+ },
191
+ )
192
+ if (authType !== 'guest') {
193
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
194
+ self.openAuthWindow(authType, resolve, reject)
195
+ return
196
+ }
197
+ const url = new URL('auth/login', baseURL)
198
+ const searchParams = new URLSearchParams({ type: authType })
199
+ url.search = searchParams.toString()
200
+ const uri = url.toString()
201
+ const response = await fetch(uri)
202
+ if (!response.ok) {
203
+ const errorMessage = await createFetchErrorMessage(
204
+ response,
205
+ 'Error when logging in',
206
+ )
207
+ return reject(new Error(errorMessage))
208
+ }
209
+ const { token } = await response.json()
210
+ resolve(token)
211
+ },
212
+ }))
213
+ .volatile(() => ({
214
+ lastChangeSequenceNumber: undefined as number | undefined,
215
+ }))
216
+ .actions((self) => ({
217
+ setLastChangeSequenceNumber(sequenceNumber: number) {
218
+ self.lastChangeSequenceNumber = sequenceNumber
219
+ },
220
+ }))
221
+ .actions((self) => ({
222
+ updateLastChangeSequenceNumber: flow(
223
+ function* updateLastChangeSequenceNumber() {
224
+ const { baseURL } = self
225
+ const url = new URL('changes', baseURL)
226
+ const searchParams = new URLSearchParams({ limit: '1' })
227
+ url.search = searchParams.toString()
228
+ const uri = url.toString()
229
+ const apolloFetch = self.getFetcher({
230
+ locationType: 'UriLocation',
231
+ uri,
232
+ })
233
+
234
+ const response = yield apolloFetch(uri, { method: 'GET' })
235
+ if (!response.ok) {
236
+ const errorMessage = yield createFetchErrorMessage(
237
+ response,
238
+ 'Error when fetching server LastChangeSequence',
239
+ )
240
+ throw new Error(errorMessage)
241
+ }
242
+ const changes = yield response.json()
243
+ const sequence = changes.length > 0 ? changes[0].sequence : 0
244
+ self.setLastChangeSequenceNumber(sequence)
245
+ },
246
+ ),
247
+ getMissingChanges: flow(function* getMissingChanges() {
248
+ const { session } = getRoot<ApolloRootModel>(self)
249
+ const { changeManager } = (session as ApolloSessionModel)
250
+ .apolloDataStore
251
+ if (!self.lastChangeSequenceNumber) {
252
+ throw new Error(
253
+ 'No LastChangeSequence stored in session. Please, refresh you browser to get last updates from server',
254
+ )
255
+ }
256
+ const { baseURL, lastChangeSequenceNumber } = self
257
+
258
+ const url = new URL('changes', baseURL)
259
+ const searchParams = new URLSearchParams({
260
+ since: String(lastChangeSequenceNumber),
261
+ sort: '1',
262
+ })
263
+ url.search = searchParams.toString()
264
+ const uri = url.toString()
265
+ const apolloFetch = self.getFetcher({
266
+ locationType: 'UriLocation',
267
+ uri,
268
+ })
269
+
270
+ const response = yield apolloFetch(uri, { method: 'GET' })
271
+ if (!response.ok) {
272
+ console.error(
273
+ `Error when fetching the last updates to recover socket connection — ${response.status}`,
274
+ )
275
+ return
276
+ }
277
+ const serializedChanges = yield response.json()
278
+ for (const serializedChange of serializedChanges) {
279
+ const change = Change.fromJSON(serializedChange)
280
+ void changeManager?.submit(change, { submitToBackend: false })
281
+ }
282
+ }),
283
+ }))
284
+ .volatile((self) => ({
285
+ socket: io(self.baseURL),
286
+ }))
287
+ .actions((self) => ({
288
+ addSocketListeners() {
289
+ const { session } = getRoot<ApolloRootModel>(self)
290
+ const { notify } = session as unknown as AbstractSessionModel
291
+ const token = self.retrieveToken()
292
+ if (!token) {
293
+ throw new Error('No Token found')
294
+ }
295
+ const { socket } = self
296
+ const { addCheckResult, changeManager, deleteCheckResult } = (
297
+ session as ApolloSessionModel
298
+ ).apolloDataStore
299
+ socket.on('connect', async () => {
300
+ await self.getMissingChanges()
301
+ })
302
+ socket.on('connect_error', () => {
303
+ notify('Could not connect to the Apollo server.', 'error')
304
+ })
305
+ socket.on('COMMON', (message: ChangeMessage | CheckResultUpdate) => {
306
+ if ('checkResult' in message) {
307
+ if (message.deleted) {
308
+ deleteCheckResult(message.checkResult._id.toString())
309
+ } else {
310
+ addCheckResult(message.checkResult)
311
+ }
312
+ return
313
+ }
314
+ // Save server last change sequence into session storage
315
+ sessionStorage.setItem(
316
+ 'LastChangeSequence',
317
+ String(message.changeSequence),
318
+ )
319
+ if (message.userSessionId === token) {
320
+ return // we did this change, no need to apply it again
321
+ }
322
+ const change = Change.fromJSON(message.changeInfo)
323
+ void changeManager?.submit(change, { submitToBackend: false })
324
+ })
325
+ socket.on('USER_LOCATION', (message: UserLocationMessage) => {
326
+ const { channel, locations, userName, userSessionId } = message
327
+ const user = getDecodedToken(token)
328
+ const localSessionId = makeUserSessionId(user)
329
+ if (channel === 'USER_LOCATION' && userSessionId !== localSessionId) {
330
+ const collaborator: Collaborator = {
331
+ name: userName,
332
+ id: userSessionId,
333
+ locations,
334
+ }
335
+ session.addOrUpdateCollaborator(collaborator)
336
+ }
337
+ })
338
+ socket.on(
339
+ 'REQUEST_INFORMATION',
340
+ (message: RequestUserInformationMessage) => {
341
+ const { channel, reqType, userSessionId } = message
342
+ if (channel === 'REQUEST_INFORMATION' && userSessionId !== token) {
343
+ switch (reqType) {
344
+ case 'CURRENT_LOCATION': {
345
+ session.broadcastLocations()
346
+ break
347
+ }
348
+ }
349
+ }
350
+ },
351
+ )
352
+ },
353
+ }))
354
+ .actions((self) => {
355
+ async function postUserLocation(userLoc: UserLocation[]) {
356
+ const { baseURL } = self
357
+ const url = new URL('users/userLocation', baseURL).href
358
+ const userLocation = new URLSearchParams(JSON.stringify(userLoc))
359
+
360
+ const apolloFetch = self.getFetcher({
361
+ locationType: 'UriLocation',
362
+ uri: url,
363
+ })
364
+ try {
365
+ const response = await apolloFetch(url, {
366
+ method: 'POST',
367
+ body: userLocation,
368
+ })
369
+ if (!response.ok) {
370
+ throw new Error('ignore') // ignore message, will get caught by "catch"
371
+ }
372
+ } catch {
373
+ console.error('Broadcasting user location failed')
374
+ }
375
+ }
376
+ const debounceTimeout = 300
377
+ const debouncePostUserLocation = (
378
+ fn: (userLocation: UserLocation[]) => void,
379
+ ) => {
380
+ let timeoutId: ReturnType<typeof setTimeout>
381
+ return (userLocation: UserLocation[]) => {
382
+ clearTimeout(timeoutId)
383
+ timeoutId = setTimeout(() => fn(userLocation), debounceTimeout)
384
+ }
385
+ }
386
+ return { postUserLocation: debouncePostUserLocation(postUserLocation) }
387
+ })
388
+ .actions((self) => ({
389
+ initialize: flow(function* initialize(role: Role) {
390
+ if (role === 'admin') {
391
+ const rootModel = getRoot(self)
392
+ if (isAbstractMenuManager(rootModel)) {
393
+ addMenuItems(rootModel)
394
+ }
395
+ }
396
+ // Get and set server last change sequence into session storage
397
+ yield self.updateLastChangeSequenceNumber()
398
+ // Open socket listeners
399
+ self.addSocketListeners()
400
+ // request user locations
401
+ const { baseURL } = self
402
+ const uri = new URL('/users/locations', baseURL).href
403
+ const apolloFetch = self.getFetcher({
404
+ locationType: 'UriLocation',
405
+ uri,
406
+ })
407
+ yield apolloFetch(uri, { method: 'GET' })
408
+ window.addEventListener('beforeunload', () => {
409
+ self.postUserLocation([])
410
+ })
411
+ document.addEventListener('visibilitychange', () => {
412
+ // fires when user switches tabs, apps, goes to homescreen, etc.
413
+ if (document.visibilityState === 'hidden') {
414
+ self.postUserLocation([])
415
+ }
416
+ // fires when app transitions from prerender, user returns to the app / tab.
417
+ if (document.visibilityState === 'visible') {
418
+ const { session } = getRoot<ApolloRootModel>(self)
419
+ session.broadcastLocations()
420
+ }
421
+ })
422
+ }),
423
+ }))
424
+ .actions((self) => ({
425
+ afterAttach() {
426
+ self.setRole()
427
+ autorun(
428
+ async (reaction) => {
429
+ if (inWebWorker) {
430
+ return
431
+ }
432
+ if (self.role) {
433
+ await self.initialize(self.role)
434
+ reaction.dispose()
435
+ }
436
+ },
437
+ { name: 'ApolloInternetAccount' },
438
+ )
439
+ },
440
+ }))
441
+ }
442
+
443
+ export default stateModelFactory
444
+ export type ApolloInternetAccountStateModel = ReturnType<
445
+ typeof stateModelFactory
446
+ >
447
+ export type ApolloInternetAccountModel =
448
+ Instance<ApolloInternetAccountStateModel>
@@ -0,0 +1,117 @@
1
+ import {
2
+ SessionWithWidgets,
3
+ getSession,
4
+ isSessionModelWithWidgets,
5
+ } from '@jbrowse/core/util'
6
+ import { types } from 'mobx-state-tree'
7
+
8
+ interface JobsEntry {
9
+ name: string
10
+ cancelCallback?: () => void
11
+ progressPct?: number
12
+ statusMessage?: string
13
+ }
14
+ interface JobsListModel {
15
+ id: number
16
+ type: 'JobsListWidget'
17
+ jobs: JobsEntry[]
18
+ finished: JobsEntry[]
19
+ queued: JobsEntry[]
20
+ aborted: JobsEntry[]
21
+ addJob(job: JobsEntry): void
22
+ removeJob(jobName: string): void
23
+ addFinishedJob(job: JobsEntry): void
24
+ addQueuedJob(job: JobsEntry): void
25
+ addAbortedJob(job: JobsEntry): void
26
+ removeQueuedJob(jobName: string): void
27
+ updateJobStatusMessage(jobName: string, message?: string): void
28
+ updateJobProgressPct(jobName: string, pct: number): void
29
+ }
30
+
31
+ export const ApolloJobModel = types
32
+ .model('JobsManager', {})
33
+ .views((self) => ({
34
+ get jobStatusWidget() {
35
+ const { widgets } = getSession(self) as SessionWithWidgets
36
+ let jobStatusWidget = widgets.get('JobsList')
37
+ if (!jobStatusWidget) {
38
+ // @ts-expect-error: addWidget function not detected on the session
39
+ jobStatusWidget = getSession(self).addWidget(
40
+ 'JobsListWidget',
41
+ 'JobsList',
42
+ )
43
+ }
44
+ return jobStatusWidget as unknown as JobsListModel
45
+ },
46
+ }))
47
+ .actions((self) => ({
48
+ /**
49
+ * updates the status message and the progress percent of the provided job
50
+ * @param jobName - the name of the job to be updated
51
+ * @param statusMessage - the message to be communicated to the user
52
+ * @param progressPct - the percent through the run the job is
53
+ */
54
+ update(jobName: string, statusMessage: string, progressPct?: number) {
55
+ self.jobStatusWidget.updateJobStatusMessage(jobName, statusMessage)
56
+ if (progressPct) {
57
+ self.jobStatusWidget.updateJobProgressPct(jobName, progressPct)
58
+ }
59
+ },
60
+ /**
61
+ * aborts the provided job with a message to the user
62
+ * @param jobName - the name of the job to be aborted
63
+ * @param msg - a message to communicate to the user about the abort operation
64
+ */
65
+ abortJob(jobName: string, msg?: string) {
66
+ const session = getSession(self)
67
+ if (isSessionModelWithWidgets(session)) {
68
+ session.showWidget(self.jobStatusWidget)
69
+ self.jobStatusWidget.updateJobStatusMessage(
70
+ jobName,
71
+ msg ?? 'Aborted unexpectedly',
72
+ )
73
+ // this is done to avoid issues with reusing nodes from other state trees
74
+ const indx = self.jobStatusWidget.jobs.findIndex(
75
+ (job) => job.name === jobName,
76
+ )
77
+ const job = self.jobStatusWidget.jobs[indx]
78
+ // object needs to be shallow copied before it is removed from the state tree
79
+ self.jobStatusWidget.addAbortedJob({ ...job })
80
+ // removes the job from the state tree, this node is inaccessible thereafter
81
+ self.jobStatusWidget.removeJob(jobName) as unknown as JobsEntry
82
+ session.notify('Job aborted', 'info')
83
+ }
84
+ },
85
+ /**
86
+ * opens the job status widget and adds the job to the running jobs
87
+ * @param job - the job to be run within the JobsManager
88
+ */
89
+ runJob(job: JobsEntry) {
90
+ const session = getSession(self)
91
+ if (isSessionModelWithWidgets(session)) {
92
+ session.showWidget(self.jobStatusWidget)
93
+ self.jobStatusWidget.addJob(job)
94
+ }
95
+ },
96
+ /**
97
+ * sets the progress and status message of the provided job
98
+ * adds the finished jobs to the list of finished jobs
99
+ * clears the jobs manager of the now done job
100
+ * begins to run the next job if one is queued
101
+ * @param job - the job to be completed
102
+ */
103
+ done(job: JobsEntry) {
104
+ const session = getSession(self)
105
+ if (isSessionModelWithWidgets(session)) {
106
+ session.showWidget(self.jobStatusWidget)
107
+ // this.setProgressPct(100)
108
+ self.jobStatusWidget.removeJob(job.name)
109
+ self.jobStatusWidget.addFinishedJob({
110
+ name: job.name,
111
+ statusMessage: 'All operations successful',
112
+ progressPct: 100,
113
+ cancelCallback: job.cancelCallback,
114
+ })
115
+ }
116
+ },
117
+ }))