mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
Merge branch 'main' into fix/post-model-sync
This commit is contained in:
@ -552,7 +552,8 @@
|
||||
"saveSteps": "Save images every n steps",
|
||||
"confirmOnDelete": "Confirm On Delete",
|
||||
"displayHelpIcons": "Display Help Icons",
|
||||
"useCanvasBeta": "Use Canvas Beta Layout",
|
||||
"alternateCanvasLayout": "Alternate Canvas Layout",
|
||||
"enableNodesEditor": "Enable Nodes Editor",
|
||||
"enableImageDebugging": "Enable Image Debugging",
|
||||
"useSlidersForAll": "Use Sliders For All Options",
|
||||
"showProgressInViewer": "Show Progress Images in Viewer",
|
||||
@ -569,7 +570,9 @@
|
||||
"ui": "User Interface",
|
||||
"favoriteSchedulers": "Favorite Schedulers",
|
||||
"favoriteSchedulersPlaceholder": "No schedulers favorited",
|
||||
"showAdvancedOptions": "Show Advanced Options"
|
||||
"showAdvancedOptions": "Show Advanced Options",
|
||||
"experimental": "Experimental",
|
||||
"beta": "Beta"
|
||||
},
|
||||
"toast": {
|
||||
"serverError": "Server Error",
|
||||
|
@ -6,11 +6,7 @@ import {
|
||||
imageSelected,
|
||||
} from 'features/gallery/store/gallerySlice';
|
||||
import { progressImageSet } from 'features/system/store/systemSlice';
|
||||
import {
|
||||
SYSTEM_BOARDS,
|
||||
imagesAdapter,
|
||||
imagesApi,
|
||||
} from 'services/api/endpoints/images';
|
||||
import { imagesAdapter, imagesApi } from 'services/api/endpoints/images';
|
||||
import { isImageOutput } from 'services/api/guards';
|
||||
import { sessionCanceled } from 'services/api/thunks/session';
|
||||
import {
|
||||
@ -32,8 +28,7 @@ export const addInvocationCompleteEventListener = () => {
|
||||
);
|
||||
const session_id = action.payload.data.graph_execution_state_id;
|
||||
|
||||
const { cancelType, isCancelScheduled, boardIdToAddTo } =
|
||||
getState().system;
|
||||
const { cancelType, isCancelScheduled } = getState().system;
|
||||
|
||||
// Handle scheduled cancelation
|
||||
if (cancelType === 'scheduled' && isCancelScheduled) {
|
||||
@ -88,26 +83,28 @@ export const addInvocationCompleteEventListener = () => {
|
||||
)
|
||||
);
|
||||
|
||||
// add image to the board if we had one selected
|
||||
if (boardIdToAddTo && !SYSTEM_BOARDS.includes(boardIdToAddTo)) {
|
||||
const { autoAddBoardId } = gallery;
|
||||
|
||||
// add image to the board if auto-add is enabled
|
||||
if (autoAddBoardId) {
|
||||
dispatch(
|
||||
imagesApi.endpoints.addImageToBoard.initiate({
|
||||
board_id: boardIdToAddTo,
|
||||
board_id: autoAddBoardId,
|
||||
imageDTO,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
const { selectedBoardId } = gallery;
|
||||
|
||||
if (boardIdToAddTo && boardIdToAddTo !== selectedBoardId) {
|
||||
dispatch(boardIdSelected(boardIdToAddTo));
|
||||
} else if (!boardIdToAddTo) {
|
||||
dispatch(boardIdSelected('all'));
|
||||
}
|
||||
const { selectedBoardId, shouldAutoSwitch } = gallery;
|
||||
|
||||
// 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));
|
||||
}
|
||||
}
|
||||
|
@ -9,7 +9,7 @@ import {
|
||||
} from '@chakra-ui/react';
|
||||
import { memo } from 'react';
|
||||
|
||||
interface Props extends SwitchProps {
|
||||
export interface IAISwitchProps extends SwitchProps {
|
||||
label?: string;
|
||||
width?: string | number;
|
||||
formControlProps?: FormControlProps;
|
||||
@ -20,7 +20,7 @@ interface Props extends SwitchProps {
|
||||
/**
|
||||
* Customized Chakra FormControl + Switch multi-part component.
|
||||
*/
|
||||
const IAISwitch = (props: Props) => {
|
||||
const IAISwitch = (props: IAISwitchProps) => {
|
||||
const {
|
||||
label,
|
||||
isDisabled = false,
|
||||
|
@ -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 { FaPlus } from 'react-icons/fa';
|
||||
import { useCreateBoardMutation } from 'services/api/endpoints/boards';
|
||||
|
||||
const DEFAULT_BOARD_NAME = 'My Board';
|
||||
@ -12,15 +13,14 @@ const AddBoardButton = () => {
|
||||
}, [createBoard]);
|
||||
|
||||
return (
|
||||
<IAIButton
|
||||
<IAIIconButton
|
||||
icon={<FaPlus />}
|
||||
isLoading={isLoading}
|
||||
tooltip="Add Board"
|
||||
aria-label="Add Board"
|
||||
onClick={handleCreateBoard}
|
||||
size="sm"
|
||||
sx={{ px: 4 }}
|
||||
>
|
||||
Add Board
|
||||
</IAIButton>
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -38,6 +38,7 @@ const AllAssetsBoard = ({ isSelected }: { isSelected: boolean }) => {
|
||||
|
||||
return (
|
||||
<GenericBoard
|
||||
board_id="assets"
|
||||
onClick={handleClick}
|
||||
isSelected={isSelected}
|
||||
icon={FaFileImage}
|
||||
|
@ -38,6 +38,7 @@ const AllImagesBoard = ({ isSelected }: { isSelected: boolean }) => {
|
||||
|
||||
return (
|
||||
<GenericBoard
|
||||
board_id="images"
|
||||
onClick={handleClick}
|
||||
isSelected={isSelected}
|
||||
icon={FaImages}
|
||||
|
@ -29,6 +29,7 @@ const BatchBoard = ({ isSelected }: { isSelected: boolean }) => {
|
||||
|
||||
return (
|
||||
<GenericBoard
|
||||
board_id="batch"
|
||||
droppableData={droppableData}
|
||||
onClick={handleBatchBoardClick}
|
||||
isSelected={isSelected}
|
||||
|
@ -1,31 +1,41 @@
|
||||
import {
|
||||
Badge,
|
||||
Box,
|
||||
ChakraProps,
|
||||
Editable,
|
||||
EditableInput,
|
||||
EditablePreview,
|
||||
Flex,
|
||||
Image,
|
||||
MenuItem,
|
||||
MenuList,
|
||||
Text,
|
||||
useColorMode,
|
||||
} from '@chakra-ui/react';
|
||||
import { createSelector } from '@reduxjs/toolkit';
|
||||
import { skipToken } from '@reduxjs/toolkit/dist/query';
|
||||
import { MoveBoardDropData } from 'app/components/ImageDnd/typesafeDnd';
|
||||
import { useAppDispatch } from 'app/store/storeHooks';
|
||||
import { ContextMenu } from 'chakra-ui-contextmenu';
|
||||
import { stateSelector } from 'app/store/store';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
|
||||
import IAIDroppable from 'common/components/IAIDroppable';
|
||||
import { IAINoContentFallback } from 'common/components/IAIImageFallback';
|
||||
import { boardIdSelected } from 'features/gallery/store/gallerySlice';
|
||||
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 { useGetImageDTOQuery } from 'services/api/endpoints/images';
|
||||
import { BoardDTO } from 'services/api/types';
|
||||
import { menuListMotionProps } from 'theme/components/menu';
|
||||
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 {
|
||||
board: BoardDTO;
|
||||
isSelected: boolean;
|
||||
@ -35,6 +45,22 @@ interface GalleryBoardProps {
|
||||
const GalleryBoard = memo(
|
||||
({ board, isSelected, setBoardToDelete }: GalleryBoardProps) => {
|
||||
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(
|
||||
board.cover_image_name ?? skipToken
|
||||
@ -53,10 +79,6 @@ const GalleryBoard = memo(
|
||||
updateBoard({ board_id, changes: { board_name: newBoardName } });
|
||||
};
|
||||
|
||||
const handleDeleteBoard = useCallback(() => {
|
||||
setBoardToDelete(board);
|
||||
}, [board, setBoardToDelete]);
|
||||
|
||||
const droppableData: MoveBoardDropData = useMemo(
|
||||
() => ({
|
||||
id: board_id,
|
||||
@ -68,37 +90,10 @@ const GalleryBoard = memo(
|
||||
|
||||
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}
|
||||
>
|
||||
{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>
|
||||
)}
|
||||
<BoardContextMenu
|
||||
board={board}
|
||||
board_id={board_id}
|
||||
setBoardToDelete={setBoardToDelete}
|
||||
>
|
||||
{(ref) => (
|
||||
<Flex
|
||||
@ -154,7 +149,16 @@ const GalleryBoard = memo(
|
||||
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>
|
||||
<IAIDroppable
|
||||
data={droppableData}
|
||||
@ -172,7 +176,7 @@ const GalleryBoard = memo(
|
||||
>
|
||||
<Editable
|
||||
defaultValue={board_name}
|
||||
submitOnBlur={false}
|
||||
submitOnBlur={true}
|
||||
onSubmit={(nextValue) => {
|
||||
handleUpdateBoardName(nextValue);
|
||||
}}
|
||||
@ -205,7 +209,7 @@ const GalleryBoard = memo(
|
||||
</Flex>
|
||||
</Flex>
|
||||
)}
|
||||
</ContextMenu>
|
||||
</BoardContextMenu>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
@ -2,9 +2,12 @@ import { As, Badge, Flex } from '@chakra-ui/react';
|
||||
import { TypesafeDroppableData } from 'app/components/ImageDnd/typesafeDnd';
|
||||
import IAIDroppable from 'common/components/IAIDroppable';
|
||||
import { IAINoContentFallback } from 'common/components/IAIImageFallback';
|
||||
import { BoardId } from 'features/gallery/store/gallerySlice';
|
||||
import { ReactNode } from 'react';
|
||||
import BoardContextMenu from '../BoardContextMenu';
|
||||
|
||||
type GenericBoardProps = {
|
||||
board_id: BoardId;
|
||||
droppableData?: TypesafeDroppableData;
|
||||
onClick: () => void;
|
||||
isSelected: boolean;
|
||||
@ -22,6 +25,7 @@ const formatBadgeCount = (count: number) =>
|
||||
|
||||
const GenericBoard = (props: GenericBoardProps) => {
|
||||
const {
|
||||
board_id,
|
||||
droppableData,
|
||||
onClick,
|
||||
isSelected,
|
||||
@ -32,67 +36,72 @@ const GenericBoard = (props: GenericBoardProps) => {
|
||||
} = props;
|
||||
|
||||
return (
|
||||
<Flex
|
||||
sx={{
|
||||
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)' },
|
||||
}}
|
||||
/>
|
||||
<BoardContextMenu board_id={board_id}>
|
||||
{(ref) => (
|
||||
<Flex
|
||||
ref={ref}
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
insetInlineEnd: 0,
|
||||
top: 0,
|
||||
p: 1,
|
||||
flexDir: 'column',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
cursor: 'pointer',
|
||||
w: 'full',
|
||||
h: 'full',
|
||||
borderRadius: 'base',
|
||||
}}
|
||||
>
|
||||
{badgeCount !== undefined && (
|
||||
<Badge variant="solid">{formatBadgeCount(badgeCount)}</Badge>
|
||||
)}
|
||||
<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
|
||||
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>
|
||||
<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>
|
||||
)}
|
||||
</BoardContextMenu>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -39,6 +39,7 @@ const NoBoardBoard = ({ isSelected }: { isSelected: boolean }) => {
|
||||
|
||||
return (
|
||||
<GenericBoard
|
||||
board_id="no_board"
|
||||
droppableData={droppableData}
|
||||
dropLabel={<Text fontSize="md">Move</Text>}
|
||||
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 { Button, Flex, Text } from '@chakra-ui/react';
|
||||
import { Box, Button, Flex, Spacer, Text } from '@chakra-ui/react';
|
||||
import { createSelector } from '@reduxjs/toolkit';
|
||||
import { stateSelector } from 'app/store/store';
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
|
||||
import { memo } from 'react';
|
||||
import { useListAllBoardsQuery } from 'services/api/endpoints/boards';
|
||||
import { useBoardName } from 'services/api/hooks/useBoardName';
|
||||
|
||||
const selector = createSelector(
|
||||
[stateSelector],
|
||||
(state) => {
|
||||
const { selectedBoardId } = state.gallery;
|
||||
|
||||
return {
|
||||
selectedBoardId,
|
||||
};
|
||||
return { selectedBoardId };
|
||||
},
|
||||
defaultSelectorOptions
|
||||
);
|
||||
@ -27,25 +25,7 @@ type Props = {
|
||||
const GalleryBoardName = (props: Props) => {
|
||||
const { isOpen, onToggle } = props;
|
||||
const { selectedBoardId } = useAppSelector(selector);
|
||||
const { selectedBoardName } = useListAllBoardsQuery(undefined, {
|
||||
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 };
|
||||
},
|
||||
});
|
||||
const boardName = useBoardName(selectedBoardId);
|
||||
|
||||
return (
|
||||
<Flex
|
||||
@ -54,6 +34,8 @@ const GalleryBoardName = (props: Props) => {
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
sx={{
|
||||
position: 'relative',
|
||||
gap: 2,
|
||||
w: 'full',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
@ -64,19 +46,22 @@ const GalleryBoardName = (props: Props) => {
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Text
|
||||
noOfLines={1}
|
||||
sx={{
|
||||
w: 'full',
|
||||
fontWeight: 600,
|
||||
color: 'base.800',
|
||||
_dark: {
|
||||
color: 'base.200',
|
||||
},
|
||||
}}
|
||||
>
|
||||
{selectedBoardName}
|
||||
</Text>
|
||||
<Spacer />
|
||||
<Box position="relative">
|
||||
<Text
|
||||
noOfLines={1}
|
||||
sx={{
|
||||
fontWeight: 600,
|
||||
color: 'base.800',
|
||||
_dark: {
|
||||
color: 'base.200',
|
||||
},
|
||||
}}
|
||||
>
|
||||
{boardName}
|
||||
</Text>
|
||||
</Box>
|
||||
<Spacer />
|
||||
<ChevronUpIcon
|
||||
sx={{
|
||||
transform: isOpen ? 'rotate(0deg)' : 'rotate(180deg)',
|
||||
|
@ -1,19 +1,20 @@
|
||||
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 { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
|
||||
import IAIIconButton from 'common/components/IAIIconButton';
|
||||
import IAIPopover from 'common/components/IAIPopover';
|
||||
import IAISimpleCheckbox from 'common/components/IAISimpleCheckbox';
|
||||
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 { useTranslation } from 'react-i18next';
|
||||
import { FaWrench } from 'react-icons/fa';
|
||||
|
||||
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';
|
||||
import BoardAutoAddSelect from './Boards/BoardAutoAddSelect';
|
||||
|
||||
const selector = createSelector(
|
||||
[stateSelector],
|
||||
@ -50,7 +51,7 @@ const GallerySettingsPopover = () => {
|
||||
/>
|
||||
}
|
||||
>
|
||||
<Flex direction="column" gap={2}>
|
||||
<Flex direction="column" gap={4}>
|
||||
<IAISlider
|
||||
value={galleryImageMinimumWidth}
|
||||
onChange={handleChangeGalleryImageMinimumWidth}
|
||||
@ -68,6 +69,7 @@ const GallerySettingsPopover = () => {
|
||||
dispatch(shouldAutoSwitchChanged(e.target.checked))
|
||||
}
|
||||
/>
|
||||
<BoardAutoAddSelect />
|
||||
</Flex>
|
||||
</IAIPopover>
|
||||
);
|
||||
|
@ -25,6 +25,7 @@ export type BoardId =
|
||||
type GalleryState = {
|
||||
selection: string[];
|
||||
shouldAutoSwitch: boolean;
|
||||
autoAddBoardId: string | null;
|
||||
galleryImageMinimumWidth: number;
|
||||
selectedBoardId: BoardId;
|
||||
batchImageNames: string[];
|
||||
@ -34,6 +35,7 @@ type GalleryState = {
|
||||
export const initialGalleryState: GalleryState = {
|
||||
selection: [],
|
||||
shouldAutoSwitch: true,
|
||||
autoAddBoardId: null,
|
||||
galleryImageMinimumWidth: 96,
|
||||
selectedBoardId: 'images',
|
||||
batchImageNames: [],
|
||||
@ -123,14 +125,34 @@ export const gallerySlice = createSlice({
|
||||
state.batchImageNames = [];
|
||||
state.selection = [];
|
||||
},
|
||||
autoAddBoardIdChanged: (state, action: PayloadAction<string | null>) => {
|
||||
state.autoAddBoardId = action.payload;
|
||||
},
|
||||
},
|
||||
extraReducers: (builder) => {
|
||||
builder.addMatcher(
|
||||
boardsApi.endpoints.deleteBoard.matchFulfilled,
|
||||
(state, action) => {
|
||||
if (action.meta.arg.originalArgs === state.selectedBoardId) {
|
||||
const deletedBoardId = action.meta.arg.originalArgs;
|
||||
if (deletedBoardId === state.selectedBoardId) {
|
||||
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,
|
||||
imagesAddedToBatch,
|
||||
imagesRemovedFromBatch,
|
||||
autoAddBoardIdChanged,
|
||||
} = gallerySlice.actions;
|
||||
|
||||
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 { stateSelector } from 'app/store/store';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
|
||||
import IAIButton, { IAIButtonProps } from 'common/components/IAIButton';
|
||||
import IAIIconButton, {
|
||||
IAIIconButtonProps,
|
||||
@ -8,11 +11,13 @@ import IAIIconButton, {
|
||||
import { useIsReadyToInvoke } from 'common/hooks/useIsReadyToInvoke';
|
||||
import { clampSymmetrySteps } from 'features/parameters/store/generationSlice';
|
||||
import ProgressBar from 'features/system/components/ProgressBar';
|
||||
import { selectIsBusy } from 'features/system/store/systemSelectors';
|
||||
import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
|
||||
import { useCallback } from 'react';
|
||||
import { useHotkeys } from 'react-hotkeys-hook';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { FaPlay } from 'react-icons/fa';
|
||||
import { useBoardName } from 'services/api/hooks/useBoardName';
|
||||
|
||||
const IN_PROGRESS_STYLES: ChakraProps['sx'] = {
|
||||
_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
|
||||
extends Omit<IAIButtonProps | IAIIconButtonProps, 'aria-label'> {
|
||||
iconButton?: boolean;
|
||||
@ -35,8 +54,8 @@ export default function InvokeButton(props: InvokeButton) {
|
||||
const { iconButton = false, ...rest } = props;
|
||||
const dispatch = useAppDispatch();
|
||||
const isReady = useIsReadyToInvoke();
|
||||
const activeTabName = useAppSelector(activeTabNameSelector);
|
||||
const isProcessing = useAppSelector((state) => state.system.isProcessing);
|
||||
const { isBusy, autoAddBoardId, activeTabName } = useAppSelector(selector);
|
||||
const autoAddBoardName = useBoardName(autoAddBoardId);
|
||||
|
||||
const handleInvoke = useCallback(() => {
|
||||
dispatch(clampSymmetrySteps());
|
||||
@ -75,43 +94,52 @@ export default function InvokeButton(props: InvokeButton) {
|
||||
<ProgressBar />
|
||||
</Box>
|
||||
)}
|
||||
{iconButton ? (
|
||||
<IAIIconButton
|
||||
aria-label={t('parameters.invoke')}
|
||||
type="submit"
|
||||
icon={<FaPlay />}
|
||||
isDisabled={!isReady || isProcessing}
|
||||
onClick={handleInvoke}
|
||||
tooltip={t('parameters.invoke')}
|
||||
tooltipProps={{ placement: 'top' }}
|
||||
colorScheme="accent"
|
||||
id="invoke-button"
|
||||
{...rest}
|
||||
sx={{
|
||||
w: 'full',
|
||||
flexGrow: 1,
|
||||
...(isProcessing ? IN_PROGRESS_STYLES : {}),
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<IAIButton
|
||||
aria-label={t('parameters.invoke')}
|
||||
type="submit"
|
||||
isDisabled={!isReady || isProcessing}
|
||||
onClick={handleInvoke}
|
||||
colorScheme="accent"
|
||||
id="invoke-button"
|
||||
{...rest}
|
||||
sx={{
|
||||
w: 'full',
|
||||
flexGrow: 1,
|
||||
fontWeight: 700,
|
||||
...(isProcessing ? IN_PROGRESS_STYLES : {}),
|
||||
}}
|
||||
>
|
||||
Invoke
|
||||
</IAIButton>
|
||||
)}
|
||||
<Tooltip
|
||||
placement="top"
|
||||
hasArrow
|
||||
openDelay={500}
|
||||
label={
|
||||
autoAddBoardId ? `Auto-Adding to ${autoAddBoardName}` : undefined
|
||||
}
|
||||
>
|
||||
{iconButton ? (
|
||||
<IAIIconButton
|
||||
aria-label={t('parameters.invoke')}
|
||||
type="submit"
|
||||
icon={<FaPlay />}
|
||||
isDisabled={!isReady || isBusy}
|
||||
onClick={handleInvoke}
|
||||
tooltip={t('parameters.invoke')}
|
||||
tooltipProps={{ placement: 'top' }}
|
||||
colorScheme="accent"
|
||||
id="invoke-button"
|
||||
{...rest}
|
||||
sx={{
|
||||
w: 'full',
|
||||
flexGrow: 1,
|
||||
...(isBusy ? IN_PROGRESS_STYLES : {}),
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<IAIButton
|
||||
aria-label={t('parameters.invoke')}
|
||||
type="submit"
|
||||
isDisabled={!isReady || isBusy}
|
||||
onClick={handleInvoke}
|
||||
colorScheme="accent"
|
||||
id="invoke-button"
|
||||
{...rest}
|
||||
sx={{
|
||||
w: 'full',
|
||||
flexGrow: 1,
|
||||
fontWeight: 700,
|
||||
...(isBusy ? IN_PROGRESS_STYLES : {}),
|
||||
}}
|
||||
>
|
||||
Invoke
|
||||
</IAIButton>
|
||||
)}
|
||||
</Tooltip>
|
||||
</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 (
|
||||
<Flex gap={2}>
|
||||
<InvokeButton />
|
||||
{/* {activeTabName === 'img2img' && <LoopbackButton />} */}
|
||||
<CancelButton />
|
||||
</Flex>
|
||||
);
|
||||
|
@ -0,0 +1,57 @@
|
||||
import { Badge, BadgeProps, Flex, Text, TextProps } from '@chakra-ui/react';
|
||||
import IAISwitch, { IAISwitchProps } from 'common/components/IAISwitch';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
type SettingSwitchProps = IAISwitchProps & {
|
||||
label: string;
|
||||
useBadge?: boolean;
|
||||
badgeLabel?: string;
|
||||
textProps?: TextProps;
|
||||
badgeProps?: BadgeProps;
|
||||
};
|
||||
|
||||
export default function SettingSwitch(props: SettingSwitchProps) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const {
|
||||
label,
|
||||
textProps,
|
||||
useBadge = false,
|
||||
badgeLabel = t('settings.experimental'),
|
||||
badgeProps,
|
||||
...rest
|
||||
} = props;
|
||||
|
||||
return (
|
||||
<Flex justifyContent="space-between" py={1}>
|
||||
<Flex gap={2} alignItems="center">
|
||||
<Text
|
||||
sx={{
|
||||
fontSize: 14,
|
||||
_dark: {
|
||||
color: 'base.300',
|
||||
},
|
||||
}}
|
||||
{...textProps}
|
||||
>
|
||||
{label}
|
||||
</Text>
|
||||
{useBadge && (
|
||||
<Badge
|
||||
size="xs"
|
||||
sx={{
|
||||
px: 2,
|
||||
color: 'base.700',
|
||||
bg: 'accent.200',
|
||||
_dark: { bg: 'accent.500', color: 'base.200' },
|
||||
}}
|
||||
{...badgeProps}
|
||||
>
|
||||
{badgeLabel}
|
||||
</Badge>
|
||||
)}
|
||||
</Flex>
|
||||
<IAISwitch {...rest} />
|
||||
</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 { useAppDispatch } from 'app/store/storeHooks';
|
||||
import { useCallback, useEffect } from 'react';
|
||||
import IAIButton from '../../../../common/components/IAIButton';
|
||||
import { useClearIntermediatesMutation } from '../../../../services/api/endpoints/images';
|
||||
import { addToast } from '../../store/systemSlice';
|
||||
import {
|
||||
useClearIntermediatesMutation,
|
||||
useGetIntermediatesCountQuery,
|
||||
} from '../../../../services/api/endpoints/images';
|
||||
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() {
|
||||
const dispatch = useAppDispatch();
|
||||
const [isDisabled, setIsDisabled] = useState(false);
|
||||
|
||||
const { data: intermediatesCount, refetch: updateIntermediatesCount } =
|
||||
useGetIntermediatesCountQuery();
|
||||
|
||||
const [clearIntermediates, { isLoading: isLoadingClearIntermediates }] =
|
||||
useClearIntermediatesMutation();
|
||||
|
||||
const handleClickClearIntermediates = useCallback(() => {
|
||||
clearIntermediates({})
|
||||
clearIntermediates()
|
||||
.unwrap()
|
||||
.then((response) => {
|
||||
dispatch(controlNetReset());
|
||||
dispatch(resetCanvas());
|
||||
dispatch(
|
||||
addToast({
|
||||
title:
|
||||
response === 0
|
||||
? `No intermediates to clear`
|
||||
: `Successfully cleared ${response} intermediates`,
|
||||
title: `Cleared ${response} intermediates`,
|
||||
status: 'info',
|
||||
})
|
||||
);
|
||||
if (response < 100) {
|
||||
setIsDisabled(true);
|
||||
}
|
||||
});
|
||||
}, [clearIntermediates, dispatch]);
|
||||
|
||||
useEffect(() => {
|
||||
// update the count on mount
|
||||
updateIntermediatesCount();
|
||||
}, [updateIntermediatesCount]);
|
||||
|
||||
const buttonText = intermediatesCount
|
||||
? `Clear ${intermediatesCount} Intermediate${
|
||||
intermediatesCount > 1 ? 's' : ''
|
||||
}`
|
||||
: 'No Intermediates to Clear';
|
||||
|
||||
return (
|
||||
<StyledFlex>
|
||||
<Heading size="sm">Clear Intermediates</Heading>
|
||||
<IAIButton
|
||||
colorScheme="error"
|
||||
colorScheme="warning"
|
||||
onClick={handleClickClearIntermediates}
|
||||
isLoading={isLoadingClearIntermediates}
|
||||
isDisabled={isDisabled}
|
||||
isDisabled={!intermediatesCount}
|
||||
>
|
||||
{isDisabled ? 'Intermediates Cleared' : 'Clear 100 Intermediates'}
|
||||
{buttonText}
|
||||
</IAIButton>
|
||||
<Text>
|
||||
Will permanently delete first 100 intermediates found on disk and in
|
||||
database
|
||||
<Text fontWeight="bold">
|
||||
Clearing intermediates will reset your Canvas and ControlNet state.
|
||||
</Text>
|
||||
<Text fontWeight="bold">This will also clear your canvas state.</Text>
|
||||
<Text>
|
||||
<Text variant="subtext">
|
||||
Intermediate images are byproducts of generation, different from the
|
||||
result images in the gallery. Purging intermediates will free disk
|
||||
space. Your gallery images will not be deleted.
|
||||
result images in the gallery. Clearing intermediates will free disk
|
||||
space.
|
||||
</Text>
|
||||
<Text variant="subtext">Your gallery images will not be deleted.</Text>
|
||||
</StyledFlex>
|
||||
);
|
||||
}
|
||||
|
@ -11,13 +11,12 @@ import {
|
||||
Text,
|
||||
useDisclosure,
|
||||
} from '@chakra-ui/react';
|
||||
import { createSelector, current } from '@reduxjs/toolkit';
|
||||
import { createSelector } from '@reduxjs/toolkit';
|
||||
import { VALID_LOG_LEVELS } from 'app/logging/useLogger';
|
||||
import { LOCALSTORAGE_KEYS, LOCALSTORAGE_PREFIX } from 'app/store/constants';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import IAIButton from 'common/components/IAIButton';
|
||||
import IAIMantineSelect from 'common/components/IAIMantineSelect';
|
||||
import IAISwitch from 'common/components/IAISwitch';
|
||||
import { systemSelector } from 'features/system/store/systemSelectors';
|
||||
import {
|
||||
SystemState,
|
||||
@ -25,7 +24,6 @@ import {
|
||||
setEnableImageDebugging,
|
||||
setIsNodesEnabled,
|
||||
setShouldConfirmOnDelete,
|
||||
setShouldDisplayGuides,
|
||||
shouldAntialiasProgressImageChanged,
|
||||
shouldLogToConsoleChanged,
|
||||
} from 'features/system/store/systemSlice';
|
||||
@ -48,15 +46,15 @@ import {
|
||||
} from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { LogLevelName } from 'roarr';
|
||||
import SettingsSchedulers from './SettingsSchedulers';
|
||||
import SettingSwitch from './SettingSwitch';
|
||||
import SettingsClearIntermediates from './SettingsClearIntermediates';
|
||||
import SettingsSchedulers from './SettingsSchedulers';
|
||||
|
||||
const selector = createSelector(
|
||||
[systemSelector, uiSelector],
|
||||
(system: SystemState, ui: UIState) => {
|
||||
const {
|
||||
shouldConfirmOnDelete,
|
||||
shouldDisplayGuides,
|
||||
enableImageDebugging,
|
||||
consoleLogLevel,
|
||||
shouldLogToConsole,
|
||||
@ -73,7 +71,6 @@ const selector = createSelector(
|
||||
|
||||
return {
|
||||
shouldConfirmOnDelete,
|
||||
shouldDisplayGuides,
|
||||
enableImageDebugging,
|
||||
shouldUseCanvasBetaLayout,
|
||||
shouldUseSliders,
|
||||
@ -139,7 +136,6 @@ const SettingsModal = ({ children, config }: SettingsModalProps) => {
|
||||
|
||||
const {
|
||||
shouldConfirmOnDelete,
|
||||
shouldDisplayGuides,
|
||||
enableImageDebugging,
|
||||
shouldUseCanvasBetaLayout,
|
||||
shouldUseSliders,
|
||||
@ -195,7 +191,7 @@ const SettingsModal = ({ children, config }: SettingsModalProps) => {
|
||||
<Modal
|
||||
isOpen={isSettingsModalOpen}
|
||||
onClose={onSettingsModalClose}
|
||||
size="xl"
|
||||
size="2xl"
|
||||
isCentered
|
||||
>
|
||||
<ModalOverlay />
|
||||
@ -206,7 +202,7 @@ const SettingsModal = ({ children, config }: SettingsModalProps) => {
|
||||
<Flex sx={{ gap: 4, flexDirection: 'column' }}>
|
||||
<StyledFlex>
|
||||
<Heading size="sm">{t('settings.general')}</Heading>
|
||||
<IAISwitch
|
||||
<SettingSwitch
|
||||
label={t('settings.confirmOnDelete')}
|
||||
isChecked={shouldConfirmOnDelete}
|
||||
onChange={(e: ChangeEvent<HTMLInputElement>) =>
|
||||
@ -214,7 +210,7 @@ const SettingsModal = ({ children, config }: SettingsModalProps) => {
|
||||
}
|
||||
/>
|
||||
{shouldShowAdvancedOptionsSettings && (
|
||||
<IAISwitch
|
||||
<SettingSwitch
|
||||
label={t('settings.showAdvancedOptions')}
|
||||
isChecked={shouldShowAdvancedOptions}
|
||||
onChange={(e: ChangeEvent<HTMLInputElement>) =>
|
||||
@ -231,37 +227,21 @@ const SettingsModal = ({ children, config }: SettingsModalProps) => {
|
||||
|
||||
<StyledFlex>
|
||||
<Heading size="sm">{t('settings.ui')}</Heading>
|
||||
<IAISwitch
|
||||
label={t('settings.displayHelpIcons')}
|
||||
isChecked={shouldDisplayGuides}
|
||||
onChange={(e: ChangeEvent<HTMLInputElement>) =>
|
||||
dispatch(setShouldDisplayGuides(e.target.checked))
|
||||
}
|
||||
/>
|
||||
{shouldShowBetaLayout && (
|
||||
<IAISwitch
|
||||
label={t('settings.useCanvasBeta')}
|
||||
isChecked={shouldUseCanvasBetaLayout}
|
||||
onChange={(e: ChangeEvent<HTMLInputElement>) =>
|
||||
dispatch(setShouldUseCanvasBetaLayout(e.target.checked))
|
||||
}
|
||||
/>
|
||||
)}
|
||||
<IAISwitch
|
||||
<SettingSwitch
|
||||
label={t('settings.useSlidersForAll')}
|
||||
isChecked={shouldUseSliders}
|
||||
onChange={(e: ChangeEvent<HTMLInputElement>) =>
|
||||
dispatch(setShouldUseSliders(e.target.checked))
|
||||
}
|
||||
/>
|
||||
<IAISwitch
|
||||
<SettingSwitch
|
||||
label={t('settings.showProgressInViewer')}
|
||||
isChecked={shouldShowProgressInViewer}
|
||||
onChange={(e: ChangeEvent<HTMLInputElement>) =>
|
||||
dispatch(setShouldShowProgressInViewer(e.target.checked))
|
||||
}
|
||||
/>
|
||||
<IAISwitch
|
||||
<SettingSwitch
|
||||
label={t('settings.antialiasProgressImages')}
|
||||
isChecked={shouldAntialiasProgressImage}
|
||||
onChange={(e: ChangeEvent<HTMLInputElement>) =>
|
||||
@ -270,9 +250,21 @@ const SettingsModal = ({ children, config }: SettingsModalProps) => {
|
||||
)
|
||||
}
|
||||
/>
|
||||
{shouldShowBetaLayout && (
|
||||
<SettingSwitch
|
||||
label={t('settings.alternateCanvasLayout')}
|
||||
useBadge
|
||||
badgeLabel={t('settings.beta')}
|
||||
isChecked={shouldUseCanvasBetaLayout}
|
||||
onChange={(e: ChangeEvent<HTMLInputElement>) =>
|
||||
dispatch(setShouldUseCanvasBetaLayout(e.target.checked))
|
||||
}
|
||||
/>
|
||||
)}
|
||||
{shouldShowNodesToggle && (
|
||||
<IAISwitch
|
||||
label="Enable Nodes Editor (Experimental)"
|
||||
<SettingSwitch
|
||||
label={t('settings.enableNodesEditor')}
|
||||
useBadge
|
||||
isChecked={isNodesEnabled}
|
||||
onChange={handleToggleNodes}
|
||||
/>
|
||||
@ -282,7 +274,7 @@ const SettingsModal = ({ children, config }: SettingsModalProps) => {
|
||||
{shouldShowDeveloperSettings && (
|
||||
<StyledFlex>
|
||||
<Heading size="sm">{t('settings.developer')}</Heading>
|
||||
<IAISwitch
|
||||
<SettingSwitch
|
||||
label={t('settings.shouldLogToConsole')}
|
||||
isChecked={shouldLogToConsole}
|
||||
onChange={handleLogToConsoleChanged}
|
||||
@ -294,7 +286,7 @@ const SettingsModal = ({ children, config }: SettingsModalProps) => {
|
||||
value={consoleLogLevel}
|
||||
data={VALID_LOG_LEVELS.concat()}
|
||||
/>
|
||||
<IAISwitch
|
||||
<SettingSwitch
|
||||
label={t('settings.enableImageDebugging')}
|
||||
isChecked={enableImageDebugging}
|
||||
onChange={(e: ChangeEvent<HTMLInputElement>) =>
|
||||
@ -313,8 +305,12 @@ const SettingsModal = ({ children, config }: SettingsModalProps) => {
|
||||
</IAIButton>
|
||||
{shouldShowResetWebUiText && (
|
||||
<>
|
||||
<Text>{t('settings.resetWebUIDesc1')}</Text>
|
||||
<Text>{t('settings.resetWebUIDesc2')}</Text>
|
||||
<Text variant="subtext">
|
||||
{t('settings.resetWebUIDesc1')}
|
||||
</Text>
|
||||
<Text variant="subtext">
|
||||
{t('settings.resetWebUIDesc2')}
|
||||
</Text>
|
||||
</>
|
||||
)}
|
||||
</StyledFlex>
|
||||
|
@ -38,7 +38,6 @@ export interface SystemState {
|
||||
currentIteration: number;
|
||||
totalIterations: number;
|
||||
currentStatusHasSteps: boolean;
|
||||
shouldDisplayGuides: boolean;
|
||||
isCancelable: boolean;
|
||||
enableImageDebugging: boolean;
|
||||
toastQueue: UseToastOptions[];
|
||||
@ -84,14 +83,12 @@ export interface SystemState {
|
||||
shouldAntialiasProgressImage: boolean;
|
||||
language: keyof typeof LANGUAGES;
|
||||
isUploading: boolean;
|
||||
boardIdToAddTo?: string;
|
||||
isNodesEnabled: boolean;
|
||||
}
|
||||
|
||||
export const initialSystemState: SystemState = {
|
||||
isConnected: false,
|
||||
isProcessing: false,
|
||||
shouldDisplayGuides: true,
|
||||
isGFPGANAvailable: true,
|
||||
isESRGANAvailable: true,
|
||||
shouldConfirmOnDelete: true,
|
||||
@ -134,9 +131,6 @@ export const systemSlice = createSlice({
|
||||
setShouldConfirmOnDelete: (state, action: PayloadAction<boolean>) => {
|
||||
state.shouldConfirmOnDelete = action.payload;
|
||||
},
|
||||
setShouldDisplayGuides: (state, action: PayloadAction<boolean>) => {
|
||||
state.shouldDisplayGuides = action.payload;
|
||||
},
|
||||
setIsCancelable: (state, action: PayloadAction<boolean>) => {
|
||||
state.isCancelable = action.payload;
|
||||
},
|
||||
@ -204,7 +198,6 @@ export const systemSlice = createSlice({
|
||||
*/
|
||||
builder.addCase(appSocketSubscribed, (state, action) => {
|
||||
state.sessionId = action.payload.sessionId;
|
||||
state.boardIdToAddTo = action.payload.boardId;
|
||||
state.canceledSession = '';
|
||||
});
|
||||
|
||||
@ -213,7 +206,6 @@ export const systemSlice = createSlice({
|
||||
*/
|
||||
builder.addCase(appSocketUnsubscribed, (state) => {
|
||||
state.sessionId = null;
|
||||
state.boardIdToAddTo = undefined;
|
||||
});
|
||||
|
||||
/**
|
||||
@ -390,7 +382,6 @@ export const {
|
||||
setIsProcessing,
|
||||
setShouldConfirmOnDelete,
|
||||
setCurrentStatus,
|
||||
setShouldDisplayGuides,
|
||||
setIsCancelable,
|
||||
setEnableImageDebugging,
|
||||
addToast,
|
||||
|
@ -75,42 +75,49 @@ const ModelList = (props: ModelListProps) => {
|
||||
labelPos="side"
|
||||
/>
|
||||
|
||||
{['images', 'diffusers'].includes(modelFormatFilter) &&
|
||||
filteredDiffusersModels.length > 0 && (
|
||||
<StyledModelContainer>
|
||||
<Flex sx={{ gap: 2, flexDir: 'column' }}>
|
||||
<Text variant="subtext" fontSize="sm">
|
||||
Diffusers
|
||||
</Text>
|
||||
{filteredDiffusersModels.map((model) => (
|
||||
<ModelListItem
|
||||
key={model.id}
|
||||
model={model}
|
||||
isSelected={selectedModelId === model.id}
|
||||
setSelectedModelId={setSelectedModelId}
|
||||
/>
|
||||
))}
|
||||
</Flex>
|
||||
</StyledModelContainer>
|
||||
)}
|
||||
{['images', 'checkpoint'].includes(modelFormatFilter) &&
|
||||
filteredCheckpointModels.length > 0 && (
|
||||
<StyledModelContainer>
|
||||
<Flex sx={{ gap: 2, flexDir: 'column' }}>
|
||||
<Text variant="subtext" fontSize="sm">
|
||||
Checkpoint
|
||||
</Text>
|
||||
{filteredCheckpointModels.map((model) => (
|
||||
<ModelListItem
|
||||
key={model.id}
|
||||
model={model}
|
||||
isSelected={selectedModelId === model.id}
|
||||
setSelectedModelId={setSelectedModelId}
|
||||
/>
|
||||
))}
|
||||
</Flex>
|
||||
</StyledModelContainer>
|
||||
)}
|
||||
<Flex
|
||||
flexDirection="column"
|
||||
gap={4}
|
||||
maxHeight={window.innerHeight - 280}
|
||||
overflow="scroll"
|
||||
>
|
||||
{['images', 'diffusers'].includes(modelFormatFilter) &&
|
||||
filteredDiffusersModels.length > 0 && (
|
||||
<StyledModelContainer>
|
||||
<Flex sx={{ gap: 2, flexDir: 'column' }}>
|
||||
<Text variant="subtext" fontSize="sm">
|
||||
Diffusers
|
||||
</Text>
|
||||
{filteredDiffusersModels.map((model) => (
|
||||
<ModelListItem
|
||||
key={model.id}
|
||||
model={model}
|
||||
isSelected={selectedModelId === model.id}
|
||||
setSelectedModelId={setSelectedModelId}
|
||||
/>
|
||||
))}
|
||||
</Flex>
|
||||
</StyledModelContainer>
|
||||
)}
|
||||
{['images', 'checkpoint'].includes(modelFormatFilter) &&
|
||||
filteredCheckpointModels.length > 0 && (
|
||||
<StyledModelContainer>
|
||||
<Flex sx={{ gap: 2, flexDir: 'column' }}>
|
||||
<Text variant="subtext" fontSize="sm">
|
||||
Checkpoints
|
||||
</Text>
|
||||
{filteredCheckpointModels.map((model) => (
|
||||
<ModelListItem
|
||||
key={model.id}
|
||||
model={model}
|
||||
isSelected={selectedModelId === model.id}
|
||||
setSelectedModelId={setSelectedModelId}
|
||||
/>
|
||||
))}
|
||||
</Flex>
|
||||
</StyledModelContainer>
|
||||
)}
|
||||
</Flex>
|
||||
</Flex>
|
||||
</Flex>
|
||||
);
|
||||
@ -146,8 +153,6 @@ const StyledModelContainer = (props: PropsWithChildren) => {
|
||||
return (
|
||||
<Flex
|
||||
flexDirection="column"
|
||||
maxHeight={window.innerHeight - 280}
|
||||
overflow="scroll"
|
||||
gap={4}
|
||||
borderRadius={4}
|
||||
p={4}
|
||||
|
@ -98,16 +98,7 @@ export default function ModelListItem(props: ModelListItemProps) {
|
||||
onClick={handleSelectModel}
|
||||
>
|
||||
<Flex gap={4} alignItems="center">
|
||||
<Badge
|
||||
minWidth={14}
|
||||
p={1}
|
||||
fontSize="sm"
|
||||
sx={{
|
||||
bg: 'base.350',
|
||||
color: 'base.900',
|
||||
_dark: { bg: 'base.500' },
|
||||
}}
|
||||
>
|
||||
<Badge minWidth={14} p={0.5} fontSize="sm" variant="solid">
|
||||
{
|
||||
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.
|
||||
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>({
|
||||
query: (image_name) => ({ url: `images/${image_name}` }),
|
||||
providesTags: (result, error, arg) => {
|
||||
@ -148,8 +155,9 @@ export const imagesApi = api.injectEndpoints({
|
||||
},
|
||||
keepUnusedDataFor: 86400, // 24 hours
|
||||
}),
|
||||
clearIntermediates: build.mutation({
|
||||
clearIntermediates: build.mutation<number, void>({
|
||||
query: () => ({ url: `images/clear-intermediates`, method: 'POST' }),
|
||||
invalidatesTags: ['IntermediatesCount'],
|
||||
}),
|
||||
deleteImage: build.mutation<void, ImageDTO>({
|
||||
query: ({ image_name }) => ({
|
||||
@ -617,6 +625,7 @@ export const imagesApi = api.injectEndpoints({
|
||||
});
|
||||
|
||||
export const {
|
||||
useGetIntermediatesCountQuery,
|
||||
useListImagesQuery,
|
||||
useLazyListImagesQuery,
|
||||
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;
|
||||
};
|
Reference in New Issue
Block a user