@jbrowse/plugin-data-management 2.6.2 → 2.7.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 (161) hide show
  1. package/dist/AddConnectionWidget/components/AddConnectionWidget.d.ts +2 -2
  2. package/dist/AddConnectionWidget/components/AddConnectionWidget.js +17 -8
  3. package/dist/AddConnectionWidget/components/ConfigureConnection.d.ts +2 -2
  4. package/dist/AddConnectionWidget/components/ConfigureConnection.js +2 -1
  5. package/dist/AddConnectionWidget/components/ConnectionTypeSelect.d.ts +2 -2
  6. package/dist/AddConnectionWidget/components/ConnectionTypeSelect.js +5 -3
  7. package/dist/AddTrackWidget/components/AddTrackWidget.d.ts +3 -4
  8. package/dist/AddTrackWidget/components/AddTrackWidget.js +3 -3
  9. package/dist/AddTrackWidget/components/ConfirmTrack.d.ts +2 -2
  10. package/dist/AddTrackWidget/components/ConfirmTrack.js +3 -2
  11. package/dist/AddTrackWidget/components/DefaultAddTrackWorkflow.d.ts +3 -4
  12. package/dist/AddTrackWidget/components/DefaultAddTrackWorkflow.js +32 -27
  13. package/dist/AddTrackWidget/components/PasteConfigWorkflow.d.ts +3 -4
  14. package/dist/AddTrackWidget/components/PasteConfigWorkflow.js +10 -7
  15. package/dist/AddTrackWidget/components/TrackAdapterSelector.js +1 -1
  16. package/dist/AddTrackWidget/components/TrackSourceSelect.d.ts +3 -4
  17. package/dist/AddTrackWidget/components/TrackSourceSelect.js +3 -3
  18. package/dist/HierarchicalTrackSelectorWidget/components/HierarchicalFab.d.ts +2 -2
  19. package/dist/HierarchicalTrackSelectorWidget/components/HierarchicalFab.js +2 -1
  20. package/dist/HierarchicalTrackSelectorWidget/components/ShoppingCart.d.ts +2 -2
  21. package/dist/HierarchicalTrackSelectorWidget/components/ShoppingCart.js +2 -1
  22. package/dist/HierarchicalTrackSelectorWidget/components/dialogs/CloseConnectionDialog.d.ts +3 -5
  23. package/dist/HierarchicalTrackSelectorWidget/components/dialogs/CloseConnectionDialog.js +2 -1
  24. package/dist/HierarchicalTrackSelectorWidget/components/dialogs/DeleteConnectionDialog.d.ts +3 -4
  25. package/dist/HierarchicalTrackSelectorWidget/components/dialogs/DeleteConnectionDialog.js +5 -8
  26. package/dist/HierarchicalTrackSelectorWidget/components/dialogs/ManageConnectionsDialog.d.ts +3 -4
  27. package/dist/HierarchicalTrackSelectorWidget/components/dialogs/ManageConnectionsDialog.js +3 -3
  28. package/dist/HierarchicalTrackSelectorWidget/components/dialogs/ToggleConnectionsDialog.d.ts +2 -3
  29. package/dist/HierarchicalTrackSelectorWidget/components/dialogs/ToggleConnectionsDialog.js +2 -3
  30. package/dist/HierarchicalTrackSelectorWidget/components/faceted/FacetFilter.d.ts +13 -0
  31. package/dist/HierarchicalTrackSelectorWidget/components/faceted/FacetFilter.js +72 -0
  32. package/dist/HierarchicalTrackSelectorWidget/components/faceted/FacetFilters.js +31 -77
  33. package/dist/HierarchicalTrackSelectorWidget/components/faceted/FacetedDialog.d.ts +3 -4
  34. package/dist/HierarchicalTrackSelectorWidget/components/faceted/FacetedDialog.js +3 -3
  35. package/dist/HierarchicalTrackSelectorWidget/components/faceted/FacetedHeader.d.ts +5 -3
  36. package/dist/HierarchicalTrackSelectorWidget/components/faceted/FacetedHeader.js +10 -4
  37. package/dist/HierarchicalTrackSelectorWidget/components/faceted/FacetedSelector.d.ts +2 -2
  38. package/dist/HierarchicalTrackSelectorWidget/components/faceted/FacetedSelector.js +53 -33
  39. package/dist/HierarchicalTrackSelectorWidget/components/tree/HamburgerMenu.d.ts +2 -2
  40. package/dist/HierarchicalTrackSelectorWidget/components/tree/HamburgerMenu.js +39 -21
  41. package/dist/HierarchicalTrackSelectorWidget/components/tree/HierarchicalHeader.d.ts +3 -4
  42. package/dist/HierarchicalTrackSelectorWidget/components/tree/HierarchicalHeader.js +13 -9
  43. package/dist/HierarchicalTrackSelectorWidget/components/tree/HierarchicalTree.d.ts +4 -3
  44. package/dist/HierarchicalTrackSelectorWidget/components/tree/HierarchicalTree.js +5 -6
  45. package/dist/HierarchicalTrackSelectorWidget/components/tree/TrackCategory.js +22 -0
  46. package/dist/HierarchicalTrackSelectorWidget/components/tree/TrackLabel.js +3 -3
  47. package/dist/HierarchicalTrackSelectorWidget/components/util.d.ts +2 -1
  48. package/dist/HierarchicalTrackSelectorWidget/components/util.js +1 -2
  49. package/dist/HierarchicalTrackSelectorWidget/filterTracks.d.ts +12 -0
  50. package/dist/HierarchicalTrackSelectorWidget/filterTracks.js +32 -0
  51. package/dist/HierarchicalTrackSelectorWidget/generateHierarchy.d.ts +21 -0
  52. package/dist/HierarchicalTrackSelectorWidget/generateHierarchy.js +98 -0
  53. package/dist/HierarchicalTrackSelectorWidget/model.d.ts +68 -28
  54. package/dist/HierarchicalTrackSelectorWidget/model.js +147 -117
  55. package/dist/HierarchicalTrackSelectorWidget/util.d.ts +12 -0
  56. package/dist/HierarchicalTrackSelectorWidget/util.js +53 -0
  57. package/dist/PluginStoreWidget/components/AddCustomPluginDialog.d.ts +7 -0
  58. package/dist/PluginStoreWidget/components/{CustomPluginForm.js → AddCustomPluginDialog.js} +13 -21
  59. package/dist/PluginStoreWidget/components/DeletePluginDialog.d.ts +5 -0
  60. package/dist/PluginStoreWidget/components/DeletePluginDialog.js +28 -0
  61. package/dist/PluginStoreWidget/components/InstalledPlugin.d.ts +3 -6
  62. package/dist/PluginStoreWidget/components/InstalledPlugin.js +24 -51
  63. package/dist/PluginStoreWidget/components/InstalledPluginsList.d.ts +3 -4
  64. package/dist/PluginStoreWidget/components/InstalledPluginsList.js +7 -9
  65. package/dist/PluginStoreWidget/components/PluginCard.d.ts +2 -2
  66. package/dist/PluginStoreWidget/components/PluginCard.js +5 -6
  67. package/dist/PluginStoreWidget/components/PluginStoreWidget.d.ts +3 -4
  68. package/dist/PluginStoreWidget/components/PluginStoreWidget.js +14 -42
  69. package/dist/PluginStoreWidget/components/util.d.ts +5 -0
  70. package/dist/PluginStoreWidget/components/util.js +29 -0
  71. package/dist/PluginStoreWidget/model.d.ts +2 -2
  72. package/dist/PluginStoreWidget/model.js +2 -2
  73. package/dist/index.d.ts +1 -9
  74. package/dist/index.js +1 -4
  75. package/dist/ucsc-trackhub/model.js +2 -2
  76. package/esm/AddConnectionWidget/components/AddConnectionWidget.d.ts +2 -2
  77. package/esm/AddConnectionWidget/components/AddConnectionWidget.js +19 -10
  78. package/esm/AddConnectionWidget/components/ConfigureConnection.d.ts +2 -2
  79. package/esm/AddConnectionWidget/components/ConfigureConnection.js +2 -1
  80. package/esm/AddConnectionWidget/components/ConnectionTypeSelect.d.ts +2 -2
  81. package/esm/AddConnectionWidget/components/ConnectionTypeSelect.js +5 -3
  82. package/esm/AddTrackWidget/components/AddTrackWidget.d.ts +3 -4
  83. package/esm/AddTrackWidget/components/AddTrackWidget.js +3 -3
  84. package/esm/AddTrackWidget/components/ConfirmTrack.d.ts +2 -2
  85. package/esm/AddTrackWidget/components/ConfirmTrack.js +4 -3
  86. package/esm/AddTrackWidget/components/DefaultAddTrackWorkflow.d.ts +3 -4
  87. package/esm/AddTrackWidget/components/DefaultAddTrackWorkflow.js +33 -28
  88. package/esm/AddTrackWidget/components/PasteConfigWorkflow.d.ts +3 -4
  89. package/esm/AddTrackWidget/components/PasteConfigWorkflow.js +11 -8
  90. package/esm/AddTrackWidget/components/TrackAdapterSelector.js +1 -1
  91. package/esm/AddTrackWidget/components/TrackSourceSelect.d.ts +3 -4
  92. package/esm/AddTrackWidget/components/TrackSourceSelect.js +3 -3
  93. package/esm/HierarchicalTrackSelectorWidget/components/HierarchicalFab.d.ts +2 -2
  94. package/esm/HierarchicalTrackSelectorWidget/components/HierarchicalFab.js +2 -1
  95. package/esm/HierarchicalTrackSelectorWidget/components/ShoppingCart.d.ts +2 -2
  96. package/esm/HierarchicalTrackSelectorWidget/components/ShoppingCart.js +2 -1
  97. package/esm/HierarchicalTrackSelectorWidget/components/dialogs/CloseConnectionDialog.d.ts +3 -5
  98. package/esm/HierarchicalTrackSelectorWidget/components/dialogs/CloseConnectionDialog.js +2 -1
  99. package/esm/HierarchicalTrackSelectorWidget/components/dialogs/DeleteConnectionDialog.d.ts +3 -4
  100. package/esm/HierarchicalTrackSelectorWidget/components/dialogs/DeleteConnectionDialog.js +6 -9
  101. package/esm/HierarchicalTrackSelectorWidget/components/dialogs/ManageConnectionsDialog.d.ts +3 -4
  102. package/esm/HierarchicalTrackSelectorWidget/components/dialogs/ManageConnectionsDialog.js +3 -3
  103. package/esm/HierarchicalTrackSelectorWidget/components/dialogs/ToggleConnectionsDialog.d.ts +2 -3
  104. package/esm/HierarchicalTrackSelectorWidget/components/dialogs/ToggleConnectionsDialog.js +3 -2
  105. package/esm/HierarchicalTrackSelectorWidget/components/faceted/FacetFilter.d.ts +13 -0
  106. package/esm/HierarchicalTrackSelectorWidget/components/faceted/FacetFilter.js +43 -0
  107. package/esm/HierarchicalTrackSelectorWidget/components/faceted/FacetFilters.js +31 -54
  108. package/esm/HierarchicalTrackSelectorWidget/components/faceted/FacetedDialog.d.ts +3 -4
  109. package/esm/HierarchicalTrackSelectorWidget/components/faceted/FacetedDialog.js +3 -3
  110. package/esm/HierarchicalTrackSelectorWidget/components/faceted/FacetedHeader.d.ts +5 -3
  111. package/esm/HierarchicalTrackSelectorWidget/components/faceted/FacetedHeader.js +10 -4
  112. package/esm/HierarchicalTrackSelectorWidget/components/faceted/FacetedSelector.d.ts +2 -2
  113. package/esm/HierarchicalTrackSelectorWidget/components/faceted/FacetedSelector.js +51 -31
  114. package/esm/HierarchicalTrackSelectorWidget/components/tree/HamburgerMenu.d.ts +2 -2
  115. package/esm/HierarchicalTrackSelectorWidget/components/tree/HamburgerMenu.js +38 -20
  116. package/esm/HierarchicalTrackSelectorWidget/components/tree/HierarchicalHeader.d.ts +3 -4
  117. package/esm/HierarchicalTrackSelectorWidget/components/tree/HierarchicalHeader.js +13 -9
  118. package/esm/HierarchicalTrackSelectorWidget/components/tree/HierarchicalTree.d.ts +4 -3
  119. package/esm/HierarchicalTrackSelectorWidget/components/tree/HierarchicalTree.js +5 -6
  120. package/esm/HierarchicalTrackSelectorWidget/components/tree/TrackCategory.js +22 -0
  121. package/esm/HierarchicalTrackSelectorWidget/components/tree/TrackLabel.js +2 -2
  122. package/esm/HierarchicalTrackSelectorWidget/components/util.d.ts +2 -1
  123. package/esm/HierarchicalTrackSelectorWidget/components/util.js +1 -2
  124. package/esm/HierarchicalTrackSelectorWidget/filterTracks.d.ts +12 -0
  125. package/esm/HierarchicalTrackSelectorWidget/filterTracks.js +28 -0
  126. package/esm/HierarchicalTrackSelectorWidget/generateHierarchy.d.ts +21 -0
  127. package/esm/HierarchicalTrackSelectorWidget/generateHierarchy.js +94 -0
  128. package/esm/HierarchicalTrackSelectorWidget/model.d.ts +68 -28
  129. package/esm/HierarchicalTrackSelectorWidget/model.js +149 -116
  130. package/esm/HierarchicalTrackSelectorWidget/util.d.ts +12 -0
  131. package/esm/HierarchicalTrackSelectorWidget/util.js +45 -0
  132. package/esm/PluginStoreWidget/components/AddCustomPluginDialog.d.ts +7 -0
  133. package/esm/PluginStoreWidget/components/{CustomPluginForm.js → AddCustomPluginDialog.js} +13 -21
  134. package/esm/PluginStoreWidget/components/DeletePluginDialog.d.ts +5 -0
  135. package/esm/PluginStoreWidget/components/DeletePluginDialog.js +22 -0
  136. package/esm/PluginStoreWidget/components/InstalledPlugin.d.ts +3 -6
  137. package/esm/PluginStoreWidget/components/InstalledPlugin.js +27 -54
  138. package/esm/PluginStoreWidget/components/InstalledPluginsList.d.ts +3 -4
  139. package/esm/PluginStoreWidget/components/InstalledPluginsList.js +7 -9
  140. package/esm/PluginStoreWidget/components/PluginCard.d.ts +2 -2
  141. package/esm/PluginStoreWidget/components/PluginCard.js +5 -6
  142. package/esm/PluginStoreWidget/components/PluginStoreWidget.d.ts +3 -4
  143. package/esm/PluginStoreWidget/components/PluginStoreWidget.js +15 -43
  144. package/esm/PluginStoreWidget/components/util.d.ts +5 -0
  145. package/esm/PluginStoreWidget/components/util.js +25 -0
  146. package/esm/PluginStoreWidget/model.d.ts +2 -2
  147. package/esm/PluginStoreWidget/model.js +1 -1
  148. package/esm/index.d.ts +1 -9
  149. package/esm/index.js +1 -3
  150. package/esm/ucsc-trackhub/model.js +2 -2
  151. package/package.json +5 -4
  152. package/dist/PluginStoreWidget/components/CustomPluginForm.d.ts +0 -9
  153. package/dist/SetDefaultSession/SetDefaultSession.d.ts +0 -6
  154. package/dist/SetDefaultSession/SetDefaultSession.js +0 -38
  155. package/dist/SetDefaultSession/index.d.ts +0 -1
  156. package/dist/SetDefaultSession/index.js +0 -8
  157. package/esm/PluginStoreWidget/components/CustomPluginForm.d.ts +0 -9
  158. package/esm/SetDefaultSession/SetDefaultSession.d.ts +0 -6
  159. package/esm/SetDefaultSession/SetDefaultSession.js +0 -33
  160. package/esm/SetDefaultSession/index.d.ts +0 -1
  161. package/esm/SetDefaultSession/index.js +0 -1
@@ -6,7 +6,7 @@ export interface InfoArgs {
6
6
  id: string;
7
7
  conf: AnyConfigurationModel;
8
8
  }
9
- declare const _default: ({ model, }: {
9
+ declare const FacetedSelector: ({ model, }: {
10
10
  model: HierarchicalTrackSelectorModel;
11
11
  }) => React.JSX.Element;
12
- export default _default;
12
+ export default FacetedSelector;
@@ -9,35 +9,43 @@ import { getTrackName } from '@jbrowse/core/util/tracks';
9
9
  import { ResizeHandle } from '@jbrowse/core/ui';
10
10
  import SanitizedHTML from '@jbrowse/core/ui/SanitizedHTML';
11
11
  import JBrowseMenu from '@jbrowse/core/ui/Menu';
12
- import ResizeBar, { useResizeBar } from '@jbrowse/core/ui/ResizeBar';
13
- import { getEnv, getSession, measureGridWidth, useDebounce, } from '@jbrowse/core/util';
12
+ import ResizeBar from '@jbrowse/core/ui/ResizeBar';
13
+ import { getEnv, getSession, measureGridWidth, useDebounce, useLocalStorage, } from '@jbrowse/core/util';
14
14
  import { readConfObject, } from '@jbrowse/core/configuration';
15
+ import { useResizeBar } from '@jbrowse/core/ui/useResizeBar';
16
+ import { makeStyles } from 'tss-react/mui';
15
17
  // icons
16
18
  import MoreHoriz from '@mui/icons-material/MoreHoriz';
17
- // locals
18
- import { matches } from '../../model';
19
+ import { matches } from '../../util';
19
20
  import FacetedHeader from './FacetedHeader';
20
21
  import FacetFilters from './FacetFilters';
21
22
  import { getRootKeys } from './util';
22
23
  const nonMetadataKeys = ['category', 'adapter', 'description'];
24
+ const useStyles = makeStyles()({
25
+ cell: {
26
+ whiteSpace: 'nowrap',
27
+ overflow: 'hidden',
28
+ textOverflow: 'ellipsis',
29
+ },
30
+ });
23
31
  const frac = 0.75;
24
- export default observer(function FacetedSelector({ model, }) {
32
+ const FacetedSelector = observer(function FacetedSelector({ model, }) {
25
33
  var _a, _b;
34
+ const { classes } = useStyles();
26
35
  const { view, selection } = model;
27
36
  const { pluginManager } = getEnv(model);
28
37
  const { ref, scrollLeft } = useResizeBar();
29
38
  const [filterText, setFilterText] = useState('');
30
- const [showOptions, setShowOptions] = useState(false);
39
+ const [showOptions, setShowOptions] = useLocalStorage('facet-showTableOptions', false);
31
40
  const [info, setInfo] = useState();
32
41
  const [useShoppingCart, setUseShoppingCart] = useState(false);
33
- const [hideSparse, setHideSparse] = useState(true);
34
- const [panelWidth, setPanelWidth] = useState(400);
42
+ const [showSparse, setShowSparse] = useLocalStorage('facet-showSparse', false);
43
+ const [showFilters, setShowFilters] = useLocalStorage('facet-showFilters', true);
44
+ const [panelWidth, setPanelWidth] = useLocalStorage('facet-panelWidth', 400);
35
45
  const session = getSession(model);
36
46
  const filterDebounced = useDebounce(filterText, 400);
37
47
  const tracks = view.tracks;
38
- const [filters, dispatch] = useReducer((state, update) => {
39
- return { ...state, [update.key]: update.val };
40
- }, {});
48
+ const [filters, dispatch] = useReducer((state, update) => ({ ...state, [update.key]: update.val }), {});
41
49
  const rows = useMemo(() => {
42
50
  // metadata is spread onto the object for easier access and sorting
43
51
  // by the mui data grid (it's unable to sort by nested objects)
@@ -58,10 +66,10 @@ export default observer(function FacetedSelector({ model, }) {
58
66
  };
59
67
  });
60
68
  }, [model, filterDebounced, session]);
61
- const filteredNonMetadataKeys = useMemo(() => nonMetadataKeys.filter(f => !hideSparse ? true : rows.map(r => r[f]).filter(f => !!f).length > 5), [hideSparse, rows]);
62
- const filteredMetadataKeys = useMemo(() => [...new Set(rows.flatMap(row => getRootKeys(row.metadata)))].filter(f => !hideSparse
69
+ const filteredNonMetadataKeys = useMemo(() => nonMetadataKeys.filter(f => showSparse ? true : rows.map(r => r[f]).filter(f => !!f).length > 5), [showSparse, rows]);
70
+ const filteredMetadataKeys = useMemo(() => [...new Set(rows.flatMap(row => getRootKeys(row.metadata)))].filter(f => showSparse
63
71
  ? true
64
- : rows.map(r => r.metadata[f]).filter(f => !!f).length > 5), [hideSparse, rows]);
72
+ : rows.map(r => r.metadata[f]).filter(f => !!f).length > 5), [showSparse, rows]);
65
73
  const fields = useMemo(() => ['name', ...filteredNonMetadataKeys, ...filteredMetadataKeys], [filteredNonMetadataKeys, filteredMetadataKeys]);
66
74
  const [widths, setWidths] = useState({
67
75
  name: measureGridWidth(rows.map(r => r.name), { maxWidth: 500, stripHTML: true }) + 15,
@@ -97,15 +105,15 @@ export default observer(function FacetedSelector({ model, }) {
97
105
  measureGridWidth(rows.map(r => r.metadata[e]), { stripHTML: true, maxWidth: 400 }),
98
106
  ])),
99
107
  }));
100
- }, [filteredMetadataKeys, visible, filteredNonMetadataKeys, hideSparse, rows]);
101
- const widthsDebounced = useDebounce(widths, 400);
108
+ }, [filteredMetadataKeys, visible, filteredNonMetadataKeys, showSparse, rows]);
109
+ const widthsDebounced = useDebounce(widths, 200);
102
110
  const columns = [
103
111
  {
104
112
  field: 'name',
105
113
  hideable: false,
106
114
  renderCell: (params) => {
107
115
  const { value, id, row } = params;
108
- return (React.createElement(React.Fragment, null,
116
+ return (React.createElement("div", { className: classes.cell },
109
117
  React.createElement(SanitizedHTML, { html: value }),
110
118
  React.createElement(IconButton, { onClick: e => setInfo({
111
119
  target: e.currentTarget,
@@ -123,7 +131,7 @@ export default observer(function FacetedSelector({ model, }) {
123
131
  width: (_a = widthsDebounced[e]) !== null && _a !== void 0 ? _a : 100,
124
132
  renderCell: (params) => {
125
133
  const { value } = params;
126
- return value ? React.createElement(SanitizedHTML, { html: value }) : '';
134
+ return (React.createElement("div", { className: classes.cell }, value ? React.createElement(SanitizedHTML, { html: value }) : ''));
127
135
  },
128
136
  });
129
137
  }),
@@ -134,19 +142,22 @@ export default observer(function FacetedSelector({ model, }) {
134
142
  width: (_a = widthsDebounced[e]) !== null && _a !== void 0 ? _a : 100,
135
143
  renderCell: (params) => {
136
144
  const { value } = params;
137
- return value ? React.createElement(SanitizedHTML, { html: value }) : '';
145
+ return (React.createElement("div", { className: classes.cell }, value ? React.createElement(SanitizedHTML, { html: value }) : ''));
138
146
  },
139
147
  });
140
148
  }),
141
149
  ];
142
- const shownTrackIds = tracks.map(t => t.configuration.trackId);
143
- const arrFilters = Object.entries(filters).filter(f => f[1].length > 0);
150
+ const shownTrackIds = new Set(tracks.map(t => t.configuration.trackId));
151
+ const arrFilters = Object.entries(filters)
152
+ .filter(f => f[1].length > 0)
153
+ .map(([key, val]) => [key, new Set(val)]);
154
+ const filteredRows = rows.filter(row => arrFilters.every(([key, val]) => val.has(row[key])));
144
155
  return (React.createElement(React.Fragment, null,
145
156
  info ? (React.createElement(JBrowseMenu, { anchorEl: info === null || info === void 0 ? void 0 : info.target, menuItems: ((_b = session.getTrackActionMenuItems) === null || _b === void 0 ? void 0 : _b.call(session, info.conf)) || [], onMenuItemClick: (_event, callback) => {
146
157
  callback();
147
158
  setInfo(undefined);
148
159
  }, open: !!info, onClose: () => setInfo(undefined) })) : null,
149
- React.createElement(FacetedHeader, { setHideSparse: setHideSparse, setShowOptions: setShowOptions, setFilterText: setFilterText, setUseShoppingCart: setUseShoppingCart, hideSparse: hideSparse, showOptions: showOptions, filterText: filterText, useShoppingCart: useShoppingCart, model: model }),
160
+ React.createElement(FacetedHeader, { setShowSparse: setShowSparse, setShowFilters: setShowFilters, setShowOptions: setShowOptions, setFilterText: setFilterText, setUseShoppingCart: setUseShoppingCart, showFilters: showFilters, showSparse: showSparse, showOptions: showOptions, filterText: filterText, useShoppingCart: useShoppingCart, model: model }),
150
161
  React.createElement("div", { ref: ref, style: {
151
162
  display: 'flex',
152
163
  overflow: 'hidden',
@@ -155,21 +166,22 @@ export default observer(function FacetedSelector({ model, }) {
155
166
  } },
156
167
  React.createElement("div", { style: {
157
168
  height: window.innerHeight * frac,
158
- width: window.innerWidth * frac - panelWidth,
169
+ width: window.innerWidth * frac - (showFilters ? panelWidth : 0),
159
170
  } },
160
171
  React.createElement(ResizeBar, { checkbox: true, widths: Object.values(widths).map(f => f !== null && f !== void 0 ? f : 100), setWidths: newWidths => setWidths(Object.fromEntries(Object.entries(widths).map((entry, idx) => [
161
172
  entry[0],
162
173
  newWidths[idx],
163
174
  ]))), scrollLeft: scrollLeft }),
164
- React.createElement(DataGrid, { rows: rows.filter(row => arrFilters.every(([key, val]) => val.includes(row[key]))), columnVisibilityModel: visible, onColumnVisibilityModelChange: newModel => setVisible(newModel), columnHeaderHeight: 35, checkboxSelection: true, disableRowSelectionOnClick: true, keepNonExistentRowsSelected: true, onRowSelectionModelChange: userSelectedIds => {
175
+ React.createElement(DataGrid, { rows: filteredRows, columnVisibilityModel: visible, onColumnVisibilityModelChange: newModel => setVisible(newModel), columnHeaderHeight: 35, checkboxSelection: true, disableRowSelectionOnClick: true, keepNonExistentRowsSelected: true, onRowSelectionModelChange: userSelectedIds => {
165
176
  if (!useShoppingCart) {
166
177
  const a1 = shownTrackIds;
167
- const a2 = userSelectedIds;
178
+ const a2 = new Set(userSelectedIds);
168
179
  // synchronize the user selection with the view
169
180
  // see share https://stackoverflow.com/a/33034768/2129219
170
181
  transaction(() => {
171
- a1.filter(x => !a2.includes(x)).map(t => view.hideTrack(t));
172
- a2.filter(x => !a1.includes(x)).map(t => view.showTrack(t));
182
+ ;
183
+ [...a1].filter(x => !a2.has(x)).map(t => view.hideTrack(t));
184
+ [...a2].filter(x => !a1.has(x)).map(t => view.showTrack(t));
173
185
  });
174
186
  }
175
187
  else {
@@ -178,10 +190,18 @@ export default observer(function FacetedSelector({ model, }) {
178
190
  const tracks = userSelectedIds.map(id => resolveIdentifier(schema, root, id));
179
191
  model.setSelection(tracks);
180
192
  }
181
- }, rowSelectionModel: useShoppingCart ? selection.map(s => s.trackId) : shownTrackIds, slots: { toolbar: showOptions ? GridToolbar : null }, slotProps: {
193
+ }, rowSelectionModel: useShoppingCart
194
+ ? selection.map(s => s.trackId)
195
+ : [...shownTrackIds], slots: { toolbar: showOptions ? GridToolbar : null }, slotProps: {
182
196
  toolbar: { printOptions: { disableToolbarButton: true } },
183
197
  }, columns: columns, rowHeight: 25 })),
184
- React.createElement(ResizeHandle, { vertical: true, onDrag: dist => setPanelWidth(panelWidth - dist), style: { background: 'grey', width: 5 } }),
185
- React.createElement("div", { style: { width: panelWidth, overflowY: 'auto', overflowX: 'hidden' } },
186
- React.createElement(FacetFilters, { width: panelWidth - 10, rows: rows, columns: columns, dispatch: dispatch, filters: filters })))));
198
+ showFilters ? (React.createElement(React.Fragment, null,
199
+ React.createElement(ResizeHandle, { vertical: true, onDrag: dist => setPanelWidth(panelWidth - dist), style: { marginLeft: 5, background: 'grey', width: 5 } }),
200
+ React.createElement("div", { style: {
201
+ width: panelWidth,
202
+ overflowY: 'auto',
203
+ overflowX: 'hidden',
204
+ } },
205
+ React.createElement(FacetFilters, { width: panelWidth - 10, rows: rows, columns: columns, dispatch: dispatch, filters: filters })))) : null)));
187
206
  });
207
+ export default FacetedSelector;
@@ -1,6 +1,6 @@
1
1
  import React from 'react';
2
2
  import { HierarchicalTrackSelectorModel } from '../../model';
3
- declare const _default: ({ model, }: {
3
+ declare const HamburgerMenu: ({ model, }: {
4
4
  model: HierarchicalTrackSelectorModel;
5
5
  }) => React.JSX.Element;
6
- export default _default;
6
+ export default HamburgerMenu;
@@ -1,10 +1,8 @@
1
1
  import React, { Suspense, lazy, useState } from 'react';
2
- import { IconButton } from '@mui/material';
3
- import { makeStyles } from 'tss-react/mui';
4
2
  import { observer } from 'mobx-react';
5
- import JBrowseMenu from '@jbrowse/core/ui/Menu';
6
3
  import { getSession, isSessionModelWithConnectionEditing, isSessionModelWithConnections, isSessionModelWithWidgets, isSessionWithAddTracks, } from '@jbrowse/core/util';
7
4
  import { readConfObject, } from '@jbrowse/core/configuration';
5
+ import CascadingMenuButton from '@jbrowse/core/ui/CascadingMenuButton';
8
6
  // icons
9
7
  import MenuIcon from '@mui/icons-material/Menu';
10
8
  // lazy components
@@ -12,23 +10,16 @@ const CloseConnectionDlg = lazy(() => import('../dialogs/CloseConnectionDialog')
12
10
  const DeleteConnectionDlg = lazy(() => import('../dialogs/DeleteConnectionDialog'));
13
11
  const ManageConnectionsDlg = lazy(() => import('../dialogs/ManageConnectionsDialog'));
14
12
  const ToggleConnectionsDlg = lazy(() => import('../dialogs/ToggleConnectionsDialog'));
15
- const useStyles = makeStyles()(theme => ({
16
- menuIcon: {
17
- marginRight: theme.spacing(1),
18
- marginBottom: 0,
19
- },
20
- }));
21
- export default observer(function HamburgerMenu({ model, }) {
13
+ const HamburgerMenu = observer(function ({ model, }) {
22
14
  const session = getSession(model);
23
- const [menuEl, setMenuEl] = useState();
24
15
  const [modalInfo, setModalInfo] = useState();
25
16
  const [deleteDlgDetails, setDeleteDlgDetails] = useState();
26
17
  const [connectionToggleOpen, setConnectionToggleOpen] = useState(false);
27
18
  const [connectionManagerOpen, setConnectionManagerOpen] = useState(false);
28
- const { classes } = useStyles();
29
19
  function breakConnection(connectionConf, deletingConnection) {
20
+ var _a;
30
21
  const name = readConfObject(connectionConf, 'name');
31
- const result = session.prepareToBreakConnection(connectionConf);
22
+ const result = (_a = session.prepareToBreakConnection) === null || _a === void 0 ? void 0 : _a.call(session, connectionConf);
32
23
  if (result) {
33
24
  const [safelyBreakConnection, dereferenceTypeCount] = result;
34
25
  if (Object.keys(dereferenceTypeCount).length > 0) {
@@ -48,12 +39,7 @@ export default observer(function HamburgerMenu({ model, }) {
48
39
  }
49
40
  }
50
41
  return (React.createElement(React.Fragment, null,
51
- React.createElement(IconButton, { className: classes.menuIcon, onClick: event => setMenuEl(event.currentTarget) },
52
- React.createElement(MenuIcon, null)),
53
- React.createElement(JBrowseMenu, { anchorEl: menuEl, open: Boolean(menuEl), onMenuItemClick: (_, callback) => {
54
- callback();
55
- setMenuEl(undefined);
56
- }, onClose: () => setMenuEl(undefined), menuItems: [
42
+ React.createElement(CascadingMenuButton, { menuItems: [
57
43
  ...(isSessionWithAddTracks(session)
58
44
  ? [
59
45
  {
@@ -92,10 +78,42 @@ export default observer(function HamburgerMenu({ model, }) {
92
78
  },
93
79
  ]
94
80
  : []),
95
- ] }),
81
+ { type: 'divider' },
82
+ {
83
+ label: 'Sort tracks by name',
84
+ type: 'checkbox',
85
+ checked: model.activeSortTrackNames,
86
+ onClick: () => model.setSortTrackNames(!model.activeSortTrackNames),
87
+ },
88
+ {
89
+ label: 'Sort categories by name',
90
+ type: 'checkbox',
91
+ checked: model.activeSortCategories,
92
+ onClick: () => model.setSortCategories(!model.activeSortCategories),
93
+ },
94
+ { type: 'divider' },
95
+ ...(model.hasAnySubcategories
96
+ ? [
97
+ {
98
+ label: 'Collapse subcategories',
99
+ onClick: () => model.collapseSubCategories(),
100
+ },
101
+ ]
102
+ : []),
103
+ {
104
+ label: 'Collapse top-level categories',
105
+ onClick: () => model.collapseTopLevelCategories(),
106
+ },
107
+ {
108
+ label: 'Expand all categories',
109
+ onClick: () => model.expandAllCategories(),
110
+ },
111
+ ] },
112
+ React.createElement(MenuIcon, null)),
96
113
  React.createElement(Suspense, { fallback: React.createElement(React.Fragment, null) },
97
114
  modalInfo ? (React.createElement(CloseConnectionDlg, { modalInfo: modalInfo, onClose: () => setModalInfo(undefined) })) : null,
98
115
  deleteDlgDetails ? (React.createElement(DeleteConnectionDlg, { handleClose: () => setDeleteDlgDetails(undefined), deleteDialogDetails: deleteDlgDetails, session: session })) : null,
99
116
  connectionManagerOpen ? (React.createElement(ManageConnectionsDlg, { handleClose: () => setConnectionManagerOpen(false), breakConnection: breakConnection, session: session })) : null,
100
117
  connectionToggleOpen ? (React.createElement(ToggleConnectionsDlg, { handleClose: () => setConnectionToggleOpen(false), session: session, breakConnection: breakConnection })) : null)));
101
118
  });
119
+ export default HamburgerMenu;
@@ -1,8 +1,7 @@
1
1
  import React from 'react';
2
2
  import { HierarchicalTrackSelectorModel } from '../../model';
3
- declare function HierarchicalTrackSelectorHeader({ model, setHeaderHeight, }: {
3
+ declare const HierarchicalTrackSelectorHeader: ({ model, setHeaderHeight, }: {
4
4
  model: HierarchicalTrackSelectorModel;
5
5
  setHeaderHeight: (n: number) => void;
6
- }): React.JSX.Element;
7
- declare const _default: typeof HierarchicalTrackSelectorHeader;
8
- export default _default;
6
+ }) => React.JSX.Element;
7
+ export default HierarchicalTrackSelectorHeader;
@@ -17,20 +17,24 @@ const useStyles = makeStyles()(theme => ({
17
17
  marginBottom: 0,
18
18
  },
19
19
  }));
20
- function HierarchicalTrackSelectorHeader({ model, setHeaderHeight, }) {
20
+ const SearchTracksTextField = observer(function ({ model, }) {
21
+ const { filterText } = model;
22
+ const { classes } = useStyles();
23
+ return (React.createElement(TextField, { className: classes.searchBox, label: "Filter tracks", value: filterText, onChange: event => model.setFilterText(event.target.value), fullWidth: true, InputProps: {
24
+ endAdornment: (React.createElement(InputAdornment, { position: "end" },
25
+ React.createElement(IconButton, { onClick: () => model.clearFilterText() },
26
+ React.createElement(ClearIcon, null)))),
27
+ } }));
28
+ });
29
+ const HierarchicalTrackSelectorHeader = observer(function ({ model, setHeaderHeight, }) {
21
30
  const { classes } = useStyles();
22
31
  const [facetedOpen, setFacetedOpen] = useState(false);
23
- const { filterText } = model;
24
32
  return (React.createElement("div", { ref: ref => setHeaderHeight((ref === null || ref === void 0 ? void 0 : ref.getBoundingClientRect().height) || 0), "data-testid": "hierarchical_track_selector" },
25
33
  React.createElement("div", { style: { display: 'flex' } },
26
34
  React.createElement(HamburgerMenu, { model: model }),
27
35
  React.createElement(ShoppingCart, { model: model }),
28
- React.createElement(TextField, { className: classes.searchBox, label: "Filter tracks", value: filterText, onChange: event => model.setFilterText(event.target.value), fullWidth: true, InputProps: {
29
- endAdornment: (React.createElement(InputAdornment, { position: "end" },
30
- React.createElement(IconButton, { onClick: () => model.clearFilterText() },
31
- React.createElement(ClearIcon, null)))),
32
- } }),
36
+ React.createElement(SearchTracksTextField, { model: model }),
33
37
  React.createElement(Button, { className: classes.menuIcon, onClick: () => setFacetedOpen(true) }, "Open faceted selector")),
34
38
  React.createElement(Suspense, { fallback: React.createElement("div", null) }, facetedOpen ? (React.createElement(FacetedDialog, { handleClose: () => setFacetedOpen(false), model: model })) : null)));
35
- }
36
- export default observer(HierarchicalTrackSelectorHeader);
39
+ });
40
+ export default HierarchicalTrackSelectorHeader;
@@ -1,8 +1,9 @@
1
1
  import React from 'react';
2
- import { TreeNode, HierarchicalTrackSelectorModel } from '../../model';
3
- declare const _default: ({ height, tree, model, }: {
2
+ import { TreeNode } from '../../generateHierarchy';
3
+ import { HierarchicalTrackSelectorModel } from '../../model';
4
+ declare const HierarchicalTree: ({ height, tree, model, }: {
4
5
  height: number;
5
6
  tree: TreeNode;
6
7
  model: HierarchicalTrackSelectorModel;
7
8
  }) => React.JSX.Element;
8
- export default _default;
9
+ export default HierarchicalTree;
@@ -23,7 +23,7 @@ function getNodeData(node, nestingLevel, extra, selection) {
23
23
  // this is the main tree component for the hierarchical track selector in note:
24
24
  // in jbrowse-web the toolbar is position="sticky" which means the autosizer
25
25
  // includes the height of the toolbar, so we subtract the given offsets
26
- export default observer(function HierarchicalTree({ height, tree, model, }) {
26
+ const HierarchicalTree = observer(function HierarchicalTree({ height, tree, model, }) {
27
27
  const { filterText, selection, view } = model;
28
28
  const treeRef = useRef(null);
29
29
  const session = getSession(model);
@@ -37,15 +37,13 @@ export default observer(function HierarchicalTree({ height, tree, model, }) {
37
37
  drawerPosition,
38
38
  }), [view, model, drawerPosition, tree]);
39
39
  const treeWalker = useCallback(function* treeWalker() {
40
- for (let i = 0; i < tree.children.length; i++) {
41
- const r = tree.children[i];
42
- yield getNodeData(r, 0, extra, obj);
40
+ for (const child of tree.children) {
41
+ yield getNodeData(child, 0, extra, obj);
43
42
  }
44
43
  while (true) {
45
44
  // @ts-expect-error
46
45
  const parentMeta = yield;
47
- for (let i = 0; i < parentMeta.node.children.length; i++) {
48
- const curr = parentMeta.node.children[i];
46
+ for (const curr of parentMeta.node.children) {
49
47
  yield getNodeData(curr, parentMeta.nestingLevel + 1, extra, obj);
50
48
  }
51
49
  }
@@ -60,3 +58,4 @@ export default observer(function HierarchicalTree({ height, tree, model, }) {
60
58
  return (React.createElement(React.Fragment, null,
61
59
  React.createElement(VariableSizeTree, { ref: treeRef, treeWalker: treeWalker, height: height }, Node)));
62
60
  });
61
+ export default HierarchicalTree;
@@ -52,6 +52,28 @@ export default function Category({ isOpen, setOpen, data, }) {
52
52
  model.removeFromSelection(getAllChildren(r));
53
53
  },
54
54
  },
55
+ {
56
+ label: 'Show all tracks',
57
+ onClick: () => {
58
+ var _a;
59
+ for (const entry of ((_a = treeToMap(tree).get(id)) === null || _a === void 0 ? void 0 : _a.children) || []) {
60
+ if (!entry.children.length) {
61
+ model.view.showTrack(entry.id);
62
+ }
63
+ }
64
+ },
65
+ },
66
+ {
67
+ label: 'Hide all tracks',
68
+ onClick: () => {
69
+ var _a;
70
+ for (const entry of ((_a = treeToMap(tree).get(id)) === null || _a === void 0 ? void 0 : _a.children) || []) {
71
+ if (!entry.children.length) {
72
+ model.view.hideTrack(entry.id);
73
+ }
74
+ }
75
+ },
76
+ },
55
77
  ], onMenuItemClick: (_event, callback) => {
56
78
  callback();
57
79
  setMenuEl(null);
@@ -3,12 +3,12 @@ import { Checkbox, FormControlLabel, IconButton, Tooltip } from '@mui/material';
3
3
  import { makeStyles } from 'tss-react/mui';
4
4
  import JBrowseMenu from '@jbrowse/core/ui/Menu';
5
5
  import { getSession } from '@jbrowse/core/util';
6
+ import SanitizedHTML from '@jbrowse/core/ui/SanitizedHTML';
6
7
  import { readConfObject, } from '@jbrowse/core/configuration';
7
8
  // icons
8
9
  import MoreHorizIcon from '@mui/icons-material/MoreHoriz';
9
10
  // locals
10
11
  import { isUnsupported } from '../util';
11
- import { SanitizedHTML } from '@jbrowse/core/ui';
12
12
  const useStyles = makeStyles()(theme => ({
13
13
  compactCheckbox: {
14
14
  padding: 0,
@@ -31,7 +31,7 @@ export default function TrackLabel({ data }) {
31
31
  React.createElement(FormControlLabel, { className: classes.checkboxLabel, control: React.createElement(Checkbox, { className: classes.compactCheckbox, checked: checked, onChange: () => onChange(id), disabled: isUnsupported(name), inputProps: {
32
32
  // @ts-expect-error
33
33
  'data-testid': `htsTrackEntry-${id}`,
34
- } }), label: React.createElement("div", { style: { background: selected ? '#cccc' : undefined } },
34
+ } }), label: React.createElement("div", { "data-testid": `htsTrackLabel-${id}`, style: { background: selected ? '#cccc' : undefined } },
35
35
  React.createElement(SanitizedHTML, { html: name })) })),
36
36
  React.createElement(IconButton, { onClick: e => setInfo({ target: e.currentTarget, id, conf }), style: { padding: 0 }, "data-testid": `htsTrackEntryMenu-${id}` },
37
37
  React.createElement(MoreHorizIcon, null)),
@@ -1,5 +1,6 @@
1
1
  import { AnyConfigurationModel } from '@jbrowse/core/configuration';
2
- import { HierarchicalTrackSelectorModel, TreeNode } from '../model';
2
+ import { HierarchicalTrackSelectorModel } from '../model';
3
+ import { TreeNode } from '../generateHierarchy';
3
4
  export interface NodeData {
4
5
  nestingLevel: number;
5
6
  checked: boolean;
@@ -6,8 +6,7 @@ export function treeToMap(tree, map = new Map()) {
6
6
  if (tree.id && tree.children.length) {
7
7
  map.set(tree.id, tree);
8
8
  }
9
- for (let i = 0; i < tree.children.length; i++) {
10
- const node = tree.children[i];
9
+ for (const node of tree.children) {
11
10
  treeToMap(node, map);
12
11
  }
13
12
  return map;
@@ -0,0 +1,12 @@
1
+ import { AnyConfigurationModel } from '@jbrowse/core/configuration';
2
+ export declare function filterTracks(tracks: AnyConfigurationModel[], self: {
3
+ view?: {
4
+ type: string;
5
+ trackSelectorAnyOverlap?: boolean;
6
+ };
7
+ assemblyNames: string[];
8
+ }): ({
9
+ [x: string]: any;
10
+ } & import("mobx-state-tree/dist/internal").NonEmptyObject & {
11
+ setSubschema(slotName: string, data: unknown): any;
12
+ } & import("mobx-state-tree").IStateTreeNode<import("@jbrowse/core/configuration").AnyConfigurationSchemaType>)[];
@@ -0,0 +1,28 @@
1
+ import { readConfObject, } from '@jbrowse/core/configuration';
2
+ import { getEnv, getSession, notEmpty } from '@jbrowse/core/util';
3
+ import { hasAllOverlap, hasAnyOverlap } from './util';
4
+ export function filterTracks(tracks, self) {
5
+ const { assemblyManager } = getSession(self);
6
+ const { pluginManager } = getEnv(self);
7
+ const { view } = self;
8
+ if (!view) {
9
+ return [];
10
+ }
11
+ const trackListAssemblies = self.assemblyNames
12
+ .map(a => assemblyManager.get(a))
13
+ .filter(notEmpty);
14
+ return tracks
15
+ .filter(c => {
16
+ const trackAssemblyNames = readConfObject(c, 'assemblyNames');
17
+ const trackAssemblies = trackAssemblyNames === null || trackAssemblyNames === void 0 ? void 0 : trackAssemblyNames.map(name => assemblyManager.get(name)).filter(notEmpty);
18
+ return view.trackSelectorAnyOverlap
19
+ ? hasAnyOverlap(trackAssemblies, trackListAssemblies)
20
+ : hasAllOverlap(trackAssemblies, trackListAssemblies);
21
+ })
22
+ .filter(c => {
23
+ const { displayTypes } = pluginManager.getViewType(view.type);
24
+ const compatDisplays = displayTypes.map(d => d.name);
25
+ const trackDisplays = c.displays.map((d) => d.type);
26
+ return hasAnyOverlap(compatDisplays, trackDisplays);
27
+ });
28
+ }
@@ -0,0 +1,21 @@
1
+ import { AnyConfigurationModel } from '@jbrowse/core/configuration';
2
+ export interface TreeNode {
3
+ name: string;
4
+ id: string;
5
+ conf?: AnyConfigurationModel;
6
+ checked?: boolean;
7
+ isOpenByDefault?: boolean;
8
+ children: TreeNode[];
9
+ }
10
+ export declare function generateHierarchy(model: {
11
+ filterText: string;
12
+ activeSortTrackNames: boolean;
13
+ activeSortCategories: boolean;
14
+ view?: {
15
+ tracks: {
16
+ configuration: AnyConfigurationModel;
17
+ }[];
18
+ };
19
+ }, trackConfs: AnyConfigurationModel[], collapsed: {
20
+ get: (arg: string) => boolean | undefined;
21
+ }, extra?: string): TreeNode[];
@@ -0,0 +1,94 @@
1
+ import { readConfObject, } from '@jbrowse/core/configuration';
2
+ import { getSession } from '@jbrowse/core/util';
3
+ import { getTrackName } from '@jbrowse/core/util/tracks';
4
+ // locals
5
+ import { matches } from './util';
6
+ function sortConfs(confs, sortNames, sortCategories) {
7
+ // uses readConfObject instead of getTrackName so that the undefined
8
+ // reference sequence track is sorted to the top
9
+ const ret = confs.map(c => {
10
+ var _a, _b, _c;
11
+ return [
12
+ c,
13
+ readConfObject(c, 'name'),
14
+ ((_a = readConfObject(c, 'category')) === null || _a === void 0 ? void 0 : _a[0]) || '',
15
+ ((_b = readConfObject(c, 'category')) === null || _b === void 0 ? void 0 : _b[1]) || '',
16
+ ((_c = readConfObject(c, 'category')) === null || _c === void 0 ? void 0 : _c[2]) || '',
17
+ ];
18
+ });
19
+ if (sortNames) {
20
+ ret.sort((a, b) => a[1].localeCompare(b[1]));
21
+ }
22
+ if (sortCategories) {
23
+ // sort up to three sub-category levels, harder to code it to go deeper
24
+ // than this and likely rarely used
25
+ ret.sort((a, b) => {
26
+ if (a[2] !== b[2]) {
27
+ return a[2].localeCompare(b[2]);
28
+ }
29
+ else if (a[3] !== b[3]) {
30
+ return a[3].localeCompare(b[3]);
31
+ }
32
+ else if (a[4] !== b[4]) {
33
+ return a[4].localeCompare(b[4]);
34
+ }
35
+ return 0;
36
+ });
37
+ }
38
+ return ret.map(a => a[0]);
39
+ }
40
+ export function generateHierarchy(model, trackConfs, collapsed, extra) {
41
+ const hierarchy = { children: [] };
42
+ const { filterText, activeSortTrackNames, activeSortCategories, view } = model;
43
+ if (!view) {
44
+ return [];
45
+ }
46
+ const session = getSession(model);
47
+ const viewTracks = view.tracks;
48
+ const confs = trackConfs.filter(conf => matches(filterText, conf, session));
49
+ // uses getConf
50
+ for (const conf of sortConfs(confs, activeSortTrackNames, activeSortCategories)) {
51
+ // copy the categories since this array can be mutated downstream
52
+ const categories = [...(readConfObject(conf, 'category') || [])];
53
+ // hack where if trackId ends with sessionTrack, then push it to a
54
+ // category that starts with a space to force sort to the top
55
+ if (conf.trackId.endsWith('sessionTrack')) {
56
+ categories.unshift(' Session tracks');
57
+ }
58
+ let currLevel = hierarchy;
59
+ // find existing category to put track into or create it
60
+ for (let i = 0; i < categories.length; i++) {
61
+ const category = categories[i];
62
+ const ret = currLevel.children.find(c => c.name === category);
63
+ const id = [extra, categories.slice(0, i + 1).join(',')]
64
+ .filter(f => !!f)
65
+ .join('-');
66
+ if (!ret) {
67
+ const n = {
68
+ children: [],
69
+ name: category,
70
+ id,
71
+ isOpenByDefault: !collapsed.get(id),
72
+ };
73
+ currLevel.children.push(n);
74
+ currLevel = n;
75
+ }
76
+ else {
77
+ currLevel = ret;
78
+ }
79
+ }
80
+ // uses splice to try to put all leaf nodes above "category nodes" if you
81
+ // change the splice to a simple push and open
82
+ // test_data/test_order/config.json you will see the weirdness
83
+ const r = currLevel.children.findIndex(elt => elt.children.length);
84
+ const idx = r === -1 ? currLevel.children.length : r;
85
+ currLevel.children.splice(idx, 0, {
86
+ id: conf.trackId,
87
+ name: getTrackName(conf, session),
88
+ conf,
89
+ checked: viewTracks.some(f => f.configuration === conf),
90
+ children: [],
91
+ });
92
+ }
93
+ return hierarchy.children;
94
+ }