Skip to content

Commit

Permalink
allow interactions with columns from outside table
Browse files Browse the repository at this point in the history
  • Loading branch information
corviday committed Dec 3, 2024
1 parent 78e0b87 commit df4bed8
Show file tree
Hide file tree
Showing 7 changed files with 3,878 additions and 3,345 deletions.
7,075 changes: 3,736 additions & 3,339 deletions package-lock.json

Large diffs are not rendered by default.

8 changes: 5 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,16 @@
"classnames": "^2.2.6",
"lodash": "^4.17.20",
"moment": "^2.29.0",
"react": "^16.13.1",
"react": "^18.3.1",
"react-bootstrap": "^1.3.0",
"react-bootstrap-icons": "^1.0.0",
"react-dom": "^16.13.1",
"react-dom": "^18.3.1",
"react-loader": "^2.4.7",
"react-scripts": "3.4.3",
"react-select": "^5.8.3",
"react-table": "^7.5.0",
"url-join": "^4.0.1"
"url-join": "^4.0.1",
"zustand": "^5.0.1"
},
"scripts": {
"start": "react-scripts start",
Expand Down
9 changes: 7 additions & 2 deletions src/components/app-root/AppBody/AppBody.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,27 @@ import { fetchWxFilesMetadata }
from '../../../data-services/wx-files-data-service';
import Help from '../../guidance/Help';
import LocationTable from '../../data-grid/two-level/LocationTable';

import FilterListDropdown from '../../controls/FilterListDropdown/FilterListDropdown';

export default function AppBody() {
const [locations, setLocations] = useState(null);

useEffect(() => {
fetchWxFilesMetadata().then(setLocations);
}, []);

return (
<>
<Row>
<Col lg={12}>
<Help/>
</Col>
</Row>
<Row>
<Col lg={12}>
<FilterListDropdown filter_id={"versions"} initialize={"CMIP6"} label={"Future-shifted Dataset"} />
</Col>
</Row>
<Row>
<Col lg={12}>
<LocationTable locations={locations}/>
Expand Down
83 changes: 83 additions & 0 deletions src/components/controls/FilterListDropdown/FilterListDropdown.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
// This component is a dropdown list from which a user picks an option
// The option is then supplied to a filter in the data table, which filters
// the data accordingly.
// This allows filtering the data on additional attributes without adding
// more columns to an already-complicated table. Additional columns may
// be added to the table and hidden, then this dropdown can be used to
// filter on the hidden column in a space-effient way.

import React, {useEffect, useState} from 'react';
import {useStore} from '../../../hooks/useZustandStore';
import map from 'lodash/fp/map';
import Select from 'react-select';

export default function FilterListDropdown( {filter_id, initialize, label} ) {
// list of available filters
const filters = useStore((state) => state.filters);
const optionLabels = useStore((state) => state.filterOptionLabels);
const [items, setItems] = useState([]);
const [filterAccess, setFilterAccess] = useState("fake!");
const [selected, setSelected] = useState("");


const makeItem = (value) => {
if((filter_id in optionLabels) && (value in optionLabels[filter_id])) {
return {"value": value, "label": optionLabels[filter_id][value]};
}
return {"value": value, "label": value}
}


const onSelect = (option) => {
// set locally so UI updates
setSelected(option);
// update in table filter
filterAccess(option["value"]);
}

//once the access function is available, set the filter to the default. Only
// done once.
useEffect(() => {
if(filterAccess && items.length > 0) {
const elem = items.find((option) => option["value"] === initialize);
if(elem) {
onSelect(elem);
}
}
}, [filterAccess, items]);

//set up the selector items and callback function, only run once.
useEffect(() => {
if (filters && (filter_id in filters) && (items.length == 0)) {

// this bit of minor weirdness is necessary because
// if you pass React a function via useState, it
// assumes you want it to invoke that function to calculate
// the next iteration of the state attribute, and calls it
// for you, passing in the current state and storing the output
// as the new state.
// if you want to actually store the function itself, you need
// to work within this functionality by passing a function
// that returns the function you actually want to store.
// react will invoke the wrapper function, and dutifully
// store the result (the desired function).
const af = filters[filter_id]["accessFunction"];
setFilterAccess(() => af);

setItems(filters[filter_id]["options"].map(makeItem));
}
}, [filters]);


return (
<div className={`${filter_id}-selector`}>
<Select
isLoading={items.length === 0}
options={items}
value={selected}
onChange={onSelect}
/>
</div>
);

}
6 changes: 6 additions & 0 deletions src/components/controls/FilterListDropdown/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"name": "FilterListDropdown",
"version": "0.0.0",
"private": true,
"main": "./FilterListDropdown.js"
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import React from 'react';
import React, {useEffect} from 'react';
import Form from 'react-bootstrap/Form';
import ClearButton from '../../misc/ClearButton';
import styles from '../ColumnFilters.module.css';
import {useStore} from '../../../../hooks/useZustandStore';

// Custom filter UI for selecting a unique option from a list that contains
// an array of option values. (Typically this array coalesces information about
Expand All @@ -12,6 +13,8 @@ export default function SelectArrayColumnFilter({
toString = option => option.toString(),
allValue = "*",
}) {
const registerFilter = useStore.getState().registerFilter;

// Calculate the options for filtering using the preFilteredRows.
// The row values themselves are arrays, and each array element is added
// to the list of options.
Expand All @@ -25,6 +28,12 @@ export default function SelectArrayColumnFilter({
return [...options.values()];
}, [id, preFilteredRows])

// register this filter so it can be used by other UI elements
// only needs to be done once.
useEffect(() => {
registerFilter(id, "list", setFilter, options? options : {});
}, [options]);

// Render a multi-select box
return (
<div className={`${styles.wrapper} ${styles.selectArrayColumn}`}>
Expand Down
31 changes: 31 additions & 0 deletions src/hooks/useZustandStore.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import {create} from 'zustand';
import merge from 'lodash/fp/merge';

export const useStore = create((set) => {
return {
// stores information about table filters.
// this allows them to be accessed from components outside the table
// each filter is represent as an object, keyed to a unique identifier.
// the object has properties
// accessFunction: (to set the filter)
// options: (describes possible selectable values
// type: describes the "API" of the filter,
// ie filters of type "list" have you pick one or more option from a provided set
// filters of type "range" have you pick a maximum and minimum value
filters: {},

registerFilter: (key, type, access, options) => {
let filter = {}
filter[key] = {"options": options, "type": type, "accessFunction": access};
set((state) => ({filters: merge(state.filters, filter)}));
},

// labels for filter dropdowns. Each set should be keyed to match an "key" in
// .filters; they will be retreived on that basis.
filterOptionLabels: {
"versions": {"CMIP5": "CMIP5+CWEC2016", "CMIP6": "CMIP6+CWEC2020",},
}


}
})

0 comments on commit df4bed8

Please sign in to comment.