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 { receivedModels } from 'services/thunks/model';
|
||||
import { receivedOpenAPISchema } from 'services/thunks/schema';
|
||||
import { receivedBoards } from '../../../../../../services/thunks/board';
|
||||
|
||||
const moduleLog = log.child({ namespace: 'socketio' });
|
||||
|
||||
@ -19,6 +20,8 @@ export const addSocketConnectedEventListener = () => {
|
||||
|
||||
const { disabledTabs } = config;
|
||||
|
||||
dispatch(receivedBoards());
|
||||
|
||||
if (!images.ids.length) {
|
||||
dispatch(receivedPageOfImages());
|
||||
}
|
||||
|
@ -22,6 +22,7 @@ import uiReducer from 'features/ui/store/uiSlice';
|
||||
import hotkeysReducer from 'features/ui/store/hotkeysSlice';
|
||||
import modelsReducer from 'features/system/store/modelSlice';
|
||||
import nodesReducer from 'features/nodes/store/nodesSlice';
|
||||
import boardsReducer from 'features/gallery/store/boardSlice';
|
||||
|
||||
import { listenerMiddleware } from './middleware/listenerMiddleware';
|
||||
|
||||
@ -47,6 +48,7 @@ const allReducers = {
|
||||
hotkeys: hotkeysReducer,
|
||||
images: imagesReducer,
|
||||
controlNet: controlNetReducer,
|
||||
boards: boardsReducer,
|
||||
// session: sessionReducer,
|
||||
};
|
||||
|
||||
@ -65,6 +67,7 @@ const rememberedKeys: (keyof typeof allReducers)[] = [
|
||||
'system',
|
||||
'ui',
|
||||
'controlNet',
|
||||
'boards',
|
||||
// 'hotkeys',
|
||||
// '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 { imageSelected } from 'features/gallery/store/gallerySlice';
|
||||
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 {
|
||||
resizeAndScaleCanvas,
|
||||
@ -168,6 +175,10 @@ const HoverableImage = memo((props: HoverableImageProps) => {
|
||||
// dispatch(setIsLightboxOpen(true));
|
||||
};
|
||||
|
||||
const handleAddToFolder = useCallback(() => {
|
||||
// dispatch(addImageToFolder(image));
|
||||
}, []);
|
||||
|
||||
const handleOpenInNewTab = () => {
|
||||
window.open(image.image_url, '_blank');
|
||||
};
|
||||
@ -244,6 +255,9 @@ const HoverableImage = memo((props: HoverableImageProps) => {
|
||||
{t('parameters.sendToUnifiedCanvas')}
|
||||
</MenuItem>
|
||||
)}
|
||||
<MenuItem icon={<FaFolder />} onClickCapture={handleAddToFolder}>
|
||||
Add to Folder
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
sx={{ color: 'error.300' }}
|
||||
icon={<FaTrash />}
|
||||
|
@ -20,6 +20,7 @@ import {
|
||||
setGalleryImageObjectFit,
|
||||
setShouldAutoSwitchToNewImages,
|
||||
setShouldUseSingleGalleryColumn,
|
||||
setGalleryView,
|
||||
} from 'features/gallery/store/gallerySlice';
|
||||
import { togglePinGalleryPanel } from 'features/ui/store/uiSlice';
|
||||
import { useOverlayScrollbars } from 'overlayscrollbars-react';
|
||||
@ -36,7 +37,7 @@ import {
|
||||
} from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
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 HoverableImage from './HoverableImage';
|
||||
|
||||
@ -53,22 +54,39 @@ import {
|
||||
selectImagesAll,
|
||||
} from '../store/imagesSlice';
|
||||
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) => {
|
||||
const { images } = state;
|
||||
const { categories } = images;
|
||||
const { images, boards, gallery } = state;
|
||||
|
||||
const allImages = selectImagesAll(state);
|
||||
const filteredImages = allImages.filter((i) =>
|
||||
categories.includes(i.image_category)
|
||||
);
|
||||
let items: Array<ImageDTO | BoardRecord> = [];
|
||||
let areMoreAvailable = false;
|
||||
let isLoading = true;
|
||||
|
||||
if (gallery.galleryView === 'images' || gallery.galleryView === 'assets') {
|
||||
const { categories } = images;
|
||||
|
||||
const allImages = selectImagesAll(state);
|
||||
items = allImages.filter((i) => 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 {
|
||||
images: filteredImages,
|
||||
isLoading: images.isLoading,
|
||||
areMoreImagesAvailable: filteredImages.length < images.total,
|
||||
items,
|
||||
isLoading,
|
||||
areMoreAvailable,
|
||||
categories: images.categories,
|
||||
};
|
||||
},
|
||||
@ -76,18 +94,21 @@ const categorySelector = createSelector(
|
||||
);
|
||||
|
||||
const mainSelector = createSelector(
|
||||
[gallerySelector, uiSelector],
|
||||
(gallery, ui) => {
|
||||
[gallerySelector, uiSelector, boardSelector],
|
||||
(gallery, ui, boardState) => {
|
||||
const {
|
||||
galleryImageMinimumWidth,
|
||||
galleryImageObjectFit,
|
||||
shouldAutoSwitchToNewImages,
|
||||
shouldUseSingleGalleryColumn,
|
||||
selectedImage,
|
||||
galleryView,
|
||||
} = gallery;
|
||||
|
||||
const { shouldPinGallery } = ui;
|
||||
|
||||
const { entities: boards } = boardState;
|
||||
|
||||
return {
|
||||
shouldPinGallery,
|
||||
galleryImageMinimumWidth,
|
||||
@ -95,6 +116,8 @@ const mainSelector = createSelector(
|
||||
shouldAutoSwitchToNewImages,
|
||||
shouldUseSingleGalleryColumn,
|
||||
selectedImage,
|
||||
galleryView,
|
||||
boards,
|
||||
};
|
||||
},
|
||||
defaultSelectorOptions
|
||||
@ -126,21 +149,23 @@ const ImageGalleryContent = () => {
|
||||
shouldAutoSwitchToNewImages,
|
||||
shouldUseSingleGalleryColumn,
|
||||
selectedImage,
|
||||
galleryView,
|
||||
boards,
|
||||
} = useAppSelector(mainSelector);
|
||||
|
||||
const { images, areMoreImagesAvailable, isLoading, categories } =
|
||||
useAppSelector(categorySelector);
|
||||
const { items, areMoreAvailable, isLoading, categories } =
|
||||
useAppSelector(itemSelector);
|
||||
|
||||
const handleLoadMoreImages = useCallback(() => {
|
||||
dispatch(receivedPageOfImages());
|
||||
}, [dispatch]);
|
||||
|
||||
const handleEndReached = useMemo(() => {
|
||||
if (areMoreImagesAvailable && !isLoading) {
|
||||
if (areMoreAvailable && !isLoading) {
|
||||
return handleLoadMoreImages;
|
||||
}
|
||||
return undefined;
|
||||
}, [areMoreImagesAvailable, handleLoadMoreImages, isLoading]);
|
||||
}, [areMoreAvailable, handleLoadMoreImages, isLoading]);
|
||||
|
||||
const handleChangeGalleryImageMinimumWidth = (v: number) => {
|
||||
dispatch(setGalleryImageMinimumWidth(v));
|
||||
@ -172,12 +197,24 @@ const ImageGalleryContent = () => {
|
||||
|
||||
const handleClickImagesCategory = useCallback(() => {
|
||||
dispatch(imageCategoriesChanged(IMAGE_CATEGORIES));
|
||||
dispatch(setGalleryView('images'));
|
||||
}, [dispatch]);
|
||||
|
||||
const handleClickAssetsCategory = useCallback(() => {
|
||||
dispatch(imageCategoriesChanged(ASSETS_CATEGORIES));
|
||||
dispatch(setGalleryView('assets'));
|
||||
}, [dispatch]);
|
||||
|
||||
const handleClickBoardsView = useCallback(() => {
|
||||
dispatch(setGalleryView('boards'));
|
||||
}, [dispatch]);
|
||||
|
||||
const [newBoardName, setNewBoardName] = useState('');
|
||||
|
||||
const handleCreateNewBoard = () => {
|
||||
dispatch(boardCreated({ requestBody: newBoardName }));
|
||||
};
|
||||
|
||||
return (
|
||||
<Flex
|
||||
sx={{
|
||||
@ -198,7 +235,7 @@ const ImageGalleryContent = () => {
|
||||
tooltip={t('gallery.images')}
|
||||
aria-label={t('gallery.images')}
|
||||
onClick={handleClickImagesCategory}
|
||||
isChecked={categories === IMAGE_CATEGORIES}
|
||||
isChecked={galleryView === 'images'}
|
||||
size="sm"
|
||||
icon={<FaImage />}
|
||||
/>
|
||||
@ -206,12 +243,47 @@ const ImageGalleryContent = () => {
|
||||
tooltip={t('gallery.assets')}
|
||||
aria-label={t('gallery.assets')}
|
||||
onClick={handleClickAssetsCategory}
|
||||
isChecked={categories === ASSETS_CATEGORIES}
|
||||
isChecked={galleryView === 'assets'}
|
||||
size="sm"
|
||||
icon={<FaServer />}
|
||||
/>
|
||||
<IAIIconButton
|
||||
tooltip={t('gallery.boards')}
|
||||
aria-label={t('gallery.boards')}
|
||||
onClick={handleClickBoardsView}
|
||||
isChecked={galleryView === 'boards'}
|
||||
size="sm"
|
||||
icon={<FaFolder />}
|
||||
/>
|
||||
</ButtonGroup>
|
||||
<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
|
||||
triggerComponent={
|
||||
<IAIIconButton
|
||||
@ -271,57 +343,75 @@ const ImageGalleryContent = () => {
|
||||
</Flex>
|
||||
</Flex>
|
||||
<Flex direction="column" gap={2} h="full">
|
||||
{images.length || areMoreImagesAvailable ? (
|
||||
{items.length || areMoreAvailable ? (
|
||||
<>
|
||||
<Box ref={rootRef} data-overlayscrollbars="" h="100%">
|
||||
{shouldUseSingleGalleryColumn ? (
|
||||
<Virtuoso
|
||||
style={{ height: '100%' }}
|
||||
data={images}
|
||||
data={items}
|
||||
endReached={handleEndReached}
|
||||
scrollerRef={(ref) => setScrollerRef(ref)}
|
||||
itemContent={(index, image) => (
|
||||
<Flex sx={{ pb: 2 }}>
|
||||
<HoverableImage
|
||||
key={`${image.image_name}-${image.thumbnail_url}`}
|
||||
image={image}
|
||||
isSelected={
|
||||
selectedImage?.image_name === image?.image_name
|
||||
}
|
||||
/>
|
||||
</Flex>
|
||||
)}
|
||||
itemContent={(index, item) => {
|
||||
if (isImageDTO(item)) {
|
||||
return (
|
||||
<Flex sx={{ pb: 2 }}>
|
||||
<HoverableImage
|
||||
key={`${item.image_name}-${item.thumbnail_url}`}
|
||||
image={item}
|
||||
isSelected={
|
||||
selectedImage?.image_name === item?.image_name
|
||||
}
|
||||
/>
|
||||
</Flex>
|
||||
);
|
||||
} else if (isBoardRecord(item)) {
|
||||
return (
|
||||
<Flex sx={{ pb: 2 }}>
|
||||
<HoverableBoard key={item.board_id} board={item} />
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<VirtuosoGrid
|
||||
style={{ height: '100%' }}
|
||||
data={images}
|
||||
data={items}
|
||||
endReached={handleEndReached}
|
||||
components={{
|
||||
Item: ItemContainer,
|
||||
List: ListContainer,
|
||||
}}
|
||||
scrollerRef={setScroller}
|
||||
itemContent={(index, image) => (
|
||||
<HoverableImage
|
||||
key={`${image.image_name}-${image.thumbnail_url}`}
|
||||
image={image}
|
||||
isSelected={
|
||||
selectedImage?.image_name === image?.image_name
|
||||
}
|
||||
/>
|
||||
)}
|
||||
itemContent={(index, item) => {
|
||||
if (isImageDTO(item)) {
|
||||
return (
|
||||
<HoverableImage
|
||||
key={`${item.image_name}-${item.thumbnail_url}`}
|
||||
image={item}
|
||||
isSelected={
|
||||
selectedImage?.image_name === item?.image_name
|
||||
}
|
||||
/>
|
||||
);
|
||||
} else if (isBoardRecord(item)) {
|
||||
return (
|
||||
<HoverableBoard key={item.board_id} board={item} />
|
||||
);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
<IAIButton
|
||||
onClick={handleLoadMoreImages}
|
||||
isDisabled={!areMoreImagesAvailable}
|
||||
isDisabled={!areMoreAvailable}
|
||||
isLoading={isLoading}
|
||||
loadingText="Loading"
|
||||
flexShrink={0}
|
||||
>
|
||||
{areMoreImagesAvailable
|
||||
{areMoreAvailable
|
||||
? t('gallery.loadMore')
|
||||
: t('gallery.allImagesLoaded')}
|
||||
</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;
|
||||
shouldAutoSwitchToNewImages: boolean;
|
||||
shouldUseSingleGalleryColumn: boolean;
|
||||
galleryView: 'images' | 'assets' | 'boards';
|
||||
}
|
||||
|
||||
export const initialGalleryState: GalleryState = {
|
||||
@ -19,6 +20,7 @@ export const initialGalleryState: GalleryState = {
|
||||
galleryImageObjectFit: 'cover',
|
||||
shouldAutoSwitchToNewImages: true,
|
||||
shouldUseSingleGalleryColumn: false,
|
||||
galleryView: 'images',
|
||||
};
|
||||
|
||||
export const gallerySlice = createSlice({
|
||||
@ -48,6 +50,12 @@ export const gallerySlice = createSlice({
|
||||
) => {
|
||||
state.shouldUseSingleGalleryColumn = action.payload;
|
||||
},
|
||||
setGalleryView: (
|
||||
state,
|
||||
action: PayloadAction<'images' | 'assets' | 'boards'>
|
||||
) => {
|
||||
state.galleryView = action.payload;
|
||||
},
|
||||
},
|
||||
extraReducers: (builder) => {
|
||||
builder.addCase(imageUpserted, (state, action) => {
|
||||
@ -75,6 +83,7 @@ export const {
|
||||
setGalleryImageObjectFit,
|
||||
setShouldAutoSwitchToNewImages,
|
||||
setShouldUseSingleGalleryColumn,
|
||||
setGalleryView,
|
||||
} = gallerySlice.actions;
|
||||
|
||||
export default gallerySlice.reducer;
|
||||
|
@ -154,3 +154,16 @@ export const selectFilteredImagesIds = createSelector(
|
||||
.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,
|
||||
ResourceOrigin,
|
||||
ImageDTO,
|
||||
BoardRecord,
|
||||
} from 'services/api';
|
||||
|
||||
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 = (
|
||||
output: GraphExecutionState['results'][string]
|
||||
): output is ImageOutput => output.type === 'image_output';
|
||||
|
Loading…
Reference in New Issue
Block a user