perf(ui): split out gallery settings popover components

This was taking over 15ms (!) to render each time a setting changed, wtf
This commit is contained in:
psychedelicious 2024-06-28 16:43:41 +10:00
parent 2b744480d6
commit 1468f4d37e
10 changed files with 251 additions and 173 deletions

View File

@ -1,172 +0,0 @@
import type { ComboboxOption, FormLabelProps } from '@invoke-ai/ui-library';
import {
Checkbox,
Combobox,
CompositeSlider,
Divider,
Flex,
FormControl,
FormControlGroup,
FormLabel,
IconButton,
Popover,
PopoverBody,
PopoverContent,
PopoverTrigger,
Switch,
} from '@invoke-ai/ui-library';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import type { SingleValue } from 'chakra-react-select';
import {
alwaysShowImageSizeBadgeChanged,
autoAssignBoardOnClickChanged,
orderDirChanged,
setGalleryImageMinimumWidth,
shouldAutoSwitchChanged,
shouldShowArchivedBoardsChanged,
starredFirstChanged,
} from 'features/gallery/store/gallerySlice';
import type { ChangeEvent } from 'react';
import { memo, useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { RiSettings4Fill } from 'react-icons/ri';
import { assert } from 'tsafe';
import BoardAutoAddSelect from './Boards/BoardAutoAddSelect';
const formLabelProps: FormLabelProps = {
flexGrow: 1,
};
const GallerySettingsPopover = () => {
const dispatch = useAppDispatch();
const { t } = useTranslation();
const galleryImageMinimumWidth = useAppSelector((s) => s.gallery.galleryImageMinimumWidth);
const shouldAutoSwitch = useAppSelector((s) => s.gallery.shouldAutoSwitch);
const autoAssignBoardOnClick = useAppSelector((s) => s.gallery.autoAssignBoardOnClick);
const alwaysShowImageSizeBadge = useAppSelector((s) => s.gallery.alwaysShowImageSizeBadge);
const shouldShowArchivedBoards = useAppSelector((s) => s.gallery.shouldShowArchivedBoards);
const orderDir = useAppSelector((s) => s.gallery.orderDir);
const starredFirst = useAppSelector((s) => s.gallery.starredFirst);
const handleChangeGalleryImageMinimumWidth = useCallback(
(v: number) => {
dispatch(setGalleryImageMinimumWidth(v));
},
[dispatch]
);
const handleChangeAutoSwitch = useCallback(
(e: ChangeEvent<HTMLInputElement>) => {
dispatch(shouldAutoSwitchChanged(e.target.checked));
},
[dispatch]
);
const handleChangeAutoAssignBoardOnClick = useCallback(
(e: ChangeEvent<HTMLInputElement>) => dispatch(autoAssignBoardOnClickChanged(e.target.checked)),
[dispatch]
);
const handleChangeAlwaysShowImageSizeBadgeChanged = useCallback(
(e: ChangeEvent<HTMLInputElement>) => dispatch(alwaysShowImageSizeBadgeChanged(e.target.checked)),
[dispatch]
);
const handleChangeShouldShowArchivedBoardsChanged = useCallback(
(e: ChangeEvent<HTMLInputElement>) => {
dispatch(shouldShowArchivedBoardsChanged(e.target.checked));
},
[dispatch]
);
const onChangeStarredFirst = useCallback(
(e: ChangeEvent<HTMLInputElement>) => {
dispatch(starredFirstChanged(e.target.checked));
},
[dispatch]
);
const orderDirOptions = useMemo<ComboboxOption[]>(
() => [
{ value: 'DESC', label: t('gallery.newestFirst') },
{ value: 'ASC', label: t('gallery.oldestFirst') },
],
[t]
);
const onChangeOrderDir = useCallback(
(v: SingleValue<ComboboxOption>) => {
assert(v?.value === 'ASC' || v?.value === 'DESC');
dispatch(orderDirChanged(v.value));
},
[dispatch]
);
const orderDirValue = useMemo(() => {
return orderDirOptions.find((opt) => opt.value === orderDir);
}, [orderDir, orderDirOptions]);
return (
<Popover isLazy>
<PopoverTrigger>
<IconButton aria-label={t('gallery.gallerySettings')} size="sm" icon={<RiSettings4Fill />} />
</PopoverTrigger>
<PopoverContent>
<PopoverBody>
<Flex direction="column" gap={2}>
<FormControl>
<FormLabel>{t('gallery.galleryImageSize')}</FormLabel>
<CompositeSlider
value={galleryImageMinimumWidth}
onChange={handleChangeGalleryImageMinimumWidth}
min={45}
max={256}
defaultValue={90}
/>
</FormControl>
<FormControlGroup formLabelProps={formLabelProps}>
<FormControl>
<FormLabel>{t('gallery.autoSwitchNewImages')}</FormLabel>
<Checkbox isChecked={shouldAutoSwitch} onChange={handleChangeAutoSwitch} />
</FormControl>
<FormControl>
<FormLabel>{t('gallery.autoAssignBoardOnClick')}</FormLabel>
<Checkbox isChecked={autoAssignBoardOnClick} onChange={handleChangeAutoAssignBoardOnClick} />
</FormControl>
<FormControl>
<FormLabel>{t('gallery.alwaysShowImageSizeBadge')}</FormLabel>
<Checkbox isChecked={alwaysShowImageSizeBadge} onChange={handleChangeAlwaysShowImageSizeBadgeChanged} />
</FormControl>
<FormControl>
<FormLabel>{t('gallery.showArchivedBoards')}</FormLabel>
<Checkbox isChecked={shouldShowArchivedBoards} onChange={handleChangeShouldShowArchivedBoardsChanged} />
</FormControl>
</FormControlGroup>
<BoardAutoAddSelect />
<Divider />
<FormControl w="full">
<FormLabel flexGrow={1} m={0}>
{t('gallery.showStarredImagesFirst')}
</FormLabel>
<Switch size="sm" isChecked={starredFirst} onChange={onChangeStarredFirst} />
</FormControl>
<FormControl>
<FormLabel flexGrow={1} m={0}>
{t('gallery.sortDirection')}
</FormLabel>
<Combobox
isSearchable={false}
value={orderDirValue}
options={orderDirOptions}
onChange={onChangeOrderDir}
/>
</FormControl>
</Flex>
</PopoverBody>
</PopoverContent>
</Popover>
);
};
export default memo(GallerySettingsPopover);

View File

@ -0,0 +1,26 @@
import { Checkbox, FormControl, FormLabel } from '@invoke-ai/ui-library';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { alwaysShowImageSizeBadgeChanged } from 'features/gallery/store/gallerySlice';
import type { ChangeEvent } from 'react';
import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
const GallerySettingsPopover = () => {
const dispatch = useAppDispatch();
const { t } = useTranslation();
const alwaysShowImageSizeBadge = useAppSelector((s) => s.gallery.alwaysShowImageSizeBadge);
const onChange = useCallback(
(e: ChangeEvent<HTMLInputElement>) => dispatch(alwaysShowImageSizeBadgeChanged(e.target.checked)),
[dispatch]
);
return (
<FormControl>
<FormLabel flexGrow={1}>{t('gallery.alwaysShowImageSizeBadge')}</FormLabel>
<Checkbox isChecked={alwaysShowImageSizeBadge} onChange={onChange} />
</FormControl>
);
};
export default memo(GallerySettingsPopover);

View File

@ -0,0 +1,26 @@
import { Checkbox, FormControl, FormLabel } from '@invoke-ai/ui-library';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { autoAssignBoardOnClickChanged } from 'features/gallery/store/gallerySlice';
import type { ChangeEvent } from 'react';
import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
const GallerySettingsPopover = () => {
const dispatch = useAppDispatch();
const { t } = useTranslation();
const autoAssignBoardOnClick = useAppSelector((s) => s.gallery.autoAssignBoardOnClick);
const onChange = useCallback(
(e: ChangeEvent<HTMLInputElement>) => dispatch(autoAssignBoardOnClickChanged(e.target.checked)),
[dispatch]
);
return (
<FormControl>
<FormLabel flexGrow={1}>{t('gallery.autoAssignBoardOnClick')}</FormLabel>
<Checkbox isChecked={autoAssignBoardOnClick} onChange={onChange} />
</FormControl>
);
};
export default memo(GallerySettingsPopover);

View File

@ -0,0 +1,28 @@
import { Checkbox, FormControl, FormLabel } from '@invoke-ai/ui-library';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { shouldAutoSwitchChanged } from 'features/gallery/store/gallerySlice';
import type { ChangeEvent } from 'react';
import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
const GallerySettingsPopover = () => {
const dispatch = useAppDispatch();
const { t } = useTranslation();
const shouldAutoSwitch = useAppSelector((s) => s.gallery.shouldAutoSwitch);
const onChange = useCallback(
(e: ChangeEvent<HTMLInputElement>) => {
dispatch(shouldAutoSwitchChanged(e.target.checked));
},
[dispatch]
);
return (
<FormControl>
<FormLabel flexGrow={1}>{t('gallery.autoSwitchNewImages')}</FormLabel>
<Checkbox isChecked={shouldAutoSwitch} onChange={onChange} />
</FormControl>
);
};
export default memo(GallerySettingsPopover);

View File

@ -0,0 +1,41 @@
import { Divider, Flex, IconButton, Popover, PopoverBody, PopoverContent, PopoverTrigger } from '@invoke-ai/ui-library';
import BoardAutoAddSelect from 'features/gallery/components/Boards/BoardAutoAddSelect';
import AlwaysShowImageSizeCheckbox from 'features/gallery/components/GallerySettingsPopover/AlwaysShowImageSizeCheckbox';
import AutoAssignBoardCheckbox from 'features/gallery/components/GallerySettingsPopover/AutoAssignBoardCheckbox';
import AutoSwitchCheckbox from 'features/gallery/components/GallerySettingsPopover/AutoSwitchCheckbox';
import ImageMinimumWidthSlider from 'features/gallery/components/GallerySettingsPopover/ImageMinimumWidthSlider';
import ShowArchivedBoardsCheckbox from 'features/gallery/components/GallerySettingsPopover/ShowArchivedBoardsCheckbox';
import ShowStarredFirstCheckbox from 'features/gallery/components/GallerySettingsPopover/ShowStarredFirstCheckbox';
import SortDirectionCombobox from 'features/gallery/components/GallerySettingsPopover/SortDirectionCombobox';
import { memo } from 'react';
import { useTranslation } from 'react-i18next';
import { RiSettings4Fill } from 'react-icons/ri';
const GallerySettingsPopover = () => {
const { t } = useTranslation();
return (
<Popover isLazy>
<PopoverTrigger>
<IconButton aria-label={t('gallery.gallerySettings')} size="sm" icon={<RiSettings4Fill />} />
</PopoverTrigger>
<PopoverContent>
<PopoverBody>
<Flex direction="column" gap={2}>
<ImageMinimumWidthSlider />
<AutoSwitchCheckbox />
<AutoAssignBoardCheckbox />
<AlwaysShowImageSizeCheckbox />
<ShowArchivedBoardsCheckbox />
<BoardAutoAddSelect />
<Divider />
<ShowStarredFirstCheckbox />
<SortDirectionCombobox />
</Flex>
</PopoverBody>
</PopoverContent>
</Popover>
);
};
export default memo(GallerySettingsPopover);

View File

@ -0,0 +1,26 @@
import { CompositeSlider, FormControl, FormLabel } from '@invoke-ai/ui-library';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { setGalleryImageMinimumWidth } from 'features/gallery/store/gallerySlice';
import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
const GallerySettingsPopover = () => {
const dispatch = useAppDispatch();
const { t } = useTranslation();
const galleryImageMinimumWidth = useAppSelector((s) => s.gallery.galleryImageMinimumWidth);
const onChange = useCallback(
(v: number) => {
dispatch(setGalleryImageMinimumWidth(v));
},
[dispatch]
);
return (
<FormControl>
<FormLabel>{t('gallery.galleryImageSize')}</FormLabel>
<CompositeSlider value={galleryImageMinimumWidth} onChange={onChange} min={45} max={256} defaultValue={90} />
</FormControl>
);
};
export default memo(GallerySettingsPopover);

View File

@ -0,0 +1,28 @@
import { Checkbox, FormControl, FormLabel } from '@invoke-ai/ui-library';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { shouldShowArchivedBoardsChanged } from 'features/gallery/store/gallerySlice';
import type { ChangeEvent } from 'react';
import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
const GallerySettingsPopover = () => {
const dispatch = useAppDispatch();
const { t } = useTranslation();
const shouldShowArchivedBoards = useAppSelector((s) => s.gallery.shouldShowArchivedBoards);
const onChange = useCallback(
(e: ChangeEvent<HTMLInputElement>) => {
dispatch(shouldShowArchivedBoardsChanged(e.target.checked));
},
[dispatch]
);
return (
<FormControl>
<FormLabel flexGrow={1}>{t('gallery.showArchivedBoards')}</FormLabel>
<Checkbox isChecked={shouldShowArchivedBoards} onChange={onChange} />
</FormControl>
);
};
export default memo(GallerySettingsPopover);

View File

@ -0,0 +1,30 @@
import { FormControl, FormLabel, Switch } from '@invoke-ai/ui-library';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { starredFirstChanged } from 'features/gallery/store/gallerySlice';
import type { ChangeEvent } from 'react';
import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
const GallerySettingsPopover = () => {
const dispatch = useAppDispatch();
const { t } = useTranslation();
const starredFirst = useAppSelector((s) => s.gallery.starredFirst);
const onChange = useCallback(
(e: ChangeEvent<HTMLInputElement>) => {
dispatch(starredFirstChanged(e.target.checked));
},
[dispatch]
);
return (
<FormControl w="full">
<FormLabel flexGrow={1} m={0}>
{t('gallery.showStarredImagesFirst')}
</FormLabel>
<Switch size="sm" isChecked={starredFirst} onChange={onChange} />
</FormControl>
);
};
export default memo(GallerySettingsPopover);

View File

@ -0,0 +1,45 @@
import type { ComboboxOption } from '@invoke-ai/ui-library';
import { Combobox, FormControl, FormLabel } from '@invoke-ai/ui-library';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import type { SingleValue } from 'chakra-react-select';
import { orderDirChanged } from 'features/gallery/store/gallerySlice';
import { memo, useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { assert } from 'tsafe';
const GallerySettingsPopover = () => {
const dispatch = useAppDispatch();
const { t } = useTranslation();
const orderDir = useAppSelector((s) => s.gallery.orderDir);
const options = useMemo<ComboboxOption[]>(
() => [
{ value: 'DESC', label: t('gallery.newestFirst') },
{ value: 'ASC', label: t('gallery.oldestFirst') },
],
[t]
);
const onChange = useCallback(
(v: SingleValue<ComboboxOption>) => {
assert(v?.value === 'ASC' || v?.value === 'DESC');
dispatch(orderDirChanged(v.value));
},
[dispatch]
);
const value = useMemo(() => {
return options.find((opt) => opt.value === orderDir);
}, [orderDir, options]);
return (
<FormControl>
<FormLabel flexGrow={1} m={0}>
{t('gallery.sortDirection')}
</FormLabel>
<Combobox isSearchable={false} value={value} options={options} onChange={onChange} />
</FormControl>
);
};
export default memo(GallerySettingsPopover);

View File

@ -10,7 +10,7 @@ import { RiServerLine } from 'react-icons/ri';
import BoardsList from './Boards/BoardsList/BoardsList';
import GalleryBoardName from './GalleryBoardName';
import GallerySettingsPopover from './GallerySettingsPopover';
import GallerySettingsPopover from './GallerySettingsPopover/GallerySettingsPopover';
import GalleryImageGrid from './ImageGrid/GalleryImageGrid';
import { GalleryPagination } from './ImageGrid/GalleryPagination';