@apollo-annotation/jbrowse-plugin-apollo 0.1.21 → 0.2.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 (57) hide show
  1. package/dist/index.esm.js +431 -570
  2. package/dist/index.esm.js.map +1 -1
  3. package/dist/jbrowse-plugin-apollo.cjs.development.js +439 -578
  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 +11064 -1091
  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 +4 -5
  12. package/src/ApolloInternetAccount/components/AuthTypeSelector.tsx +4 -2
  13. package/src/ApolloInternetAccount/configSchema.ts +1 -1
  14. package/src/ApolloInternetAccount/model.ts +5 -10
  15. package/src/ApolloRefNameAliasAdapter/ApolloRefNameAliasAdapter.ts +1 -1
  16. package/src/ApolloSixFrameRenderer/components/ApolloRendering.tsx +4 -5
  17. package/src/BackendDrivers/DesktopFileDriver.ts +3 -2
  18. package/src/FeatureDetailsWidget/Attributes.tsx +1 -6
  19. package/src/FeatureDetailsWidget/NumberTextField.tsx +1 -0
  20. package/src/FeatureDetailsWidget/StringTextField.tsx +1 -0
  21. package/src/FeatureDetailsWidget/TranscriptBasic.tsx +131 -382
  22. package/src/FeatureDetailsWidget/TranscriptSequence.tsx +209 -284
  23. package/src/FeatureDetailsWidget/model.ts +4 -4
  24. package/src/LinearApolloDisplay/components/LinearApolloDisplay.tsx +1 -0
  25. package/src/LinearApolloDisplay/glyphs/BoxGlyph.ts +25 -3
  26. package/src/LinearApolloDisplay/glyphs/GeneGlyph.ts +95 -32
  27. package/src/LinearApolloDisplay/stateModel/base.ts +5 -3
  28. package/src/LinearApolloDisplay/stateModel/index.ts +1 -1
  29. package/src/LinearApolloDisplay/stateModel/mouseEvents.ts +1 -1
  30. package/src/LinearApolloDisplay/stateModel/rendering.ts +1 -1
  31. package/src/OntologyManager/OntologyStore/fulltext.ts +5 -2
  32. package/src/OntologyManager/OntologyStore/index.ts +25 -22
  33. package/src/OntologyManager/OntologyStore/indexeddb-storage.ts +8 -3
  34. package/src/OntologyManager/index.ts +31 -8
  35. package/src/SixFrameFeatureDisplay/stateModel.ts +1 -1
  36. package/src/TabularEditor/HybridGrid/HybridGrid.tsx +1 -0
  37. package/src/TabularEditor/HybridGrid/NumberCell.tsx +1 -0
  38. package/src/TabularEditor/model.ts +1 -1
  39. package/src/components/AddChildFeature.tsx +1 -0
  40. package/src/components/AddFeature.tsx +1 -1
  41. package/src/components/AddRefSeqAliases.tsx +1 -0
  42. package/src/components/CopyFeature.tsx +1 -0
  43. package/src/components/DeleteAssembly.tsx +1 -0
  44. package/src/components/DeleteFeature.tsx +1 -0
  45. package/src/components/LogOut.tsx +2 -1
  46. package/src/components/ManageChecks.tsx +1 -1
  47. package/src/components/ManageUsers.tsx +2 -1
  48. package/src/components/OntologyTermAutocomplete.tsx +7 -9
  49. package/src/components/OntologyTermMultiSelect.tsx +2 -1
  50. package/src/components/OpenLocalFile.tsx +3 -1
  51. package/src/components/ViewChangeLog.tsx +1 -0
  52. package/src/components/ViewCheckResults.tsx +1 -0
  53. package/src/config.ts +5 -0
  54. package/src/extensions/annotationFromPileup.ts +1 -1
  55. package/src/makeDisplayComponent.tsx +28 -7
  56. package/src/session/ClientDataStore.ts +1 -1
  57. package/src/session/session.ts +2 -1
@@ -3,71 +3,23 @@ import {
3
3
  LocationEndChange,
4
4
  LocationStartChange,
5
5
  } from '@apollo-annotation/shared'
6
- import { AbstractSessionModel, revcom } from '@jbrowse/core/util'
7
- import { Typography } from '@mui/material'
6
+ import { AbstractSessionModel, getFrame, revcom } from '@jbrowse/core/util'
7
+ import {
8
+ Paper,
9
+ Typography,
10
+ Table,
11
+ TableBody,
12
+ TableCell,
13
+ TableContainer,
14
+ TableRow,
15
+ useTheme,
16
+ } from '@mui/material'
8
17
  import { observer } from 'mobx-react'
9
18
  import React from 'react'
10
19
 
11
20
  import { ApolloSessionModel } from '../session'
12
- import { CDSInfo } from './TranscriptSequence'
13
21
  import { NumberTextField } from './NumberTextField'
14
22
 
15
- interface ExonInfo {
16
- min: number
17
- max: number
18
- }
19
-
20
- /**
21
- * Get single feature by featureId
22
- * @param feature -
23
- * @param featureId -
24
- * @returns
25
- */
26
- function getFeatureFromId(
27
- feature: AnnotationFeature,
28
- featureId: string,
29
- ): AnnotationFeature | undefined {
30
- if (feature._id === featureId) {
31
- return feature
32
- }
33
- // Check if there is also childFeatures in parent feature and it's not empty
34
- // Let's get featureId from recursive method
35
- if (!feature.children) {
36
- return
37
- }
38
- for (const [, childFeature] of feature.children) {
39
- const subFeature = getFeatureFromId(childFeature, featureId)
40
- if (subFeature) {
41
- return subFeature
42
- }
43
- }
44
- return
45
- }
46
-
47
- function findExonInRange(
48
- exons: ExonInfo[],
49
- pairStart: number,
50
- pairEnd: number,
51
- ): ExonInfo | null {
52
- for (const exon of exons) {
53
- if (Number(exon.min) <= pairStart && Number(exon.max) >= pairEnd) {
54
- return exon
55
- }
56
- }
57
- return null
58
- }
59
-
60
- function removeMatchingExon(
61
- exons: ExonInfo[],
62
- matchStart: number,
63
- matchEnd: number,
64
- ): ExonInfo[] {
65
- // Filter the array to remove elements matching the specified start and end
66
- return exons.filter(
67
- (exon) => !(exon.min === matchStart && exon.max === matchEnd),
68
- )
69
- }
70
-
71
23
  export const TranscriptBasicInformation = observer(
72
24
  function TranscriptBasicInformation({
73
25
  assembly,
@@ -84,362 +36,159 @@ export const TranscriptBasicInformation = observer(
84
36
  const currentAssembly = session.apolloDataStore.assemblies.get(assembly)
85
37
  const refData = currentAssembly?.getByRefName(refName)
86
38
  const { changeManager } = session.apolloDataStore
39
+ const theme = useTheme()
87
40
 
88
- function handleStartChange(
89
- newStart: number,
90
- featureId: string,
91
- oldStart: number,
41
+ function handleLocationChange(
42
+ oldLocation: number,
43
+ newLocation: number,
44
+ feature: AnnotationFeature,
45
+ isMin: boolean,
92
46
  ) {
93
- newStart--
94
- oldStart--
95
- if (newStart < feature.min) {
96
- notify('Feature start cannot be less than parent starts', 'error')
97
- return
98
- }
99
- const subFeature = getFeatureFromId(feature, featureId)
100
- if (!subFeature?.children) {
101
- return
47
+ if (!feature.children) {
48
+ throw new Error('Transcript should have child features')
102
49
  }
103
- // Let's check CDS start and end values. And possibly update those too
104
- for (const child of subFeature.children) {
105
- if (
106
- (child[1].type === 'CDS' || child[1].type === 'exon') &&
107
- child[1].min === oldStart
108
- ) {
50
+ for (const [, child] of feature.children) {
51
+ if (isMin && oldLocation - 1 === child.min) {
109
52
  const change = new LocationStartChange({
110
53
  typeName: 'LocationStartChange',
111
- changedIds: [child[1]._id],
112
- featureId,
113
- oldStart,
114
- newStart,
54
+ changedIds: [child._id],
55
+ featureId: feature._id,
56
+ oldStart: oldLocation - 1,
57
+ newStart: newLocation - 1,
115
58
  assembly,
116
59
  })
117
60
  changeManager.submit(change).catch(() => {
118
61
  notify('Error updating feature start position', 'error')
119
62
  })
63
+ return
120
64
  }
121
- }
122
- return
123
- }
124
-
125
- function handleEndChange(
126
- newEnd: number,
127
- featureId: string,
128
- oldEnd: number,
129
- ) {
130
- const subFeature = getFeatureFromId(feature, featureId)
131
- if (newEnd > feature.max) {
132
- notify('Feature start cannot be greater than parent end', 'error')
133
- return
134
- }
135
- if (!subFeature?.children) {
136
- return
137
- }
138
- // Let's check CDS start and end values. And possibly update those too
139
- for (const child of subFeature.children) {
140
- if (
141
- (child[1].type === 'CDS' || child[1].type === 'exon') &&
142
- child[1].max === oldEnd
143
- ) {
65
+ if (!isMin && newLocation === child.max) {
144
66
  const change = new LocationEndChange({
145
67
  typeName: 'LocationEndChange',
146
- changedIds: [child[1]._id],
147
- featureId,
148
- oldEnd,
149
- newEnd,
68
+ changedIds: [child._id],
69
+ featureId: feature._id,
70
+ oldEnd: child.max,
71
+ newEnd: newLocation,
150
72
  assembly,
151
73
  })
152
74
  changeManager.submit(change).catch(() => {
153
- notify('Error updating feature end position', 'error')
75
+ notify('Error updating feature start position', 'error')
154
76
  })
77
+ return
155
78
  }
156
79
  }
157
- return
158
80
  }
159
81
 
160
- const featureNew = feature
161
- let exonsArray: ExonInfo[] = []
162
- const traverse = (currentFeature: AnnotationFeature) => {
163
- if (currentFeature.type === 'exon') {
164
- exonsArray.push({
165
- min: currentFeature.min + 1,
166
- max: currentFeature.max,
167
- })
168
- }
169
- if (currentFeature.children) {
170
- for (const child of currentFeature.children) {
171
- traverse(child[1])
172
- }
173
- }
82
+ if (!refData) {
83
+ return null
174
84
  }
175
- traverse(featureNew)
176
-
177
- const CDSresult: CDSInfo[] = []
178
- const CDSData = featureNew.cdsLocations
179
- if (refData) {
180
- for (const CDSDatum of CDSData) {
181
- for (const dataPoint of CDSDatum) {
182
- let startSeq = refData.getSequence(
183
- Number(dataPoint.min) - 2,
184
- Number(dataPoint.min),
185
- )
186
- let endSeq = refData.getSequence(
187
- Number(dataPoint.max),
188
- Number(dataPoint.max) + 2,
189
- )
190
-
191
- if (featureNew.strand === -1 && startSeq && endSeq) {
192
- startSeq = revcom(startSeq)
193
- endSeq = revcom(endSeq)
194
- }
195
- const oneCDS: CDSInfo = {
196
- id: featureNew._id,
197
- type: 'CDS',
198
- strand: Number(featureNew.strand),
199
- min: dataPoint.min + 1,
200
- max: dataPoint.max,
201
- oldMin: dataPoint.min + 1,
202
- oldMax: dataPoint.max,
203
- startSeq,
204
- endSeq,
205
- }
206
- // CDSresult.push(oneCDS)
207
- // Check if there is already an object with the same start and end
208
- const exists = CDSresult.some(
209
- (obj) =>
210
- obj.min === oneCDS.min &&
211
- obj.max === oneCDS.max &&
212
- obj.type === oneCDS.type,
213
- )
214
85
 
215
- // If no such object exists, add the new object to the array
216
- if (!exists) {
217
- CDSresult.push(oneCDS)
218
- }
219
-
220
- // Add possible UTRs
221
- const foundExon = findExonInRange(
222
- exonsArray,
223
- dataPoint.min + 1,
224
- dataPoint.max,
225
- )
226
- if (foundExon && Number(foundExon.min) < dataPoint.min) {
227
- if (feature.strand === 1) {
228
- const oneCDS: CDSInfo = {
229
- id: feature._id,
230
- type: 'five_prime_UTR',
231
- strand: Number(feature.strand),
232
- min: foundExon.min,
233
- max: dataPoint.min,
234
- oldMin: foundExon.min,
235
- oldMax: dataPoint.min,
236
- startSeq: '',
237
- endSeq: '',
238
- }
239
- CDSresult.push(oneCDS)
240
- } else {
241
- const oneCDS: CDSInfo = {
242
- id: feature._id,
243
- type: 'three_prime_UTR',
244
- strand: Number(feature.strand),
245
- min: dataPoint.min + 1,
246
- max: foundExon.min + 1,
247
- oldMin: dataPoint.min + 1,
248
- oldMax: foundExon.min + 1,
249
- startSeq: '',
250
- endSeq: '',
251
- }
252
- CDSresult.push(oneCDS)
86
+ const { strand, transcriptParts } = feature
87
+ const [firstLocation] = transcriptParts
88
+
89
+ const locationData = firstLocation
90
+ .map((loc, idx) => {
91
+ const { max, min, type } = loc
92
+ let label: string = type
93
+ if (label === 'threePrimeUTR') {
94
+ label = '3` UTR'
95
+ } else if (label === 'fivePrimeUTR') {
96
+ label = '5` UTR'
97
+ }
98
+ let fivePrimeSpliceSite
99
+ let threePrimeSpliceSite
100
+ let frameColor
101
+ if (type === 'CDS') {
102
+ const { phase } = loc
103
+ const frame = getFrame(min, max, strand ?? 1, phase)
104
+ frameColor = theme.palette.framesCDS.at(frame)?.main
105
+ const previousLoc = firstLocation.at(idx - 1)
106
+ const nextLoc = firstLocation.at(idx + 1)
107
+ if (strand === 1) {
108
+ if (previousLoc?.type === 'intron') {
109
+ fivePrimeSpliceSite = refData.getSequence(min - 2, min)
253
110
  }
254
- exonsArray = removeMatchingExon(
255
- exonsArray,
256
- foundExon.min,
257
- foundExon.max,
258
- )
259
- }
260
- if (foundExon && Number(foundExon.max) > dataPoint.max) {
261
- if (feature.strand === 1) {
262
- const oneCDS: CDSInfo = {
263
- id: feature._id,
264
- type: 'three_prime_UTR',
265
- strand: Number(feature.strand),
266
- min: dataPoint.max + 1,
267
- max: foundExon.max,
268
- oldMin: dataPoint.max + 1,
269
- oldMax: foundExon.max,
270
- startSeq: '',
271
- endSeq: '',
272
- }
273
- CDSresult.push(oneCDS)
274
- } else {
275
- const oneCDS: CDSInfo = {
276
- id: feature._id,
277
- type: 'five_prime_UTR',
278
- strand: Number(feature.strand),
279
- min: dataPoint.min + 1,
280
- max: foundExon.max,
281
- oldMin: dataPoint.min + 1,
282
- oldMax: foundExon.max,
283
- startSeq: '',
284
- endSeq: '',
285
- }
286
- CDSresult.push(oneCDS)
111
+ if (nextLoc?.type === 'intron') {
112
+ threePrimeSpliceSite = refData.getSequence(max, max + 2)
113
+ }
114
+ } else {
115
+ if (previousLoc?.type === 'intron') {
116
+ fivePrimeSpliceSite = revcom(refData.getSequence(max, max + 2))
117
+ }
118
+ if (nextLoc?.type === 'intron') {
119
+ threePrimeSpliceSite = revcom(refData.getSequence(min - 2, min))
287
120
  }
288
- exonsArray = removeMatchingExon(
289
- exonsArray,
290
- foundExon.min,
291
- foundExon.max,
292
- )
293
- }
294
- if (
295
- dataPoint.min + 1 === foundExon?.min &&
296
- dataPoint.max === foundExon.max
297
- ) {
298
- exonsArray = removeMatchingExon(
299
- exonsArray,
300
- foundExon.min,
301
- foundExon.max,
302
- )
303
121
  }
304
122
  }
305
- }
306
- }
307
-
308
- // Add remaining UTRs if any
309
- if (exonsArray.length > 0) {
310
- // eslint-disable-next-line unicorn/no-array-for-each
311
- exonsArray.forEach((element: ExonInfo) => {
312
- if (featureNew.strand === 1) {
313
- const oneCDS: CDSInfo = {
314
- id: featureNew._id,
315
- type: 'five_prime_UTR',
316
- strand: Number(featureNew.strand),
317
- min: element.min + 1,
318
- max: element.max,
319
- oldMin: element.min + 1,
320
- oldMax: element.max,
321
- startSeq: '',
322
- endSeq: '',
323
- }
324
- CDSresult.push(oneCDS)
325
- } else {
326
- const oneCDS: CDSInfo = {
327
- id: featureNew._id,
328
- type: 'three_prime_UTR',
329
- strand: Number(featureNew.strand),
330
- min: element.min + 1,
331
- max: element.max + 1,
332
- oldMin: element.min + 1,
333
- oldMax: element.max + 1,
334
- startSeq: '',
335
- endSeq: '',
336
- }
337
- CDSresult.push(oneCDS)
123
+ return {
124
+ min,
125
+ max,
126
+ label,
127
+ fivePrimeSpliceSite,
128
+ threePrimeSpliceSite,
129
+ frameColor,
338
130
  }
339
- exonsArray = removeMatchingExon(exonsArray, element.min, element.max)
340
131
  })
341
- }
342
-
343
- CDSresult.sort((a, b) => {
344
- // Primary sorting by 'start' property
345
- const startDifference = Number(a.min) - Number(b.min)
346
- if (startDifference !== 0) {
347
- return startDifference
348
- }
349
- return Number(a.max) - Number(b.max)
350
- })
351
- if (CDSresult.length > 0) {
352
- CDSresult[0].startSeq = ''
353
-
354
- // eslint-disable-next-line unicorn/prefer-at
355
- CDSresult[CDSresult.length - 1].endSeq = ''
356
-
357
- // Loop through the array and clear "startSeq" or "endSeq" based on the conditions
358
- for (let i = 0; i < CDSresult.length; i++) {
359
- if (i > 0 && CDSresult[i].min === CDSresult[i - 1].max) {
360
- // Clear "startSeq" if the current item's "start" is equal to the previous item's "end"
361
- CDSresult[i].startSeq = ''
362
- }
363
- if (
364
- i < CDSresult.length - 1 &&
365
- CDSresult[i].max === CDSresult[i + 1].min
366
- ) {
367
- // Clear "endSeq" if the next item's "start" is equal to the current item's "end"
368
- CDSresult[i].endSeq = ''
369
- }
370
- }
371
- }
372
-
373
- const transcriptItems = CDSresult
132
+ .filter((loc) => loc.label !== 'intron')
374
133
 
375
134
  return (
376
135
  <>
377
- <Typography
378
- variant="h5"
379
- style={{ marginLeft: '15px', marginBottom: '0' }}
380
- >
381
- CDS and UTRs
136
+ <Typography variant="h5">Structure</Typography>
137
+ <Typography variant="h6">
138
+ {strand === 1 ? 'Forward' : 'Reverse'} strand
382
139
  </Typography>
383
- <div>
384
- {transcriptItems.map((item, index) => (
385
- <div key={index} style={{ display: 'flex', alignItems: 'center' }}>
386
- <span style={{ marginLeft: '20px', width: '50px' }}>
387
- {item.type === 'three_prime_UTR'
388
- ? '3 UTR'
389
- : // eslint-disable-next-line unicorn/no-nested-ternary
390
- item.type === 'five_prime_UTR'
391
- ? '5 UTR'
392
- : 'CDS'}
393
- </span>
394
- <span style={{ fontWeight: 'bold', width: '30px' }}>
395
- {item.startSeq}
396
- </span>
397
- <NumberTextField
398
- margin="dense"
399
- id={item.id}
400
- disabled={item.type !== 'CDS'}
401
- style={{
402
- width: '150px',
403
- marginLeft: '8px',
404
- backgroundColor:
405
- item.startSeq.trim() === '' && index !== 0
406
- ? 'lightblue'
407
- : 'inherit',
408
- }}
409
- variant="outlined"
410
- value={item.min}
411
- onChangeCommitted={(newStart: number) => {
412
- handleStartChange(newStart, item.id, Number(item.oldMin))
413
- }}
414
- />
415
- <span style={{ margin: '0 10px' }}>
416
- {/* eslint-disable-next-line unicorn/no-nested-ternary */}
417
- {item.strand === -1 ? '-' : item.strand === 1 ? '+' : ''}
418
- </span>
419
- <NumberTextField
420
- margin="dense"
421
- id={item.id}
422
- disabled={item.type !== 'CDS'}
423
- style={{
424
- width: '150px',
425
- backgroundColor:
426
- item.endSeq.trim() === '' &&
427
- index + 1 !== transcriptItems.length
428
- ? 'lightblue'
429
- : 'inherit',
430
- }}
431
- variant="outlined"
432
- value={item.max}
433
- onChangeCommitted={(newEnd: number) => {
434
- handleEndChange(newEnd, item.id, Number(item.oldMax))
435
- }}
436
- />
437
- <span style={{ marginLeft: '8px', fontWeight: 'bold' }}>
438
- {item.endSeq}
439
- </span>
440
- </div>
441
- ))}
442
- </div>
140
+ <TableContainer component={Paper}>
141
+ <Table size="small">
142
+ <TableBody>
143
+ {locationData.map((loc) => (
144
+ <TableRow key={`${loc.label}:${loc.min}-${loc.max}`}>
145
+ <TableCell
146
+ component="th"
147
+ scope="row"
148
+ style={{ background: loc.frameColor }}
149
+ >
150
+ {loc.label}
151
+ </TableCell>
152
+ <TableCell>{loc.fivePrimeSpliceSite ?? ''}</TableCell>
153
+ <TableCell padding="none">
154
+ <NumberTextField
155
+ margin="dense"
156
+ variant="outlined"
157
+ value={strand === 1 ? loc.min + 1 : loc.max}
158
+ onChangeCommitted={(newLocation: number) => {
159
+ handleLocationChange(
160
+ strand === 1 ? loc.min + 1 : loc.max,
161
+ newLocation,
162
+ feature,
163
+ strand === 1,
164
+ )
165
+ }}
166
+ />
167
+ {/* {strand === 1 ? loc.min : loc.max} */}
168
+ </TableCell>
169
+ <TableCell padding="none">
170
+ <NumberTextField
171
+ margin="dense"
172
+ // disabled={item.type !== 'CDS'}
173
+ variant="outlined"
174
+ value={strand === 1 ? loc.max : loc.min + 1}
175
+ onChangeCommitted={(newLocation: number) => {
176
+ handleLocationChange(
177
+ strand === 1 ? loc.max : loc.min + 1,
178
+ newLocation,
179
+ feature,
180
+ strand !== 1,
181
+ )
182
+ }}
183
+ />
184
+ {/* {strand === 1 ? loc.max : loc.min} */}
185
+ </TableCell>
186
+ <TableCell>{loc.threePrimeSpliceSite ?? ''}</TableCell>
187
+ </TableRow>
188
+ ))}
189
+ </TableBody>
190
+ </Table>
191
+ </TableContainer>
443
192
  </>
444
193
  )
445
194
  },