@jbrowse/plugin-data-management 2.0.1 → 2.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 (76) hide show
  1. package/dist/AddTrackWidget/components/AddTrackWidget.d.ts +2 -2
  2. package/dist/AddTrackWidget/components/AddTrackWidget.js +18 -189
  3. package/dist/AddTrackWidget/components/AddTrackWidget.js.map +1 -1
  4. package/dist/AddTrackWidget/components/ConfirmTrack.js +5 -6
  5. package/dist/AddTrackWidget/components/ConfirmTrack.js.map +1 -1
  6. package/dist/AddTrackWidget/components/DefaultAddTrackWorkflow.d.ts +7 -0
  7. package/dist/AddTrackWidget/components/DefaultAddTrackWorkflow.js +222 -0
  8. package/dist/AddTrackWidget/components/DefaultAddTrackWorkflow.js.map +1 -0
  9. package/dist/HierarchicalTrackSelectorWidget/components/Header.d.ts +10 -0
  10. package/dist/HierarchicalTrackSelectorWidget/components/Header.js +209 -0
  11. package/dist/HierarchicalTrackSelectorWidget/components/Header.js.map +1 -0
  12. package/dist/HierarchicalTrackSelectorWidget/components/HierarchicalTrackSelector.js +22 -231
  13. package/dist/HierarchicalTrackSelectorWidget/components/HierarchicalTrackSelector.js.map +1 -1
  14. package/dist/HierarchicalTrackSelectorWidget/components/Node.d.ts +29 -0
  15. package/dist/HierarchicalTrackSelectorWidget/components/Node.js +207 -0
  16. package/dist/HierarchicalTrackSelectorWidget/components/Node.js.map +1 -0
  17. package/dist/HierarchicalTrackSelectorWidget/components/util.d.ts +3 -0
  18. package/dist/HierarchicalTrackSelectorWidget/components/util.js +11 -0
  19. package/dist/HierarchicalTrackSelectorWidget/components/util.js.map +1 -0
  20. package/dist/HierarchicalTrackSelectorWidget/configSchema.d.ts +2 -0
  21. package/dist/HierarchicalTrackSelectorWidget/configSchema.js +6 -0
  22. package/dist/HierarchicalTrackSelectorWidget/configSchema.js.map +1 -0
  23. package/dist/HierarchicalTrackSelectorWidget/index.d.ts +4 -2
  24. package/dist/HierarchicalTrackSelectorWidget/index.js +4 -4
  25. package/dist/HierarchicalTrackSelectorWidget/index.js.map +1 -1
  26. package/dist/HierarchicalTrackSelectorWidget/model.d.ts +10 -2
  27. package/dist/HierarchicalTrackSelectorWidget/model.js +35 -31
  28. package/dist/HierarchicalTrackSelectorWidget/model.js.map +1 -1
  29. package/dist/index.d.ts +4 -1
  30. package/dist/index.js +1 -1
  31. package/dist/index.js.map +1 -1
  32. package/esm/AddTrackWidget/components/AddTrackWidget.d.ts +2 -2
  33. package/esm/AddTrackWidget/components/AddTrackWidget.js +22 -135
  34. package/esm/AddTrackWidget/components/AddTrackWidget.js.map +1 -1
  35. package/esm/AddTrackWidget/components/ConfirmTrack.js +5 -6
  36. package/esm/AddTrackWidget/components/ConfirmTrack.js.map +1 -1
  37. package/esm/AddTrackWidget/components/DefaultAddTrackWorkflow.d.ts +7 -0
  38. package/esm/AddTrackWidget/components/DefaultAddTrackWorkflow.js +134 -0
  39. package/esm/AddTrackWidget/components/DefaultAddTrackWorkflow.js.map +1 -0
  40. package/esm/HierarchicalTrackSelectorWidget/components/Header.d.ts +10 -0
  41. package/esm/HierarchicalTrackSelectorWidget/components/Header.js +149 -0
  42. package/esm/HierarchicalTrackSelectorWidget/components/Header.js.map +1 -0
  43. package/esm/HierarchicalTrackSelectorWidget/components/HierarchicalTrackSelector.js +24 -223
  44. package/esm/HierarchicalTrackSelectorWidget/components/HierarchicalTrackSelector.js.map +1 -1
  45. package/esm/HierarchicalTrackSelectorWidget/components/Node.d.ts +29 -0
  46. package/esm/HierarchicalTrackSelectorWidget/components/Node.js +149 -0
  47. package/esm/HierarchicalTrackSelectorWidget/components/Node.js.map +1 -0
  48. package/esm/HierarchicalTrackSelectorWidget/components/util.d.ts +3 -0
  49. package/esm/HierarchicalTrackSelectorWidget/components/util.js +5 -0
  50. package/esm/HierarchicalTrackSelectorWidget/components/util.js.map +1 -0
  51. package/esm/HierarchicalTrackSelectorWidget/configSchema.d.ts +2 -0
  52. package/esm/HierarchicalTrackSelectorWidget/configSchema.js +4 -0
  53. package/esm/HierarchicalTrackSelectorWidget/configSchema.js.map +1 -0
  54. package/esm/HierarchicalTrackSelectorWidget/index.d.ts +4 -2
  55. package/esm/HierarchicalTrackSelectorWidget/index.js +3 -3
  56. package/esm/HierarchicalTrackSelectorWidget/index.js.map +1 -1
  57. package/esm/HierarchicalTrackSelectorWidget/model.d.ts +10 -2
  58. package/esm/HierarchicalTrackSelectorWidget/model.js +36 -32
  59. package/esm/HierarchicalTrackSelectorWidget/model.js.map +1 -1
  60. package/esm/index.d.ts +4 -1
  61. package/esm/index.js +1 -1
  62. package/esm/index.js.map +1 -1
  63. package/package.json +2 -2
  64. package/src/AddTrackWidget/components/{AddTrackWidget.test.js → AddTrackWidget.test.tsx} +17 -32
  65. package/src/AddTrackWidget/components/AddTrackWidget.tsx +36 -200
  66. package/src/AddTrackWidget/components/ConfirmTrack.tsx +10 -10
  67. package/src/AddTrackWidget/components/DefaultAddTrackWorkflow.tsx +205 -0
  68. package/src/HierarchicalTrackSelectorWidget/components/Header.tsx +287 -0
  69. package/src/HierarchicalTrackSelectorWidget/components/HierarchicalTrackSelector.tsx +19 -438
  70. package/src/HierarchicalTrackSelectorWidget/components/Node.tsx +284 -0
  71. package/src/HierarchicalTrackSelectorWidget/components/util.ts +11 -0
  72. package/src/HierarchicalTrackSelectorWidget/configSchema.ts +3 -0
  73. package/src/HierarchicalTrackSelectorWidget/index.ts +4 -6
  74. package/src/HierarchicalTrackSelectorWidget/model.ts +45 -41
  75. package/src/index.ts +4 -1
  76. package/src/AddTrackWidget/components/__snapshots__/AddTrackWidget.test.js.snap +0 -331
@@ -0,0 +1,205 @@
1
+ import React, { useState } from 'react'
2
+ import {
3
+ Alert,
4
+ Button,
5
+ Step,
6
+ StepContent,
7
+ StepLabel,
8
+ Stepper,
9
+ Typography,
10
+ } from '@mui/material'
11
+ import { makeStyles } from 'tss-react/mui'
12
+
13
+ import {
14
+ getSession,
15
+ isElectron,
16
+ supportedIndexingAdapters,
17
+ } from '@jbrowse/core/util'
18
+ import { getConf } from '@jbrowse/core/configuration'
19
+ import { observer } from 'mobx-react'
20
+ import { getEnv } from 'mobx-state-tree'
21
+
22
+ // locals
23
+ import ConfirmTrack from './ConfirmTrack'
24
+ import TrackSourceSelect from './TrackSourceSelect'
25
+
26
+ import { AddTrackModel } from '../model'
27
+
28
+ const useStyles = makeStyles()(theme => ({
29
+ root: {
30
+ marginTop: theme.spacing(1),
31
+ },
32
+ stepper: {
33
+ backgroundColor: theme.palette.background.default,
34
+ },
35
+ button: {
36
+ marginTop: theme.spacing(1),
37
+ marginRight: theme.spacing(1),
38
+ },
39
+ actionsContainer: {
40
+ marginBottom: theme.spacing(2),
41
+ },
42
+ stepContent: {
43
+ margin: theme.spacing(1),
44
+ },
45
+ alertContainer: {
46
+ padding: `${theme.spacing(2)}px 0px ${theme.spacing(2)}px 0px`,
47
+ },
48
+ }))
49
+
50
+ const steps = ['Enter track data', 'Confirm track type']
51
+
52
+ function AddTrackWorkflow({ model }: { model: AddTrackModel }) {
53
+ const [activeStep, setActiveStep] = useState(0)
54
+ const { classes } = useStyles()
55
+ const { pluginManager } = getEnv(model)
56
+ const { rootModel } = pluginManager
57
+ const { jobsManager } = rootModel
58
+ const session = getSession(model)
59
+ const {
60
+ assembly,
61
+ trackAdapter,
62
+ trackData,
63
+ trackName,
64
+ trackType,
65
+ textIndexTrack,
66
+ textIndexingConf,
67
+ } = model
68
+ const [trackErrorMessage, setTrackErrorMessage] = useState<string>()
69
+
70
+ function getStepContent(step: number) {
71
+ switch (step) {
72
+ case 0:
73
+ return <TrackSourceSelect model={model} />
74
+ case 1:
75
+ return <ConfirmTrack model={model} />
76
+ default:
77
+ return <Typography>Unknown step</Typography>
78
+ }
79
+ }
80
+
81
+ async function handleNext() {
82
+ if (activeStep !== steps.length - 1) {
83
+ setActiveStep(activeStep + 1)
84
+ return
85
+ }
86
+
87
+ const trackId = [
88
+ `${trackName.toLowerCase().replace(/ /g, '_')}-${Date.now()}`,
89
+ `${session.adminMode ? '' : '-sessionTrack'}`,
90
+ ].join('')
91
+
92
+ const assemblyInstance = session.assemblyManager.get(assembly)
93
+
94
+ if (trackAdapter && trackAdapter.type !== 'UNKNOWN') {
95
+ session.addTrackConf({
96
+ trackId,
97
+ type: trackType,
98
+ name: trackName,
99
+ assemblyNames: [assembly],
100
+ adapter: {
101
+ ...trackAdapter,
102
+ sequenceAdapter: getConf(assemblyInstance, ['sequence', 'adapter']),
103
+ },
104
+ })
105
+ if (model.view) {
106
+ model.view.showTrack(trackId)
107
+ if (
108
+ isElectron &&
109
+ textIndexTrack &&
110
+ supportedIndexingAdapters(trackAdapter.type)
111
+ ) {
112
+ const attr = textIndexingConf || {
113
+ attributes: ['Name', 'ID'],
114
+ exclude: ['CDS', 'exon'],
115
+ }
116
+ const indexName = trackName + '-index'
117
+ const newEntry = {
118
+ indexingParams: {
119
+ ...attr,
120
+ assemblies: [assembly],
121
+ tracks: [trackId],
122
+ indexType: 'perTrack',
123
+ name: indexName,
124
+ timestamp: new Date().toISOString(),
125
+ },
126
+ name: indexName,
127
+ cancelCallback: () => jobsManager.abortJob(),
128
+ }
129
+ jobsManager.queueJob(newEntry)
130
+ }
131
+ } else {
132
+ session.notify(
133
+ 'Open a new view, or use the track selector in an existing view, to view this track',
134
+ 'info',
135
+ )
136
+ }
137
+ model.clearData()
138
+ session.hideWidget(model)
139
+ } else {
140
+ setTrackErrorMessage(
141
+ 'Failed to add track.\nThe configuration of this file is not currently supported.',
142
+ )
143
+ }
144
+ }
145
+
146
+ function handleBack() {
147
+ setTrackErrorMessage(undefined)
148
+ setActiveStep(activeStep - 1)
149
+ }
150
+
151
+ function isNextDisabled() {
152
+ switch (activeStep) {
153
+ case 0:
154
+ return !trackData
155
+ case 1:
156
+ return !(trackName && trackType && trackAdapter?.type && assembly)
157
+ default:
158
+ return true
159
+ }
160
+ }
161
+
162
+ return (
163
+ <div className={classes.root}>
164
+ <Stepper
165
+ className={classes.stepper}
166
+ activeStep={activeStep}
167
+ orientation="vertical"
168
+ >
169
+ {steps.map((label, idx) => (
170
+ <Step key={label}>
171
+ <StepLabel>{label}</StepLabel>
172
+ <StepContent>
173
+ {getStepContent(idx)}
174
+ <div className={classes.actionsContainer}>
175
+ <Button
176
+ disabled={activeStep === 0}
177
+ onClick={handleBack}
178
+ className={classes.button}
179
+ >
180
+ Back
181
+ </Button>
182
+ <Button
183
+ disabled={isNextDisabled()}
184
+ variant="contained"
185
+ color="primary"
186
+ onClick={handleNext}
187
+ className={classes.button}
188
+ data-testid="addTrackNextButton"
189
+ >
190
+ {activeStep === steps.length - 1 ? 'Add' : 'Next'}
191
+ </Button>
192
+ </div>
193
+ {trackErrorMessage ? (
194
+ <div className={classes.alertContainer}>
195
+ <Alert severity="error">{trackErrorMessage}</Alert>
196
+ </div>
197
+ ) : null}
198
+ </StepContent>
199
+ </Step>
200
+ ))}
201
+ </Stepper>
202
+ </div>
203
+ )
204
+ }
205
+ export default observer(AddTrackWorkflow)
@@ -0,0 +1,287 @@
1
+ import React, { Suspense, lazy, useState } from 'react'
2
+ import { makeStyles } from 'tss-react/mui'
3
+ import { Badge, IconButton, InputAdornment, TextField } from '@mui/material'
4
+ // icons
5
+ import ClearIcon from '@mui/icons-material/Clear'
6
+ import MenuIcon from '@mui/icons-material/Menu'
7
+ import ShoppingCartIcon from '@mui/icons-material/ShoppingCart'
8
+ import { Cable } from '@jbrowse/core/ui/Icons'
9
+
10
+ // other
11
+ import JBrowseMenu, { MenuItem } from '@jbrowse/core/ui/Menu'
12
+ import {
13
+ getSession,
14
+ isSessionModelWithWidgets,
15
+ isSessionModelWithConnections,
16
+ isSessionWithAddTracks,
17
+ } from '@jbrowse/core/util'
18
+ import {
19
+ AnyConfigurationModel,
20
+ readConfObject,
21
+ } from '@jbrowse/core/configuration'
22
+ import { observer } from 'mobx-react'
23
+ import { getEnv } from 'mobx-state-tree'
24
+
25
+ // locals
26
+ import { HierarchicalTrackSelectorModel } from '../model'
27
+
28
+ // lazy components
29
+ const CloseConnectionDialog = lazy(() => import('./CloseConnectionDialog'))
30
+ const DeleteConnectionDialog = lazy(() => import('./DeleteConnectionDialog'))
31
+ const ManageConnectionsDialog = lazy(() => import('./ManageConnectionsDialog'))
32
+ const ToggleConnectionsDialog = lazy(() => import('./ToggleConnectionsDialog'))
33
+
34
+ const useStyles = makeStyles()(theme => ({
35
+ searchBox: {
36
+ margin: theme.spacing(2),
37
+ },
38
+ menuIcon: {
39
+ marginRight: theme.spacing(1),
40
+ marginBottom: 0,
41
+ },
42
+ }))
43
+
44
+ interface ModalArgs {
45
+ connectionConf: AnyConfigurationModel
46
+ safelyBreakConnection: Function
47
+ dereferenceTypeCount: { [key: string]: number }
48
+ name: string
49
+ }
50
+
51
+ interface DialogDetails {
52
+ name: string
53
+ connectionConf: AnyConfigurationModel
54
+ }
55
+
56
+ function HierarchicalTrackSelectorHeader({
57
+ model,
58
+ setHeaderHeight,
59
+ setAssemblyIdx,
60
+ assemblyIdx,
61
+ }: {
62
+ model: HierarchicalTrackSelectorModel
63
+ setHeaderHeight: (n: number) => void
64
+ setAssemblyIdx: (n: number) => void
65
+ assemblyIdx: number
66
+ }) {
67
+ const { classes } = useStyles()
68
+ const session = getSession(model)
69
+ const [connectionEl, setConnectionEl] = useState<HTMLButtonElement>()
70
+ const [selectionEl, setSelectionEl] = useState<HTMLButtonElement>()
71
+ const [menuEl, setMenuEl] = useState<HTMLButtonElement>()
72
+ const [modalInfo, setModalInfo] = useState<ModalArgs>()
73
+ const [deleteDlgDetails, setDeleteDlgDetails] = useState<DialogDetails>()
74
+ const [connectionManagerOpen, setConnectionManagerOpen] = useState(false)
75
+ const [connectionToggleOpen, setConnectionToggleOpen] = useState(false)
76
+ const { assemblyNames } = model
77
+ const assemblyName = assemblyNames[assemblyIdx]
78
+
79
+ function breakConnection(
80
+ connectionConf: AnyConfigurationModel,
81
+ deletingConnection?: boolean,
82
+ ) {
83
+ const name = readConfObject(connectionConf, 'name')
84
+
85
+ // @ts-ignore
86
+ const result = session.prepareToBreakConnection(connectionConf)
87
+ if (result) {
88
+ const [safelyBreakConnection, dereferenceTypeCount] = result
89
+ if (Object.keys(dereferenceTypeCount).length > 0) {
90
+ setModalInfo({
91
+ connectionConf,
92
+ safelyBreakConnection,
93
+ dereferenceTypeCount,
94
+ name,
95
+ })
96
+ } else {
97
+ safelyBreakConnection()
98
+ }
99
+ }
100
+ if (deletingConnection) {
101
+ setDeleteDlgDetails({ name, connectionConf })
102
+ }
103
+ }
104
+
105
+ const connectionMenuItems = [
106
+ {
107
+ label: 'Turn on/off connections...',
108
+ onClick: () => setConnectionToggleOpen(true),
109
+ },
110
+ ]
111
+
112
+ if (isSessionModelWithConnections(session)) {
113
+ connectionMenuItems.unshift({
114
+ label: 'Add connection',
115
+ onClick: () => {
116
+ if (isSessionModelWithWidgets(session)) {
117
+ session.showWidget(
118
+ session.addWidget('AddConnectionWidget', 'addConnectionWidget'),
119
+ )
120
+ }
121
+ },
122
+ })
123
+
124
+ connectionMenuItems.push({
125
+ label: 'Delete connections...',
126
+ onClick: () => setConnectionManagerOpen(true),
127
+ })
128
+ }
129
+
130
+ const assemblyMenuItems =
131
+ assemblyNames.length > 1
132
+ ? [
133
+ {
134
+ label: 'Select assembly...',
135
+ subMenu: assemblyNames.map((name, idx) => ({
136
+ label: name,
137
+ onClick: () => setAssemblyIdx(idx),
138
+ })),
139
+ },
140
+ ]
141
+ : []
142
+
143
+ const menuItems = [
144
+ {
145
+ label: 'Add track...',
146
+ onClick: () => {
147
+ if (isSessionModelWithWidgets(session)) {
148
+ session.showWidget(
149
+ session.addWidget('AddTrackWidget', 'addTrackWidget', {
150
+ view: model.view.id,
151
+ }),
152
+ )
153
+ }
154
+ },
155
+ },
156
+
157
+ ...assemblyMenuItems,
158
+ ]
159
+
160
+ const items = getEnv(model).pluginManager.evaluateExtensionPoint(
161
+ 'TrackSelector-multiTrackMenuItems',
162
+ ) as MenuItem[]
163
+ return (
164
+ <div
165
+ ref={ref => setHeaderHeight(ref?.getBoundingClientRect().height || 0)}
166
+ data-testid="hierarchical_track_selector"
167
+ >
168
+ <div style={{ display: 'flex' }}>
169
+ {isSessionWithAddTracks(session) && (
170
+ <IconButton
171
+ className={classes.menuIcon}
172
+ onClick={event => setMenuEl(event.currentTarget)}
173
+ >
174
+ <MenuIcon />
175
+ </IconButton>
176
+ )}
177
+
178
+ {session.makeConnection && (
179
+ <IconButton
180
+ className={classes.menuIcon}
181
+ onClick={event => setConnectionEl(event.currentTarget)}
182
+ >
183
+ <Cable />
184
+ </IconButton>
185
+ )}
186
+
187
+ {model.selection.length ? (
188
+ <IconButton
189
+ className={classes.menuIcon}
190
+ onClick={event => setSelectionEl(event.currentTarget)}
191
+ >
192
+ <Badge badgeContent={model.selection.length} color="primary">
193
+ <ShoppingCartIcon />
194
+ </Badge>
195
+ </IconButton>
196
+ ) : null}
197
+
198
+ <TextField
199
+ className={classes.searchBox}
200
+ label="Filter tracks"
201
+ value={model.filterText}
202
+ onChange={event => model.setFilterText(event.target.value)}
203
+ fullWidth
204
+ InputProps={{
205
+ endAdornment: (
206
+ <InputAdornment position="end">
207
+ <IconButton color="secondary" onClick={model.clearFilterText}>
208
+ <ClearIcon />
209
+ </IconButton>
210
+ </InputAdornment>
211
+ ),
212
+ }}
213
+ />
214
+ </div>
215
+ <JBrowseMenu
216
+ anchorEl={connectionEl}
217
+ open={Boolean(connectionEl)}
218
+ onMenuItemClick={(_, callback) => {
219
+ callback()
220
+ setConnectionEl(undefined)
221
+ }}
222
+ onClose={() => setConnectionEl(undefined)}
223
+ menuItems={connectionMenuItems}
224
+ />
225
+ <JBrowseMenu
226
+ anchorEl={menuEl}
227
+ open={Boolean(menuEl)}
228
+ onMenuItemClick={(_, callback) => {
229
+ callback()
230
+ setMenuEl(undefined)
231
+ }}
232
+ onClose={() => setMenuEl(undefined)}
233
+ menuItems={menuItems}
234
+ />
235
+ <JBrowseMenu
236
+ anchorEl={selectionEl}
237
+ open={Boolean(selectionEl)}
238
+ onMenuItemClick={(_, callback) => {
239
+ callback()
240
+ setSelectionEl(undefined)
241
+ }}
242
+ onClose={() => setSelectionEl(undefined)}
243
+ menuItems={[
244
+ { label: 'Clear', onClick: () => model.clearSelection() },
245
+ ...items.map(item => ({
246
+ ...item,
247
+ ...('onClick' in item
248
+ ? { onClick: () => item.onClick(model) }
249
+ : {}),
250
+ })),
251
+ ]}
252
+ />
253
+ <Suspense fallback={<div />}>
254
+ {modalInfo ? (
255
+ <CloseConnectionDialog
256
+ modalInfo={modalInfo}
257
+ setModalInfo={setModalInfo}
258
+ />
259
+ ) : null}
260
+ {deleteDlgDetails ? (
261
+ <DeleteConnectionDialog
262
+ handleClose={() => setDeleteDlgDetails(undefined)}
263
+ deleteDialogDetails={deleteDlgDetails}
264
+ session={session}
265
+ />
266
+ ) : null}
267
+ {connectionManagerOpen ? (
268
+ <ManageConnectionsDialog
269
+ handleClose={() => setConnectionManagerOpen(false)}
270
+ breakConnection={breakConnection}
271
+ session={session}
272
+ />
273
+ ) : null}
274
+ {connectionToggleOpen ? (
275
+ <ToggleConnectionsDialog
276
+ handleClose={() => setConnectionToggleOpen(false)}
277
+ session={session}
278
+ breakConnection={breakConnection}
279
+ assemblyName={assemblyName}
280
+ />
281
+ ) : null}
282
+ </Suspense>
283
+ </div>
284
+ )
285
+ }
286
+
287
+ export default observer(HierarchicalTrackSelectorHeader)