@apollo-annotation/jbrowse-plugin-apollo 0.3.4 → 0.3.5

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 (44) hide show
  1. package/dist/index.esm.js +1513 -1074
  2. package/dist/index.esm.js.map +1 -1
  3. package/dist/jbrowse-plugin-apollo.cjs.development.js +1510 -1065
  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 +4681 -2097
  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 -4
  12. package/src/ApolloSequenceAdapter/ApolloSequenceAdapter.ts +13 -0
  13. package/src/FeatureDetailsWidget/ApolloFeatureDetailsWidget.tsx +88 -17
  14. package/src/FeatureDetailsWidget/ApolloTranscriptDetailsWidget.tsx +144 -22
  15. package/src/FeatureDetailsWidget/Attributes.tsx +93 -43
  16. package/src/FeatureDetailsWidget/BasicInformation.tsx +2 -4
  17. package/src/FeatureDetailsWidget/FeatureDetailsNavigation.tsx +6 -4
  18. package/src/FeatureDetailsWidget/Sequence.tsx +16 -33
  19. package/src/FeatureDetailsWidget/TranscriptSequence.tsx +137 -92
  20. package/src/FeatureDetailsWidget/TranscriptWidgetEditLocation.tsx +600 -0
  21. package/src/FeatureDetailsWidget/TranscriptWidgetSummary.tsx +54 -0
  22. package/src/LinearApolloDisplay/components/LinearApolloDisplay.tsx +13 -6
  23. package/src/LinearApolloDisplay/glyphs/BoxGlyph.ts +6 -27
  24. package/src/LinearApolloDisplay/glyphs/GeneGlyph.ts +35 -13
  25. package/src/LinearApolloDisplay/stateModel/layouts.ts +7 -2
  26. package/src/LinearApolloDisplay/stateModel/rendering.ts +4 -0
  27. package/src/OntologyManager/OntologyStore/fulltext-stopwords.ts +10 -1
  28. package/src/OntologyManager/OntologyStore/index.test.ts +1 -0
  29. package/src/OntologyManager/index.ts +2 -0
  30. package/src/SixFrameFeatureDisplay/stateModel.ts +5 -1
  31. package/src/TabularEditor/HybridGrid/Feature.tsx +0 -1
  32. package/src/TabularEditor/HybridGrid/NumberCell.tsx +8 -1
  33. package/src/TabularEditor/HybridGrid/ToolBar.tsx +14 -12
  34. package/src/TabularEditor/HybridGrid/featureContextMenuItems.ts +3 -27
  35. package/src/components/AddAssembly.tsx +608 -291
  36. package/src/components/CreateApolloAnnotation.tsx +144 -37
  37. package/src/components/OntologyTermMultiSelect.tsx +3 -0
  38. package/src/components/index.ts +0 -1
  39. package/src/extensions/annotationFromJBrowseFeature.ts +3 -2
  40. package/src/makeDisplayComponent.tsx +3 -4
  41. package/src/util/annotationFeatureUtils.ts +53 -0
  42. package/src/util/index.ts +1 -0
  43. package/src/FeatureDetailsWidget/TranscriptBasic.tsx +0 -200
  44. package/src/components/ModifyFeatureAttribute.tsx +0 -460
@@ -1,460 +0,0 @@
1
- /* eslint-disable @typescript-eslint/unbound-method */
2
- /* eslint-disable @typescript-eslint/no-unnecessary-condition */
3
- /* eslint-disable @typescript-eslint/no-misused-promises */
4
- import { AnnotationFeature } from '@apollo-annotation/mst'
5
- import { FeatureAttributeChange } from '@apollo-annotation/shared'
6
- import { AbstractSessionModel } from '@jbrowse/core/util'
7
- import DeleteIcon from '@mui/icons-material/Delete'
8
- import {
9
- Button,
10
- DialogActions,
11
- DialogContent,
12
- DialogContentText,
13
- FormControl,
14
- FormControlLabel,
15
- FormLabel,
16
- Grid2,
17
- IconButton,
18
- Paper,
19
- Radio,
20
- RadioGroup,
21
- TextField,
22
- Typography,
23
- } from '@mui/material'
24
- import { getRoot, getSnapshot } from 'mobx-state-tree'
25
- import React, { useMemo, useState } from 'react'
26
- import { makeStyles } from 'tss-react/mui'
27
-
28
- import { ApolloInternetAccountModel } from '../ApolloInternetAccount/model'
29
- import { ChangeManager } from '../ChangeManager'
30
- import { ApolloSessionModel } from '../session'
31
- import { ApolloRootModel } from '../types'
32
- import { Dialog } from './Dialog'
33
- import { OntologyTermMultiSelect } from './OntologyTermMultiSelect'
34
-
35
- interface ModifyFeatureAttributeProps {
36
- session: ApolloSessionModel
37
- handleClose(): void
38
- sourceFeature: AnnotationFeature
39
- sourceAssemblyId: string
40
- changeManager: ChangeManager
41
- }
42
-
43
- const reservedKeys = new Map([
44
- [
45
- 'Gene Ontology',
46
- (props: AttributeValueEditorProps) => {
47
- return <OntologyTermMultiSelect {...props} ontologyName="Gene Ontology" />
48
- },
49
- ],
50
- [
51
- 'Sequence Ontology',
52
- (props: AttributeValueEditorProps) => {
53
- return (
54
- <OntologyTermMultiSelect {...props} ontologyName="Sequence Ontology" />
55
- )
56
- },
57
- ],
58
- ])
59
-
60
- const useStyles = makeStyles()((theme) => ({
61
- attributeInput: {
62
- maxWidth: 600,
63
- },
64
- newAttributePaper: {
65
- padding: theme.spacing(2),
66
- },
67
- attributeName: {
68
- background: theme.palette.secondary.main,
69
- color: theme.palette.secondary.contrastText,
70
- padding: theme.spacing(1),
71
- },
72
- }))
73
-
74
- export interface AttributeValueEditorProps {
75
- session: ApolloSessionModel
76
- value: string[]
77
- onChange(newValue: string[]): void
78
- }
79
-
80
- const reservedTerms = [
81
- 'ID',
82
- 'Name',
83
- 'Alias',
84
- 'Target',
85
- 'Gap',
86
- 'Derives_from',
87
- 'Note',
88
- 'Dbxref',
89
- 'Ontology',
90
- 'Is_Circular',
91
- ]
92
-
93
- function CustomAttributeValueEditor(props: AttributeValueEditorProps) {
94
- const { onChange, value } = props
95
- return (
96
- <TextField
97
- type="text"
98
- value={value}
99
- onChange={(event) => {
100
- onChange(event.target.value.split(','))
101
- }}
102
- variant="outlined"
103
- fullWidth
104
- helperText="Separate multiple values for the attribute with commas"
105
- />
106
- )
107
- }
108
-
109
- export interface GOTerm {
110
- id: string
111
- label: string
112
- }
113
-
114
- export function ModifyFeatureAttribute({
115
- changeManager,
116
- handleClose,
117
- session,
118
- sourceAssemblyId,
119
- sourceFeature,
120
- }: ModifyFeatureAttributeProps) {
121
- const { notify } = session as unknown as AbstractSessionModel
122
-
123
- const { internetAccounts } = getRoot<ApolloRootModel>(session)
124
- const internetAccount = useMemo(() => {
125
- return internetAccounts.find(
126
- (ia) => ia.type === 'ApolloInternetAccount',
127
- ) as ApolloInternetAccountModel | undefined
128
- }, [internetAccounts])
129
- const role = internetAccount ? internetAccount.role : 'admin'
130
- const editable = ['admin', 'user'].includes(role ?? '')
131
-
132
- const [errorMessage, setErrorMessage] = useState('')
133
- const [attributes, setAttributes] = useState<Record<string, string[]>>(
134
- Object.fromEntries(
135
- [...sourceFeature.attributes.entries()].map(([key, value]) => {
136
- if (key.startsWith('gff_')) {
137
- const newKey = key.slice(4)
138
- const capitalizedKey =
139
- newKey.charAt(0).toUpperCase() + newKey.slice(1)
140
- return [capitalizedKey, getSnapshot(value)]
141
- }
142
- if (key === '_id') {
143
- return ['ID', getSnapshot(value)]
144
- }
145
- return [key, getSnapshot(value)]
146
- }),
147
- ),
148
- )
149
- const [showAddNewForm, setShowAddNewForm] = useState(false)
150
- const [newAttributeKey, setNewAttributeKey] = useState('')
151
- const { classes } = useStyles()
152
- async function onSubmit(event: React.FormEvent<HTMLFormElement>) {
153
- event.preventDefault()
154
- setErrorMessage('')
155
-
156
- const attrs: Record<string, string[]> = {}
157
- if (attributes) {
158
- for (const [key, val] of Object.entries(attributes)) {
159
- if (!val) {
160
- continue
161
- }
162
- const newKey = key.toLowerCase()
163
- if (newKey === 'parent') {
164
- continue
165
- }
166
- if ([...reservedKeys.keys()].includes(key)) {
167
- attrs[key] = val
168
- continue
169
- }
170
- switch (key) {
171
- case 'ID': {
172
- attrs._id = val
173
- break
174
- }
175
- case 'Name': {
176
- attrs.gff_name = val
177
- break
178
- }
179
- case 'Alias': {
180
- attrs.gff_alias = val
181
- break
182
- }
183
- case 'Target': {
184
- attrs.gff_target = val
185
- break
186
- }
187
- case 'Gap': {
188
- attrs.gff_gap = val
189
- break
190
- }
191
- case 'Derives_from': {
192
- attrs.gff_derives_from = val
193
- break
194
- }
195
- case 'Note': {
196
- attrs.gff_note = val
197
- break
198
- }
199
- case 'Dbxref': {
200
- attrs.gff_dbxref = val
201
- break
202
- }
203
- case 'Ontology_term': {
204
- attrs.gff_ontology_term = val
205
- break
206
- }
207
- case 'Is_circular': {
208
- attrs.gff_is_circular = val
209
- break
210
- }
211
- default: {
212
- attrs[key.toLowerCase()] = val
213
- }
214
- }
215
- }
216
- }
217
-
218
- const change = new FeatureAttributeChange({
219
- changedIds: [sourceFeature._id],
220
- typeName: 'FeatureAttributeChange',
221
- assembly: sourceAssemblyId,
222
- featureId: sourceFeature._id,
223
- attributes: attrs,
224
- })
225
- await changeManager.submit(change)
226
- notify('Feature attributes modified successfully', 'success')
227
- handleClose()
228
- event.preventDefault()
229
- }
230
-
231
- function handleAddNewAttributeChange() {
232
- setErrorMessage('')
233
- if (newAttributeKey.trim().length === 0) {
234
- setErrorMessage('Attribute key is mandatory')
235
- return
236
- }
237
- if (newAttributeKey === 'Parent') {
238
- setErrorMessage(
239
- '"Parent" -key is handled internally and it cannot be modified manually',
240
- )
241
- return
242
- }
243
- if (newAttributeKey in attributes) {
244
- setErrorMessage(`Attribute "${newAttributeKey}" already exists`)
245
- return
246
- }
247
- if (
248
- /^[A-Z]/.test(newAttributeKey) &&
249
- !reservedTerms.includes(newAttributeKey) &&
250
- ![...reservedKeys.keys()].includes(newAttributeKey)
251
- ) {
252
- setErrorMessage(
253
- `Key cannot starts with uppercase letter unless key is one of these: ${reservedTerms.join(
254
- ', ',
255
- )}`,
256
- )
257
- return
258
- }
259
- setAttributes({ ...attributes, [newAttributeKey]: [] })
260
- setShowAddNewForm(false)
261
- setNewAttributeKey('')
262
- }
263
-
264
- function deleteAttribute(key: string) {
265
- setErrorMessage('')
266
- const { [key]: remove, ...rest } = attributes
267
- setAttributes(rest)
268
- }
269
-
270
- function makeOnChange(id: string) {
271
- return (newValue: string[]) => {
272
- setAttributes({ ...attributes, [id]: newValue })
273
- }
274
- }
275
-
276
- function handleRadioButtonChange(
277
- event: React.ChangeEvent<HTMLInputElement>,
278
- value: string,
279
- ) {
280
- if (value === 'custom') {
281
- setNewAttributeKey('')
282
- } else if (reservedKeys.has(value)) {
283
- setNewAttributeKey(value)
284
- } else {
285
- setErrorMessage('Unknown attribute type')
286
- }
287
- }
288
-
289
- const hasEmptyAttributes = Object.values(attributes).some(
290
- (value) => value.length === 0 || value.includes(''),
291
- )
292
-
293
- return (
294
- <Dialog
295
- open
296
- title="Feature attributes"
297
- handleClose={handleClose}
298
- maxWidth={false}
299
- data-testid="modify-feature-attribute"
300
- >
301
- <form onSubmit={onSubmit}>
302
- <DialogContent>
303
- <Grid2 container direction="column" spacing={1}>
304
- {Object.entries(attributes).map(([key, value]) => {
305
- const EditorComponent =
306
- reservedKeys.get(key) ?? CustomAttributeValueEditor
307
- return (
308
- <Grid2 container spacing={3} alignItems="center" key={key}>
309
- <Grid2>
310
- <Paper variant="outlined" className={classes.attributeName}>
311
- <Typography>{key}</Typography>
312
- </Paper>
313
- </Grid2>
314
- <Grid2 flexGrow={1}>
315
- <EditorComponent
316
- session={session}
317
- value={value}
318
- onChange={makeOnChange(key)}
319
- />
320
- </Grid2>
321
- <Grid2>
322
- <IconButton
323
- aria-label="delete"
324
- size="medium"
325
- disabled={!editable}
326
- onClick={() => {
327
- deleteAttribute(key)
328
- }}
329
- >
330
- <DeleteIcon fontSize="medium" key={key} />
331
- </IconButton>
332
- </Grid2>
333
- </Grid2>
334
- )
335
- })}
336
- <Grid2>
337
- <Button
338
- color="primary"
339
- variant="contained"
340
- disabled={showAddNewForm || !editable}
341
- onClick={() => {
342
- setShowAddNewForm(true)
343
- }}
344
- >
345
- Add new
346
- </Button>
347
- </Grid2>
348
- {showAddNewForm ? (
349
- <Grid2>
350
- <Paper elevation={8} className={classes.newAttributePaper}>
351
- <Grid2 container direction="column">
352
- <Grid2>
353
- <FormControl>
354
- <FormLabel id="attribute-radio-button-group">
355
- Select attribute type
356
- </FormLabel>
357
- <RadioGroup
358
- aria-labelledby="demo-radio-buttons-group-label"
359
- defaultValue="custom"
360
- name="radio-buttons-group"
361
- onChange={handleRadioButtonChange}
362
- >
363
- <FormControlLabel
364
- value="custom"
365
- control={<Radio />}
366
- disableTypography
367
- label={
368
- <Grid2 container spacing={1} alignItems="center">
369
- <Grid2>
370
- <Typography>Custom</Typography>
371
- </Grid2>
372
- <Grid2>
373
- <TextField
374
- label="Custom attribute key"
375
- variant="outlined"
376
- value={
377
- reservedKeys.has(newAttributeKey)
378
- ? ''
379
- : newAttributeKey
380
- }
381
- disabled={reservedKeys.has(newAttributeKey)}
382
- onChange={(event) => {
383
- setNewAttributeKey(event.target.value)
384
- }}
385
- />
386
- </Grid2>
387
- </Grid2>
388
- }
389
- />
390
- {[...reservedKeys.keys()].map((key) => (
391
- <FormControlLabel
392
- key={key}
393
- value={key}
394
- control={<Radio />}
395
- label={key}
396
- />
397
- ))}
398
- </RadioGroup>
399
- </FormControl>
400
- </Grid2>
401
- <Grid2>
402
- <DialogActions>
403
- <Button
404
- key="addButton"
405
- color="primary"
406
- variant="contained"
407
- style={{ margin: 2 }}
408
- onClick={handleAddNewAttributeChange}
409
- disabled={!newAttributeKey}
410
- >
411
- Add
412
- </Button>
413
- <Button
414
- key="cancelAddButton"
415
- variant="outlined"
416
- type="submit"
417
- onClick={() => {
418
- setShowAddNewForm(false)
419
- setNewAttributeKey('')
420
- setErrorMessage('')
421
- }}
422
- >
423
- Cancel
424
- </Button>
425
- </DialogActions>
426
- </Grid2>
427
- </Grid2>
428
- </Paper>
429
- </Grid2>
430
- ) : null}
431
- </Grid2>
432
- {errorMessage ? (
433
- <DialogContentText color="error">{errorMessage}</DialogContentText>
434
- ) : null}
435
- </DialogContent>
436
- <DialogActions>
437
- <Button
438
- variant="contained"
439
- type="submit"
440
- disabled={showAddNewForm || hasEmptyAttributes || !editable}
441
- >
442
- Submit changes
443
- </Button>
444
- <Button
445
- variant="outlined"
446
- type="submit"
447
- disabled={showAddNewForm}
448
- onClick={handleClose}
449
- >
450
- Cancel
451
- </Button>
452
- </DialogActions>
453
- </form>
454
-
455
- {/* <DialogContentText>
456
- Separate multiple values for an attribute with a comma
457
- </DialogContentText> */}
458
- </Dialog>
459
- )
460
- }