@weng-lab/genomebrowser-ui 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/dist/TrackSelect/Data/modifiedHumanTracks.json.d.ts +37222 -0
  2. package/dist/TrackSelect/DataGrid/CustomToolbar.d.ts +12 -0
  3. package/dist/TrackSelect/DataGrid/DataGridWrapper.d.ts +2 -0
  4. package/dist/TrackSelect/DataGrid/columns.d.ts +4 -0
  5. package/dist/TrackSelect/DataGrid/dataGridHelpers.d.ts +30 -0
  6. package/dist/TrackSelect/TrackSelect.d.ts +5 -0
  7. package/dist/TrackSelect/TreeView/TreeViewWrapper.d.ts +2 -0
  8. package/dist/TrackSelect/TreeView/treeViewHelpers.d.ts +49 -0
  9. package/dist/TrackSelect/consts.d.ts +21 -0
  10. package/dist/TrackSelect/store.d.ts +4 -0
  11. package/dist/TrackSelect/types.d.ts +123 -0
  12. package/dist/genomebrowser-ui.es.js +2299 -0
  13. package/dist/genomebrowser-ui.es.js.map +1 -0
  14. package/dist/lib.d.ts +4 -0
  15. package/eslint.config.js +30 -0
  16. package/index.html +14 -0
  17. package/package.json +47 -0
  18. package/src/TrackSelect/Data/humanTracks.json +35711 -0
  19. package/src/TrackSelect/Data/human_chromhmm_biosamples_with_all_urls.json +35716 -0
  20. package/src/TrackSelect/Data/modifiedHumanTracks.json +37220 -0
  21. package/src/TrackSelect/DataGrid/CustomToolbar.tsx +160 -0
  22. package/src/TrackSelect/DataGrid/DataGridWrapper.tsx +119 -0
  23. package/src/TrackSelect/DataGrid/columns.tsx +134 -0
  24. package/src/TrackSelect/DataGrid/dataGridHelpers.tsx +114 -0
  25. package/src/TrackSelect/TrackSelect.tsx +258 -0
  26. package/src/TrackSelect/TreeView/TreeViewWrapper.tsx +115 -0
  27. package/src/TrackSelect/TreeView/treeViewHelpers.tsx +428 -0
  28. package/src/TrackSelect/bug.md +4 -0
  29. package/src/TrackSelect/consts.ts +92 -0
  30. package/src/TrackSelect/store.ts +26 -0
  31. package/src/TrackSelect/types.ts +139 -0
  32. package/src/lib.ts +8 -0
  33. package/test/main.tsx +13 -0
  34. package/tsconfig.app.json +25 -0
  35. package/tsconfig.json +4 -0
  36. package/tsconfig.node.json +25 -0
  37. package/vite.config.ts +66 -0
@@ -0,0 +1,428 @@
1
+ import Folder from "@mui/icons-material/Folder";
2
+ import IndeterminateCheckBoxRoundedIcon from "@mui/icons-material/IndeterminateCheckBoxRounded";
3
+ import { Box, Typography, Stack } from "@mui/material";
4
+ import Collapse from "@mui/material/Collapse";
5
+ import { alpha, styled } from "@mui/material/styles";
6
+ import { TreeViewBaseItem } from "@mui/x-tree-view";
7
+ import {
8
+ TreeItemCheckbox,
9
+ TreeItemIconContainer,
10
+ TreeItemLabel,
11
+ } from "@mui/x-tree-view/TreeItem";
12
+ import { TreeItemIcon } from "@mui/x-tree-view/TreeItemIcon";
13
+ import { TreeItemProvider } from "@mui/x-tree-view/TreeItemProvider";
14
+ import { useTreeItemModel } from "@mui/x-tree-view/hooks";
15
+ import { useTreeItem } from "@mui/x-tree-view/useTreeItem";
16
+ import React from "react";
17
+ import {
18
+ CustomLabelProps,
19
+ CustomTreeItemProps,
20
+ ExtendedTreeItemProps,
21
+ RowInfo,
22
+ } from "../types";
23
+ import Fuse, { FuseResult } from "fuse.js";
24
+ import { SearchTracksProps } from "../types";
25
+ import { assayTypes, ontologyTypes } from "../consts";
26
+
27
+
28
+ /**
29
+ * Builds tree in the sorted by assay view
30
+ * @param selectedIds: list of ids (from useSelectionStore)
31
+ * @param root: Root TreeViewBaseItem
32
+ * @param rowById: Mapping between an id (experimentAccession) and its RowInfo object
33
+ * @returns all of the items for the RichTreeView in TreeViewWrapper
34
+ */
35
+ export function buildSortedAssayTreeView(
36
+ selectedIds: string[],
37
+ root: TreeViewBaseItem<ExtendedTreeItemProps>,
38
+ rowById: Map<string, RowInfo>,
39
+ ): TreeViewBaseItem<ExtendedTreeItemProps>[] {
40
+ const assayMap = new Map<string, TreeViewBaseItem<ExtendedTreeItemProps>>(); // keep track of top level nodes
41
+ const ontologyMap = new Map<
42
+ string,
43
+ TreeViewBaseItem<ExtendedTreeItemProps>
44
+ >();
45
+ const sampleAssayMap = new Map<
46
+ string,
47
+ TreeViewBaseItem<ExtendedTreeItemProps>
48
+ >();
49
+ let idx = 1;
50
+
51
+ const selectedRows = selectedIds.reduce<RowInfo[]>((acc, id) => {
52
+ const row = rowById.get(id);
53
+ if (row) acc.push(row);
54
+ return acc;
55
+ }, []);
56
+
57
+ selectedRows.forEach((row) => {
58
+ let assayNode = assayMap.get(row.assay);
59
+ if (!assayNode) {
60
+ assayNode = {
61
+ id: row.assay,
62
+ isAssayItem: true,
63
+ label: row.assay,
64
+ icon: "removeable",
65
+ children: [],
66
+ allExpAccessions: [],
67
+ };
68
+ assayMap.set(row.assay, assayNode);
69
+ root.children!.push(assayNode);
70
+ }
71
+
72
+ let ontologyNode = ontologyMap.get(row.ontology + row.assay);
73
+ if (!ontologyNode) {
74
+ ontologyNode = {
75
+ id: row.ontology + "_" + idx++,
76
+ isAssayItem: false,
77
+ label: row.ontology,
78
+ icon: "removeable",
79
+ children: [],
80
+ allExpAccessions: [],
81
+ };
82
+ assayNode.children!.push(ontologyNode);
83
+ ontologyMap.set(row.ontology + row.assay, ontologyNode);
84
+ }
85
+
86
+ const displayNameNode: TreeViewBaseItem<ExtendedTreeItemProps> = {
87
+ id: row.displayname + "_" + idx++,
88
+ isAssayItem: false,
89
+ label: row.displayname,
90
+ icon: "removeable",
91
+ children: [],
92
+ allExpAccessions: [],
93
+ };
94
+ ontologyNode.children!.push(displayNameNode);
95
+
96
+ let expNode = sampleAssayMap.get(row.displayname + row.experimentAccession);
97
+ if (!expNode) {
98
+ expNode = {
99
+ id: row.experimentAccession,
100
+ isAssayItem: false,
101
+ label: row.experimentAccession,
102
+ icon: row.assay,
103
+ children: [],
104
+ };
105
+ sampleAssayMap.set(row.displayname + row.assay, expNode);
106
+ displayNameNode.children!.push(expNode);
107
+ }
108
+ assayNode.allExpAccessions!.push(row.experimentAccession);
109
+ ontologyNode.allExpAccessions!.push(row.experimentAccession);
110
+ displayNameNode.allExpAccessions!.push(row.experimentAccession);
111
+ root.allRowInfo!.push(row);
112
+ });
113
+ // standardize the order of the assay folders everytime one is added
114
+ root.children!.sort((a, b): number => {
115
+ return assayTypes.indexOf(a.id) - assayTypes.indexOf(b.id);
116
+ });
117
+ return [root];
118
+ }
119
+
120
+ /**
121
+ * Builds tree in the sorted by assay view
122
+ * @param selectedIds: list of ids (from useSelectionStore)
123
+ * @param root: Root TreeViewBaseItem
124
+ * @param rowById: Mapping between an id (experimentAccession) and its RowInfo object
125
+ * @returns all of the items for the RichTreeView in TreeViewWrapper
126
+ */
127
+ export function buildTreeView(
128
+ selectedIds: string[],
129
+ root: TreeViewBaseItem<ExtendedTreeItemProps>,
130
+ rowById: Map<string, RowInfo>,
131
+ ): TreeViewBaseItem<ExtendedTreeItemProps>[] {
132
+ const ontologyMap = new Map<
133
+ string,
134
+ TreeViewBaseItem<ExtendedTreeItemProps>
135
+ >(); // keep track of top level nodes
136
+ const displayNameMap = new Map<
137
+ string,
138
+ TreeViewBaseItem<ExtendedTreeItemProps>
139
+ >();
140
+ const sampleAssayMap = new Map<
141
+ string,
142
+ TreeViewBaseItem<ExtendedTreeItemProps>
143
+ >();
144
+
145
+ const selectedRows = selectedIds.reduce<RowInfo[]>((acc, id) => {
146
+ const row = rowById.get(id);
147
+ if (row) acc.push(row);
148
+ return acc;
149
+ }, []);
150
+
151
+ selectedRows.forEach((row) => {
152
+ if (!row) {
153
+ return;
154
+ }
155
+ let ontologyNode = ontologyMap.get(row.ontology);
156
+ if (!ontologyNode) {
157
+ ontologyNode = {
158
+ id: row.ontology,
159
+ label: row.ontology,
160
+ icon: "removeable",
161
+ children: [],
162
+ allExpAccessions: [],
163
+ };
164
+ ontologyMap.set(row.ontology, ontologyNode);
165
+ root.children!.push(ontologyNode);
166
+ }
167
+
168
+ let displayNameNode = displayNameMap.get(row.displayname);
169
+ if (!displayNameNode) {
170
+ displayNameNode = {
171
+ id: row.displayname,
172
+ label: row.displayname,
173
+ icon: "removeable",
174
+ children: [],
175
+ allExpAccessions: [],
176
+ };
177
+ ontologyNode.children!.push(displayNameNode);
178
+ displayNameMap.set(row.displayname, displayNameNode);
179
+ }
180
+
181
+ let expNode = sampleAssayMap.get(row.displayname + row.assay);
182
+ if (!expNode) {
183
+ expNode = {
184
+ id: row.experimentAccession,
185
+ label: row.experimentAccession,
186
+ icon: row.assay,
187
+ children: [],
188
+ };
189
+ sampleAssayMap.set(row.displayname + row.assay, expNode);
190
+ displayNameNode.children!.push(expNode);
191
+ }
192
+ ontologyNode.allExpAccessions!.push(row.experimentAccession);
193
+ displayNameNode.allExpAccessions!.push(row.experimentAccession);
194
+ root.allRowInfo!.push(row);
195
+ });
196
+ // standardize the order of the assay folders everytime one is added
197
+ root.children!.sort((a, b): number => {
198
+ return ontologyTypes.indexOf(a.id) - ontologyTypes.indexOf(b.id);
199
+ });
200
+ return [root];
201
+ }
202
+
203
+ /**
204
+ * Fuzzy search of active tracks.
205
+ *
206
+ * @param treeItems - TreeBaseViewItems from the tree.
207
+ * @param query - The search query string.
208
+ * @param keyWeightMap - Array of keys to search within each track object.
209
+ * Can look like ["name", "author"] or if weighted, [
210
+ {
211
+ name: 'title',
212
+ weight: 0.3
213
+ },
214
+ {
215
+ name: 'author',
216
+ weight: 0.7
217
+ }
218
+ ].
219
+ * @param threshold - (Optional) Threshold for the fuzzy search (default is 0.5).
220
+ * Smaller = stricter match, larger = fuzzier since 0 is perfect match and 1 is worst match.
221
+ * @param limit - (Optional) Maximum number of results to return (default is 10).
222
+ * @returns FuseResult object containing the search results.
223
+ */
224
+ export function searchTreeItems({
225
+ treeItems,
226
+ query,
227
+ keyWeightMap,
228
+ threshold,
229
+ limit = 10
230
+ }: SearchTracksProps): FuseResult<RowInfo>[] {
231
+ const data = treeItems![0].allRowInfo ?? [];
232
+ const fuse = new Fuse(data, {
233
+ includeScore: true,
234
+ shouldSort: true,
235
+ threshold: threshold,
236
+ keys: keyWeightMap,
237
+ });
238
+ return fuse.search(query, { limit: limit });
239
+ }
240
+
241
+ /**
242
+ * Creates the assay icon for DataGrid and RichTreeView
243
+ * @param type: assay type
244
+ * @returns an icon of the assay's respective color
245
+ */
246
+ export function AssayIcon(type: string) {
247
+ const colorMap: { [key: string]: string } = {
248
+ DNase: "#06da93",
249
+ ATAC: "#02c7b9",
250
+ H3K4me3: "#ff2020",
251
+ ChromHMM: "#0097a7",
252
+ H3K27ac: "#fdc401",
253
+ CTCF: "#01a6f1",
254
+ };
255
+ const color = colorMap[type];
256
+ return (
257
+ <Box
258
+ sx={{
259
+ width: 12,
260
+ height: 12,
261
+ borderRadius: "20%",
262
+ bgcolor: color,
263
+ }}
264
+ />
265
+ );
266
+ }
267
+
268
+ // Everything below is styling for the custom directory look of the tree view
269
+ const TreeItemRoot = styled("li")(({ theme }) => ({
270
+ listStyle: "none",
271
+ margin: 0,
272
+ padding: 0,
273
+ outline: 4,
274
+ color: theme.palette.grey[400],
275
+ ...theme.applyStyles("light", {
276
+ color: theme.palette.grey[600], // controls colors of the MUI icons
277
+ }),
278
+ }));
279
+
280
+ const TreeItemLabelText = styled(Typography)({
281
+ color: "black",
282
+ fontFamily: "inherit",
283
+ });
284
+
285
+ function CustomLabel({ icon: Icon, children, ...other }: CustomLabelProps) {
286
+ const variant = other.isAssayItem ? "subtitle2" : "body2";
287
+ const fontWeight = other.isAssayItem ? "bold" : 500;
288
+ return (
289
+ <TreeItemLabel
290
+ {...other}
291
+ sx={{
292
+ display: "flex",
293
+ alignItems: "center",
294
+ }}
295
+ >
296
+ {Icon && React.isValidElement(Icon) ? (
297
+ <Box className="labelIcon" sx={{ mr: 1 }}>
298
+ {Icon}
299
+ </Box>
300
+ ) : (
301
+ <Box
302
+ component={Icon as React.ElementType}
303
+ className="labelIcon"
304
+ color="inherit"
305
+ sx={{ mr: 1, fontSize: "1.2rem" }}
306
+ />
307
+ )}
308
+ <Stack direction="row" spacing={2} alignItems="center">
309
+ { other.isAssayItem && AssayIcon(other.id) }
310
+ <TreeItemLabelText fontWeight={fontWeight} variant={variant}>{children}</TreeItemLabelText>
311
+ </Stack>
312
+ </TreeItemLabel>
313
+ );
314
+ }
315
+
316
+ const TreeItemContent = styled("div")(({ theme }) => ({
317
+ padding: theme.spacing(0.5),
318
+ paddingRight: theme.spacing(2),
319
+ paddingLeft: `calc(${theme.spacing(1)} + var(--TreeView-itemChildrenIndentation) * var(--TreeView-itemDepth))`,
320
+ width: "100%",
321
+ boxSizing: "border-box", // prevent width + padding to overflow
322
+ position: "relative",
323
+ display: "flex",
324
+ alignItems: "center",
325
+ gap: theme.spacing(1),
326
+ cursor: "pointer",
327
+ WebkitTapHighlightColor: "transparent",
328
+ flexDirection: "row-reverse",
329
+ borderRadius: theme.spacing(0.7),
330
+ marginBottom: theme.spacing(0.5),
331
+ marginTop: theme.spacing(0.5),
332
+ fontWeight: 500,
333
+ [`&[data-focused], &[data-selected]`]: {
334
+ backgroundColor: theme.palette.primary.dark,
335
+ color: theme.palette.primary.contrastText,
336
+ ...theme.applyStyles("light", {
337
+ backgroundColor: theme.palette.primary.main,
338
+ }),
339
+ },
340
+ "&:not([data-focused], [data-selected]):hover": {
341
+ backgroundColor: alpha(theme.palette.primary.main, 0.1),
342
+ color: "white",
343
+ ...theme.applyStyles("light", {
344
+ color: theme.palette.primary.main,
345
+ }),
346
+ },
347
+ }));
348
+
349
+ const getIconFromTreeItemType = (itemType: string) => {
350
+ switch (itemType) {
351
+ case "folder":
352
+ return Folder;
353
+ case "removeable":
354
+ return IndeterminateCheckBoxRoundedIcon;
355
+ default:
356
+ return AssayIcon(itemType);
357
+ }
358
+ };
359
+
360
+ export const CustomTreeItem = React.forwardRef(function CustomTreeItem(
361
+ props: CustomTreeItemProps,
362
+ ref: React.Ref<HTMLLIElement>,
363
+ ) {
364
+ const { id, itemId, label, disabled, children, onRemove, ...other } = props;
365
+
366
+ const {
367
+ getContextProviderProps,
368
+ getRootProps,
369
+ getContentProps,
370
+ getIconContainerProps,
371
+ getCheckboxProps,
372
+ getLabelProps,
373
+ getGroupTransitionProps,
374
+ status,
375
+ } = useTreeItem({ id, itemId, children, label, disabled, rootRef: ref });
376
+
377
+ const item = useTreeItemModel<ExtendedTreeItemProps>(itemId)!;
378
+ const icon = getIconFromTreeItemType(item.icon);
379
+
380
+ const handleRemoveIconClick = (e: React.MouseEvent) => {
381
+ e.stopPropagation(); // prevent item expand/select
382
+ onRemove?.(item);
383
+ };
384
+
385
+ return (
386
+ <TreeItemProvider {...getContextProviderProps()}>
387
+ <TreeItemRoot {...getRootProps(other)}>
388
+ <TreeItemContent {...getContentProps()}>
389
+ <TreeItemIconContainer {...getIconContainerProps()}>
390
+ <TreeItemIcon status={status} />
391
+ </TreeItemIconContainer>
392
+ <TreeItemCheckbox {...getCheckboxProps()} />
393
+ <CustomLabel
394
+ {...getLabelProps({
395
+ icon:
396
+ item.icon === "removeable" ? (
397
+ <Box
398
+ onClick={handleRemoveIconClick}
399
+ sx={{
400
+ width: 20,
401
+ height: 20,
402
+ display: "flex",
403
+ alignItems: "center",
404
+ justifyContent: "center",
405
+ borderRadius: "4px",
406
+ cursor: "pointer",
407
+ mr: 1,
408
+ "&:hover": {
409
+ backgroundColor: "rgba(0,0,0,0.1)",
410
+ },
411
+ }}
412
+ >
413
+ <IndeterminateCheckBoxRoundedIcon fontSize="small" />
414
+ </Box>
415
+ ) : (
416
+ icon
417
+ ),
418
+ expandable: (status.expandable && status.expanded).toString(),
419
+ isAssayItem: item.isAssayItem,
420
+ id: item.id
421
+ })}
422
+ />
423
+ </TreeItemContent>
424
+ {children && <Collapse {...getGroupTransitionProps()} />}
425
+ </TreeItemRoot>
426
+ </TreeItemProvider>
427
+ );
428
+ });
@@ -0,0 +1,4 @@
1
+ # Known Bugs/Issues
2
+
3
+ 1. There's a lag in the rendering of the search results + the words in the search bar.
4
+ 2. Upon reaching the limit of added tracks, you're unable to remove tracks from the DataGrid and repeatedly triggers the dialog to pop up whenever you try to unselect tracks from the DataGrid.
@@ -0,0 +1,92 @@
1
+ import {
2
+ getTracksByAssayAndOntology,
3
+ flattenIntoRow,
4
+ } from "./DataGrid/dataGridHelpers";
5
+ import { RowInfo, TrackInfo } from "./types";
6
+
7
+ export const assayTypes = [
8
+ "DNase",
9
+ "H3K4me3",
10
+ "H3K27ac",
11
+ "ATAC",
12
+ "CTCF",
13
+ "ChromHMM",
14
+ ];
15
+
16
+ export const ontologyTypes = [
17
+ "Adipose",
18
+ "Adrenal gland",
19
+ "Blood",
20
+ "Blood vessel",
21
+ "Bone",
22
+ "Bone marrow",
23
+ "Brain",
24
+ "Breast",
25
+ "Connective tissue",
26
+ "Embryo",
27
+ "Epithelium",
28
+ "Esophagus",
29
+ "Eye",
30
+ "Fallopian Tube",
31
+ "Gallbladder",
32
+ "Heart",
33
+ "Kidney",
34
+ "Large Intestine",
35
+ "Limb",
36
+ "Liver",
37
+ "Lung",
38
+ "Lymphoid Tissue",
39
+ "Muscle",
40
+ "Mouth",
41
+ "Nerve",
42
+ "Nose",
43
+ "Pancreas",
44
+ "Parathyroid Gland",
45
+ "Ovary",
46
+ "Penis",
47
+ "Placenta",
48
+ "Prostate",
49
+ "Skin",
50
+ "Small Intestine",
51
+ "Spinal Cord",
52
+ "Spleen",
53
+ "Stomach",
54
+ "Testis",
55
+ "Thymus",
56
+ "Thyroid",
57
+ "Urinary Bladder",
58
+ "Uterus",
59
+ "Vagina",
60
+ ];
61
+
62
+ export const rows = ontologyTypes.flatMap((ontology) =>
63
+ assayTypes.flatMap((assay) =>
64
+ getTracksByAssayAndOntology(
65
+ assay.toLowerCase(),
66
+ ontology.toLowerCase(),
67
+ ).map((r: TrackInfo) => {
68
+ const flat = flattenIntoRow(r);
69
+ return {
70
+ ...flat,
71
+ assay,
72
+ ontology,
73
+ };
74
+ }),
75
+ ),
76
+ );
77
+
78
+ // map of experimentAccession -> rowInfo for faster row lookup
79
+ export const rowById = new Map<string, RowInfo>(
80
+ rows.map((r) => [r.experimentAccession, r]),
81
+ );
82
+
83
+ /**
84
+ * Check if an ID is a real track (exists in rowById) vs an auto-generated group ID
85
+ */
86
+ export const isTrackId = (id: string): boolean => rowById.has(id);
87
+
88
+ /**
89
+ * Filter a set of IDs to return only real track IDs (no auto-generated group IDs)
90
+ */
91
+ export const getActiveTracks = (selectedIds: Set<string>): Set<string> =>
92
+ new Set(Array.from(selectedIds).filter(isTrackId));
@@ -0,0 +1,26 @@
1
+ import { create, StoreApi, UseBoundStore } from "zustand";
2
+ import { SelectionState, SelectionAction } from "./types";
3
+
4
+ export type SelectionStoreInstance = UseBoundStore<
5
+ StoreApi<SelectionState & SelectionAction>
6
+ >;
7
+
8
+ export function createSelectionStore() {
9
+ return create<SelectionState & SelectionAction>((set) => ({
10
+ maxTracks: 30,
11
+ selectedIds: new Set<string>(),
12
+ setSelected: (ids: Set<string>) =>
13
+ set(() => ({
14
+ selectedIds: new Set(ids),
15
+ })),
16
+ removeIds: (removedIds: Set<string>) =>
17
+ set((state) => {
18
+ const next = new Set(state.selectedIds);
19
+ removedIds.forEach((id) => {
20
+ next.delete(id);
21
+ });
22
+ return { selectedIds: next };
23
+ }),
24
+ clear: () => set(() => ({ selectedIds: new Set<string>() })),
25
+ }));
26
+ }
@@ -0,0 +1,139 @@
1
+ import { FuseOptionKey } from "fuse.js";
2
+ import { UseTreeItemParameters } from "@mui/x-tree-view/useTreeItem";
3
+ import { TreeViewBaseItem } from "@mui/x-tree-view";
4
+ import {
5
+ DataGridPremiumProps,
6
+ GridRowSelectionModel,
7
+ } from "@mui/x-data-grid-premium";
8
+ import { ReactElement, ReactNode } from "react";
9
+ import { SvgIconOwnProps } from "@mui/material";
10
+ import { SelectionStoreInstance } from "./store";
11
+
12
+ export interface SearchTracksProps {
13
+ query: string;
14
+ keyWeightMap: FuseOptionKey<any>[];
15
+ jsonStructure?: string;
16
+ treeItems?: TreeViewBaseItem<ExtendedTreeItemProps>[];
17
+ threshold?: number;
18
+ limit?: number;
19
+ }
20
+
21
+ /**
22
+ * Types for the JSON-formatted tracks fomr modifiedHumanTracks.json
23
+ */
24
+ export type AssayInfo = {
25
+ assay: string;
26
+ url: string;
27
+ experimentAccession: string;
28
+ fileAccession: string;
29
+ };
30
+
31
+ export type TrackInfo = {
32
+ name: string;
33
+ ontology: string;
34
+ lifeStage: string;
35
+ sampleType: string;
36
+ displayname: string;
37
+ assays: AssayInfo[];
38
+ };
39
+
40
+ /**
41
+ * Row format for DataGrid
42
+ */
43
+ export type RowInfo = {
44
+ ontology: string;
45
+ lifeStage: string;
46
+ sampleType: string;
47
+ displayname: string;
48
+ assay: string;
49
+ experimentAccession: string;
50
+ fileAccession: string;
51
+ };
52
+
53
+ /**
54
+ * Custom Tree Props for RichTreeView Panel
55
+ */
56
+ export type ExtendedTreeItemProps = {
57
+ id: string;
58
+ label: string;
59
+ icon: string;
60
+ isAssayItem?: boolean;
61
+ /**
62
+ * list of all the experimentAccession values in the children/grandchildren of the item, or the accession of the item itself
63
+ * this is used in updating the rowSelectionModel when removing items from the Tree View panel
64
+ */
65
+ allExpAccessions?: string[];
66
+ // list to allow search functionality in the treeview
67
+ allRowInfo?: RowInfo[];
68
+ };
69
+
70
+ export type TreeViewWrapperProps = {
71
+ store: SelectionStoreInstance;
72
+ items: TreeViewBaseItem<ExtendedTreeItemProps>[];
73
+ selectedIds: Set<string>;
74
+ activeTracks: Set<string>; // doesn't have the autogenerated row groupings to provide accurate number of tracks
75
+ isSearchResult: boolean;
76
+ };
77
+
78
+ export interface CustomLabelProps {
79
+ id: string;
80
+ children: React.ReactNode;
81
+ isAssayItem?: boolean;
82
+ icon: React.ElementType | React.ReactElement;
83
+ }
84
+
85
+ export interface CustomTreeItemProps
86
+ extends Omit<UseTreeItemParameters, "rootRef">,
87
+ Omit<React.HTMLAttributes<HTMLLIElement>, "onFocus"> {
88
+ onRemove?: (item: TreeViewBaseItem<ExtendedTreeItemProps>) => void;
89
+ }
90
+
91
+ /**
92
+ * Types for useSelectionStore to keep track of selected DataGrid row ids/tracks
93
+ */
94
+ export type SelectionState = {
95
+ maxTracks: number;
96
+ selectedIds: Set<string>;
97
+ };
98
+
99
+ export type SelectionAction = {
100
+ setSelected: (ids: Set<string>) => void;
101
+ removeIds: (removedIds: Set<string>) => void;
102
+ clear: () => void;
103
+ };
104
+
105
+ /**
106
+ * DataGrid Props
107
+ */
108
+ interface BaseTableProps extends Omit<DataGridPremiumProps, "columns"> {
109
+ toolbarSlot?: ReactNode;
110
+ /**
111
+ * If anything besides an element, renders tooltip icon to the right of the table label with specified string as tooltip contents.
112
+ * If an element, renders the element to the right of the table label.
113
+ */
114
+ labelTooltip?: ReactNode;
115
+ /**
116
+ * Styling object for the toolbar
117
+ */
118
+ toolbarStyle?: React.CSSProperties;
119
+ /**
120
+ * Color passed as `htmlColor` to columns, filter, download and search icons
121
+ */
122
+ toolbarIconColor?: SvgIconOwnProps["htmlColor"];
123
+ }
124
+
125
+ type DataGridWrapperProps = {
126
+ rows: RowInfo[];
127
+ selectedIds: Set<string>;
128
+ handleSelection: (newSelection: GridRowSelectionModel) => void;
129
+ sortedAssay: boolean;
130
+ };
131
+
132
+ //This enforces that a downloadFileName is specified if a ReactElement is used as the label (no default )
133
+ export type DataGridProps = DataGridWrapperProps &
134
+ BaseTableProps &
135
+ (
136
+ | { label?: string; downloadFileName?: string }
137
+ | { label: ReactElement; downloadFileName: string }
138
+ | { label?: undefined; downloadFileName?: string }
139
+ );
package/src/lib.ts ADDED
@@ -0,0 +1,8 @@
1
+ import TrackSelect, { type TrackSelectProps } from "./TrackSelect/TrackSelect";
2
+ export { TrackSelect, TrackSelectProps };
3
+
4
+ import {
5
+ createSelectionStore,
6
+ type SelectionStoreInstance,
7
+ } from "./TrackSelect/store.ts";
8
+ export { createSelectionStore, SelectionStoreInstance };