mirror of
https://github.com/hibiken/asynqmon.git
synced 2025-08-24 06:38:42 +08:00
Use virtualized list
This commit is contained in:
@@ -1,7 +1,10 @@
|
||||
import React from "react";
|
||||
import { makeStyles } from "@material-ui/core/styles";
|
||||
import { makeStyles, useTheme } from "@material-ui/core/styles";
|
||||
import TextField from "@material-ui/core/TextField";
|
||||
import Autocomplete from "@material-ui/lab/Autocomplete";
|
||||
import useMediaQuery from "@material-ui/core/useMediaQuery";
|
||||
import ListSubheader from "@material-ui/core/ListSubheader";
|
||||
import { VariableSizeList, ListChildComponentProps } from "react-window";
|
||||
import { GroupInfo } from "../api";
|
||||
import { isDarkTheme } from "../theme";
|
||||
|
||||
@@ -34,6 +37,12 @@ export default function GroupSelect(props: Props) {
|
||||
return (
|
||||
<Autocomplete
|
||||
id="task-group-selector"
|
||||
disableListWrap
|
||||
ListboxComponent={
|
||||
ListboxComponent as React.ComponentType<
|
||||
React.HTMLAttributes<HTMLElement>
|
||||
>
|
||||
}
|
||||
options={props.groups}
|
||||
getOptionLabel={(option: GroupInfo) => option.group}
|
||||
style={{ width: 300 }}
|
||||
@@ -49,3 +58,83 @@ export default function GroupSelect(props: Props) {
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
// Virtualized list.
|
||||
// Reference: https://v4.mui.com/components/autocomplete/#virtualization
|
||||
|
||||
const LISTBOX_PADDING = 8; // px
|
||||
|
||||
function renderRow(props: ListChildComponentProps) {
|
||||
const { data, index, style } = props;
|
||||
return React.cloneElement(data[index], {
|
||||
style: {
|
||||
...style,
|
||||
top: (style.top as number) + LISTBOX_PADDING,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const OuterElementContext = React.createContext({});
|
||||
|
||||
const OuterElementType = React.forwardRef<HTMLDivElement>((props, ref) => {
|
||||
const outerProps = React.useContext(OuterElementContext);
|
||||
return <div ref={ref} {...props} {...outerProps} />;
|
||||
});
|
||||
|
||||
function useResetCache(data: any) {
|
||||
const ref = React.useRef<VariableSizeList>(null);
|
||||
React.useEffect(() => {
|
||||
if (ref.current != null) {
|
||||
ref.current.resetAfterIndex(0, true);
|
||||
}
|
||||
}, [data]);
|
||||
return ref;
|
||||
}
|
||||
|
||||
// Adapter for react-window
|
||||
const ListboxComponent = React.forwardRef<HTMLDivElement>(
|
||||
function ListboxComponent(props, ref) {
|
||||
const { children, ...other } = props;
|
||||
const itemData = React.Children.toArray(children);
|
||||
const theme = useTheme();
|
||||
const smUp = useMediaQuery(theme.breakpoints.up("sm"), { noSsr: true });
|
||||
const itemCount = itemData.length;
|
||||
const itemSize = smUp ? 36 : 48;
|
||||
|
||||
const getChildSize = (child: React.ReactNode) => {
|
||||
if (React.isValidElement(child) && child.type === ListSubheader) {
|
||||
return 48;
|
||||
}
|
||||
return itemSize;
|
||||
};
|
||||
|
||||
const getHeight = () => {
|
||||
if (itemCount > 8) {
|
||||
return 8 * itemSize;
|
||||
}
|
||||
return itemData.map(getChildSize).reduce((a, b) => a + b, 0);
|
||||
};
|
||||
|
||||
const gridRef = useResetCache(itemCount);
|
||||
|
||||
return (
|
||||
<div ref={ref}>
|
||||
<OuterElementContext.Provider value={other}>
|
||||
<VariableSizeList
|
||||
itemData={itemData}
|
||||
height={getHeight() + 2 * LISTBOX_PADDING}
|
||||
width="100%"
|
||||
ref={gridRef}
|
||||
outerElementType={OuterElementType}
|
||||
innerElementType="ul"
|
||||
itemSize={(index) => getChildSize(itemData[index])}
|
||||
overscanCount={5}
|
||||
itemCount={itemCount}
|
||||
>
|
||||
{renderRow}
|
||||
</VariableSizeList>
|
||||
</OuterElementContext.Provider>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
Reference in New Issue
Block a user