Skip to content

Commit

Permalink
feat: Show loading indicator in select when hierarchy is being loaded
Browse files Browse the repository at this point in the history
  • Loading branch information
ptbrowne committed Nov 10, 2022
1 parent fd646d9 commit 2fb80d1
Show file tree
Hide file tree
Showing 5 changed files with 132 additions and 49 deletions.
2 changes: 1 addition & 1 deletion app/charts/shared/chart-data-filters.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ const DataFilter = ({
},
});

const hierarchyParents = useHierarchyParents(
const { data: hierarchyParents } = useHierarchyParents(
dataSetIri,
dataSource,
data?.dataCubeByIri?.dimensionByIri!,
Expand Down
136 changes: 100 additions & 36 deletions app/components/form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
Box,
BoxProps,
ButtonBase,
Paper,
Checkbox as MUICheckbox,
InputProps,
FormControlLabel,
Expand All @@ -19,11 +20,16 @@ import {
MenuItem,
TypographyProps,
Stack,
CircularProgress,
styled,
PaperProps,
} from "@mui/material";
import { useId } from "@reach/auto-id";
import { timeFormat } from "d3-time-format";
import flatten from "lodash/flatten";
import React, { useContext } from "react";
import { ChangeEvent, ReactNode, useCallback, useMemo } from "react";
import { forwardRef } from "react";

import VisuallyHidden from "@/components/visually-hidden";
import {
Expand Down Expand Up @@ -253,6 +259,54 @@ export type Group = {
value: string;
};

// Copied over from https://github.com/mui/material-ui/blob/master/packages/mui-material/src/Menu/Menu.js
const MenuPaper = styled(Paper, {
name: "MuiMenu",
slot: "Paper",
overridesResolver: (_props: PaperProps, styles) => styles.paper,
})({
// specZ: The maximum height of a simple menu should be one or more rows less than the view
// height. This ensures a tapable area outside of the simple menu with which to dismiss
// the menu.
maxHeight: "calc(100% - 96px)",
// Add iOS momentum scrolling for iOS < 13.0
WebkitOverflowScrolling: "touch",
});

const LoadingMenuPaperContext = React.createContext(
false as boolean | undefined
);

/**
* Shows a loading indicator when hierarchy is loading
*/
const LoadingMenuPaper = forwardRef<HTMLDivElement>(
(props: PaperProps, ref) => {
const loading = useContext(LoadingMenuPaperContext);
return (
<MenuPaper {...props} ref={ref}>
{props.children}
{loading ? (
<Box
px={4}
py={2}
sx={{
position: "sticky",
bottom: 0,
backgroundColor: "warning.light",
}}
>
<Typography variant="body2">
<Trans id="hint.loading.data" />
<CircularProgress size={12} color="inherit" />
</Typography>
</Box>
) : null}
</MenuPaper>
);
}
);

export const Select = ({
label,
id,
Expand All @@ -265,6 +319,7 @@ export const Select = ({
optionGroups,
tooltipText,
onOpen,
loading,
}: {
id: string;
options: Option[];
Expand All @@ -274,6 +329,7 @@ export const Select = ({
controls?: React.ReactNode;
optionGroups?: [OptionGroup, Option[]][];
tooltipText?: string;
loading?: boolean;
} & SelectProps) => {
const locale = useLocale();

Expand All @@ -294,42 +350,50 @@ export const Select = ({
}, [optionGroups, sortOptions, locale, options]);

return (
<Box>
{label && (
<Label htmlFor={id} smaller tooltipText={tooltipText} sx={{ mb: 1 }}>
{label}
{controls}
</Label>
)}
<MUISelect
sx={{
width: "100%",
}}
id={id}
name={id}
onChange={onChange}
value={value}
disabled={disabled}
onOpen={onOpen}
>
{sortedOptions.map((opt) => {
if (!opt.value) {
return null;
}
return opt.type === "group" ? (
<ListSubheader key={opt.label}>{opt.label}</ListSubheader>
) : (
<MenuItem
key={opt.value}
disabled={opt.disabled}
value={opt.value ?? undefined}
>
{opt.label}
</MenuItem>
);
})}
</MUISelect>
</Box>
<LoadingMenuPaperContext.Provider value={loading}>
<Box>
{label && (
<Label htmlFor={id} smaller tooltipText={tooltipText} sx={{ mb: 1 }}>
{label}
{controls}
</Label>
)}
<MUISelect
sx={{
width: "100%",
}}
id={id}
name={id}
onChange={onChange}
value={value}
disabled={disabled}
onOpen={onOpen}
MenuProps={{
PaperProps: {
// @ts-ignore - It works
component: LoadingMenuPaper,
},
}}
>
{sortedOptions.map((opt) => {
if (!opt.value) {
return null;
}
return opt.type === "group" ? (
<ListSubheader key={opt.label}>{opt.label}</ListSubheader>
) : (
<MenuItem
key={opt.value}
disabled={opt.disabled}
value={opt.value ?? undefined}
>
{opt.label}
</MenuItem>
);
})}
</MUISelect>
</Box>
</LoadingMenuPaperContext.Provider>
);
};

Expand Down
16 changes: 9 additions & 7 deletions app/configurator/components/chart-configurator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -83,13 +83,14 @@ const DataFilterSelectGeneric = ({
const [state] = useConfiguratorState(isConfiguring);
const locale = useLocale();
const [pause, setPause] = useState(true);
const hierarchyParents = useHierarchyParents(
state.dataSet,
state.dataSource,
dimension,
locale,
pause
);
const { data: hierarchyParents, fetching: fetchingHierarchy } =
useHierarchyParents(
state.dataSet,
state.dataSource,
dimension,
locale,
pause
);

const optionGroups = useMemo(() => {
return makeOptionGroups(hierarchyParents);
Expand Down Expand Up @@ -145,6 +146,7 @@ const DataFilterSelectGeneric = ({
options={values}
optionGroups={optionGroups}
onOpen={handleOpen}
loading={fetchingHierarchy}
/>
);
}
Expand Down
3 changes: 3 additions & 0 deletions app/configurator/components/field.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@ export const DataFilterSelect = ({
optionGroups,
tooltipText,
onOpen,
loading,
}: {
dimensionIri: string;
label: string;
Expand All @@ -156,6 +157,7 @@ export const DataFilterSelect = ({
optionGroups?: [OptionGroup, Option[]][];
tooltipText?: string;
onOpen?: () => void;
loading?: boolean;
}) => {
const fieldProps = useSingleFilterSelect({ dimensionIri });

Expand Down Expand Up @@ -192,6 +194,7 @@ export const DataFilterSelect = ({
optionGroups={optionGroups}
tooltipText={tooltipText}
onOpen={onOpen}
loading={loading}
{...fieldProps}
/>
);
Expand Down
24 changes: 19 additions & 5 deletions app/configurator/components/use-hierarchy-parents.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,10 @@ const useHierarchyParents = (
dimension: DataCubeMetadata["dimensions"][number],
locale: string,
pause?: boolean
): HierarchyParents | undefined => {
): {
fetching: boolean;
data: HierarchyParents | undefined;
} => {
const [hierarchyResp] = useDimensionHierarchyQuery({
variables: {
cubeIri: dataSet,
Expand All @@ -49,20 +52,31 @@ const useHierarchyParents = (
},
pause: pause || !dimension,
});

const hierarchy =
hierarchyResp?.data?.dataCubeByIri?.dimensionByIri?.hierarchy;
return useMemo(() => {
const data = useMemo(() => {
if (!hierarchy || !dimension) {
return;
}
const values = dimension.values;
const valueSet = new Set(values.map((v) => v.value));
const valueGroups = groupByParents(hierarchy);

return valueGroups.map(([parents, nodes]) => {
return [parents, nodes.filter((n) => valueSet.has(n.node.value))];
});
return valueGroups
.map(([parents, nodes]) => {
return [parents, nodes.filter((n) => valueSet.has(n.node.value))];
})
.filter((x) => x[1].length > 0) as HierarchyParents;
}, [dimension, hierarchy]);

return useMemo(
() => ({
data,
fetching: hierarchyResp.fetching,
}),
[hierarchyResp.fetching, data]
);
};

/**
Expand Down

0 comments on commit 2fb80d1

Please sign in to comment.