mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
ui: enhance intermediates clear, enhance board auto-add (#3851)
* feat(ui): enhance clear intermediates feature - retrieve the # of intermediates using a new query (just uses list images endpoint w/ limit of 0) - display the count in the UI - add types for clearIntermediates mutation - minor styling and verbiage changes * feat(ui): remove unused settings option for guides * feat(ui): use solid badge variant consistent with the rest of the usage of badges * feat(ui): update board ctx menu, add board auto-add - add context menu to system boards - only open is select board. did this so that you dont think its broken when you click it - add auto-add board. you can right click a user board to enable it for auto-add, or use the gallery settings popover to select it. the invoke button has a tooltip on a short delay to remind you that you have auto-add enabled - made useBoardName hook, provide it a board id and it gets your the board name - removed `boardIdToAdTo` state & logic, updated workflows to auto-switch and auto-add on image generation * fix(ui): clear controlnet when clearing intermediates * feat: Make Add Board icon a button * feat(db, api): clear intermediates now clears all of them * feat(ui): make reset webui text subtext style * feat(ui): board name change submits on blur --------- Co-authored-by: blessedcoolant <54517381+blessedcoolant@users.noreply.github.com>
This commit is contained in:
parent
82554b25fe
commit
187cf906fa
@ -1,8 +1,7 @@
|
|||||||
import io
|
import io
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
from fastapi import (Body, HTTPException, Path, Query, Request, Response,
|
from fastapi import Body, HTTPException, Path, Query, Request, Response, UploadFile
|
||||||
UploadFile)
|
|
||||||
from fastapi.responses import FileResponse
|
from fastapi.responses import FileResponse
|
||||||
from fastapi.routing import APIRouter
|
from fastapi.routing import APIRouter
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
@ -11,9 +10,11 @@ from invokeai.app.invocations.metadata import ImageMetadata
|
|||||||
from invokeai.app.models.image import ImageCategory, ResourceOrigin
|
from invokeai.app.models.image import ImageCategory, ResourceOrigin
|
||||||
from invokeai.app.services.image_record_storage import OffsetPaginatedResults
|
from invokeai.app.services.image_record_storage import OffsetPaginatedResults
|
||||||
from invokeai.app.services.item_storage import PaginatedResults
|
from invokeai.app.services.item_storage import PaginatedResults
|
||||||
from invokeai.app.services.models.image_record import (ImageDTO,
|
from invokeai.app.services.models.image_record import (
|
||||||
ImageRecordChanges,
|
ImageDTO,
|
||||||
ImageUrlsDTO)
|
ImageRecordChanges,
|
||||||
|
ImageUrlsDTO,
|
||||||
|
)
|
||||||
|
|
||||||
from ..dependencies import ApiDependencies
|
from ..dependencies import ApiDependencies
|
||||||
|
|
||||||
@ -84,15 +85,16 @@ async def delete_image(
|
|||||||
# TODO: Does this need any exception handling at all?
|
# TODO: Does this need any exception handling at all?
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
@images_router.post("/clear-intermediates", operation_id="clear_intermediates")
|
@images_router.post("/clear-intermediates", operation_id="clear_intermediates")
|
||||||
async def clear_intermediates() -> int:
|
async def clear_intermediates() -> int:
|
||||||
"""Clears first 100 intermediates"""
|
"""Clears all intermediates"""
|
||||||
|
|
||||||
try:
|
try:
|
||||||
count_deleted = ApiDependencies.invoker.services.images.delete_many(is_intermediate=True)
|
count_deleted = ApiDependencies.invoker.services.images.delete_intermediates()
|
||||||
return count_deleted
|
return count_deleted
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
# TODO: Does this need any exception handling at all?
|
raise HTTPException(status_code=500, detail="Failed to clear intermediates")
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
@ -130,6 +132,7 @@ async def get_image_dto(
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise HTTPException(status_code=404)
|
raise HTTPException(status_code=404)
|
||||||
|
|
||||||
|
|
||||||
@images_router.get(
|
@images_router.get(
|
||||||
"/{image_name}/metadata",
|
"/{image_name}/metadata",
|
||||||
operation_id="get_image_metadata",
|
operation_id="get_image_metadata",
|
||||||
@ -254,7 +257,8 @@ async def list_image_dtos(
|
|||||||
default=None, description="Whether to list intermediate images."
|
default=None, description="Whether to list intermediate images."
|
||||||
),
|
),
|
||||||
board_id: Optional[str] = Query(
|
board_id: Optional[str] = Query(
|
||||||
default=None, description="The board id to filter by. Use 'none' to find images without a board."
|
default=None,
|
||||||
|
description="The board id to filter by. Use 'none' to find images without a board.",
|
||||||
),
|
),
|
||||||
offset: int = Query(default=0, description="The page offset"),
|
offset: int = Query(default=0, description="The page offset"),
|
||||||
limit: int = Query(default=10, description="The number of images per page"),
|
limit: int = Query(default=10, description="The number of images per page"),
|
||||||
|
@ -122,6 +122,11 @@ class ImageRecordStorageBase(ABC):
|
|||||||
"""Deletes many image records."""
|
"""Deletes many image records."""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def delete_intermediates(self) -> list[str]:
|
||||||
|
"""Deletes all intermediate image records, returning a list of deleted image names."""
|
||||||
|
pass
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def save(
|
def save(
|
||||||
self,
|
self,
|
||||||
@ -461,6 +466,32 @@ class SqliteImageRecordStorage(ImageRecordStorageBase):
|
|||||||
finally:
|
finally:
|
||||||
self._lock.release()
|
self._lock.release()
|
||||||
|
|
||||||
|
|
||||||
|
def delete_intermediates(self) -> list[str]:
|
||||||
|
try:
|
||||||
|
self._lock.acquire()
|
||||||
|
self._cursor.execute(
|
||||||
|
"""--sql
|
||||||
|
SELECT image_name FROM images
|
||||||
|
WHERE is_intermediate = TRUE;
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
result = cast(list[sqlite3.Row], self._cursor.fetchall())
|
||||||
|
image_names = list(map(lambda r: r[0], result))
|
||||||
|
self._cursor.execute(
|
||||||
|
"""--sql
|
||||||
|
DELETE FROM images
|
||||||
|
WHERE is_intermediate = TRUE;
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
self._conn.commit()
|
||||||
|
return image_names
|
||||||
|
except sqlite3.Error as e:
|
||||||
|
self._conn.rollback()
|
||||||
|
raise ImageRecordDeleteException from e
|
||||||
|
finally:
|
||||||
|
self._lock.release()
|
||||||
|
|
||||||
def save(
|
def save(
|
||||||
self,
|
self,
|
||||||
image_name: str,
|
image_name: str,
|
||||||
|
@ -6,21 +6,33 @@ from typing import TYPE_CHECKING, Optional
|
|||||||
from PIL.Image import Image as PILImageType
|
from PIL.Image import Image as PILImageType
|
||||||
|
|
||||||
from invokeai.app.invocations.metadata import ImageMetadata
|
from invokeai.app.invocations.metadata import ImageMetadata
|
||||||
from invokeai.app.models.image import (ImageCategory,
|
from invokeai.app.models.image import (
|
||||||
InvalidImageCategoryException,
|
ImageCategory,
|
||||||
InvalidOriginException, ResourceOrigin)
|
InvalidImageCategoryException,
|
||||||
from invokeai.app.services.board_image_record_storage import \
|
InvalidOriginException,
|
||||||
BoardImageRecordStorageBase
|
ResourceOrigin,
|
||||||
|
)
|
||||||
|
from invokeai.app.services.board_image_record_storage import BoardImageRecordStorageBase
|
||||||
from invokeai.app.services.image_file_storage import (
|
from invokeai.app.services.image_file_storage import (
|
||||||
ImageFileDeleteException, ImageFileNotFoundException,
|
ImageFileDeleteException,
|
||||||
ImageFileSaveException, ImageFileStorageBase)
|
ImageFileNotFoundException,
|
||||||
|
ImageFileSaveException,
|
||||||
|
ImageFileStorageBase,
|
||||||
|
)
|
||||||
from invokeai.app.services.image_record_storage import (
|
from invokeai.app.services.image_record_storage import (
|
||||||
ImageRecordDeleteException, ImageRecordNotFoundException,
|
ImageRecordDeleteException,
|
||||||
ImageRecordSaveException, ImageRecordStorageBase, OffsetPaginatedResults)
|
ImageRecordNotFoundException,
|
||||||
|
ImageRecordSaveException,
|
||||||
|
ImageRecordStorageBase,
|
||||||
|
OffsetPaginatedResults,
|
||||||
|
)
|
||||||
from invokeai.app.services.item_storage import ItemStorageABC
|
from invokeai.app.services.item_storage import ItemStorageABC
|
||||||
from invokeai.app.services.models.image_record import (ImageDTO, ImageRecord,
|
from invokeai.app.services.models.image_record import (
|
||||||
ImageRecordChanges,
|
ImageDTO,
|
||||||
image_record_to_dto)
|
ImageRecord,
|
||||||
|
ImageRecordChanges,
|
||||||
|
image_record_to_dto,
|
||||||
|
)
|
||||||
from invokeai.app.services.resource_name import NameServiceBase
|
from invokeai.app.services.resource_name import NameServiceBase
|
||||||
from invokeai.app.services.urls import UrlServiceBase
|
from invokeai.app.services.urls import UrlServiceBase
|
||||||
from invokeai.app.util.metadata import get_metadata_graph_from_raw_session
|
from invokeai.app.util.metadata import get_metadata_graph_from_raw_session
|
||||||
@ -109,12 +121,10 @@ class ImageServiceABC(ABC):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def delete_many(self, is_intermediate: bool) -> int:
|
def delete_intermediates(self) -> int:
|
||||||
"""Deletes many images."""
|
"""Deletes all intermediate images."""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def delete_images_on_board(self, board_id: str):
|
def delete_images_on_board(self, board_id: str):
|
||||||
"""Deletes all images on a board."""
|
"""Deletes all images on a board."""
|
||||||
@ -401,21 +411,13 @@ class ImageService(ImageServiceABC):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
self._services.logger.error("Problem deleting image records and files")
|
self._services.logger.error("Problem deleting image records and files")
|
||||||
raise e
|
raise e
|
||||||
|
|
||||||
def delete_many(self, is_intermediate: bool):
|
def delete_intermediates(self) -> int:
|
||||||
try:
|
try:
|
||||||
# only clears 100 at a time
|
image_names = self._services.image_records.delete_intermediates()
|
||||||
images = self._services.image_records.get_many(offset=0, limit=100, is_intermediate=is_intermediate,)
|
count = len(image_names)
|
||||||
count = len(images.items)
|
for image_name in image_names:
|
||||||
image_name_list = list(
|
|
||||||
map(
|
|
||||||
lambda r: r.image_name,
|
|
||||||
images.items,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
for image_name in image_name_list:
|
|
||||||
self._services.image_files.delete(image_name)
|
self._services.image_files.delete(image_name)
|
||||||
self._services.image_records.delete_many(image_name_list)
|
|
||||||
return count
|
return count
|
||||||
except ImageRecordDeleteException:
|
except ImageRecordDeleteException:
|
||||||
self._services.logger.error(f"Failed to delete image records")
|
self._services.logger.error(f"Failed to delete image records")
|
||||||
|
@ -6,11 +6,7 @@ import {
|
|||||||
imageSelected,
|
imageSelected,
|
||||||
} from 'features/gallery/store/gallerySlice';
|
} from 'features/gallery/store/gallerySlice';
|
||||||
import { progressImageSet } from 'features/system/store/systemSlice';
|
import { progressImageSet } from 'features/system/store/systemSlice';
|
||||||
import {
|
import { imagesAdapter, imagesApi } from 'services/api/endpoints/images';
|
||||||
SYSTEM_BOARDS,
|
|
||||||
imagesAdapter,
|
|
||||||
imagesApi,
|
|
||||||
} from 'services/api/endpoints/images';
|
|
||||||
import { isImageOutput } from 'services/api/guards';
|
import { isImageOutput } from 'services/api/guards';
|
||||||
import { sessionCanceled } from 'services/api/thunks/session';
|
import { sessionCanceled } from 'services/api/thunks/session';
|
||||||
import {
|
import {
|
||||||
@ -32,8 +28,7 @@ export const addInvocationCompleteEventListener = () => {
|
|||||||
);
|
);
|
||||||
const session_id = action.payload.data.graph_execution_state_id;
|
const session_id = action.payload.data.graph_execution_state_id;
|
||||||
|
|
||||||
const { cancelType, isCancelScheduled, boardIdToAddTo } =
|
const { cancelType, isCancelScheduled } = getState().system;
|
||||||
getState().system;
|
|
||||||
|
|
||||||
// Handle scheduled cancelation
|
// Handle scheduled cancelation
|
||||||
if (cancelType === 'scheduled' && isCancelScheduled) {
|
if (cancelType === 'scheduled' && isCancelScheduled) {
|
||||||
@ -88,26 +83,28 @@ export const addInvocationCompleteEventListener = () => {
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
// add image to the board if we had one selected
|
const { autoAddBoardId } = gallery;
|
||||||
if (boardIdToAddTo && !SYSTEM_BOARDS.includes(boardIdToAddTo)) {
|
|
||||||
|
// add image to the board if auto-add is enabled
|
||||||
|
if (autoAddBoardId) {
|
||||||
dispatch(
|
dispatch(
|
||||||
imagesApi.endpoints.addImageToBoard.initiate({
|
imagesApi.endpoints.addImageToBoard.initiate({
|
||||||
board_id: boardIdToAddTo,
|
board_id: autoAddBoardId,
|
||||||
imageDTO,
|
imageDTO,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const { selectedBoardId } = gallery;
|
const { selectedBoardId, shouldAutoSwitch } = gallery;
|
||||||
|
|
||||||
if (boardIdToAddTo && boardIdToAddTo !== selectedBoardId) {
|
|
||||||
dispatch(boardIdSelected(boardIdToAddTo));
|
|
||||||
} else if (!boardIdToAddTo) {
|
|
||||||
dispatch(boardIdSelected('all'));
|
|
||||||
}
|
|
||||||
|
|
||||||
// If auto-switch is enabled, select the new image
|
// If auto-switch is enabled, select the new image
|
||||||
if (getState().gallery.shouldAutoSwitch) {
|
if (shouldAutoSwitch) {
|
||||||
|
// if auto-add is enabled, switch the board as the image comes in
|
||||||
|
if (autoAddBoardId && autoAddBoardId !== selectedBoardId) {
|
||||||
|
dispatch(boardIdSelected(autoAddBoardId));
|
||||||
|
} else if (!autoAddBoardId) {
|
||||||
|
dispatch(boardIdSelected('images'));
|
||||||
|
}
|
||||||
dispatch(imageSelected(imageDTO.image_name));
|
dispatch(imageSelected(imageDTO.image_name));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,80 @@
|
|||||||
|
import { SelectItem } from '@mantine/core';
|
||||||
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
|
import { stateSelector } from 'app/store/store';
|
||||||
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
|
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
|
||||||
|
import IAIMantineSearchableSelect from 'common/components/IAIMantineSearchableSelect';
|
||||||
|
import IAIMantineSelectItemWithTooltip from 'common/components/IAIMantineSelectItemWithTooltip';
|
||||||
|
import { autoAddBoardIdChanged } from 'features/gallery/store/gallerySlice';
|
||||||
|
import { useCallback, useRef } from 'react';
|
||||||
|
import { useListAllBoardsQuery } from 'services/api/endpoints/boards';
|
||||||
|
|
||||||
|
const selector = createSelector(
|
||||||
|
[stateSelector],
|
||||||
|
({ gallery }) => {
|
||||||
|
const { autoAddBoardId } = gallery;
|
||||||
|
|
||||||
|
return {
|
||||||
|
autoAddBoardId,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
defaultSelectorOptions
|
||||||
|
);
|
||||||
|
|
||||||
|
const BoardAutoAddSelect = () => {
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
const { autoAddBoardId } = useAppSelector(selector);
|
||||||
|
const inputRef = useRef<HTMLInputElement>(null);
|
||||||
|
const { boards, hasBoards } = useListAllBoardsQuery(undefined, {
|
||||||
|
selectFromResult: ({ data }) => {
|
||||||
|
const boards: SelectItem[] = [
|
||||||
|
{
|
||||||
|
label: 'None',
|
||||||
|
value: 'none',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
data?.forEach(({ board_id, board_name }) => {
|
||||||
|
boards.push({
|
||||||
|
label: board_name,
|
||||||
|
value: board_id,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
boards,
|
||||||
|
hasBoards: boards.length > 1,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleChange = useCallback(
|
||||||
|
(v: string | null) => {
|
||||||
|
if (!v) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
dispatch(autoAddBoardIdChanged(v === 'none' ? null : v));
|
||||||
|
},
|
||||||
|
[dispatch]
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<IAIMantineSearchableSelect
|
||||||
|
label="Auto-Add Board"
|
||||||
|
inputRef={inputRef}
|
||||||
|
autoFocus
|
||||||
|
placeholder={'Select a Board'}
|
||||||
|
value={autoAddBoardId}
|
||||||
|
data={boards}
|
||||||
|
nothingFound="No matching Boards"
|
||||||
|
itemComponent={IAIMantineSelectItemWithTooltip}
|
||||||
|
disabled={!hasBoards}
|
||||||
|
filter={(value, item: SelectItem) =>
|
||||||
|
item.label?.toLowerCase().includes(value.toLowerCase().trim()) ||
|
||||||
|
item.value.toLowerCase().includes(value.toLowerCase().trim())
|
||||||
|
}
|
||||||
|
onChange={handleChange}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default BoardAutoAddSelect;
|
@ -0,0 +1,60 @@
|
|||||||
|
import { Box, MenuItem, MenuList } from '@chakra-ui/react';
|
||||||
|
import { useAppDispatch } from 'app/store/storeHooks';
|
||||||
|
import { ContextMenu, ContextMenuProps } from 'chakra-ui-contextmenu';
|
||||||
|
import { boardIdSelected } from 'features/gallery/store/gallerySlice';
|
||||||
|
import { memo, useCallback } from 'react';
|
||||||
|
import { FaFolder } from 'react-icons/fa';
|
||||||
|
import { BoardDTO } from 'services/api/types';
|
||||||
|
import { menuListMotionProps } from 'theme/components/menu';
|
||||||
|
import GalleryBoardContextMenuItems from './GalleryBoardContextMenuItems';
|
||||||
|
import SystemBoardContextMenuItems from './SystemBoardContextMenuItems';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
board?: BoardDTO;
|
||||||
|
board_id: string;
|
||||||
|
children: ContextMenuProps<HTMLDivElement>['children'];
|
||||||
|
setBoardToDelete?: (board?: BoardDTO) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
const BoardContextMenu = memo(
|
||||||
|
({ board, board_id, setBoardToDelete, children }: Props) => {
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
const handleSelectBoard = useCallback(() => {
|
||||||
|
dispatch(boardIdSelected(board?.board_id ?? board_id));
|
||||||
|
}, [board?.board_id, board_id, dispatch]);
|
||||||
|
return (
|
||||||
|
<Box sx={{ touchAction: 'none', height: 'full' }}>
|
||||||
|
<ContextMenu<HTMLDivElement>
|
||||||
|
menuProps={{ size: 'sm', isLazy: true }}
|
||||||
|
menuButtonProps={{
|
||||||
|
bg: 'transparent',
|
||||||
|
_hover: { bg: 'transparent' },
|
||||||
|
}}
|
||||||
|
renderMenu={() => (
|
||||||
|
<MenuList
|
||||||
|
sx={{ visibility: 'visible !important' }}
|
||||||
|
motionProps={menuListMotionProps}
|
||||||
|
>
|
||||||
|
<MenuItem icon={<FaFolder />} onClickCapture={handleSelectBoard}>
|
||||||
|
Select Board
|
||||||
|
</MenuItem>
|
||||||
|
{!board && <SystemBoardContextMenuItems board_id={board_id} />}
|
||||||
|
{board && (
|
||||||
|
<GalleryBoardContextMenuItems
|
||||||
|
board={board}
|
||||||
|
setBoardToDelete={setBoardToDelete}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</MenuList>
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</ContextMenu>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
BoardContextMenu.displayName = 'HoverableBoard';
|
||||||
|
|
||||||
|
export default BoardContextMenu;
|
@ -1,5 +1,6 @@
|
|||||||
import IAIButton from 'common/components/IAIButton';
|
import IAIIconButton from 'common/components/IAIIconButton';
|
||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
|
import { FaPlus } from 'react-icons/fa';
|
||||||
import { useCreateBoardMutation } from 'services/api/endpoints/boards';
|
import { useCreateBoardMutation } from 'services/api/endpoints/boards';
|
||||||
|
|
||||||
const DEFAULT_BOARD_NAME = 'My Board';
|
const DEFAULT_BOARD_NAME = 'My Board';
|
||||||
@ -12,15 +13,14 @@ const AddBoardButton = () => {
|
|||||||
}, [createBoard]);
|
}, [createBoard]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<IAIButton
|
<IAIIconButton
|
||||||
|
icon={<FaPlus />}
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
|
tooltip="Add Board"
|
||||||
aria-label="Add Board"
|
aria-label="Add Board"
|
||||||
onClick={handleCreateBoard}
|
onClick={handleCreateBoard}
|
||||||
size="sm"
|
size="sm"
|
||||||
sx={{ px: 4 }}
|
/>
|
||||||
>
|
|
||||||
Add Board
|
|
||||||
</IAIButton>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -38,6 +38,7 @@ const AllAssetsBoard = ({ isSelected }: { isSelected: boolean }) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<GenericBoard
|
<GenericBoard
|
||||||
|
board_id="assets"
|
||||||
onClick={handleClick}
|
onClick={handleClick}
|
||||||
isSelected={isSelected}
|
isSelected={isSelected}
|
||||||
icon={FaFileImage}
|
icon={FaFileImage}
|
||||||
|
@ -38,6 +38,7 @@ const AllImagesBoard = ({ isSelected }: { isSelected: boolean }) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<GenericBoard
|
<GenericBoard
|
||||||
|
board_id="images"
|
||||||
onClick={handleClick}
|
onClick={handleClick}
|
||||||
isSelected={isSelected}
|
isSelected={isSelected}
|
||||||
icon={FaImages}
|
icon={FaImages}
|
||||||
|
@ -29,6 +29,7 @@ const BatchBoard = ({ isSelected }: { isSelected: boolean }) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<GenericBoard
|
<GenericBoard
|
||||||
|
board_id="batch"
|
||||||
droppableData={droppableData}
|
droppableData={droppableData}
|
||||||
onClick={handleBatchBoardClick}
|
onClick={handleBatchBoardClick}
|
||||||
isSelected={isSelected}
|
isSelected={isSelected}
|
||||||
|
@ -1,31 +1,41 @@
|
|||||||
import {
|
import {
|
||||||
Badge,
|
Badge,
|
||||||
Box,
|
Box,
|
||||||
|
ChakraProps,
|
||||||
Editable,
|
Editable,
|
||||||
EditableInput,
|
EditableInput,
|
||||||
EditablePreview,
|
EditablePreview,
|
||||||
Flex,
|
Flex,
|
||||||
Image,
|
Image,
|
||||||
MenuItem,
|
|
||||||
MenuList,
|
|
||||||
Text,
|
Text,
|
||||||
useColorMode,
|
useColorMode,
|
||||||
} from '@chakra-ui/react';
|
} from '@chakra-ui/react';
|
||||||
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
import { skipToken } from '@reduxjs/toolkit/dist/query';
|
import { skipToken } from '@reduxjs/toolkit/dist/query';
|
||||||
import { MoveBoardDropData } from 'app/components/ImageDnd/typesafeDnd';
|
import { MoveBoardDropData } from 'app/components/ImageDnd/typesafeDnd';
|
||||||
import { useAppDispatch } from 'app/store/storeHooks';
|
import { stateSelector } from 'app/store/store';
|
||||||
import { ContextMenu } from 'chakra-ui-contextmenu';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
|
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
|
||||||
import IAIDroppable from 'common/components/IAIDroppable';
|
import IAIDroppable from 'common/components/IAIDroppable';
|
||||||
import { IAINoContentFallback } from 'common/components/IAIImageFallback';
|
import { IAINoContentFallback } from 'common/components/IAIImageFallback';
|
||||||
import { boardIdSelected } from 'features/gallery/store/gallerySlice';
|
import { boardIdSelected } from 'features/gallery/store/gallerySlice';
|
||||||
import { memo, useCallback, useMemo } from 'react';
|
import { memo, useCallback, useMemo } from 'react';
|
||||||
import { FaTrash, FaUser } from 'react-icons/fa';
|
import { FaUser } from 'react-icons/fa';
|
||||||
import { useUpdateBoardMutation } from 'services/api/endpoints/boards';
|
import { useUpdateBoardMutation } from 'services/api/endpoints/boards';
|
||||||
import { useGetImageDTOQuery } from 'services/api/endpoints/images';
|
import { useGetImageDTOQuery } from 'services/api/endpoints/images';
|
||||||
import { BoardDTO } from 'services/api/types';
|
import { BoardDTO } from 'services/api/types';
|
||||||
import { menuListMotionProps } from 'theme/components/menu';
|
|
||||||
import { mode } from 'theme/util/mode';
|
import { mode } from 'theme/util/mode';
|
||||||
|
import BoardContextMenu from '../BoardContextMenu';
|
||||||
|
|
||||||
|
const AUTO_ADD_BADGE_STYLES: ChakraProps['sx'] = {
|
||||||
|
bg: 'accent.200',
|
||||||
|
color: 'blackAlpha.900',
|
||||||
|
};
|
||||||
|
|
||||||
|
const BASE_BADGE_STYLES: ChakraProps['sx'] = {
|
||||||
|
bg: 'base.500',
|
||||||
|
color: 'whiteAlpha.900',
|
||||||
|
};
|
||||||
interface GalleryBoardProps {
|
interface GalleryBoardProps {
|
||||||
board: BoardDTO;
|
board: BoardDTO;
|
||||||
isSelected: boolean;
|
isSelected: boolean;
|
||||||
@ -35,6 +45,22 @@ interface GalleryBoardProps {
|
|||||||
const GalleryBoard = memo(
|
const GalleryBoard = memo(
|
||||||
({ board, isSelected, setBoardToDelete }: GalleryBoardProps) => {
|
({ board, isSelected, setBoardToDelete }: GalleryBoardProps) => {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
|
const selector = useMemo(
|
||||||
|
() =>
|
||||||
|
createSelector(
|
||||||
|
stateSelector,
|
||||||
|
({ gallery }) => {
|
||||||
|
const isSelectedForAutoAdd =
|
||||||
|
board.board_id === gallery.autoAddBoardId;
|
||||||
|
|
||||||
|
return { isSelectedForAutoAdd };
|
||||||
|
},
|
||||||
|
defaultSelectorOptions
|
||||||
|
),
|
||||||
|
[board.board_id]
|
||||||
|
);
|
||||||
|
|
||||||
|
const { isSelectedForAutoAdd } = useAppSelector(selector);
|
||||||
|
|
||||||
const { currentData: coverImage } = useGetImageDTOQuery(
|
const { currentData: coverImage } = useGetImageDTOQuery(
|
||||||
board.cover_image_name ?? skipToken
|
board.cover_image_name ?? skipToken
|
||||||
@ -53,10 +79,6 @@ const GalleryBoard = memo(
|
|||||||
updateBoard({ board_id, changes: { board_name: newBoardName } });
|
updateBoard({ board_id, changes: { board_name: newBoardName } });
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDeleteBoard = useCallback(() => {
|
|
||||||
setBoardToDelete(board);
|
|
||||||
}, [board, setBoardToDelete]);
|
|
||||||
|
|
||||||
const droppableData: MoveBoardDropData = useMemo(
|
const droppableData: MoveBoardDropData = useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
id: board_id,
|
id: board_id,
|
||||||
@ -68,37 +90,10 @@ const GalleryBoard = memo(
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Box sx={{ touchAction: 'none', height: 'full' }}>
|
<Box sx={{ touchAction: 'none', height: 'full' }}>
|
||||||
<ContextMenu<HTMLDivElement>
|
<BoardContextMenu
|
||||||
menuProps={{ size: 'sm', isLazy: true }}
|
board={board}
|
||||||
menuButtonProps={{
|
board_id={board_id}
|
||||||
bg: 'transparent',
|
setBoardToDelete={setBoardToDelete}
|
||||||
_hover: { bg: 'transparent' },
|
|
||||||
}}
|
|
||||||
renderMenu={() => (
|
|
||||||
<MenuList
|
|
||||||
sx={{ visibility: 'visible !important' }}
|
|
||||||
motionProps={menuListMotionProps}
|
|
||||||
>
|
|
||||||
{board.image_count > 0 && (
|
|
||||||
<>
|
|
||||||
{/* <MenuItem
|
|
||||||
isDisabled={!board.image_count}
|
|
||||||
icon={<FaImages />}
|
|
||||||
onClickCapture={handleAddBoardToBatch}
|
|
||||||
>
|
|
||||||
Add Board to Batch
|
|
||||||
</MenuItem> */}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
<MenuItem
|
|
||||||
sx={{ color: 'error.600', _dark: { color: 'error.300' } }}
|
|
||||||
icon={<FaTrash />}
|
|
||||||
onClickCapture={handleDeleteBoard}
|
|
||||||
>
|
|
||||||
Delete Board
|
|
||||||
</MenuItem>
|
|
||||||
</MenuList>
|
|
||||||
)}
|
|
||||||
>
|
>
|
||||||
{(ref) => (
|
{(ref) => (
|
||||||
<Flex
|
<Flex
|
||||||
@ -154,7 +149,16 @@ const GalleryBoard = memo(
|
|||||||
p: 1,
|
p: 1,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Badge variant="solid">{board.image_count}</Badge>
|
<Badge
|
||||||
|
variant="solid"
|
||||||
|
sx={
|
||||||
|
isSelectedForAutoAdd
|
||||||
|
? AUTO_ADD_BADGE_STYLES
|
||||||
|
: BASE_BADGE_STYLES
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{board.image_count}
|
||||||
|
</Badge>
|
||||||
</Flex>
|
</Flex>
|
||||||
<IAIDroppable
|
<IAIDroppable
|
||||||
data={droppableData}
|
data={droppableData}
|
||||||
@ -172,7 +176,7 @@ const GalleryBoard = memo(
|
|||||||
>
|
>
|
||||||
<Editable
|
<Editable
|
||||||
defaultValue={board_name}
|
defaultValue={board_name}
|
||||||
submitOnBlur={false}
|
submitOnBlur={true}
|
||||||
onSubmit={(nextValue) => {
|
onSubmit={(nextValue) => {
|
||||||
handleUpdateBoardName(nextValue);
|
handleUpdateBoardName(nextValue);
|
||||||
}}
|
}}
|
||||||
@ -205,7 +209,7 @@ const GalleryBoard = memo(
|
|||||||
</Flex>
|
</Flex>
|
||||||
</Flex>
|
</Flex>
|
||||||
)}
|
)}
|
||||||
</ContextMenu>
|
</BoardContextMenu>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -2,9 +2,12 @@ import { As, Badge, Flex } from '@chakra-ui/react';
|
|||||||
import { TypesafeDroppableData } from 'app/components/ImageDnd/typesafeDnd';
|
import { TypesafeDroppableData } from 'app/components/ImageDnd/typesafeDnd';
|
||||||
import IAIDroppable from 'common/components/IAIDroppable';
|
import IAIDroppable from 'common/components/IAIDroppable';
|
||||||
import { IAINoContentFallback } from 'common/components/IAIImageFallback';
|
import { IAINoContentFallback } from 'common/components/IAIImageFallback';
|
||||||
|
import { BoardId } from 'features/gallery/store/gallerySlice';
|
||||||
import { ReactNode } from 'react';
|
import { ReactNode } from 'react';
|
||||||
|
import BoardContextMenu from '../BoardContextMenu';
|
||||||
|
|
||||||
type GenericBoardProps = {
|
type GenericBoardProps = {
|
||||||
|
board_id: BoardId;
|
||||||
droppableData?: TypesafeDroppableData;
|
droppableData?: TypesafeDroppableData;
|
||||||
onClick: () => void;
|
onClick: () => void;
|
||||||
isSelected: boolean;
|
isSelected: boolean;
|
||||||
@ -22,6 +25,7 @@ const formatBadgeCount = (count: number) =>
|
|||||||
|
|
||||||
const GenericBoard = (props: GenericBoardProps) => {
|
const GenericBoard = (props: GenericBoardProps) => {
|
||||||
const {
|
const {
|
||||||
|
board_id,
|
||||||
droppableData,
|
droppableData,
|
||||||
onClick,
|
onClick,
|
||||||
isSelected,
|
isSelected,
|
||||||
@ -32,67 +36,72 @@ const GenericBoard = (props: GenericBoardProps) => {
|
|||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex
|
<BoardContextMenu board_id={board_id}>
|
||||||
sx={{
|
{(ref) => (
|
||||||
flexDir: 'column',
|
|
||||||
justifyContent: 'space-between',
|
|
||||||
alignItems: 'center',
|
|
||||||
cursor: 'pointer',
|
|
||||||
w: 'full',
|
|
||||||
h: 'full',
|
|
||||||
borderRadius: 'base',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Flex
|
|
||||||
onClick={onClick}
|
|
||||||
sx={{
|
|
||||||
position: 'relative',
|
|
||||||
justifyContent: 'center',
|
|
||||||
alignItems: 'center',
|
|
||||||
borderRadius: 'base',
|
|
||||||
w: 'full',
|
|
||||||
aspectRatio: '1/1',
|
|
||||||
overflow: 'hidden',
|
|
||||||
shadow: isSelected ? 'selected.light' : undefined,
|
|
||||||
_dark: { shadow: isSelected ? 'selected.dark' : undefined },
|
|
||||||
flexShrink: 0,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<IAINoContentFallback
|
|
||||||
boxSize={8}
|
|
||||||
icon={icon}
|
|
||||||
sx={{
|
|
||||||
border: '2px solid var(--invokeai-colors-base-200)',
|
|
||||||
_dark: { border: '2px solid var(--invokeai-colors-base-800)' },
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<Flex
|
<Flex
|
||||||
|
ref={ref}
|
||||||
sx={{
|
sx={{
|
||||||
position: 'absolute',
|
flexDir: 'column',
|
||||||
insetInlineEnd: 0,
|
justifyContent: 'space-between',
|
||||||
top: 0,
|
alignItems: 'center',
|
||||||
p: 1,
|
cursor: 'pointer',
|
||||||
|
w: 'full',
|
||||||
|
h: 'full',
|
||||||
|
borderRadius: 'base',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{badgeCount !== undefined && (
|
<Flex
|
||||||
<Badge variant="solid">{formatBadgeCount(badgeCount)}</Badge>
|
onClick={onClick}
|
||||||
)}
|
sx={{
|
||||||
|
position: 'relative',
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
borderRadius: 'base',
|
||||||
|
w: 'full',
|
||||||
|
aspectRatio: '1/1',
|
||||||
|
overflow: 'hidden',
|
||||||
|
shadow: isSelected ? 'selected.light' : undefined,
|
||||||
|
_dark: { shadow: isSelected ? 'selected.dark' : undefined },
|
||||||
|
flexShrink: 0,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<IAINoContentFallback
|
||||||
|
boxSize={8}
|
||||||
|
icon={icon}
|
||||||
|
sx={{
|
||||||
|
border: '2px solid var(--invokeai-colors-base-200)',
|
||||||
|
_dark: { border: '2px solid var(--invokeai-colors-base-800)' },
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Flex
|
||||||
|
sx={{
|
||||||
|
position: 'absolute',
|
||||||
|
insetInlineEnd: 0,
|
||||||
|
top: 0,
|
||||||
|
p: 1,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{badgeCount !== undefined && (
|
||||||
|
<Badge variant="solid">{formatBadgeCount(badgeCount)}</Badge>
|
||||||
|
)}
|
||||||
|
</Flex>
|
||||||
|
<IAIDroppable data={droppableData} dropLabel={dropLabel} />
|
||||||
|
</Flex>
|
||||||
|
<Flex
|
||||||
|
sx={{
|
||||||
|
h: 'full',
|
||||||
|
alignItems: 'center',
|
||||||
|
fontWeight: isSelected ? 600 : undefined,
|
||||||
|
fontSize: 'xs',
|
||||||
|
color: isSelected ? 'base.900' : 'base.700',
|
||||||
|
_dark: { color: isSelected ? 'base.50' : 'base.200' },
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{label}
|
||||||
|
</Flex>
|
||||||
</Flex>
|
</Flex>
|
||||||
<IAIDroppable data={droppableData} dropLabel={dropLabel} />
|
)}
|
||||||
</Flex>
|
</BoardContextMenu>
|
||||||
<Flex
|
|
||||||
sx={{
|
|
||||||
h: 'full',
|
|
||||||
alignItems: 'center',
|
|
||||||
fontWeight: isSelected ? 600 : undefined,
|
|
||||||
fontSize: 'xs',
|
|
||||||
color: isSelected ? 'base.900' : 'base.700',
|
|
||||||
_dark: { color: isSelected ? 'base.50' : 'base.200' },
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{label}
|
|
||||||
</Flex>
|
|
||||||
</Flex>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -39,6 +39,7 @@ const NoBoardBoard = ({ isSelected }: { isSelected: boolean }) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<GenericBoard
|
<GenericBoard
|
||||||
|
board_id="no_board"
|
||||||
droppableData={droppableData}
|
droppableData={droppableData}
|
||||||
dropLabel={<Text fontSize="md">Move</Text>}
|
dropLabel={<Text fontSize="md">Move</Text>}
|
||||||
onClick={handleClick}
|
onClick={handleClick}
|
||||||
|
@ -0,0 +1,79 @@
|
|||||||
|
import { MenuItem } from '@chakra-ui/react';
|
||||||
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
|
import { stateSelector } from 'app/store/store';
|
||||||
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
|
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
|
||||||
|
import { autoAddBoardIdChanged } from 'features/gallery/store/gallerySlice';
|
||||||
|
import { memo, useCallback, useMemo } from 'react';
|
||||||
|
import { FaMinus, FaPlus, FaTrash } from 'react-icons/fa';
|
||||||
|
import { BoardDTO } from 'services/api/types';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
board: BoardDTO;
|
||||||
|
setBoardToDelete?: (board?: BoardDTO) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
const GalleryBoardContextMenuItems = ({ board, setBoardToDelete }: Props) => {
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
|
const selector = useMemo(
|
||||||
|
() =>
|
||||||
|
createSelector(
|
||||||
|
stateSelector,
|
||||||
|
({ gallery }) => {
|
||||||
|
const isSelectedForAutoAdd =
|
||||||
|
board.board_id === gallery.autoAddBoardId;
|
||||||
|
|
||||||
|
return { isSelectedForAutoAdd };
|
||||||
|
},
|
||||||
|
defaultSelectorOptions
|
||||||
|
),
|
||||||
|
[board.board_id]
|
||||||
|
);
|
||||||
|
|
||||||
|
const { isSelectedForAutoAdd } = useAppSelector(selector);
|
||||||
|
|
||||||
|
const handleDelete = useCallback(() => {
|
||||||
|
if (!setBoardToDelete) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setBoardToDelete(board);
|
||||||
|
}, [board, setBoardToDelete]);
|
||||||
|
|
||||||
|
const handleToggleAutoAdd = useCallback(() => {
|
||||||
|
dispatch(
|
||||||
|
autoAddBoardIdChanged(isSelectedForAutoAdd ? null : board.board_id)
|
||||||
|
);
|
||||||
|
}, [board.board_id, dispatch, isSelectedForAutoAdd]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{board.image_count > 0 && (
|
||||||
|
<>
|
||||||
|
{/* <MenuItem
|
||||||
|
isDisabled={!board.image_count}
|
||||||
|
icon={<FaImages />}
|
||||||
|
onClickCapture={handleAddBoardToBatch}
|
||||||
|
>
|
||||||
|
Add Board to Batch
|
||||||
|
</MenuItem> */}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
<MenuItem
|
||||||
|
icon={isSelectedForAutoAdd ? <FaMinus /> : <FaPlus />}
|
||||||
|
onClickCapture={handleToggleAutoAdd}
|
||||||
|
>
|
||||||
|
{isSelectedForAutoAdd ? 'Disable Auto-Add' : 'Auto-Add to this Board'}
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem
|
||||||
|
sx={{ color: 'error.600', _dark: { color: 'error.300' } }}
|
||||||
|
icon={<FaTrash />}
|
||||||
|
onClickCapture={handleDelete}
|
||||||
|
>
|
||||||
|
Delete Board
|
||||||
|
</MenuItem>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default memo(GalleryBoardContextMenuItems);
|
@ -0,0 +1,12 @@
|
|||||||
|
import { BoardId } from 'features/gallery/store/gallerySlice';
|
||||||
|
import { memo } from 'react';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
board_id: BoardId;
|
||||||
|
};
|
||||||
|
|
||||||
|
const SystemBoardContextMenuItems = ({ board_id }: Props) => {
|
||||||
|
return <></>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default memo(SystemBoardContextMenuItems);
|
@ -1,20 +1,18 @@
|
|||||||
import { ChevronUpIcon } from '@chakra-ui/icons';
|
import { ChevronUpIcon } from '@chakra-ui/icons';
|
||||||
import { Button, Flex, Text } from '@chakra-ui/react';
|
import { Box, Button, Flex, Spacer, Text } from '@chakra-ui/react';
|
||||||
import { createSelector } from '@reduxjs/toolkit';
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
import { stateSelector } from 'app/store/store';
|
import { stateSelector } from 'app/store/store';
|
||||||
import { useAppSelector } from 'app/store/storeHooks';
|
import { useAppSelector } from 'app/store/storeHooks';
|
||||||
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
|
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
|
||||||
import { memo } from 'react';
|
import { memo } from 'react';
|
||||||
import { useListAllBoardsQuery } from 'services/api/endpoints/boards';
|
import { useBoardName } from 'services/api/hooks/useBoardName';
|
||||||
|
|
||||||
const selector = createSelector(
|
const selector = createSelector(
|
||||||
[stateSelector],
|
[stateSelector],
|
||||||
(state) => {
|
(state) => {
|
||||||
const { selectedBoardId } = state.gallery;
|
const { selectedBoardId } = state.gallery;
|
||||||
|
|
||||||
return {
|
return { selectedBoardId };
|
||||||
selectedBoardId,
|
|
||||||
};
|
|
||||||
},
|
},
|
||||||
defaultSelectorOptions
|
defaultSelectorOptions
|
||||||
);
|
);
|
||||||
@ -27,25 +25,7 @@ type Props = {
|
|||||||
const GalleryBoardName = (props: Props) => {
|
const GalleryBoardName = (props: Props) => {
|
||||||
const { isOpen, onToggle } = props;
|
const { isOpen, onToggle } = props;
|
||||||
const { selectedBoardId } = useAppSelector(selector);
|
const { selectedBoardId } = useAppSelector(selector);
|
||||||
const { selectedBoardName } = useListAllBoardsQuery(undefined, {
|
const boardName = useBoardName(selectedBoardId);
|
||||||
selectFromResult: ({ data }) => {
|
|
||||||
let selectedBoardName = '';
|
|
||||||
if (selectedBoardId === 'images') {
|
|
||||||
selectedBoardName = 'All Images';
|
|
||||||
} else if (selectedBoardId === 'assets') {
|
|
||||||
selectedBoardName = 'All Assets';
|
|
||||||
} else if (selectedBoardId === 'no_board') {
|
|
||||||
selectedBoardName = 'No Board';
|
|
||||||
} else if (selectedBoardId === 'batch') {
|
|
||||||
selectedBoardName = 'Batch';
|
|
||||||
} else {
|
|
||||||
const selectedBoard = data?.find((b) => b.board_id === selectedBoardId);
|
|
||||||
selectedBoardName = selectedBoard?.board_name || 'Unknown Board';
|
|
||||||
}
|
|
||||||
|
|
||||||
return { selectedBoardName };
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex
|
<Flex
|
||||||
@ -54,6 +34,8 @@ const GalleryBoardName = (props: Props) => {
|
|||||||
size="sm"
|
size="sm"
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
sx={{
|
sx={{
|
||||||
|
position: 'relative',
|
||||||
|
gap: 2,
|
||||||
w: 'full',
|
w: 'full',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
@ -64,19 +46,22 @@ const GalleryBoardName = (props: Props) => {
|
|||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Text
|
<Spacer />
|
||||||
noOfLines={1}
|
<Box position="relative">
|
||||||
sx={{
|
<Text
|
||||||
w: 'full',
|
noOfLines={1}
|
||||||
fontWeight: 600,
|
sx={{
|
||||||
color: 'base.800',
|
fontWeight: 600,
|
||||||
_dark: {
|
color: 'base.800',
|
||||||
color: 'base.200',
|
_dark: {
|
||||||
},
|
color: 'base.200',
|
||||||
}}
|
},
|
||||||
>
|
}}
|
||||||
{selectedBoardName}
|
>
|
||||||
</Text>
|
{boardName}
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
<Spacer />
|
||||||
<ChevronUpIcon
|
<ChevronUpIcon
|
||||||
sx={{
|
sx={{
|
||||||
transform: isOpen ? 'rotate(0deg)' : 'rotate(180deg)',
|
transform: isOpen ? 'rotate(0deg)' : 'rotate(180deg)',
|
||||||
|
@ -1,19 +1,20 @@
|
|||||||
import { Flex } from '@chakra-ui/react';
|
import { Flex } from '@chakra-ui/react';
|
||||||
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
|
import { stateSelector } from 'app/store/store';
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
|
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
|
||||||
import IAIIconButton from 'common/components/IAIIconButton';
|
import IAIIconButton from 'common/components/IAIIconButton';
|
||||||
import IAIPopover from 'common/components/IAIPopover';
|
import IAIPopover from 'common/components/IAIPopover';
|
||||||
import IAISimpleCheckbox from 'common/components/IAISimpleCheckbox';
|
import IAISimpleCheckbox from 'common/components/IAISimpleCheckbox';
|
||||||
import IAISlider from 'common/components/IAISlider';
|
import IAISlider from 'common/components/IAISlider';
|
||||||
import { setGalleryImageMinimumWidth } from 'features/gallery/store/gallerySlice';
|
import {
|
||||||
|
setGalleryImageMinimumWidth,
|
||||||
|
shouldAutoSwitchChanged,
|
||||||
|
} from 'features/gallery/store/gallerySlice';
|
||||||
import { ChangeEvent } from 'react';
|
import { ChangeEvent } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { FaWrench } from 'react-icons/fa';
|
import { FaWrench } from 'react-icons/fa';
|
||||||
|
import BoardAutoAddSelect from './Boards/BoardAutoAddSelect';
|
||||||
import { createSelector } from '@reduxjs/toolkit';
|
|
||||||
import { stateSelector } from 'app/store/store';
|
|
||||||
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
|
|
||||||
import { shouldAutoSwitchChanged } from 'features/gallery/store/gallerySlice';
|
|
||||||
|
|
||||||
const selector = createSelector(
|
const selector = createSelector(
|
||||||
[stateSelector],
|
[stateSelector],
|
||||||
@ -50,7 +51,7 @@ const GallerySettingsPopover = () => {
|
|||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Flex direction="column" gap={2}>
|
<Flex direction="column" gap={4}>
|
||||||
<IAISlider
|
<IAISlider
|
||||||
value={galleryImageMinimumWidth}
|
value={galleryImageMinimumWidth}
|
||||||
onChange={handleChangeGalleryImageMinimumWidth}
|
onChange={handleChangeGalleryImageMinimumWidth}
|
||||||
@ -68,6 +69,7 @@ const GallerySettingsPopover = () => {
|
|||||||
dispatch(shouldAutoSwitchChanged(e.target.checked))
|
dispatch(shouldAutoSwitchChanged(e.target.checked))
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
<BoardAutoAddSelect />
|
||||||
</Flex>
|
</Flex>
|
||||||
</IAIPopover>
|
</IAIPopover>
|
||||||
);
|
);
|
||||||
|
@ -25,6 +25,7 @@ export type BoardId =
|
|||||||
type GalleryState = {
|
type GalleryState = {
|
||||||
selection: string[];
|
selection: string[];
|
||||||
shouldAutoSwitch: boolean;
|
shouldAutoSwitch: boolean;
|
||||||
|
autoAddBoardId: string | null;
|
||||||
galleryImageMinimumWidth: number;
|
galleryImageMinimumWidth: number;
|
||||||
selectedBoardId: BoardId;
|
selectedBoardId: BoardId;
|
||||||
batchImageNames: string[];
|
batchImageNames: string[];
|
||||||
@ -34,6 +35,7 @@ type GalleryState = {
|
|||||||
export const initialGalleryState: GalleryState = {
|
export const initialGalleryState: GalleryState = {
|
||||||
selection: [],
|
selection: [],
|
||||||
shouldAutoSwitch: true,
|
shouldAutoSwitch: true,
|
||||||
|
autoAddBoardId: null,
|
||||||
galleryImageMinimumWidth: 96,
|
galleryImageMinimumWidth: 96,
|
||||||
selectedBoardId: 'images',
|
selectedBoardId: 'images',
|
||||||
batchImageNames: [],
|
batchImageNames: [],
|
||||||
@ -123,14 +125,34 @@ export const gallerySlice = createSlice({
|
|||||||
state.batchImageNames = [];
|
state.batchImageNames = [];
|
||||||
state.selection = [];
|
state.selection = [];
|
||||||
},
|
},
|
||||||
|
autoAddBoardIdChanged: (state, action: PayloadAction<string | null>) => {
|
||||||
|
state.autoAddBoardId = action.payload;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
extraReducers: (builder) => {
|
extraReducers: (builder) => {
|
||||||
builder.addMatcher(
|
builder.addMatcher(
|
||||||
boardsApi.endpoints.deleteBoard.matchFulfilled,
|
boardsApi.endpoints.deleteBoard.matchFulfilled,
|
||||||
(state, action) => {
|
(state, action) => {
|
||||||
if (action.meta.arg.originalArgs === state.selectedBoardId) {
|
const deletedBoardId = action.meta.arg.originalArgs;
|
||||||
|
if (deletedBoardId === state.selectedBoardId) {
|
||||||
state.selectedBoardId = 'images';
|
state.selectedBoardId = 'images';
|
||||||
}
|
}
|
||||||
|
if (deletedBoardId === state.autoAddBoardId) {
|
||||||
|
state.autoAddBoardId = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
builder.addMatcher(
|
||||||
|
boardsApi.endpoints.listAllBoards.matchFulfilled,
|
||||||
|
(state, action) => {
|
||||||
|
const boards = action.payload;
|
||||||
|
if (!state.autoAddBoardId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!boards.map((b) => b.board_id).includes(state.autoAddBoardId)) {
|
||||||
|
state.autoAddBoardId = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@ -147,6 +169,7 @@ export const {
|
|||||||
isBatchEnabledChanged,
|
isBatchEnabledChanged,
|
||||||
imagesAddedToBatch,
|
imagesAddedToBatch,
|
||||||
imagesRemovedFromBatch,
|
imagesRemovedFromBatch,
|
||||||
|
autoAddBoardIdChanged,
|
||||||
} = gallerySlice.actions;
|
} = gallerySlice.actions;
|
||||||
|
|
||||||
export default gallerySlice.reducer;
|
export default gallerySlice.reducer;
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
import { Box, ChakraProps } from '@chakra-ui/react';
|
import { Box, ChakraProps, Tooltip } from '@chakra-ui/react';
|
||||||
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
import { userInvoked } from 'app/store/actions';
|
import { userInvoked } from 'app/store/actions';
|
||||||
|
import { stateSelector } from 'app/store/store';
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
|
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
|
||||||
import IAIButton, { IAIButtonProps } from 'common/components/IAIButton';
|
import IAIButton, { IAIButtonProps } from 'common/components/IAIButton';
|
||||||
import IAIIconButton, {
|
import IAIIconButton, {
|
||||||
IAIIconButtonProps,
|
IAIIconButtonProps,
|
||||||
@ -8,11 +11,13 @@ import IAIIconButton, {
|
|||||||
import { useIsReadyToInvoke } from 'common/hooks/useIsReadyToInvoke';
|
import { useIsReadyToInvoke } from 'common/hooks/useIsReadyToInvoke';
|
||||||
import { clampSymmetrySteps } from 'features/parameters/store/generationSlice';
|
import { clampSymmetrySteps } from 'features/parameters/store/generationSlice';
|
||||||
import ProgressBar from 'features/system/components/ProgressBar';
|
import ProgressBar from 'features/system/components/ProgressBar';
|
||||||
|
import { selectIsBusy } from 'features/system/store/systemSelectors';
|
||||||
import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
|
import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
|
||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
import { useHotkeys } from 'react-hotkeys-hook';
|
import { useHotkeys } from 'react-hotkeys-hook';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { FaPlay } from 'react-icons/fa';
|
import { FaPlay } from 'react-icons/fa';
|
||||||
|
import { useBoardName } from 'services/api/hooks/useBoardName';
|
||||||
|
|
||||||
const IN_PROGRESS_STYLES: ChakraProps['sx'] = {
|
const IN_PROGRESS_STYLES: ChakraProps['sx'] = {
|
||||||
_disabled: {
|
_disabled: {
|
||||||
@ -26,6 +31,20 @@ const IN_PROGRESS_STYLES: ChakraProps['sx'] = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const selector = createSelector(
|
||||||
|
[stateSelector, activeTabNameSelector, selectIsBusy],
|
||||||
|
({ gallery }, activeTabName, isBusy) => {
|
||||||
|
const { autoAddBoardId } = gallery;
|
||||||
|
|
||||||
|
return {
|
||||||
|
isBusy,
|
||||||
|
autoAddBoardId,
|
||||||
|
activeTabName,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
defaultSelectorOptions
|
||||||
|
);
|
||||||
|
|
||||||
interface InvokeButton
|
interface InvokeButton
|
||||||
extends Omit<IAIButtonProps | IAIIconButtonProps, 'aria-label'> {
|
extends Omit<IAIButtonProps | IAIIconButtonProps, 'aria-label'> {
|
||||||
iconButton?: boolean;
|
iconButton?: boolean;
|
||||||
@ -35,8 +54,8 @@ export default function InvokeButton(props: InvokeButton) {
|
|||||||
const { iconButton = false, ...rest } = props;
|
const { iconButton = false, ...rest } = props;
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const isReady = useIsReadyToInvoke();
|
const isReady = useIsReadyToInvoke();
|
||||||
const activeTabName = useAppSelector(activeTabNameSelector);
|
const { isBusy, autoAddBoardId, activeTabName } = useAppSelector(selector);
|
||||||
const isProcessing = useAppSelector((state) => state.system.isProcessing);
|
const autoAddBoardName = useBoardName(autoAddBoardId);
|
||||||
|
|
||||||
const handleInvoke = useCallback(() => {
|
const handleInvoke = useCallback(() => {
|
||||||
dispatch(clampSymmetrySteps());
|
dispatch(clampSymmetrySteps());
|
||||||
@ -75,43 +94,52 @@ export default function InvokeButton(props: InvokeButton) {
|
|||||||
<ProgressBar />
|
<ProgressBar />
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
{iconButton ? (
|
<Tooltip
|
||||||
<IAIIconButton
|
placement="top"
|
||||||
aria-label={t('parameters.invoke')}
|
hasArrow
|
||||||
type="submit"
|
openDelay={500}
|
||||||
icon={<FaPlay />}
|
label={
|
||||||
isDisabled={!isReady || isProcessing}
|
autoAddBoardId ? `Auto-Adding to ${autoAddBoardName}` : undefined
|
||||||
onClick={handleInvoke}
|
}
|
||||||
tooltip={t('parameters.invoke')}
|
>
|
||||||
tooltipProps={{ placement: 'top' }}
|
{iconButton ? (
|
||||||
colorScheme="accent"
|
<IAIIconButton
|
||||||
id="invoke-button"
|
aria-label={t('parameters.invoke')}
|
||||||
{...rest}
|
type="submit"
|
||||||
sx={{
|
icon={<FaPlay />}
|
||||||
w: 'full',
|
isDisabled={!isReady || isBusy}
|
||||||
flexGrow: 1,
|
onClick={handleInvoke}
|
||||||
...(isProcessing ? IN_PROGRESS_STYLES : {}),
|
tooltip={t('parameters.invoke')}
|
||||||
}}
|
tooltipProps={{ placement: 'top' }}
|
||||||
/>
|
colorScheme="accent"
|
||||||
) : (
|
id="invoke-button"
|
||||||
<IAIButton
|
{...rest}
|
||||||
aria-label={t('parameters.invoke')}
|
sx={{
|
||||||
type="submit"
|
w: 'full',
|
||||||
isDisabled={!isReady || isProcessing}
|
flexGrow: 1,
|
||||||
onClick={handleInvoke}
|
...(isBusy ? IN_PROGRESS_STYLES : {}),
|
||||||
colorScheme="accent"
|
}}
|
||||||
id="invoke-button"
|
/>
|
||||||
{...rest}
|
) : (
|
||||||
sx={{
|
<IAIButton
|
||||||
w: 'full',
|
aria-label={t('parameters.invoke')}
|
||||||
flexGrow: 1,
|
type="submit"
|
||||||
fontWeight: 700,
|
isDisabled={!isReady || isBusy}
|
||||||
...(isProcessing ? IN_PROGRESS_STYLES : {}),
|
onClick={handleInvoke}
|
||||||
}}
|
colorScheme="accent"
|
||||||
>
|
id="invoke-button"
|
||||||
Invoke
|
{...rest}
|
||||||
</IAIButton>
|
sx={{
|
||||||
)}
|
w: 'full',
|
||||||
|
flexGrow: 1,
|
||||||
|
fontWeight: 700,
|
||||||
|
...(isBusy ? IN_PROGRESS_STYLES : {}),
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Invoke
|
||||||
|
</IAIButton>
|
||||||
|
)}
|
||||||
|
</Tooltip>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
|
@ -1,33 +0,0 @@
|
|||||||
import { createSelector } from '@reduxjs/toolkit';
|
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
|
||||||
import IAIIconButton from 'common/components/IAIIconButton';
|
|
||||||
import { postprocessingSelector } from 'features/parameters/store/postprocessingSelectors';
|
|
||||||
import { setShouldLoopback } from 'features/parameters/store/postprocessingSlice';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
import { FaRecycle } from 'react-icons/fa';
|
|
||||||
|
|
||||||
const loopbackSelector = createSelector(
|
|
||||||
postprocessingSelector,
|
|
||||||
({ shouldLoopback }) => shouldLoopback
|
|
||||||
);
|
|
||||||
|
|
||||||
const LoopbackButton = () => {
|
|
||||||
const dispatch = useAppDispatch();
|
|
||||||
const shouldLoopback = useAppSelector(loopbackSelector);
|
|
||||||
|
|
||||||
const { t } = useTranslation();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<IAIIconButton
|
|
||||||
aria-label={t('parameters.toggleLoopback')}
|
|
||||||
tooltip={t('parameters.toggleLoopback')}
|
|
||||||
isChecked={shouldLoopback}
|
|
||||||
icon={<FaRecycle />}
|
|
||||||
onClick={() => {
|
|
||||||
dispatch(setShouldLoopback(!shouldLoopback));
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default LoopbackButton;
|
|
@ -9,7 +9,6 @@ const ProcessButtons = () => {
|
|||||||
return (
|
return (
|
||||||
<Flex gap={2}>
|
<Flex gap={2}>
|
||||||
<InvokeButton />
|
<InvokeButton />
|
||||||
{/* {activeTabName === 'img2img' && <LoopbackButton />} */}
|
|
||||||
<CancelButton />
|
<CancelButton />
|
||||||
</Flex>
|
</Flex>
|
||||||
);
|
);
|
||||||
|
@ -1,60 +1,71 @@
|
|||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
|
||||||
import { useCallback, useEffect, useState } from 'react';
|
|
||||||
import { StyledFlex } from './SettingsModal';
|
|
||||||
import { Heading, Text } from '@chakra-ui/react';
|
import { Heading, Text } from '@chakra-ui/react';
|
||||||
|
import { useAppDispatch } from 'app/store/storeHooks';
|
||||||
|
import { useCallback, useEffect } from 'react';
|
||||||
import IAIButton from '../../../../common/components/IAIButton';
|
import IAIButton from '../../../../common/components/IAIButton';
|
||||||
import { useClearIntermediatesMutation } from '../../../../services/api/endpoints/images';
|
import {
|
||||||
import { addToast } from '../../store/systemSlice';
|
useClearIntermediatesMutation,
|
||||||
|
useGetIntermediatesCountQuery,
|
||||||
|
} from '../../../../services/api/endpoints/images';
|
||||||
import { resetCanvas } from '../../../canvas/store/canvasSlice';
|
import { resetCanvas } from '../../../canvas/store/canvasSlice';
|
||||||
|
import { addToast } from '../../store/systemSlice';
|
||||||
|
import { StyledFlex } from './SettingsModal';
|
||||||
|
import { controlNetReset } from 'features/controlNet/store/controlNetSlice';
|
||||||
|
|
||||||
export default function SettingsClearIntermediates() {
|
export default function SettingsClearIntermediates() {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const [isDisabled, setIsDisabled] = useState(false);
|
|
||||||
|
const { data: intermediatesCount, refetch: updateIntermediatesCount } =
|
||||||
|
useGetIntermediatesCountQuery();
|
||||||
|
|
||||||
const [clearIntermediates, { isLoading: isLoadingClearIntermediates }] =
|
const [clearIntermediates, { isLoading: isLoadingClearIntermediates }] =
|
||||||
useClearIntermediatesMutation();
|
useClearIntermediatesMutation();
|
||||||
|
|
||||||
const handleClickClearIntermediates = useCallback(() => {
|
const handleClickClearIntermediates = useCallback(() => {
|
||||||
clearIntermediates({})
|
clearIntermediates()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
|
dispatch(controlNetReset());
|
||||||
dispatch(resetCanvas());
|
dispatch(resetCanvas());
|
||||||
dispatch(
|
dispatch(
|
||||||
addToast({
|
addToast({
|
||||||
title:
|
title: `Cleared ${response} intermediates`,
|
||||||
response === 0
|
|
||||||
? `No intermediates to clear`
|
|
||||||
: `Successfully cleared ${response} intermediates`,
|
|
||||||
status: 'info',
|
status: 'info',
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
if (response < 100) {
|
|
||||||
setIsDisabled(true);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}, [clearIntermediates, dispatch]);
|
}, [clearIntermediates, dispatch]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// update the count on mount
|
||||||
|
updateIntermediatesCount();
|
||||||
|
}, [updateIntermediatesCount]);
|
||||||
|
|
||||||
|
const buttonText = intermediatesCount
|
||||||
|
? `Clear ${intermediatesCount} Intermediate${
|
||||||
|
intermediatesCount > 1 ? 's' : ''
|
||||||
|
}`
|
||||||
|
: 'No Intermediates to Clear';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledFlex>
|
<StyledFlex>
|
||||||
<Heading size="sm">Clear Intermediates</Heading>
|
<Heading size="sm">Clear Intermediates</Heading>
|
||||||
<IAIButton
|
<IAIButton
|
||||||
colorScheme="error"
|
colorScheme="warning"
|
||||||
onClick={handleClickClearIntermediates}
|
onClick={handleClickClearIntermediates}
|
||||||
isLoading={isLoadingClearIntermediates}
|
isLoading={isLoadingClearIntermediates}
|
||||||
isDisabled={isDisabled}
|
isDisabled={!intermediatesCount}
|
||||||
>
|
>
|
||||||
{isDisabled ? 'Intermediates Cleared' : 'Clear 100 Intermediates'}
|
{buttonText}
|
||||||
</IAIButton>
|
</IAIButton>
|
||||||
<Text>
|
<Text fontWeight="bold">
|
||||||
Will permanently delete first 100 intermediates found on disk and in
|
Clearing intermediates will reset your Canvas and ControlNet state.
|
||||||
database
|
|
||||||
</Text>
|
</Text>
|
||||||
<Text fontWeight="bold">This will also clear your canvas state.</Text>
|
<Text variant="subtext">
|
||||||
<Text>
|
|
||||||
Intermediate images are byproducts of generation, different from the
|
Intermediate images are byproducts of generation, different from the
|
||||||
result images in the gallery. Purging intermediates will free disk
|
result images in the gallery. Clearing intermediates will free disk
|
||||||
space. Your gallery images will not be deleted.
|
space.
|
||||||
</Text>
|
</Text>
|
||||||
|
<Text variant="subtext">Your gallery images will not be deleted.</Text>
|
||||||
</StyledFlex>
|
</StyledFlex>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -24,7 +24,6 @@ import {
|
|||||||
setEnableImageDebugging,
|
setEnableImageDebugging,
|
||||||
setIsNodesEnabled,
|
setIsNodesEnabled,
|
||||||
setShouldConfirmOnDelete,
|
setShouldConfirmOnDelete,
|
||||||
setShouldDisplayGuides,
|
|
||||||
shouldAntialiasProgressImageChanged,
|
shouldAntialiasProgressImageChanged,
|
||||||
shouldLogToConsoleChanged,
|
shouldLogToConsoleChanged,
|
||||||
} from 'features/system/store/systemSlice';
|
} from 'features/system/store/systemSlice';
|
||||||
@ -56,7 +55,6 @@ const selector = createSelector(
|
|||||||
(system: SystemState, ui: UIState) => {
|
(system: SystemState, ui: UIState) => {
|
||||||
const {
|
const {
|
||||||
shouldConfirmOnDelete,
|
shouldConfirmOnDelete,
|
||||||
shouldDisplayGuides,
|
|
||||||
enableImageDebugging,
|
enableImageDebugging,
|
||||||
consoleLogLevel,
|
consoleLogLevel,
|
||||||
shouldLogToConsole,
|
shouldLogToConsole,
|
||||||
@ -73,7 +71,6 @@ const selector = createSelector(
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
shouldConfirmOnDelete,
|
shouldConfirmOnDelete,
|
||||||
shouldDisplayGuides,
|
|
||||||
enableImageDebugging,
|
enableImageDebugging,
|
||||||
shouldUseCanvasBetaLayout,
|
shouldUseCanvasBetaLayout,
|
||||||
shouldUseSliders,
|
shouldUseSliders,
|
||||||
@ -139,7 +136,6 @@ const SettingsModal = ({ children, config }: SettingsModalProps) => {
|
|||||||
|
|
||||||
const {
|
const {
|
||||||
shouldConfirmOnDelete,
|
shouldConfirmOnDelete,
|
||||||
shouldDisplayGuides,
|
|
||||||
enableImageDebugging,
|
enableImageDebugging,
|
||||||
shouldUseCanvasBetaLayout,
|
shouldUseCanvasBetaLayout,
|
||||||
shouldUseSliders,
|
shouldUseSliders,
|
||||||
@ -195,7 +191,7 @@ const SettingsModal = ({ children, config }: SettingsModalProps) => {
|
|||||||
<Modal
|
<Modal
|
||||||
isOpen={isSettingsModalOpen}
|
isOpen={isSettingsModalOpen}
|
||||||
onClose={onSettingsModalClose}
|
onClose={onSettingsModalClose}
|
||||||
size="xl"
|
size="2xl"
|
||||||
isCentered
|
isCentered
|
||||||
>
|
>
|
||||||
<ModalOverlay />
|
<ModalOverlay />
|
||||||
@ -231,14 +227,6 @@ const SettingsModal = ({ children, config }: SettingsModalProps) => {
|
|||||||
|
|
||||||
<StyledFlex>
|
<StyledFlex>
|
||||||
<Heading size="sm">{t('settings.ui')}</Heading>
|
<Heading size="sm">{t('settings.ui')}</Heading>
|
||||||
<SettingSwitch
|
|
||||||
label={t('settings.displayHelpIcons')}
|
|
||||||
isChecked={shouldDisplayGuides}
|
|
||||||
onChange={(e: ChangeEvent<HTMLInputElement>) =>
|
|
||||||
dispatch(setShouldDisplayGuides(e.target.checked))
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<SettingSwitch
|
<SettingSwitch
|
||||||
label={t('settings.useSlidersForAll')}
|
label={t('settings.useSlidersForAll')}
|
||||||
isChecked={shouldUseSliders}
|
isChecked={shouldUseSliders}
|
||||||
@ -317,8 +305,12 @@ const SettingsModal = ({ children, config }: SettingsModalProps) => {
|
|||||||
</IAIButton>
|
</IAIButton>
|
||||||
{shouldShowResetWebUiText && (
|
{shouldShowResetWebUiText && (
|
||||||
<>
|
<>
|
||||||
<Text>{t('settings.resetWebUIDesc1')}</Text>
|
<Text variant="subtext">
|
||||||
<Text>{t('settings.resetWebUIDesc2')}</Text>
|
{t('settings.resetWebUIDesc1')}
|
||||||
|
</Text>
|
||||||
|
<Text variant="subtext">
|
||||||
|
{t('settings.resetWebUIDesc2')}
|
||||||
|
</Text>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</StyledFlex>
|
</StyledFlex>
|
||||||
|
@ -38,7 +38,6 @@ export interface SystemState {
|
|||||||
currentIteration: number;
|
currentIteration: number;
|
||||||
totalIterations: number;
|
totalIterations: number;
|
||||||
currentStatusHasSteps: boolean;
|
currentStatusHasSteps: boolean;
|
||||||
shouldDisplayGuides: boolean;
|
|
||||||
isCancelable: boolean;
|
isCancelable: boolean;
|
||||||
enableImageDebugging: boolean;
|
enableImageDebugging: boolean;
|
||||||
toastQueue: UseToastOptions[];
|
toastQueue: UseToastOptions[];
|
||||||
@ -84,14 +83,12 @@ export interface SystemState {
|
|||||||
shouldAntialiasProgressImage: boolean;
|
shouldAntialiasProgressImage: boolean;
|
||||||
language: keyof typeof LANGUAGES;
|
language: keyof typeof LANGUAGES;
|
||||||
isUploading: boolean;
|
isUploading: boolean;
|
||||||
boardIdToAddTo?: string;
|
|
||||||
isNodesEnabled: boolean;
|
isNodesEnabled: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const initialSystemState: SystemState = {
|
export const initialSystemState: SystemState = {
|
||||||
isConnected: false,
|
isConnected: false,
|
||||||
isProcessing: false,
|
isProcessing: false,
|
||||||
shouldDisplayGuides: true,
|
|
||||||
isGFPGANAvailable: true,
|
isGFPGANAvailable: true,
|
||||||
isESRGANAvailable: true,
|
isESRGANAvailable: true,
|
||||||
shouldConfirmOnDelete: true,
|
shouldConfirmOnDelete: true,
|
||||||
@ -134,9 +131,6 @@ export const systemSlice = createSlice({
|
|||||||
setShouldConfirmOnDelete: (state, action: PayloadAction<boolean>) => {
|
setShouldConfirmOnDelete: (state, action: PayloadAction<boolean>) => {
|
||||||
state.shouldConfirmOnDelete = action.payload;
|
state.shouldConfirmOnDelete = action.payload;
|
||||||
},
|
},
|
||||||
setShouldDisplayGuides: (state, action: PayloadAction<boolean>) => {
|
|
||||||
state.shouldDisplayGuides = action.payload;
|
|
||||||
},
|
|
||||||
setIsCancelable: (state, action: PayloadAction<boolean>) => {
|
setIsCancelable: (state, action: PayloadAction<boolean>) => {
|
||||||
state.isCancelable = action.payload;
|
state.isCancelable = action.payload;
|
||||||
},
|
},
|
||||||
@ -204,7 +198,6 @@ export const systemSlice = createSlice({
|
|||||||
*/
|
*/
|
||||||
builder.addCase(appSocketSubscribed, (state, action) => {
|
builder.addCase(appSocketSubscribed, (state, action) => {
|
||||||
state.sessionId = action.payload.sessionId;
|
state.sessionId = action.payload.sessionId;
|
||||||
state.boardIdToAddTo = action.payload.boardId;
|
|
||||||
state.canceledSession = '';
|
state.canceledSession = '';
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -213,7 +206,6 @@ export const systemSlice = createSlice({
|
|||||||
*/
|
*/
|
||||||
builder.addCase(appSocketUnsubscribed, (state) => {
|
builder.addCase(appSocketUnsubscribed, (state) => {
|
||||||
state.sessionId = null;
|
state.sessionId = null;
|
||||||
state.boardIdToAddTo = undefined;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -390,7 +382,6 @@ export const {
|
|||||||
setIsProcessing,
|
setIsProcessing,
|
||||||
setShouldConfirmOnDelete,
|
setShouldConfirmOnDelete,
|
||||||
setCurrentStatus,
|
setCurrentStatus,
|
||||||
setShouldDisplayGuides,
|
|
||||||
setIsCancelable,
|
setIsCancelable,
|
||||||
setEnableImageDebugging,
|
setEnableImageDebugging,
|
||||||
addToast,
|
addToast,
|
||||||
|
@ -98,16 +98,7 @@ export default function ModelListItem(props: ModelListItemProps) {
|
|||||||
onClick={handleSelectModel}
|
onClick={handleSelectModel}
|
||||||
>
|
>
|
||||||
<Flex gap={4} alignItems="center">
|
<Flex gap={4} alignItems="center">
|
||||||
<Badge
|
<Badge minWidth={14} p={0.5} fontSize="sm" variant="solid">
|
||||||
minWidth={14}
|
|
||||||
p={1}
|
|
||||||
fontSize="sm"
|
|
||||||
sx={{
|
|
||||||
bg: 'base.350',
|
|
||||||
color: 'base.900',
|
|
||||||
_dark: { bg: 'base.500' },
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{
|
{
|
||||||
modelBaseTypeMap[
|
modelBaseTypeMap[
|
||||||
model.base_model as keyof typeof modelBaseTypeMap
|
model.base_model as keyof typeof modelBaseTypeMap
|
||||||
|
@ -127,6 +127,13 @@ export const imagesApi = api.injectEndpoints({
|
|||||||
// 24 hours - reducing this to a few minutes would reduce memory usage.
|
// 24 hours - reducing this to a few minutes would reduce memory usage.
|
||||||
keepUnusedDataFor: 86400,
|
keepUnusedDataFor: 86400,
|
||||||
}),
|
}),
|
||||||
|
getIntermediatesCount: build.query<number, void>({
|
||||||
|
query: () => ({ url: getListImagesUrl({ is_intermediate: true }) }),
|
||||||
|
providesTags: ['IntermediatesCount'],
|
||||||
|
transformResponse: (response: OffsetPaginatedResults_ImageDTO_) => {
|
||||||
|
return response.total;
|
||||||
|
},
|
||||||
|
}),
|
||||||
getImageDTO: build.query<ImageDTO, string>({
|
getImageDTO: build.query<ImageDTO, string>({
|
||||||
query: (image_name) => ({ url: `images/${image_name}` }),
|
query: (image_name) => ({ url: `images/${image_name}` }),
|
||||||
providesTags: (result, error, arg) => {
|
providesTags: (result, error, arg) => {
|
||||||
@ -148,8 +155,9 @@ export const imagesApi = api.injectEndpoints({
|
|||||||
},
|
},
|
||||||
keepUnusedDataFor: 86400, // 24 hours
|
keepUnusedDataFor: 86400, // 24 hours
|
||||||
}),
|
}),
|
||||||
clearIntermediates: build.mutation({
|
clearIntermediates: build.mutation<number, void>({
|
||||||
query: () => ({ url: `images/clear-intermediates`, method: 'POST' }),
|
query: () => ({ url: `images/clear-intermediates`, method: 'POST' }),
|
||||||
|
invalidatesTags: ['IntermediatesCount'],
|
||||||
}),
|
}),
|
||||||
deleteImage: build.mutation<void, ImageDTO>({
|
deleteImage: build.mutation<void, ImageDTO>({
|
||||||
query: ({ image_name }) => ({
|
query: ({ image_name }) => ({
|
||||||
@ -617,6 +625,7 @@ export const imagesApi = api.injectEndpoints({
|
|||||||
});
|
});
|
||||||
|
|
||||||
export const {
|
export const {
|
||||||
|
useGetIntermediatesCountQuery,
|
||||||
useListImagesQuery,
|
useListImagesQuery,
|
||||||
useLazyListImagesQuery,
|
useLazyListImagesQuery,
|
||||||
useGetImageDTOQuery,
|
useGetImageDTOQuery,
|
||||||
|
26
invokeai/frontend/web/src/services/api/hooks/useBoardName.ts
Normal file
26
invokeai/frontend/web/src/services/api/hooks/useBoardName.ts
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import { BoardId } from 'features/gallery/store/gallerySlice';
|
||||||
|
import { useListAllBoardsQuery } from '../endpoints/boards';
|
||||||
|
|
||||||
|
export const useBoardName = (board_id: BoardId | null | undefined) => {
|
||||||
|
const { boardName } = useListAllBoardsQuery(undefined, {
|
||||||
|
selectFromResult: ({ data }) => {
|
||||||
|
let boardName = '';
|
||||||
|
if (board_id === 'images') {
|
||||||
|
boardName = 'All Images';
|
||||||
|
} else if (board_id === 'assets') {
|
||||||
|
boardName = 'All Assets';
|
||||||
|
} else if (board_id === 'no_board') {
|
||||||
|
boardName = 'No Board';
|
||||||
|
} else if (board_id === 'batch') {
|
||||||
|
boardName = 'Batch';
|
||||||
|
} else {
|
||||||
|
const selectedBoard = data?.find((b) => b.board_id === board_id);
|
||||||
|
boardName = selectedBoard?.board_name || 'Unknown Board';
|
||||||
|
}
|
||||||
|
|
||||||
|
return { boardName };
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return boardName;
|
||||||
|
};
|
Loading…
Reference in New Issue
Block a user