@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,284 @@
1
+ import React, { useState } from 'react'
2
+ import {
3
+ Checkbox,
4
+ FormControlLabel,
5
+ IconButton,
6
+ Tooltip,
7
+ Typography,
8
+ } from '@mui/material'
9
+ import { makeStyles } from 'tss-react/mui'
10
+
11
+ // icons
12
+ import ArrowDropDownIcon from '@mui/icons-material/ArrowDropDown'
13
+ import ArrowRightIcon from '@mui/icons-material/ArrowRight'
14
+ import MoreHorizIcon from '@mui/icons-material/MoreHoriz'
15
+
16
+ // other
17
+ import { HierarchicalTrackSelectorModel, TreeNode } from '../model'
18
+ import JBrowseMenu from '@jbrowse/core/ui/Menu'
19
+ import { getSession } from '@jbrowse/core/util'
20
+
21
+ import {
22
+ AnyConfigurationModel,
23
+ readConfObject,
24
+ } from '@jbrowse/core/configuration'
25
+ import { getAllChildren } from './util'
26
+
27
+ const useStyles = makeStyles()(theme => ({
28
+ compactCheckbox: {
29
+ padding: 0,
30
+ },
31
+
32
+ checkboxLabel: {
33
+ marginRight: 0,
34
+ '&:hover': {
35
+ backgroundColor: '#eee',
36
+ },
37
+ },
38
+
39
+ contrastColor: {
40
+ color: theme.palette.secondary.contrastText,
41
+ },
42
+
43
+ // this accordionBase element's small padding is used to give a margin to
44
+ // accordionColor it a "margin" because the virtualized elements can't really
45
+ // use margin in a conventional way (it doesn't affect layout)
46
+ accordionBase: {
47
+ display: 'flex',
48
+ },
49
+
50
+ accordionCard: {
51
+ padding: 3,
52
+ cursor: 'pointer',
53
+ display: 'flex',
54
+ },
55
+
56
+ nestingLevelMarker: {
57
+ position: 'absolute',
58
+ borderLeft: '1.5px solid #555',
59
+ },
60
+ // accordionColor set's display:flex so that the child accordionText use
61
+ // vertically centered text
62
+ accordionColor: {
63
+ // @ts-ignore
64
+ background: theme.palette.tertiary?.main,
65
+ // @ts-ignore
66
+ color: theme.palette.tertiary?.contrastText,
67
+ width: '100%',
68
+ display: 'flex',
69
+ paddingLeft: 5,
70
+ },
71
+
72
+ // margin:auto 0 to center text vertically
73
+ accordionText: {
74
+ margin: 'auto 0',
75
+ },
76
+ }))
77
+
78
+ export interface InfoArgs {
79
+ target: HTMLElement
80
+ id: string
81
+ conf: AnyConfigurationModel
82
+ }
83
+
84
+ function treeToMap(tree: TreeNode, map = new Map<string, TreeNode>()) {
85
+ if (tree.id && tree.children.length) {
86
+ map.set(tree.id, tree)
87
+ }
88
+ for (let i = 0; i < tree.children.length; i++) {
89
+ const node = tree.children[i]
90
+ treeToMap(node, map)
91
+ }
92
+ return map
93
+ }
94
+
95
+ function isUnsupported(name = '') {
96
+ return name.endsWith('(Unsupported)') || name.endsWith('(Unknown)')
97
+ }
98
+
99
+ // An individual node in the track selector. Note: manually sets cursor:
100
+ // pointer improves usability for what can be clicked
101
+ export default function Node(props: {
102
+ data: {
103
+ nestingLevel: number
104
+ checked: boolean
105
+ conf: AnyConfigurationModel
106
+ drawerPosition: unknown
107
+ id: string
108
+ isLeaf: boolean
109
+ name: string
110
+ onChange: Function
111
+ toggleCollapse: (arg: string) => void
112
+ tree: TreeNode
113
+ selected: boolean
114
+ model: HierarchicalTrackSelectorModel
115
+ }
116
+ isOpen: boolean
117
+ style?: { height: number }
118
+ setOpen: (arg: boolean) => void
119
+ }) {
120
+ const { data, isOpen, style, setOpen } = props
121
+ const {
122
+ checked,
123
+ conf,
124
+ drawerPosition,
125
+ id,
126
+ isLeaf,
127
+ model,
128
+ name,
129
+ nestingLevel,
130
+ onChange,
131
+ selected,
132
+ toggleCollapse,
133
+ tree,
134
+ } = data
135
+
136
+ const { classes } = useStyles()
137
+ const width = 10
138
+ const [menuEl, setMenuEl] = useState<HTMLElement | null>(null)
139
+ const [info, setInfo] = useState<InfoArgs>()
140
+ const marginLeft = nestingLevel * width + (isLeaf ? width : 0)
141
+ const description = (conf && readConfObject(conf, ['description'])) || ''
142
+
143
+ return (
144
+ <div style={style} className={!isLeaf ? classes.accordionBase : undefined}>
145
+ {new Array(nestingLevel).fill(0).map((_, idx) => (
146
+ <div
147
+ key={`mark-${idx}`}
148
+ style={{ left: idx * width + 4, height: style?.height }}
149
+ className={classes.nestingLevelMarker}
150
+ />
151
+ ))}
152
+ <div
153
+ className={!isLeaf ? classes.accordionCard : undefined}
154
+ onClick={() => {
155
+ if (!menuEl) {
156
+ toggleCollapse(id)
157
+ setOpen(!isOpen)
158
+ }
159
+ }}
160
+ style={{
161
+ marginLeft,
162
+ whiteSpace: 'nowrap',
163
+ width: '100%',
164
+ }}
165
+ >
166
+ <div className={!isLeaf ? classes.accordionColor : undefined}>
167
+ {!isLeaf ? (
168
+ <div className={classes.accordionText}>
169
+ <Typography>
170
+ {isOpen ? <ArrowDropDownIcon /> : <ArrowRightIcon />}
171
+ {name}
172
+ <IconButton
173
+ onClick={event => {
174
+ setMenuEl(event.currentTarget)
175
+ event.stopPropagation()
176
+ }}
177
+ className={classes.contrastColor}
178
+ >
179
+ <MoreHorizIcon />
180
+ </IconButton>
181
+ </Typography>
182
+ </div>
183
+ ) : (
184
+ <>
185
+ <Tooltip
186
+ title={description + (selected ? ' (in selection)' : '')}
187
+ placement={drawerPosition === 'left' ? 'right' : 'left'}
188
+ >
189
+ <FormControlLabel
190
+ className={classes.checkboxLabel}
191
+ control={
192
+ <Checkbox
193
+ className={classes.compactCheckbox}
194
+ checked={checked}
195
+ onChange={() => onChange(id)}
196
+ disabled={isUnsupported(name)}
197
+ inputProps={{
198
+ // @ts-ignore
199
+ 'data-testid': `htsTrackEntry-${id}`,
200
+ }}
201
+ />
202
+ }
203
+ label={
204
+ <div
205
+ style={{
206
+ background: selected ? '#cccc' : undefined,
207
+ padding: 1,
208
+ }}
209
+ >
210
+ {name}
211
+ </div>
212
+ }
213
+ />
214
+ </Tooltip>
215
+ <IconButton
216
+ onClick={e => setInfo({ target: e.currentTarget, id, conf })}
217
+ style={{ padding: 0 }}
218
+ color="secondary"
219
+ data-testid={`htsTrackEntryMenu-${id}`}
220
+ >
221
+ <MoreHorizIcon />
222
+ </IconButton>
223
+ </>
224
+ )}
225
+ {menuEl ? (
226
+ <JBrowseMenu
227
+ anchorEl={menuEl}
228
+ menuItems={[
229
+ {
230
+ label: 'Add to selection',
231
+ onClick: () =>
232
+ model.addToSelection(
233
+ getAllChildren(treeToMap(tree).get(id)),
234
+ ),
235
+ },
236
+ {
237
+ label: 'Remove from selection',
238
+ onClick: () =>
239
+ model.removeFromSelection(
240
+ getAllChildren(treeToMap(tree).get(id)),
241
+ ),
242
+ },
243
+ ]}
244
+ onMenuItemClick={(_event, callback) => {
245
+ callback()
246
+ setMenuEl(null)
247
+ }}
248
+ open={Boolean(menuEl)}
249
+ onClose={() => setMenuEl(null)}
250
+ />
251
+ ) : null}
252
+
253
+ {info ? (
254
+ <JBrowseMenu
255
+ anchorEl={info?.target}
256
+ menuItems={[
257
+ ...(getSession(model).getTrackActionMenuItems?.(info.conf) ||
258
+ []),
259
+ {
260
+ label: 'Add to selection',
261
+ onClick: () => model.addToSelection([info.conf]),
262
+ },
263
+ ...(selected
264
+ ? [
265
+ {
266
+ label: 'Remove from selection',
267
+ onClick: () => model.removeFromSelection([info.conf]),
268
+ },
269
+ ]
270
+ : []),
271
+ ]}
272
+ onMenuItemClick={(_event, callback) => {
273
+ callback()
274
+ setInfo(undefined)
275
+ }}
276
+ open={Boolean(info)}
277
+ onClose={() => setInfo(undefined)}
278
+ />
279
+ ) : null}
280
+ </div>
281
+ </div>
282
+ </div>
283
+ )
284
+ }
@@ -0,0 +1,11 @@
1
+ import { AnyConfigurationModel } from '@jbrowse/core/configuration'
2
+ import { TreeNode } from '../model'
3
+
4
+ export function getAllChildren(subtree?: TreeNode): AnyConfigurationModel[] {
5
+ // @ts-ignore
6
+ return (
7
+ subtree?.children.map(t =>
8
+ t.children.length ? getAllChildren(t) : (t.conf as AnyConfigurationModel),
9
+ ) || []
10
+ ).flat(Infinity)
11
+ }
@@ -0,0 +1,3 @@
1
+ import { ConfigurationSchema } from '@jbrowse/core/configuration'
2
+ const configSchema = ConfigurationSchema('HierarchicalTrackSelectorWidget', {})
3
+ export default configSchema
@@ -1,7 +1,5 @@
1
- import { ConfigurationSchema } from '@jbrowse/core/configuration'
1
+ import stateModelFactory, { HierarchicalTrackSelectorModel } from './model'
2
+ import configSchema from './configSchema'
2
3
 
3
- export { default as stateModelFactory } from './model'
4
- export const configSchema = ConfigurationSchema(
5
- 'HierarchicalTrackSelectorWidget',
6
- {},
7
- )
4
+ export { stateModelFactory, configSchema }
5
+ export type { HierarchicalTrackSelectorModel }
@@ -1,13 +1,12 @@
1
- import { types, getParent, Instance } from 'mobx-state-tree'
1
+ import { types, getParent, getEnv, Instance } from 'mobx-state-tree'
2
2
  import {
3
- AnyConfigurationModel,
4
3
  getConf,
5
4
  readConfObject,
5
+ AnyConfigurationModel,
6
6
  } from '@jbrowse/core/configuration'
7
7
  import { getSession } from '@jbrowse/core/util'
8
8
  import { ElementId } from '@jbrowse/core/util/types/mst'
9
9
  import PluginManager from '@jbrowse/core/PluginManager'
10
- import { AbstractView } from 'react'
11
10
 
12
11
  function hasAnyOverlap<T>(a1: T[] = [], a2: T[] = []) {
13
12
  return !!a1.find(value => a2.includes(value))
@@ -18,11 +17,11 @@ function passesFilter(filter: string, config: AnyConfigurationModel) {
18
17
  const filterLower = filter.toLowerCase()
19
18
  return (
20
19
  getTrackName(config).toLowerCase().includes(filterLower) ||
21
- categories?.filter(c => c.toLowerCase().includes(filterLower)).length
20
+ !!categories?.filter(c => c.toLowerCase().includes(filterLower)).length
22
21
  )
23
22
  }
24
23
 
25
- function getTrackName(config: AnyConfigurationModel) {
24
+ function getTrackName(config: AnyConfigurationModel): string {
26
25
  if (!config.trackId) {
27
26
  throw new Error('not a track')
28
27
  }
@@ -41,6 +40,32 @@ export type TreeNode = {
41
40
  children: TreeNode[]
42
41
  }
43
42
 
43
+ function filterTracks(
44
+ tracks: AnyConfigurationModel[],
45
+ self: { view: { type: string } },
46
+ assemblyName: string,
47
+ ) {
48
+ const { assemblyManager } = getSession(self)
49
+ const { pluginManager } = getEnv(self)
50
+ const assembly = assemblyManager.get(assemblyName)
51
+
52
+ if (!assembly) {
53
+ return []
54
+ }
55
+ return tracks
56
+ .filter(c => {
57
+ const trackConfAssemblies = readConfObject(c, 'assemblyNames')
58
+ const { allAliases } = assembly
59
+ return hasAnyOverlap(allAliases, trackConfAssemblies)
60
+ })
61
+ .filter(c => {
62
+ const { displayTypes } = pluginManager.getViewType(self.view.type)
63
+ const compatDisplays = displayTypes.map((d: { name: string }) => d.name)
64
+ const trackDisplays = c.displays.map((d: { type: string }) => d.type)
65
+ return hasAnyOverlap(compatDisplays, trackDisplays)
66
+ })
67
+ }
68
+
44
69
  export function generateHierarchy(
45
70
  model: HierarchicalTrackSelectorModel,
46
71
  trackConfigurations: AnyConfigurationModel[],
@@ -112,8 +137,20 @@ export default function stateTreeFactory(pluginManager: PluginManager) {
112
137
  pluginManager.pluggableMstType('view', 'stateModel'),
113
138
  ),
114
139
  })
140
+ .volatile(() => ({
141
+ selection: [] as AnyConfigurationModel[],
142
+ }))
115
143
  .actions(self => ({
116
- setView(view: AbstractView) {
144
+ addToSelection(elt: AnyConfigurationModel[]) {
145
+ self.selection = [...self.selection, ...elt]
146
+ },
147
+ removeFromSelection(elt: AnyConfigurationModel[]) {
148
+ self.selection = self.selection.filter(f => !elt.includes(f))
149
+ },
150
+ clearSelection() {
151
+ self.selection = []
152
+ },
153
+ setView(view: unknown) {
117
154
  self.view = view
118
155
  },
119
156
  toggleCategory(pathName: string) {
@@ -156,20 +193,7 @@ export default function stateTreeFactory(pluginManager: PluginManager) {
156
193
  // filter out tracks that don't match the current assembly (check all
157
194
  // assembly aliases) and display types
158
195
  return (refseq ? [refseq] : []).concat([
159
- ...tracks
160
- .filter(c => {
161
- const trackConfAssemblies = readConfObject(c, 'assemblyNames')
162
- const { allAliases } = assembly
163
- return hasAnyOverlap(allAliases, trackConfAssemblies)
164
- })
165
- .filter(c => {
166
- const { displayTypes } = pluginManager.getViewType(self.view.type)
167
- const compatibleDisplays = displayTypes.map(d => d.name)
168
- const trackDisplays = c.displays.map(
169
- (d: { type: string }) => d.type,
170
- )
171
- return hasAnyOverlap(compatibleDisplays, trackDisplays)
172
- }),
196
+ ...filterTracks(tracks, self, assemblyName),
173
197
  ])
174
198
  },
175
199
 
@@ -184,29 +208,9 @@ export default function stateTreeFactory(pluginManager: PluginManager) {
184
208
  if (!self.view) {
185
209
  return []
186
210
  }
187
- const trackConfigurations = connection.tracks
188
- const { assemblyManager } = getSession(self)
189
- const assembly = assemblyManager.get(assemblyName)
190
-
191
- if (!(assembly && assembly.initialized)) {
192
- return []
193
- }
194
211
 
195
212
  // filter out tracks that don't match the current display types
196
- return trackConfigurations
197
- .filter(c => {
198
- const trackConfAssemblies = readConfObject(c, 'assemblyNames')
199
- const { allAliases } = assembly
200
- return hasAnyOverlap(allAliases, trackConfAssemblies)
201
- })
202
- .filter(c => {
203
- const { displayTypes } = pluginManager.getViewType(self.view.type)
204
- const compatibleDisplays = displayTypes.map(d => d.name)
205
- const trackDisplays = c.displays.map(
206
- (d: { type: string }) => d.type,
207
- )
208
- return hasAnyOverlap(compatibleDisplays, trackDisplays)
209
- })
213
+ return filterTracks(connection.tracks, self, assemblyName)
210
214
  },
211
215
  }))
212
216
  .views(self => ({
package/src/index.ts CHANGED
@@ -11,6 +11,7 @@ import {
11
11
  stateModelFactory as AddTrackStateModelFactory,
12
12
  configSchema as AddTrackConfigSchema,
13
13
  } from './AddTrackWidget'
14
+ import { AddTrackModel } from './AddTrackWidget/model'
14
15
  import {
15
16
  stateModel as AddConnectionStateModel,
16
17
  configSchema as AddConnectionConfigSchema,
@@ -18,6 +19,7 @@ import {
18
19
  import {
19
20
  stateModelFactory as HierarchicalTrackSelectorStateModelFactory,
20
21
  configSchema as HierarchicalTrackSelectorConfigSchema,
22
+ HierarchicalTrackSelectorModel,
21
23
  } from './HierarchicalTrackSelectorWidget'
22
24
  import {
23
25
  stateModelFactory as PluginStoreStateModelFactory,
@@ -101,7 +103,8 @@ export default class extends Plugin {
101
103
  })
102
104
  }
103
105
 
104
- configure(pluginManager: PluginManager) {}
106
+ configure(_pluginManager: PluginManager) {}
105
107
  }
106
108
 
107
109
  export { AssemblyManager, SetDefaultSession }
110
+ export type { HierarchicalTrackSelectorModel, AddTrackModel }