@apollo-annotation/jbrowse-plugin-apollo 0.3.7 → 0.3.8

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 (71) hide show
  1. package/dist/index.esm.js +2371 -1642
  2. package/dist/index.esm.js.map +1 -1
  3. package/dist/jbrowse-plugin-apollo.cjs.development.js +2384 -1641
  4. package/dist/jbrowse-plugin-apollo.cjs.development.js.map +1 -1
  5. package/dist/jbrowse-plugin-apollo.cjs.production.min.js +1 -1
  6. package/dist/jbrowse-plugin-apollo.cjs.production.min.js.map +1 -1
  7. package/dist/jbrowse-plugin-apollo.umd.development.js +4387 -2952
  8. package/dist/jbrowse-plugin-apollo.umd.development.js.map +1 -1
  9. package/dist/jbrowse-plugin-apollo.umd.production.min.js +1 -1
  10. package/dist/jbrowse-plugin-apollo.umd.production.min.js.map +1 -1
  11. package/package.json +15 -15
  12. package/src/ApolloInternetAccount/model.ts +48 -13
  13. package/src/BackendDrivers/CollaborationServerDriver.ts +23 -2
  14. package/src/ChangeManager.ts +33 -13
  15. package/src/FeatureDetailsWidget/ApolloTranscriptDetailsWidget.tsx +64 -5
  16. package/src/FeatureDetailsWidget/TranscriptSequence.tsx +70 -73
  17. package/src/FeatureDetailsWidget/TranscriptWidgetEditLocation.tsx +33 -31
  18. package/src/LinearApolloDisplay/components/LinearApolloDisplay.tsx +60 -72
  19. package/src/LinearApolloDisplay/glyphs/BoxGlyph.ts +50 -194
  20. package/src/LinearApolloDisplay/glyphs/GeneGlyph.ts +441 -180
  21. package/src/LinearApolloDisplay/glyphs/GenericChildGlyph.ts +53 -34
  22. package/src/LinearApolloDisplay/glyphs/Glyph.ts +7 -9
  23. package/src/LinearApolloDisplay/stateModel/base.ts +34 -43
  24. package/src/LinearApolloDisplay/stateModel/layouts.ts +3 -2
  25. package/src/LinearApolloDisplay/stateModel/mouseEvents.ts +32 -261
  26. package/src/LinearApolloDisplay/stateModel/rendering.ts +43 -343
  27. package/src/LinearApolloReferenceSequenceDisplay/components/LinearApolloReferenceSequenceDisplay.tsx +87 -0
  28. package/src/LinearApolloReferenceSequenceDisplay/components/index.ts +1 -0
  29. package/src/LinearApolloReferenceSequenceDisplay/configSchema.ts +7 -0
  30. package/src/LinearApolloReferenceSequenceDisplay/index.ts +3 -0
  31. package/src/LinearApolloReferenceSequenceDisplay/stateModel/base.ts +227 -0
  32. package/src/LinearApolloReferenceSequenceDisplay/stateModel/index.ts +25 -0
  33. package/src/LinearApolloReferenceSequenceDisplay/stateModel/rendering.ts +481 -0
  34. package/src/LinearApolloSixFrameDisplay/components/LinearApolloSixFrameDisplay.tsx +95 -38
  35. package/src/LinearApolloSixFrameDisplay/glyphs/GeneGlyph.ts +221 -201
  36. package/src/LinearApolloSixFrameDisplay/glyphs/Glyph.ts +12 -8
  37. package/src/LinearApolloSixFrameDisplay/stateModel/base.ts +42 -4
  38. package/src/LinearApolloSixFrameDisplay/stateModel/layouts.ts +4 -8
  39. package/src/LinearApolloSixFrameDisplay/stateModel/mouseEvents.ts +73 -97
  40. package/src/LinearApolloSixFrameDisplay/stateModel/rendering.ts +49 -61
  41. package/src/TabularEditor/HybridGrid/Feature.tsx +16 -14
  42. package/src/TabularEditor/HybridGrid/HybridGrid.tsx +7 -5
  43. package/src/components/AddAssembly.tsx +1 -1
  44. package/src/components/AddAssemblyAliases.tsx +1 -1
  45. package/src/components/AddChildFeature.tsx +5 -2
  46. package/src/components/AddFeature.tsx +9 -3
  47. package/src/components/AddRefSeqAliases.tsx +9 -9
  48. package/src/components/CopyFeature.tsx +3 -1
  49. package/src/components/CreateApolloAnnotation.tsx +1 -0
  50. package/src/components/DeleteAssembly.tsx +1 -1
  51. package/src/components/EditZoomThresholdDialog.tsx +69 -0
  52. package/src/components/FilterFeatures.tsx +7 -7
  53. package/src/components/FilterTranscripts.tsx +6 -6
  54. package/src/components/ImportFeatures.tsx +1 -1
  55. package/src/components/ManageChecks.tsx +1 -1
  56. package/src/components/MergeTranscripts.tsx +12 -15
  57. package/src/components/OntologyTermMultiSelect.tsx +11 -11
  58. package/src/components/OpenLocalFile.tsx +11 -7
  59. package/src/components/ViewCheckResults.tsx +1 -1
  60. package/src/components/index.ts +1 -0
  61. package/src/config.ts +6 -0
  62. package/src/index.ts +42 -105
  63. package/src/makeDisplayComponent.tsx +0 -1
  64. package/src/menus/index.ts +1 -0
  65. package/src/{ApolloInternetAccount/addMenuItems.ts → menus/topLevelMenu.ts} +56 -47
  66. package/src/menus/topLevelMenuAdmin.ts +154 -0
  67. package/src/session/session.ts +162 -116
  68. package/src/util/annotationFeatureUtils.ts +15 -21
  69. package/src/util/displayUtils.ts +149 -0
  70. package/src/util/glyphUtils.ts +152 -0
  71. package/src/util/mouseEventsUtils.ts +32 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@apollo-annotation/jbrowse-plugin-apollo",
3
- "version": "0.3.7",
3
+ "version": "0.3.8",
4
4
  "description": "Apollo plugin for JBrowse 2",
5
5
  "keywords": [
6
6
  "jbrowse",
@@ -48,15 +48,15 @@
48
48
  }
49
49
  },
50
50
  "dependencies": {
51
- "@apollo-annotation/common": "^0.3.7",
52
- "@apollo-annotation/mst": "^0.3.7",
53
- "@apollo-annotation/shared": "^0.3.7",
51
+ "@apollo-annotation/common": "^0.3.8",
52
+ "@apollo-annotation/mst": "^0.3.8",
53
+ "@apollo-annotation/shared": "^0.3.8",
54
54
  "@emotion/react": "^11.10.6",
55
55
  "@emotion/styled": "^11.10.6",
56
56
  "@gmod/gff": "1.2.0",
57
- "@jbrowse/plugin-authentication": "^3.0.1",
58
- "@jbrowse/plugin-linear-genome-view": "^3.0.1",
59
- "@mui/icons-material": "^5.8.4",
57
+ "@jbrowse/plugin-authentication": "^3.6.5",
58
+ "@jbrowse/plugin-linear-genome-view": "^3.6.5",
59
+ "@mui/icons-material": "^6.5.0",
60
60
  "@types/jsonpath": "^0.2.0",
61
61
  "autosuggest-highlight": "^3.3.4",
62
62
  "bson-objectid": "^2.0.4",
@@ -71,12 +71,12 @@
71
71
  "tslib": "^2.3.1"
72
72
  },
73
73
  "devDependencies": {
74
- "@jbrowse/cli": "^3.0.1",
75
- "@jbrowse/core": "^3.0.1",
76
- "@jbrowse/development-tools": "^2.1.1",
74
+ "@jbrowse/cli": "^3.6.5",
75
+ "@jbrowse/core": "^3.6.5",
76
+ "@jbrowse/development-tools": "^2.2.1",
77
77
  "@jest/globals": "^29.0.3",
78
- "@mui/material": "^6.0.0",
79
- "@mui/x-data-grid": "^7.0.0",
78
+ "@mui/material": "^7.0.0",
79
+ "@mui/x-data-grid": "^8.0.0",
80
80
  "@types/autosuggest-highlight": "^3",
81
81
  "@types/file-saver": "^2",
82
82
  "@types/node": "^18.14.2",
@@ -99,7 +99,7 @@
99
99
  "react": "^18.2.0",
100
100
  "react-dom": "^18.2.0",
101
101
  "rimraf": "^3.0.2",
102
- "rollup": "^2.59.0",
102
+ "rollup": "^2.79.2",
103
103
  "rxjs": "^7.4.0",
104
104
  "serve": "^14.0.1",
105
105
  "shx": "^0.3.3",
@@ -111,9 +111,9 @@
111
111
  },
112
112
  "peerDependencies": {
113
113
  "@jbrowse/core": "^3.0.1",
114
- "@mui/material": "^6.0.0",
114
+ "@mui/material": "^7.0.0",
115
115
  "mobx": "^6.6.1",
116
- "mobx-react": "^7.2.1",
116
+ "mobx-react": "^9.0.0",
117
117
  "mobx-state-tree": "^5.4.0",
118
118
  "prop-types": "^15.8.1",
119
119
  "react": "^18.2.0",
@@ -23,14 +23,14 @@ import {
23
23
  isElectron,
24
24
  } from '@jbrowse/core/util'
25
25
  import { autorun } from 'mobx'
26
- import { type Instance, flow, getRoot, types } from 'mobx-state-tree'
26
+ import { type Instance, flow, getRoot, isAlive, types } from 'mobx-state-tree'
27
27
  import { io } from 'socket.io-client'
28
28
 
29
+ import { addTopLevelAdminMenus } from '../menus/topLevelMenuAdmin'
29
30
  import { type Collaborator } from '../session'
30
31
  import { type ApolloRootModel } from '../types'
31
32
  import { createFetchErrorMessage } from '../util'
32
33
 
33
- import { addMenuItems } from './addMenuItems'
34
34
  import { AuthTypeSelector } from './components/AuthTypeSelector'
35
35
  import { type ApolloInternetAccountConfigModel } from './configSchema'
36
36
 
@@ -61,7 +61,9 @@ const stateModelFactory = (configSchema: ApolloInternetAccountConfigModel) => {
61
61
  }))
62
62
  .volatile(() => ({
63
63
  role: undefined as Role | undefined,
64
+ controller: new AbortController(),
64
65
  }))
66
+
65
67
  .actions((self) => {
66
68
  let roleNotificationSent = false
67
69
  return {
@@ -205,7 +207,7 @@ const stateModelFactory = (configSchema: ApolloInternetAccountConfigModel) => {
205
207
  const searchParams = new URLSearchParams({ type: authType })
206
208
  url.search = searchParams.toString()
207
209
  const uri = url.toString()
208
- const response = await fetch(uri)
210
+ const response = await fetch(uri, { signal: self.controller.signal })
209
211
  if (!response.ok) {
210
212
  const errorMessage = await createFetchErrorMessage(
211
213
  response,
@@ -239,7 +241,18 @@ const stateModelFactory = (configSchema: ApolloInternetAccountConfigModel) => {
239
241
  uri,
240
242
  })
241
243
 
242
- const response = yield apolloFetch(uri, { method: 'GET' })
244
+ let response: Response
245
+ try {
246
+ response = yield apolloFetch(uri, {
247
+ method: 'GET',
248
+ signal: self.controller.signal,
249
+ })
250
+ } catch (error) {
251
+ if (!self.controller.signal.aborted) {
252
+ console.error(error)
253
+ }
254
+ return
255
+ }
243
256
  if (!response.ok) {
244
257
  const errorMessage = yield createFetchErrorMessage(
245
258
  response,
@@ -274,7 +287,18 @@ const stateModelFactory = (configSchema: ApolloInternetAccountConfigModel) => {
274
287
  uri,
275
288
  })
276
289
 
277
- const response = yield apolloFetch(uri, { method: 'GET' })
290
+ let response: Response
291
+ try {
292
+ response = yield apolloFetch(uri, {
293
+ method: 'GET',
294
+ signal: self.controller.signal,
295
+ })
296
+ } catch (error) {
297
+ if (!self.controller.signal.aborted) {
298
+ console.error(error)
299
+ }
300
+ return
301
+ }
278
302
  if (!response.ok) {
279
303
  console.error(
280
304
  `Error when fetching the last updates to recover socket connection — ${response.status}`,
@@ -300,11 +324,13 @@ const stateModelFactory = (configSchema: ApolloInternetAccountConfigModel) => {
300
324
  if (!token) {
301
325
  throw new Error('No Token found')
302
326
  }
327
+ const user = getDecodedToken(token)
328
+ const localSessionId = makeUserSessionId(user)
303
329
  const { socket } = self
304
330
  const { addCheckResult, changeManager, deleteCheckResult } =
305
331
  session.apolloDataStore
306
- socket.on('connect', async () => {
307
- await self.getMissingChanges()
332
+ socket.on('connect', () => {
333
+ void self.getMissingChanges()
308
334
  })
309
335
  socket.on('connect_error', (error) => {
310
336
  console.error(error)
@@ -324,7 +350,7 @@ const stateModelFactory = (configSchema: ApolloInternetAccountConfigModel) => {
324
350
  'LastChangeSequence',
325
351
  String(message.changeSequence),
326
352
  )
327
- if (message.userSessionId === token) {
353
+ if (message.userSessionId === localSessionId) {
328
354
  return // we did this change, no need to apply it again
329
355
  }
330
356
  const change = Change.fromJSON(message.changeInfo)
@@ -332,8 +358,6 @@ const stateModelFactory = (configSchema: ApolloInternetAccountConfigModel) => {
332
358
  })
333
359
  socket.on('USER_LOCATION', (message: UserLocationMessage) => {
334
360
  const { channel, locations, userName, userSessionId } = message
335
- const user = getDecodedToken(token)
336
- const localSessionId = makeUserSessionId(user)
337
361
  if (channel === 'USER_LOCATION' && userSessionId !== localSessionId) {
338
362
  const collaborator: Collaborator = {
339
363
  name: userName,
@@ -356,7 +380,10 @@ const stateModelFactory = (configSchema: ApolloInternetAccountConfigModel) => {
356
380
  }))
357
381
  .actions((self) => {
358
382
  async function postUserLocation(userLoc: UserLocation[]) {
359
- const { baseURL } = self
383
+ if (!isAlive(self)) {
384
+ return
385
+ }
386
+ const { baseURL, controller } = self
360
387
  const url = new URL('users/userLocation', baseURL).href
361
388
  const userLocation = new URLSearchParams(JSON.stringify(userLoc))
362
389
 
@@ -368,6 +395,7 @@ const stateModelFactory = (configSchema: ApolloInternetAccountConfigModel) => {
368
395
  const response = await apolloFetch(url, {
369
396
  method: 'POST',
370
397
  body: userLocation,
398
+ signal: controller.signal,
371
399
  })
372
400
  if (!response.ok) {
373
401
  throw new Error('ignore') // ignore message, will get caught by "catch"
@@ -395,7 +423,7 @@ const stateModelFactory = (configSchema: ApolloInternetAccountConfigModel) => {
395
423
  if (role === 'admin') {
396
424
  const rootModel = getRoot(self)
397
425
  if (isAbstractMenuManager(rootModel)) {
398
- addMenuItems(rootModel)
426
+ addTopLevelAdminMenus(rootModel)
399
427
  }
400
428
  }
401
429
  // Get and set server last change sequence into session storage
@@ -409,7 +437,10 @@ const stateModelFactory = (configSchema: ApolloInternetAccountConfigModel) => {
409
437
  locationType: 'UriLocation',
410
438
  uri,
411
439
  })
412
- yield apolloFetch(uri, { method: 'GET' })
440
+ yield apolloFetch(uri, {
441
+ method: 'GET',
442
+ signal: self.controller.signal,
443
+ })
413
444
  window.addEventListener('beforeunload', () => {
414
445
  self.postUserLocation([])
415
446
  })
@@ -449,6 +480,10 @@ const stateModelFactory = (configSchema: ApolloInternetAccountConfigModel) => {
449
480
  { name: 'ApolloInternetAccount' },
450
481
  )
451
482
  },
483
+ beforeDestroy() {
484
+ self.controller.abort('internet account beforeDestroy')
485
+ self.socket.close()
486
+ },
452
487
  }))
453
488
  }
454
489
 
@@ -4,7 +4,12 @@
4
4
  /* eslint-disable @typescript-eslint/no-unsafe-member-access */
5
5
  /* eslint-disable @typescript-eslint/no-unsafe-call */
6
6
  /* eslint-disable @typescript-eslint/no-unnecessary-condition */
7
- import { type AssemblySpecificChange, Change } from '@apollo-annotation/common'
7
+ import {
8
+ type AssemblySpecificChange,
9
+ Change,
10
+ type FeatureChange,
11
+ isFeatureChange,
12
+ } from '@apollo-annotation/common'
8
13
  import {
9
14
  type AnnotationFeatureSnapshot,
10
15
  type ApolloRefSeqI,
@@ -151,12 +156,28 @@ export class CollaborationServerDriver extends BackendDriver {
151
156
  )
152
157
  if (message.userSessionId !== token && message.channel === channel) {
153
158
  const change = Change.fromJSON(message.changeInfo)
154
- await changeManager.submit(change, { submitToBackend: false })
159
+ if (isFeatureChange(change) && this.haveDataForChange(change)) {
160
+ await changeManager.submit(change, { submitToBackend: false })
161
+ }
155
162
  }
156
163
  })
157
164
  }
158
165
  }
159
166
 
167
+ private haveDataForChange(change: FeatureChange): boolean {
168
+ const { assembly, changedIds } = change
169
+ const apolloAssembly = this.clientStore.assemblies.get(assembly)
170
+ if (!apolloAssembly) {
171
+ return false
172
+ }
173
+ for (const changedId of changedIds) {
174
+ if (this.clientStore.getFeature(changedId)) {
175
+ return true
176
+ }
177
+ }
178
+ return false
179
+ }
180
+
160
181
  /**
161
182
  * Call backend endpoint to get sequence by criteria
162
183
  * @param region - Searchable region containing refSeq, start and end
@@ -30,6 +30,7 @@ export class ChangeManager {
30
30
  constructor(private dataStore: ClientDataStore & IAnyStateTreeNode) {}
31
31
 
32
32
  recentChanges: Change[] = []
33
+ undoneChanges: Change[] = []
33
34
 
34
35
  async submit(change: Change, opts: SubmitOpts = {}) {
35
36
  const {
@@ -41,10 +42,15 @@ export class ChangeManager {
41
42
  const session = getSession(this.dataStore)
42
43
  const controller = new AbortController()
43
44
 
44
- const { jobsManager } = getSession(
45
+ const { jobsManager, isLocked } = getSession(
45
46
  this.dataStore,
46
47
  ) as unknown as ApolloSessionModel
47
48
 
49
+ if (isLocked) {
50
+ session.notify('Cannot submit changes in locked mode')
51
+ return
52
+ }
53
+
48
54
  const job = {
49
55
  name: change.typeName,
50
56
  statusMessage: 'Pre-validating',
@@ -90,7 +96,7 @@ export class ChangeManager {
90
96
  )
91
97
  if (!results2.ok) {
92
98
  // notify of invalid change and revert
93
- await this.revert(change)
99
+ await this.undo(change)
94
100
  }
95
101
 
96
102
  if (submitToBackend) {
@@ -111,7 +117,7 @@ export class ChangeManager {
111
117
  }
112
118
  console.error(error)
113
119
  session.notify(String(error), 'error')
114
- await this.revert(change, false)
120
+ await this.undo(change, false)
115
121
  return
116
122
  }
117
123
  if (!backendResult.ok) {
@@ -120,15 +126,15 @@ export class ChangeManager {
120
126
  jobsManager.abortJob(job.name, msg)
121
127
  }
122
128
  session.notify(msg, 'error')
123
- await this.revert(change, false)
129
+ await this.undo(change, false)
124
130
  return
125
131
  }
126
132
  if (change.notification) {
127
133
  session.notify(change.notification, 'success')
128
134
  }
129
135
  if (addToRecents) {
130
- // Push the change into array
131
136
  this.recentChanges.push(change)
137
+ this.undoneChanges = []
132
138
  }
133
139
  }
134
140
 
@@ -137,22 +143,36 @@ export class ChangeManager {
137
143
  }
138
144
  }
139
145
 
140
- async revert(change: Change, submitToBackend = true) {
146
+ async undo(change: Change, submitToBackend = true) {
141
147
  const inverseChange = change.getInverse()
142
148
  const opts = { submitToBackend, addToRecents: false }
143
149
  return this.submit(inverseChange, opts)
144
150
  }
145
151
 
146
- /**
147
- * Undo the last change
148
- */
149
- async revertLastChange() {
152
+ async redo(change: Change, submitToBackend = true) {
153
+ const opts = { submitToBackend, addToRecents: false }
154
+ return this.submit(change, opts)
155
+ }
156
+
157
+ async undoLastChange() {
158
+ const session = getSession(this.dataStore)
150
159
  const lastChange = this.recentChanges.pop()
151
160
  if (!lastChange) {
152
- const session = getSession(this.dataStore)
153
- session.notify('No changes to undo!', 'warning')
161
+ session.notify('No changes to undo!', 'info')
162
+ return
163
+ }
164
+ this.undoneChanges.push(lastChange)
165
+ return this.undo(lastChange)
166
+ }
167
+
168
+ async redoLastChange() {
169
+ const session = getSession(this.dataStore)
170
+ const lastChange = this.undoneChanges.pop()
171
+ if (!lastChange) {
172
+ session.notify('No changes to redo!', 'info')
154
173
  return
155
174
  }
156
- return this.revert(lastChange)
175
+ this.recentChanges.push(lastChange)
176
+ return this.redo(lastChange)
157
177
  }
158
178
  }
@@ -58,7 +58,7 @@ export const ApolloTranscriptDetailsWidget = observer(
58
58
  model: ApolloTranscriptDetailsWidgetState
59
59
  }) {
60
60
  const { classes } = useStyles()
61
- const DEFAULT_PANELS = ['summary', 'location', 'attrs']
61
+ const DEFAULT_PANELS = ['summary', 'location']
62
62
  const [panelState, setPanelState] = useState<string[]>(DEFAULT_PANELS)
63
63
 
64
64
  const { model } = props
@@ -106,10 +106,53 @@ export const ApolloTranscriptDetailsWidget = observer(
106
106
  }
107
107
  }
108
108
 
109
- const CustomComponent = pluginManager.evaluateExtensionPoint(
110
- 'Apollo-TranscriptDetailsCustomComponent',
109
+ const CustomComponentInsideSummary = pluginManager.evaluateExtensionPoint(
110
+ 'Apollo-TranscriptDetailsCustomComponent-InsideSummary',
111
111
  NoOpCustomComponent,
112
- props,
112
+ { feature, session },
113
+ ) as React.ElementType<CustomComponentProps>
114
+
115
+ const CustomComponentAfterSummary = pluginManager.evaluateExtensionPoint(
116
+ 'Apollo-TranscriptDetailsCustomComponent-AfterSummary',
117
+ NoOpCustomComponent,
118
+ { feature, session },
119
+ ) as React.ElementType<CustomComponentProps>
120
+
121
+ const CustomComponentInsideLocation = pluginManager.evaluateExtensionPoint(
122
+ 'Apollo-TranscriptDetailsCustomComponent-InsideLocation',
123
+ NoOpCustomComponent,
124
+ { feature, session },
125
+ ) as React.ElementType<CustomComponentProps>
126
+
127
+ const CustomComponentAfterLocation = pluginManager.evaluateExtensionPoint(
128
+ 'Apollo-TranscriptDetailsCustomComponent-AfterLocation',
129
+ NoOpCustomComponent,
130
+ { feature, session },
131
+ ) as React.ElementType<CustomComponentProps>
132
+
133
+ const CustomComponentInsideAttributes =
134
+ pluginManager.evaluateExtensionPoint(
135
+ 'Apollo-TranscriptDetailsCustomComponent-InsideAttributes',
136
+ NoOpCustomComponent,
137
+ { feature, session },
138
+ ) as React.ElementType<CustomComponentProps>
139
+
140
+ const CustomComponentAfterAttributes = pluginManager.evaluateExtensionPoint(
141
+ 'Apollo-TranscriptDetailsCustomComponent-AfterAttributes',
142
+ NoOpCustomComponent,
143
+ { feature, session },
144
+ ) as React.ElementType<CustomComponentProps>
145
+
146
+ const CustomComponentInsideSequence = pluginManager.evaluateExtensionPoint(
147
+ 'Apollo-TranscriptDetailsCustomComponent-InsideSequence',
148
+ NoOpCustomComponent,
149
+ { feature, session },
150
+ ) as React.ElementType<CustomComponentProps>
151
+
152
+ const CustomComponentAfterSequence = pluginManager.evaluateExtensionPoint(
153
+ 'Apollo-TranscriptDetailsCustomComponent-AfterSequence',
154
+ NoOpCustomComponent,
155
+ { feature, session },
113
156
  ) as React.ElementType<CustomComponentProps>
114
157
 
115
158
  return (
@@ -131,9 +174,10 @@ export const ApolloTranscriptDetailsWidget = observer(
131
174
  </StyledAccordionSummary>
132
175
  <AccordionDetails>
133
176
  <TranscriptWidgetSummary feature={feature} refName={refName} />
177
+ <CustomComponentInsideSummary session={session} feature={feature} />
134
178
  </AccordionDetails>
135
179
  </Accordion>
136
- <CustomComponent session={session} feature={feature} />
180
+ <CustomComponentAfterSummary session={session} feature={feature} />
137
181
  <Accordion
138
182
  style={{ marginTop: 5 }}
139
183
  expanded={panelState.includes('location')}
@@ -157,8 +201,13 @@ export const ApolloTranscriptDetailsWidget = observer(
157
201
  session={apolloSession}
158
202
  assembly={currentAssembly._id || ''}
159
203
  />
204
+ <CustomComponentInsideLocation
205
+ session={session}
206
+ feature={feature}
207
+ />
160
208
  </AccordionDetails>
161
209
  </Accordion>
210
+ <CustomComponentAfterLocation session={session} feature={feature} />
162
211
  <Accordion
163
212
  style={{ marginTop: 5 }}
164
213
  expanded={panelState.includes('attrs')}
@@ -189,8 +238,13 @@ export const ApolloTranscriptDetailsWidget = observer(
189
238
  assembly={currentAssembly._id || ''}
190
239
  editable={editable}
191
240
  />
241
+ <CustomComponentInsideAttributes
242
+ session={session}
243
+ feature={feature}
244
+ />
192
245
  </AccordionDetails>
193
246
  </Accordion>
247
+ <CustomComponentAfterAttributes session={session} feature={feature} />
194
248
  <Accordion
195
249
  style={{ marginTop: 5 }}
196
250
  expanded={panelState.includes('sequence')}
@@ -216,8 +270,13 @@ export const ApolloTranscriptDetailsWidget = observer(
216
270
  refName={refName}
217
271
  />
218
272
  )}
273
+ <CustomComponentInsideSequence
274
+ session={session}
275
+ feature={feature}
276
+ />
219
277
  </AccordionDetails>
220
278
  </Accordion>
279
+ <CustomComponentAfterSequence feature={feature} session={session} />
221
280
  </div>
222
281
  )
223
282
  },