mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
board UI updates: always show search for boards and images if a term is entered, clear search when view is toggled off
This commit is contained in:
parent
2172e4d292
commit
b7383cc0e5
@ -374,6 +374,8 @@
|
|||||||
"displayBoardSearch": "Display Board Search",
|
"displayBoardSearch": "Display Board Search",
|
||||||
"displaySearch": "Display Search",
|
"displaySearch": "Display Search",
|
||||||
"download": "Download",
|
"download": "Download",
|
||||||
|
"exitBoardSearch": "Exit Board Search",
|
||||||
|
"exitSearch": "Exit Search",
|
||||||
"featuresWillReset": "If you delete this image, those features will immediately be reset.",
|
"featuresWillReset": "If you delete this image, those features will immediately be reset.",
|
||||||
"galleryImageSize": "Image Size",
|
"galleryImageSize": "Image Size",
|
||||||
"gallerySettings": "Gallery Settings",
|
"gallerySettings": "Gallery Settings",
|
||||||
|
@ -28,8 +28,10 @@ export const BoardsList = ({ isPrivate }: { isPrivate?: boolean }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return boards.filter((board) => {
|
return boards.filter((board) => {
|
||||||
if (boardSearchText) {
|
if (boardSearchText.length) {
|
||||||
return board.is_private === isPrivate && board.board_name.toLowerCase().includes(boardSearchText.toLowerCase());
|
return (
|
||||||
|
board.is_private === !!isPrivate && board.board_name.toLowerCase().includes(boardSearchText.toLowerCase())
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
return board.is_private === !!isPrivate;
|
return board.is_private === !!isPrivate;
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,85 @@
|
|||||||
|
import type { ChakraProps } from '@invoke-ai/ui-library';
|
||||||
|
import { Box, Collapse, Flex, IconButton, Spacer, Tab, TabList, Tabs, useDisclosure } from '@invoke-ai/ui-library';
|
||||||
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
|
import { galleryViewChanged, searchTermChanged } from 'features/gallery/store/gallerySlice';
|
||||||
|
import { useCallback } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { MdSearch, MdSearchOff } from 'react-icons/md';
|
||||||
|
|
||||||
|
import { COLLAPSE_STYLES } from './ImageGalleryContent';
|
||||||
|
import GalleryImageGrid from './ImageGrid/GalleryImageGrid';
|
||||||
|
import { GalleryPagination } from './ImageGrid/GalleryPagination';
|
||||||
|
import { GallerySearch } from './ImageGrid/GallerySearch';
|
||||||
|
|
||||||
|
const BASE_STYLES: ChakraProps['sx'] = {
|
||||||
|
fontWeight: 'semibold',
|
||||||
|
fontSize: 'sm',
|
||||||
|
color: 'base.300',
|
||||||
|
};
|
||||||
|
|
||||||
|
const SELECTED_STYLES: ChakraProps['sx'] = {
|
||||||
|
borderColor: 'base.800',
|
||||||
|
borderBottomColor: 'base.900',
|
||||||
|
color: 'invokeBlue.300',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Gallery = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
const galleryView = useAppSelector((s) => s.gallery.galleryView);
|
||||||
|
const searchTerm = useAppSelector((s) => s.gallery.searchTerm);
|
||||||
|
const searchDisclosure = useDisclosure({ defaultIsOpen: !!searchTerm.length });
|
||||||
|
|
||||||
|
const handleClickImages = useCallback(() => {
|
||||||
|
dispatch(galleryViewChanged('images'));
|
||||||
|
}, [dispatch]);
|
||||||
|
|
||||||
|
const handleClickAssets = useCallback(() => {
|
||||||
|
dispatch(galleryViewChanged('assets'));
|
||||||
|
}, [dispatch]);
|
||||||
|
|
||||||
|
const handleClickSearch = useCallback(() => {
|
||||||
|
if (searchTerm.length) {
|
||||||
|
dispatch(searchTermChanged(''));
|
||||||
|
searchDisclosure.onToggle();
|
||||||
|
} else {
|
||||||
|
searchDisclosure.onToggle();
|
||||||
|
}
|
||||||
|
}, [searchTerm, dispatch, searchDisclosure]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Flex flexDirection="column" alignItems="center" justifyContent="space-between" h="full" w="full">
|
||||||
|
<Tabs index={galleryView === 'images' ? 0 : 1} variant="enclosed" display="flex" flexDir="column" w="full">
|
||||||
|
<TabList gap={2} fontSize="sm" borderColor="base.800">
|
||||||
|
<Tab sx={BASE_STYLES} _selected={SELECTED_STYLES} onClick={handleClickImages} data-testid="images-tab">
|
||||||
|
{t('parameters.images')}
|
||||||
|
</Tab>
|
||||||
|
<Tab sx={BASE_STYLES} _selected={SELECTED_STYLES} onClick={handleClickAssets} data-testid="assets-tab">
|
||||||
|
{t('gallery.assets')}
|
||||||
|
</Tab>
|
||||||
|
<Spacer />
|
||||||
|
<Box position="relative">
|
||||||
|
<IconButton
|
||||||
|
w="full"
|
||||||
|
h="full"
|
||||||
|
onClick={handleClickSearch}
|
||||||
|
tooltip={searchDisclosure.isOpen ? `${t('gallery.exitSearch')}` : `${t('gallery.displaySearch')}`}
|
||||||
|
aria-label={t('gallery.displaySearch')}
|
||||||
|
icon={searchTerm.length ? <MdSearchOff /> : <MdSearch />}
|
||||||
|
variant="link"
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</TabList>
|
||||||
|
</Tabs>
|
||||||
|
<Box w="full">
|
||||||
|
<Collapse in={searchDisclosure.isOpen} style={COLLAPSE_STYLES}>
|
||||||
|
<Box w="full" pt={2}>
|
||||||
|
<GallerySearch />
|
||||||
|
</Box>
|
||||||
|
</Collapse>
|
||||||
|
</Box>
|
||||||
|
<GalleryImageGrid />
|
||||||
|
<GalleryPagination />
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
};
|
@ -1,57 +1,28 @@
|
|||||||
import type { ChakraProps } from '@invoke-ai/ui-library';
|
import { Box, Collapse, Divider, Flex, IconButton, useDisclosure } from '@invoke-ai/ui-library';
|
||||||
import {
|
|
||||||
Box,
|
|
||||||
Collapse,
|
|
||||||
Divider,
|
|
||||||
Flex,
|
|
||||||
IconButton,
|
|
||||||
Spacer,
|
|
||||||
Tab,
|
|
||||||
TabList,
|
|
||||||
Tabs,
|
|
||||||
useDisclosure,
|
|
||||||
} from '@invoke-ai/ui-library';
|
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import { GalleryHeader } from 'features/gallery/components/GalleryHeader';
|
import { GalleryHeader } from 'features/gallery/components/GalleryHeader';
|
||||||
import { galleryViewChanged } from 'features/gallery/store/gallerySlice';
|
import { boardSearchTextChanged } from 'features/gallery/store/gallerySlice';
|
||||||
import ResizeHandle from 'features/ui/components/tabs/ResizeHandle';
|
import ResizeHandle from 'features/ui/components/tabs/ResizeHandle';
|
||||||
import { usePanel, type UsePanelOptions } from 'features/ui/hooks/usePanel';
|
import { usePanel, type UsePanelOptions } from 'features/ui/hooks/usePanel';
|
||||||
import type { CSSProperties } from 'react';
|
import type { CSSProperties } from 'react';
|
||||||
import { memo, useCallback, useMemo, useRef } from 'react';
|
import { memo, useCallback, useEffect, useMemo, useRef } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { PiMagnifyingGlassBold } from 'react-icons/pi';
|
import { MdSearch, MdSearchOff } from 'react-icons/md';
|
||||||
import type { ImperativePanelGroupHandle } from 'react-resizable-panels';
|
import type { ImperativePanelGroupHandle } from 'react-resizable-panels';
|
||||||
import { Panel, PanelGroup } from 'react-resizable-panels';
|
import { Panel, PanelGroup } from 'react-resizable-panels';
|
||||||
|
|
||||||
import BoardsListWrapper from './Boards/BoardsList/BoardsListWrapper';
|
import BoardsListWrapper from './Boards/BoardsList/BoardsListWrapper';
|
||||||
import BoardsSearch from './Boards/BoardsList/BoardsSearch';
|
import BoardsSearch from './Boards/BoardsList/BoardsSearch';
|
||||||
|
import { Gallery } from './Gallery';
|
||||||
import GallerySettingsPopover from './GallerySettingsPopover/GallerySettingsPopover';
|
import GallerySettingsPopover from './GallerySettingsPopover/GallerySettingsPopover';
|
||||||
import GalleryImageGrid from './ImageGrid/GalleryImageGrid';
|
|
||||||
import { GalleryPagination } from './ImageGrid/GalleryPagination';
|
|
||||||
import { GallerySearch } from './ImageGrid/GallerySearch';
|
|
||||||
|
|
||||||
const COLLAPSE_STYLES: CSSProperties = { flexShrink: 0, minHeight: 0 };
|
export const COLLAPSE_STYLES: CSSProperties = { flexShrink: 0, minHeight: 0 };
|
||||||
|
|
||||||
const BASE_STYLES: ChakraProps['sx'] = {
|
|
||||||
fontWeight: 'semibold',
|
|
||||||
fontSize: 'sm',
|
|
||||||
color: 'base.300',
|
|
||||||
};
|
|
||||||
|
|
||||||
const SELECTED_STYLES: ChakraProps['sx'] = {
|
|
||||||
borderColor: 'base.800',
|
|
||||||
borderBottomColor: 'base.900',
|
|
||||||
color: 'invokeBlue.300',
|
|
||||||
};
|
|
||||||
|
|
||||||
const ImageGalleryContent = () => {
|
const ImageGalleryContent = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const galleryView = useAppSelector((s) => s.gallery.galleryView);
|
|
||||||
const searchTerm = useAppSelector((s) => s.gallery.searchTerm);
|
|
||||||
const boardSearchText = useAppSelector((s) => s.gallery.boardSearchText);
|
const boardSearchText = useAppSelector((s) => s.gallery.boardSearchText);
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const searchDisclosure = useDisclosure({ defaultIsOpen: false });
|
const boardSearchDisclosure = useDisclosure({ defaultIsOpen: !!boardSearchText.length });
|
||||||
const boardSearchDisclosure = useDisclosure({ defaultIsOpen: false });
|
|
||||||
const panelGroupRef = useRef<ImperativePanelGroupHandle>(null);
|
const panelGroupRef = useRef<ImperativePanelGroupHandle>(null);
|
||||||
|
|
||||||
const boardsListPanelOptions = useMemo<UsePanelOptions>(
|
const boardsListPanelOptions = useMemo<UsePanelOptions>(
|
||||||
@ -67,13 +38,20 @@ const ImageGalleryContent = () => {
|
|||||||
);
|
);
|
||||||
const boardsListPanel = usePanel(boardsListPanelOptions);
|
const boardsListPanel = usePanel(boardsListPanelOptions);
|
||||||
|
|
||||||
const handleClickImages = useCallback(() => {
|
const handleClickBoardSearch = useCallback(() => {
|
||||||
dispatch(galleryViewChanged('images'));
|
if (boardSearchText.length) {
|
||||||
}, [dispatch]);
|
dispatch(boardSearchTextChanged(''));
|
||||||
|
boardSearchDisclosure.onToggle();
|
||||||
|
} else {
|
||||||
|
boardSearchDisclosure.onToggle();
|
||||||
|
}
|
||||||
|
}, [boardSearchText, dispatch, boardSearchDisclosure]);
|
||||||
|
|
||||||
const handleClickAssets = useCallback(() => {
|
useEffect(() => {
|
||||||
dispatch(galleryViewChanged('assets'));
|
if (boardSearchDisclosure.isOpen) {
|
||||||
}, [dispatch]);
|
boardsListPanel.expand();
|
||||||
|
}
|
||||||
|
}, [boardSearchDisclosure, boardsListPanel]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex position="relative" flexDirection="column" h="full" w="full" pt={2}>
|
<Flex position="relative" flexDirection="column" h="full" w="full" pt={2}>
|
||||||
@ -84,25 +62,17 @@ const ImageGalleryContent = () => {
|
|||||||
<IconButton
|
<IconButton
|
||||||
w="full"
|
w="full"
|
||||||
h="full"
|
h="full"
|
||||||
onClick={boardSearchDisclosure.onToggle}
|
onClick={handleClickBoardSearch}
|
||||||
tooltip={`${t('gallery.displayBoardSearch')}`}
|
tooltip={
|
||||||
|
boardSearchDisclosure.isOpen ? `${t('gallery.exitBoardSearch')}` : `${t('gallery.displayBoardSearch')}`
|
||||||
|
}
|
||||||
aria-label={t('gallery.displayBoardSearch')}
|
aria-label={t('gallery.displayBoardSearch')}
|
||||||
icon={<PiMagnifyingGlassBold />}
|
icon={boardSearchText.length ? <MdSearchOff /> : <MdSearch />}
|
||||||
variant="link"
|
variant="link"
|
||||||
/>
|
/>
|
||||||
{boardSearchText && (
|
|
||||||
<Box
|
|
||||||
position="absolute"
|
|
||||||
w={2}
|
|
||||||
h={2}
|
|
||||||
bg="invokeBlue.300"
|
|
||||||
borderRadius="full"
|
|
||||||
insetBlockStart={2}
|
|
||||||
insetInlineEnd={2}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Box>
|
</Box>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|
||||||
<PanelGroup ref={panelGroupRef} direction="vertical">
|
<PanelGroup ref={panelGroupRef} direction="vertical">
|
||||||
<Panel
|
<Panel
|
||||||
id="boards-list-panel"
|
id="boards-list-panel"
|
||||||
@ -127,50 +97,7 @@ const ImageGalleryContent = () => {
|
|||||||
onDoubleClick={boardsListPanel.onDoubleClickHandle}
|
onDoubleClick={boardsListPanel.onDoubleClickHandle}
|
||||||
/>
|
/>
|
||||||
<Panel id="gallery-wrapper-panel" minSize={20}>
|
<Panel id="gallery-wrapper-panel" minSize={20}>
|
||||||
<Flex flexDirection="column" alignItems="center" justifyContent="space-between" h="full" w="full">
|
<Gallery />
|
||||||
<Tabs index={galleryView === 'images' ? 0 : 1} variant="enclosed" display="flex" flexDir="column" w="full">
|
|
||||||
<TabList gap={2} fontSize="sm" borderColor="base.800">
|
|
||||||
<Tab sx={BASE_STYLES} _selected={SELECTED_STYLES} onClick={handleClickImages} data-testid="images-tab">
|
|
||||||
{t('parameters.images')}
|
|
||||||
</Tab>
|
|
||||||
<Tab sx={BASE_STYLES} _selected={SELECTED_STYLES} onClick={handleClickAssets} data-testid="assets-tab">
|
|
||||||
{t('gallery.assets')}
|
|
||||||
</Tab>
|
|
||||||
<Spacer />
|
|
||||||
<Box position="relative">
|
|
||||||
<IconButton
|
|
||||||
w="full"
|
|
||||||
h="full"
|
|
||||||
onClick={searchDisclosure.onToggle}
|
|
||||||
tooltip={`${t('gallery.displaySearch')}`}
|
|
||||||
aria-label={t('gallery.displaySearch')}
|
|
||||||
icon={<PiMagnifyingGlassBold />}
|
|
||||||
variant="link"
|
|
||||||
/>
|
|
||||||
{searchTerm && (
|
|
||||||
<Box
|
|
||||||
position="absolute"
|
|
||||||
w={2}
|
|
||||||
h={2}
|
|
||||||
bg="invokeBlue.300"
|
|
||||||
borderRadius="full"
|
|
||||||
insetBlockStart={2}
|
|
||||||
insetInlineEnd={2}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Box>
|
|
||||||
</TabList>
|
|
||||||
</Tabs>
|
|
||||||
<Box w="full">
|
|
||||||
<Collapse in={searchDisclosure.isOpen} style={COLLAPSE_STYLES}>
|
|
||||||
<Box w="full" pt={2}>
|
|
||||||
<GallerySearch />
|
|
||||||
</Box>
|
|
||||||
</Collapse>
|
|
||||||
</Box>
|
|
||||||
<GalleryImageGrid />
|
|
||||||
<GalleryPagination />
|
|
||||||
</Flex>
|
|
||||||
</Panel>
|
</Panel>
|
||||||
</PanelGroup>
|
</PanelGroup>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
@ -4,7 +4,7 @@ import { selectListImagesQueryArgs } from 'features/gallery/store/gallerySelecto
|
|||||||
import { searchTermChanged } from 'features/gallery/store/gallerySlice';
|
import { searchTermChanged } from 'features/gallery/store/gallerySlice';
|
||||||
import { debounce } from 'lodash-es';
|
import { debounce } from 'lodash-es';
|
||||||
import type { ChangeEvent } from 'react';
|
import type { ChangeEvent } from 'react';
|
||||||
import { useCallback, useMemo, useState } from 'react';
|
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { PiXBold } from 'react-icons/pi';
|
import { PiXBold } from 'react-icons/pi';
|
||||||
import { useListImagesQuery } from 'services/api/endpoints/images';
|
import { useListImagesQuery } from 'services/api/endpoints/images';
|
||||||
@ -37,6 +37,10 @@ export const GallerySearch = () => {
|
|||||||
dispatch(searchTermChanged(''));
|
dispatch(searchTermChanged(''));
|
||||||
}, [dispatch]);
|
}, [dispatch]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setSearchTermInput(searchTerm);
|
||||||
|
}, [searchTerm]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<InputGroup>
|
<InputGroup>
|
||||||
<Input
|
<Input
|
||||||
|
Loading…
Reference in New Issue
Block a user