mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
lint fix
This commit is contained in:
parent
c009f46b00
commit
e06c43adc8
@ -4,6 +4,7 @@ import { appSocketConnected, socketConnected } from 'services/events/actions';
|
|||||||
import { receivedPageOfImages } from 'services/thunks/image';
|
import { receivedPageOfImages } from 'services/thunks/image';
|
||||||
import { receivedModels } from 'services/thunks/model';
|
import { receivedModels } from 'services/thunks/model';
|
||||||
import { receivedOpenAPISchema } from 'services/thunks/schema';
|
import { receivedOpenAPISchema } from 'services/thunks/schema';
|
||||||
|
import { receivedBoards } from '../../../../../../services/thunks/board';
|
||||||
|
|
||||||
const moduleLog = log.child({ namespace: 'socketio' });
|
const moduleLog = log.child({ namespace: 'socketio' });
|
||||||
|
|
||||||
@ -19,6 +20,8 @@ export const addSocketConnectedEventListener = () => {
|
|||||||
|
|
||||||
const { disabledTabs } = config;
|
const { disabledTabs } = config;
|
||||||
|
|
||||||
|
dispatch(receivedBoards());
|
||||||
|
|
||||||
if (!images.ids.length) {
|
if (!images.ids.length) {
|
||||||
dispatch(receivedPageOfImages());
|
dispatch(receivedPageOfImages());
|
||||||
}
|
}
|
||||||
|
@ -22,6 +22,7 @@ import uiReducer from 'features/ui/store/uiSlice';
|
|||||||
import hotkeysReducer from 'features/ui/store/hotkeysSlice';
|
import hotkeysReducer from 'features/ui/store/hotkeysSlice';
|
||||||
import modelsReducer from 'features/system/store/modelSlice';
|
import modelsReducer from 'features/system/store/modelSlice';
|
||||||
import nodesReducer from 'features/nodes/store/nodesSlice';
|
import nodesReducer from 'features/nodes/store/nodesSlice';
|
||||||
|
import boardsReducer from 'features/gallery/store/boardSlice';
|
||||||
|
|
||||||
import { listenerMiddleware } from './middleware/listenerMiddleware';
|
import { listenerMiddleware } from './middleware/listenerMiddleware';
|
||||||
|
|
||||||
@ -47,6 +48,7 @@ const allReducers = {
|
|||||||
hotkeys: hotkeysReducer,
|
hotkeys: hotkeysReducer,
|
||||||
images: imagesReducer,
|
images: imagesReducer,
|
||||||
controlNet: controlNetReducer,
|
controlNet: controlNetReducer,
|
||||||
|
boards: boardsReducer,
|
||||||
// session: sessionReducer,
|
// session: sessionReducer,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -65,6 +67,7 @@ const rememberedKeys: (keyof typeof allReducers)[] = [
|
|||||||
'system',
|
'system',
|
||||||
'ui',
|
'ui',
|
||||||
'controlNet',
|
'controlNet',
|
||||||
|
'boards',
|
||||||
// 'hotkeys',
|
// 'hotkeys',
|
||||||
// 'config',
|
// 'config',
|
||||||
];
|
];
|
||||||
|
@ -0,0 +1,101 @@
|
|||||||
|
import { Box, Image, MenuItem, MenuList, Text } from '@chakra-ui/react';
|
||||||
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
|
import { memo, useCallback, useState } from 'react';
|
||||||
|
import { FaImage } from 'react-icons/fa';
|
||||||
|
import { ContextMenu } from 'chakra-ui-contextmenu';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { ExternalLinkIcon } from '@chakra-ui/icons';
|
||||||
|
import { useAppToaster } from 'app/components/Toaster';
|
||||||
|
import { BoardRecord } from 'services/api';
|
||||||
|
import { EntityId, createSelector } from '@reduxjs/toolkit';
|
||||||
|
import {
|
||||||
|
selectFilteredImagesIds,
|
||||||
|
selectImagesById,
|
||||||
|
} from '../store/imagesSlice';
|
||||||
|
import { RootState } from '../../../app/store/store';
|
||||||
|
import { defaultSelectorOptions } from '../../../app/store/util/defaultMemoizeOptions';
|
||||||
|
import { useSelector } from 'react-redux';
|
||||||
|
|
||||||
|
interface HoverableBoardProps {
|
||||||
|
board: BoardRecord;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gallery image component with delete/use all/use seed buttons on hover.
|
||||||
|
*/
|
||||||
|
const HoverableBoard = memo(({ board }: HoverableBoardProps) => {
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
|
const { board_name, board_id, cover_image_name } = board;
|
||||||
|
|
||||||
|
const coverImage = useAppSelector((state) =>
|
||||||
|
selectImagesById(state, cover_image_name as EntityId)
|
||||||
|
);
|
||||||
|
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const handleSelectBoard = useCallback(() => {
|
||||||
|
// dispatch(imageSelected(board_id));
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box sx={{ w: 'full', h: 'full', touchAction: 'none' }}>
|
||||||
|
<ContextMenu<HTMLDivElement>
|
||||||
|
menuProps={{ size: 'sm', isLazy: true }}
|
||||||
|
renderMenu={() => (
|
||||||
|
<MenuList sx={{ visibility: 'visible !important' }}>
|
||||||
|
<MenuItem
|
||||||
|
icon={<ExternalLinkIcon />}
|
||||||
|
// onClickCapture={handleOpenInNewTab}
|
||||||
|
>
|
||||||
|
Sample Menu Item
|
||||||
|
</MenuItem>
|
||||||
|
</MenuList>
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{(ref) => (
|
||||||
|
<Box
|
||||||
|
position="relative"
|
||||||
|
key={board_id}
|
||||||
|
userSelect="none"
|
||||||
|
onClick={handleSelectBoard}
|
||||||
|
ref={ref}
|
||||||
|
sx={{
|
||||||
|
display: 'flex',
|
||||||
|
flexDir: 'column',
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
w: 'full',
|
||||||
|
h: 'full',
|
||||||
|
transition: 'transform 0.2s ease-out',
|
||||||
|
aspectRatio: '1/1',
|
||||||
|
cursor: 'pointer',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Image
|
||||||
|
loading="lazy"
|
||||||
|
// objectFit={
|
||||||
|
// shouldUseSingleGalleryColumn ? 'contain' : galleryImageObjectFit
|
||||||
|
// }
|
||||||
|
draggable={false}
|
||||||
|
rounded="md"
|
||||||
|
src={coverImage ? coverImage.thumbnail_url : undefined}
|
||||||
|
fallback={<FaImage />}
|
||||||
|
sx={{
|
||||||
|
width: '100%',
|
||||||
|
height: '100%',
|
||||||
|
maxWidth: '100%',
|
||||||
|
maxHeight: '100%',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Text textAlign="center">{board_name}</Text>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</ContextMenu>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
HoverableBoard.displayName = 'HoverableBoard';
|
||||||
|
|
||||||
|
export default HoverableBoard;
|
@ -2,7 +2,14 @@ import { Box, Flex, Icon, Image, MenuItem, MenuList } from '@chakra-ui/react';
|
|||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import { imageSelected } from 'features/gallery/store/gallerySlice';
|
import { imageSelected } from 'features/gallery/store/gallerySlice';
|
||||||
import { memo, useCallback, useContext, useState } from 'react';
|
import { memo, useCallback, useContext, useState } from 'react';
|
||||||
import { FaCheck, FaExpand, FaImage, FaShare, FaTrash } from 'react-icons/fa';
|
import {
|
||||||
|
FaCheck,
|
||||||
|
FaExpand,
|
||||||
|
FaFolder,
|
||||||
|
FaImage,
|
||||||
|
FaShare,
|
||||||
|
FaTrash,
|
||||||
|
} from 'react-icons/fa';
|
||||||
import { ContextMenu } from 'chakra-ui-contextmenu';
|
import { ContextMenu } from 'chakra-ui-contextmenu';
|
||||||
import {
|
import {
|
||||||
resizeAndScaleCanvas,
|
resizeAndScaleCanvas,
|
||||||
@ -168,6 +175,10 @@ const HoverableImage = memo((props: HoverableImageProps) => {
|
|||||||
// dispatch(setIsLightboxOpen(true));
|
// dispatch(setIsLightboxOpen(true));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleAddToFolder = useCallback(() => {
|
||||||
|
// dispatch(addImageToFolder(image));
|
||||||
|
}, []);
|
||||||
|
|
||||||
const handleOpenInNewTab = () => {
|
const handleOpenInNewTab = () => {
|
||||||
window.open(image.image_url, '_blank');
|
window.open(image.image_url, '_blank');
|
||||||
};
|
};
|
||||||
@ -244,6 +255,9 @@ const HoverableImage = memo((props: HoverableImageProps) => {
|
|||||||
{t('parameters.sendToUnifiedCanvas')}
|
{t('parameters.sendToUnifiedCanvas')}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
)}
|
)}
|
||||||
|
<MenuItem icon={<FaFolder />} onClickCapture={handleAddToFolder}>
|
||||||
|
Add to Folder
|
||||||
|
</MenuItem>
|
||||||
<MenuItem
|
<MenuItem
|
||||||
sx={{ color: 'error.300' }}
|
sx={{ color: 'error.300' }}
|
||||||
icon={<FaTrash />}
|
icon={<FaTrash />}
|
||||||
|
@ -20,6 +20,7 @@ import {
|
|||||||
setGalleryImageObjectFit,
|
setGalleryImageObjectFit,
|
||||||
setShouldAutoSwitchToNewImages,
|
setShouldAutoSwitchToNewImages,
|
||||||
setShouldUseSingleGalleryColumn,
|
setShouldUseSingleGalleryColumn,
|
||||||
|
setGalleryView,
|
||||||
} from 'features/gallery/store/gallerySlice';
|
} from 'features/gallery/store/gallerySlice';
|
||||||
import { togglePinGalleryPanel } from 'features/ui/store/uiSlice';
|
import { togglePinGalleryPanel } from 'features/ui/store/uiSlice';
|
||||||
import { useOverlayScrollbars } from 'overlayscrollbars-react';
|
import { useOverlayScrollbars } from 'overlayscrollbars-react';
|
||||||
@ -36,7 +37,7 @@ import {
|
|||||||
} from 'react';
|
} from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { BsPinAngle, BsPinAngleFill } from 'react-icons/bs';
|
import { BsPinAngle, BsPinAngleFill } from 'react-icons/bs';
|
||||||
import { FaImage, FaServer, FaWrench } from 'react-icons/fa';
|
import { FaFolder, FaImage, FaPlus, FaServer, FaWrench } from 'react-icons/fa';
|
||||||
import { MdPhotoLibrary } from 'react-icons/md';
|
import { MdPhotoLibrary } from 'react-icons/md';
|
||||||
import HoverableImage from './HoverableImage';
|
import HoverableImage from './HoverableImage';
|
||||||
|
|
||||||
@ -53,22 +54,39 @@ import {
|
|||||||
selectImagesAll,
|
selectImagesAll,
|
||||||
} from '../store/imagesSlice';
|
} from '../store/imagesSlice';
|
||||||
import { receivedPageOfImages } from 'services/thunks/image';
|
import { receivedPageOfImages } from 'services/thunks/image';
|
||||||
|
import { boardSelector } from '../store/boardSelectors';
|
||||||
|
import { BoardRecord, ImageDTO } from '../../../services/api';
|
||||||
|
import { isBoardRecord, isImageDTO } from '../../../services/types/guards';
|
||||||
|
import HoverableBoard from './HoverableBoard';
|
||||||
|
import IAIInput from '../../../common/components/IAIInput';
|
||||||
|
import { boardCreated } from '../../../services/thunks/board';
|
||||||
|
|
||||||
const categorySelector = createSelector(
|
const itemSelector = createSelector(
|
||||||
[(state: RootState) => state],
|
[(state: RootState) => state],
|
||||||
(state) => {
|
(state) => {
|
||||||
const { images } = state;
|
const { images, boards, gallery } = state;
|
||||||
|
|
||||||
|
let items: Array<ImageDTO | BoardRecord> = [];
|
||||||
|
let areMoreAvailable = false;
|
||||||
|
let isLoading = true;
|
||||||
|
|
||||||
|
if (gallery.galleryView === 'images' || gallery.galleryView === 'assets') {
|
||||||
const { categories } = images;
|
const { categories } = images;
|
||||||
|
|
||||||
const allImages = selectImagesAll(state);
|
const allImages = selectImagesAll(state);
|
||||||
const filteredImages = allImages.filter((i) =>
|
items = allImages.filter((i) => categories.includes(i.image_category));
|
||||||
categories.includes(i.image_category)
|
areMoreAvailable = items.length < images.total;
|
||||||
);
|
isLoading = images.isLoading;
|
||||||
|
} else if (gallery.galleryView === 'boards') {
|
||||||
|
items = Object.values(boards.entities) as BoardRecord[];
|
||||||
|
areMoreAvailable = items.length < boards.total;
|
||||||
|
isLoading = boards.isLoading;
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
images: filteredImages,
|
items,
|
||||||
isLoading: images.isLoading,
|
isLoading,
|
||||||
areMoreImagesAvailable: filteredImages.length < images.total,
|
areMoreAvailable,
|
||||||
categories: images.categories,
|
categories: images.categories,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
@ -76,18 +94,21 @@ const categorySelector = createSelector(
|
|||||||
);
|
);
|
||||||
|
|
||||||
const mainSelector = createSelector(
|
const mainSelector = createSelector(
|
||||||
[gallerySelector, uiSelector],
|
[gallerySelector, uiSelector, boardSelector],
|
||||||
(gallery, ui) => {
|
(gallery, ui, boardState) => {
|
||||||
const {
|
const {
|
||||||
galleryImageMinimumWidth,
|
galleryImageMinimumWidth,
|
||||||
galleryImageObjectFit,
|
galleryImageObjectFit,
|
||||||
shouldAutoSwitchToNewImages,
|
shouldAutoSwitchToNewImages,
|
||||||
shouldUseSingleGalleryColumn,
|
shouldUseSingleGalleryColumn,
|
||||||
selectedImage,
|
selectedImage,
|
||||||
|
galleryView,
|
||||||
} = gallery;
|
} = gallery;
|
||||||
|
|
||||||
const { shouldPinGallery } = ui;
|
const { shouldPinGallery } = ui;
|
||||||
|
|
||||||
|
const { entities: boards } = boardState;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
shouldPinGallery,
|
shouldPinGallery,
|
||||||
galleryImageMinimumWidth,
|
galleryImageMinimumWidth,
|
||||||
@ -95,6 +116,8 @@ const mainSelector = createSelector(
|
|||||||
shouldAutoSwitchToNewImages,
|
shouldAutoSwitchToNewImages,
|
||||||
shouldUseSingleGalleryColumn,
|
shouldUseSingleGalleryColumn,
|
||||||
selectedImage,
|
selectedImage,
|
||||||
|
galleryView,
|
||||||
|
boards,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
defaultSelectorOptions
|
defaultSelectorOptions
|
||||||
@ -126,21 +149,23 @@ const ImageGalleryContent = () => {
|
|||||||
shouldAutoSwitchToNewImages,
|
shouldAutoSwitchToNewImages,
|
||||||
shouldUseSingleGalleryColumn,
|
shouldUseSingleGalleryColumn,
|
||||||
selectedImage,
|
selectedImage,
|
||||||
|
galleryView,
|
||||||
|
boards,
|
||||||
} = useAppSelector(mainSelector);
|
} = useAppSelector(mainSelector);
|
||||||
|
|
||||||
const { images, areMoreImagesAvailable, isLoading, categories } =
|
const { items, areMoreAvailable, isLoading, categories } =
|
||||||
useAppSelector(categorySelector);
|
useAppSelector(itemSelector);
|
||||||
|
|
||||||
const handleLoadMoreImages = useCallback(() => {
|
const handleLoadMoreImages = useCallback(() => {
|
||||||
dispatch(receivedPageOfImages());
|
dispatch(receivedPageOfImages());
|
||||||
}, [dispatch]);
|
}, [dispatch]);
|
||||||
|
|
||||||
const handleEndReached = useMemo(() => {
|
const handleEndReached = useMemo(() => {
|
||||||
if (areMoreImagesAvailable && !isLoading) {
|
if (areMoreAvailable && !isLoading) {
|
||||||
return handleLoadMoreImages;
|
return handleLoadMoreImages;
|
||||||
}
|
}
|
||||||
return undefined;
|
return undefined;
|
||||||
}, [areMoreImagesAvailable, handleLoadMoreImages, isLoading]);
|
}, [areMoreAvailable, handleLoadMoreImages, isLoading]);
|
||||||
|
|
||||||
const handleChangeGalleryImageMinimumWidth = (v: number) => {
|
const handleChangeGalleryImageMinimumWidth = (v: number) => {
|
||||||
dispatch(setGalleryImageMinimumWidth(v));
|
dispatch(setGalleryImageMinimumWidth(v));
|
||||||
@ -172,12 +197,24 @@ const ImageGalleryContent = () => {
|
|||||||
|
|
||||||
const handleClickImagesCategory = useCallback(() => {
|
const handleClickImagesCategory = useCallback(() => {
|
||||||
dispatch(imageCategoriesChanged(IMAGE_CATEGORIES));
|
dispatch(imageCategoriesChanged(IMAGE_CATEGORIES));
|
||||||
|
dispatch(setGalleryView('images'));
|
||||||
}, [dispatch]);
|
}, [dispatch]);
|
||||||
|
|
||||||
const handleClickAssetsCategory = useCallback(() => {
|
const handleClickAssetsCategory = useCallback(() => {
|
||||||
dispatch(imageCategoriesChanged(ASSETS_CATEGORIES));
|
dispatch(imageCategoriesChanged(ASSETS_CATEGORIES));
|
||||||
|
dispatch(setGalleryView('assets'));
|
||||||
}, [dispatch]);
|
}, [dispatch]);
|
||||||
|
|
||||||
|
const handleClickBoardsView = useCallback(() => {
|
||||||
|
dispatch(setGalleryView('boards'));
|
||||||
|
}, [dispatch]);
|
||||||
|
|
||||||
|
const [newBoardName, setNewBoardName] = useState('');
|
||||||
|
|
||||||
|
const handleCreateNewBoard = () => {
|
||||||
|
dispatch(boardCreated({ requestBody: newBoardName }));
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex
|
<Flex
|
||||||
sx={{
|
sx={{
|
||||||
@ -198,7 +235,7 @@ const ImageGalleryContent = () => {
|
|||||||
tooltip={t('gallery.images')}
|
tooltip={t('gallery.images')}
|
||||||
aria-label={t('gallery.images')}
|
aria-label={t('gallery.images')}
|
||||||
onClick={handleClickImagesCategory}
|
onClick={handleClickImagesCategory}
|
||||||
isChecked={categories === IMAGE_CATEGORIES}
|
isChecked={galleryView === 'images'}
|
||||||
size="sm"
|
size="sm"
|
||||||
icon={<FaImage />}
|
icon={<FaImage />}
|
||||||
/>
|
/>
|
||||||
@ -206,12 +243,47 @@ const ImageGalleryContent = () => {
|
|||||||
tooltip={t('gallery.assets')}
|
tooltip={t('gallery.assets')}
|
||||||
aria-label={t('gallery.assets')}
|
aria-label={t('gallery.assets')}
|
||||||
onClick={handleClickAssetsCategory}
|
onClick={handleClickAssetsCategory}
|
||||||
isChecked={categories === ASSETS_CATEGORIES}
|
isChecked={galleryView === 'assets'}
|
||||||
size="sm"
|
size="sm"
|
||||||
icon={<FaServer />}
|
icon={<FaServer />}
|
||||||
/>
|
/>
|
||||||
|
<IAIIconButton
|
||||||
|
tooltip={t('gallery.boards')}
|
||||||
|
aria-label={t('gallery.boards')}
|
||||||
|
onClick={handleClickBoardsView}
|
||||||
|
isChecked={galleryView === 'boards'}
|
||||||
|
size="sm"
|
||||||
|
icon={<FaFolder />}
|
||||||
|
/>
|
||||||
</ButtonGroup>
|
</ButtonGroup>
|
||||||
<Flex gap={2}>
|
<Flex gap={2}>
|
||||||
|
<IAIPopover
|
||||||
|
triggerComponent={
|
||||||
|
<IAIIconButton
|
||||||
|
tooltip="Add Board"
|
||||||
|
aria-label="Add Board"
|
||||||
|
size="sm"
|
||||||
|
icon={<FaPlus />}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Flex direction="column" gap={2}>
|
||||||
|
<IAIInput
|
||||||
|
label="Board Name"
|
||||||
|
placeholder="Board Name"
|
||||||
|
value={newBoardName}
|
||||||
|
onChange={(e) => setNewBoardName(e.target.value)}
|
||||||
|
/>
|
||||||
|
<IAIButton
|
||||||
|
size="sm"
|
||||||
|
onClick={handleCreateNewBoard}
|
||||||
|
disabled={true}
|
||||||
|
isLoading={false}
|
||||||
|
>
|
||||||
|
Create
|
||||||
|
</IAIButton>
|
||||||
|
</Flex>
|
||||||
|
</IAIPopover>
|
||||||
<IAIPopover
|
<IAIPopover
|
||||||
triggerComponent={
|
triggerComponent={
|
||||||
<IAIIconButton
|
<IAIIconButton
|
||||||
@ -271,57 +343,75 @@ const ImageGalleryContent = () => {
|
|||||||
</Flex>
|
</Flex>
|
||||||
</Flex>
|
</Flex>
|
||||||
<Flex direction="column" gap={2} h="full">
|
<Flex direction="column" gap={2} h="full">
|
||||||
{images.length || areMoreImagesAvailable ? (
|
{items.length || areMoreAvailable ? (
|
||||||
<>
|
<>
|
||||||
<Box ref={rootRef} data-overlayscrollbars="" h="100%">
|
<Box ref={rootRef} data-overlayscrollbars="" h="100%">
|
||||||
{shouldUseSingleGalleryColumn ? (
|
{shouldUseSingleGalleryColumn ? (
|
||||||
<Virtuoso
|
<Virtuoso
|
||||||
style={{ height: '100%' }}
|
style={{ height: '100%' }}
|
||||||
data={images}
|
data={items}
|
||||||
endReached={handleEndReached}
|
endReached={handleEndReached}
|
||||||
scrollerRef={(ref) => setScrollerRef(ref)}
|
scrollerRef={(ref) => setScrollerRef(ref)}
|
||||||
itemContent={(index, image) => (
|
itemContent={(index, item) => {
|
||||||
|
if (isImageDTO(item)) {
|
||||||
|
return (
|
||||||
<Flex sx={{ pb: 2 }}>
|
<Flex sx={{ pb: 2 }}>
|
||||||
<HoverableImage
|
<HoverableImage
|
||||||
key={`${image.image_name}-${image.thumbnail_url}`}
|
key={`${item.image_name}-${item.thumbnail_url}`}
|
||||||
image={image}
|
image={item}
|
||||||
isSelected={
|
isSelected={
|
||||||
selectedImage?.image_name === image?.image_name
|
selectedImage?.image_name === item?.image_name
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</Flex>
|
</Flex>
|
||||||
)}
|
);
|
||||||
|
} else if (isBoardRecord(item)) {
|
||||||
|
return (
|
||||||
|
<Flex sx={{ pb: 2 }}>
|
||||||
|
<HoverableBoard key={item.board_id} board={item} />
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<VirtuosoGrid
|
<VirtuosoGrid
|
||||||
style={{ height: '100%' }}
|
style={{ height: '100%' }}
|
||||||
data={images}
|
data={items}
|
||||||
endReached={handleEndReached}
|
endReached={handleEndReached}
|
||||||
components={{
|
components={{
|
||||||
Item: ItemContainer,
|
Item: ItemContainer,
|
||||||
List: ListContainer,
|
List: ListContainer,
|
||||||
}}
|
}}
|
||||||
scrollerRef={setScroller}
|
scrollerRef={setScroller}
|
||||||
itemContent={(index, image) => (
|
itemContent={(index, item) => {
|
||||||
|
if (isImageDTO(item)) {
|
||||||
|
return (
|
||||||
<HoverableImage
|
<HoverableImage
|
||||||
key={`${image.image_name}-${image.thumbnail_url}`}
|
key={`${item.image_name}-${item.thumbnail_url}`}
|
||||||
image={image}
|
image={item}
|
||||||
isSelected={
|
isSelected={
|
||||||
selectedImage?.image_name === image?.image_name
|
selectedImage?.image_name === item?.image_name
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
)}
|
);
|
||||||
|
} else if (isBoardRecord(item)) {
|
||||||
|
return (
|
||||||
|
<HoverableBoard key={item.board_id} board={item} />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
<IAIButton
|
<IAIButton
|
||||||
onClick={handleLoadMoreImages}
|
onClick={handleLoadMoreImages}
|
||||||
isDisabled={!areMoreImagesAvailable}
|
isDisabled={!areMoreAvailable}
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
loadingText="Loading"
|
loadingText="Loading"
|
||||||
flexShrink={0}
|
flexShrink={0}
|
||||||
>
|
>
|
||||||
{areMoreImagesAvailable
|
{areMoreAvailable
|
||||||
? t('gallery.loadMore')
|
? t('gallery.loadMore')
|
||||||
: t('gallery.allImagesLoaded')}
|
: t('gallery.allImagesLoaded')}
|
||||||
</IAIButton>
|
</IAIButton>
|
||||||
|
@ -0,0 +1,3 @@
|
|||||||
|
import { RootState } from 'app/store/store';
|
||||||
|
|
||||||
|
export const boardSelector = (state: RootState) => state.boards;
|
@ -0,0 +1,77 @@
|
|||||||
|
import {
|
||||||
|
PayloadAction,
|
||||||
|
Update,
|
||||||
|
createEntityAdapter,
|
||||||
|
createSlice,
|
||||||
|
} from '@reduxjs/toolkit';
|
||||||
|
import { RootState } from 'app/store/store';
|
||||||
|
import { BoardRecord } from 'services/api';
|
||||||
|
import { dateComparator } from 'common/util/dateComparator';
|
||||||
|
import { receivedBoards } from '../../../services/thunks/board';
|
||||||
|
|
||||||
|
export const boardsAdapter = createEntityAdapter<BoardRecord>({
|
||||||
|
selectId: (board) => board.board_id,
|
||||||
|
sortComparer: (a, b) => dateComparator(b.updated_at, a.updated_at),
|
||||||
|
});
|
||||||
|
|
||||||
|
type AdditionalBoardsState = {
|
||||||
|
offset: number;
|
||||||
|
limit: number;
|
||||||
|
total: number;
|
||||||
|
isLoading: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const initialBoardsState =
|
||||||
|
boardsAdapter.getInitialState<AdditionalBoardsState>({
|
||||||
|
offset: 0,
|
||||||
|
limit: 0,
|
||||||
|
total: 0,
|
||||||
|
isLoading: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
export type BoardsState = typeof initialBoardsState;
|
||||||
|
|
||||||
|
const boardsSlice = createSlice({
|
||||||
|
name: 'boards',
|
||||||
|
initialState: initialBoardsState,
|
||||||
|
reducers: {
|
||||||
|
boardUpserted: (state, action: PayloadAction<BoardRecord>) => {
|
||||||
|
boardsAdapter.upsertOne(state, action.payload);
|
||||||
|
},
|
||||||
|
boardUpdatedOne: (state, action: PayloadAction<Update<BoardRecord>>) => {
|
||||||
|
boardsAdapter.updateOne(state, action.payload);
|
||||||
|
},
|
||||||
|
boardRemoved: (state, action: PayloadAction<string>) => {
|
||||||
|
boardsAdapter.removeOne(state, action.payload);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
extraReducers: (builder) => {
|
||||||
|
builder.addCase(receivedBoards.pending, (state) => {
|
||||||
|
state.isLoading = true;
|
||||||
|
});
|
||||||
|
builder.addCase(receivedBoards.rejected, (state) => {
|
||||||
|
state.isLoading = false;
|
||||||
|
});
|
||||||
|
builder.addCase(receivedBoards.fulfilled, (state, action) => {
|
||||||
|
state.isLoading = false;
|
||||||
|
const { items, offset, limit, total } = action.payload;
|
||||||
|
state.offset = offset;
|
||||||
|
state.limit = limit;
|
||||||
|
state.total = total;
|
||||||
|
boardsAdapter.upsertMany(state, items);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export const {
|
||||||
|
selectAll: selectBoardsAll,
|
||||||
|
selectById: selectBoardsById,
|
||||||
|
selectEntities: selectBoardsEntities,
|
||||||
|
selectIds: selectBoardsIds,
|
||||||
|
selectTotal: selectBoardsTotal,
|
||||||
|
} = boardsAdapter.getSelectors<RootState>((state) => state.boards);
|
||||||
|
|
||||||
|
export const { boardUpserted, boardUpdatedOne, boardRemoved } =
|
||||||
|
boardsSlice.actions;
|
||||||
|
|
||||||
|
export default boardsSlice.reducer;
|
@ -12,6 +12,7 @@ export interface GalleryState {
|
|||||||
galleryImageObjectFit: GalleryImageObjectFitType;
|
galleryImageObjectFit: GalleryImageObjectFitType;
|
||||||
shouldAutoSwitchToNewImages: boolean;
|
shouldAutoSwitchToNewImages: boolean;
|
||||||
shouldUseSingleGalleryColumn: boolean;
|
shouldUseSingleGalleryColumn: boolean;
|
||||||
|
galleryView: 'images' | 'assets' | 'boards';
|
||||||
}
|
}
|
||||||
|
|
||||||
export const initialGalleryState: GalleryState = {
|
export const initialGalleryState: GalleryState = {
|
||||||
@ -19,6 +20,7 @@ export const initialGalleryState: GalleryState = {
|
|||||||
galleryImageObjectFit: 'cover',
|
galleryImageObjectFit: 'cover',
|
||||||
shouldAutoSwitchToNewImages: true,
|
shouldAutoSwitchToNewImages: true,
|
||||||
shouldUseSingleGalleryColumn: false,
|
shouldUseSingleGalleryColumn: false,
|
||||||
|
galleryView: 'images',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const gallerySlice = createSlice({
|
export const gallerySlice = createSlice({
|
||||||
@ -48,6 +50,12 @@ export const gallerySlice = createSlice({
|
|||||||
) => {
|
) => {
|
||||||
state.shouldUseSingleGalleryColumn = action.payload;
|
state.shouldUseSingleGalleryColumn = action.payload;
|
||||||
},
|
},
|
||||||
|
setGalleryView: (
|
||||||
|
state,
|
||||||
|
action: PayloadAction<'images' | 'assets' | 'boards'>
|
||||||
|
) => {
|
||||||
|
state.galleryView = action.payload;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
extraReducers: (builder) => {
|
extraReducers: (builder) => {
|
||||||
builder.addCase(imageUpserted, (state, action) => {
|
builder.addCase(imageUpserted, (state, action) => {
|
||||||
@ -75,6 +83,7 @@ export const {
|
|||||||
setGalleryImageObjectFit,
|
setGalleryImageObjectFit,
|
||||||
setShouldAutoSwitchToNewImages,
|
setShouldAutoSwitchToNewImages,
|
||||||
setShouldUseSingleGalleryColumn,
|
setShouldUseSingleGalleryColumn,
|
||||||
|
setGalleryView,
|
||||||
} = gallerySlice.actions;
|
} = gallerySlice.actions;
|
||||||
|
|
||||||
export default gallerySlice.reducer;
|
export default gallerySlice.reducer;
|
||||||
|
@ -154,3 +154,16 @@ export const selectFilteredImagesIds = createSelector(
|
|||||||
.map((i) => i.image_name);
|
.map((i) => i.image_name);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// export const selectImageById = createSelector(
|
||||||
|
// (state: RootState, imageId) => state,
|
||||||
|
// (state) => {
|
||||||
|
// const {
|
||||||
|
// images: { categories },
|
||||||
|
// } = state;
|
||||||
|
|
||||||
|
// return selectImagesAll(state)
|
||||||
|
// .filter((i) => categories.includes(i.image_category))
|
||||||
|
// .map((i) => i.image_name);
|
||||||
|
// }
|
||||||
|
// );
|
||||||
|
23
invokeai/frontend/web/src/services/thunks/board.ts
Normal file
23
invokeai/frontend/web/src/services/thunks/board.ts
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import { createAppAsyncThunk } from '../../app/store/storeUtils';
|
||||||
|
import { BoardsService } from '../api';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* `BoardsService.listBoards()` thunk
|
||||||
|
*/
|
||||||
|
export const receivedBoards = createAppAsyncThunk(
|
||||||
|
'api/receivedBoards',
|
||||||
|
async (_, { getState }) => {
|
||||||
|
const response = await BoardsService.listBoards({});
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
type BoardCreatedArg = Parameters<(typeof BoardsService)['createBoard']>[0];
|
||||||
|
|
||||||
|
export const boardCreated = createAppAsyncThunk(
|
||||||
|
'api/boardCreated',
|
||||||
|
async (arg: BoardCreatedArg) => {
|
||||||
|
const response = await BoardsService.createBoard(arg);
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
);
|
@ -11,6 +11,7 @@ import {
|
|||||||
LatentsOutput,
|
LatentsOutput,
|
||||||
ResourceOrigin,
|
ResourceOrigin,
|
||||||
ImageDTO,
|
ImageDTO,
|
||||||
|
BoardRecord,
|
||||||
} from 'services/api';
|
} from 'services/api';
|
||||||
|
|
||||||
export const isImageDTO = (obj: unknown): obj is ImageDTO => {
|
export const isImageDTO = (obj: unknown): obj is ImageDTO => {
|
||||||
@ -29,6 +30,16 @@ export const isImageDTO = (obj: unknown): obj is ImageDTO => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const isBoardRecord = (obj: unknown): obj is BoardRecord => {
|
||||||
|
return (
|
||||||
|
isObject(obj) &&
|
||||||
|
'board_id' in obj &&
|
||||||
|
isString(obj?.board_id) &&
|
||||||
|
'board_name' in obj &&
|
||||||
|
isString(obj?.board_name)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export const isImageOutput = (
|
export const isImageOutput = (
|
||||||
output: GraphExecutionState['results'][string]
|
output: GraphExecutionState['results'][string]
|
||||||
): output is ImageOutput => output.type === 'image_output';
|
): output is ImageOutput => output.type === 'image_output';
|
||||||
|
Loading…
Reference in New Issue
Block a user