Merge branch 'main' into feat/compel_node

This commit is contained in:
StAlKeR7779
2023-05-04 00:28:33 +03:00
committed by GitHub
329 changed files with 6233 additions and 5270 deletions

View File

@@ -1,4 +1,4 @@
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import IAIAlertDialog from 'common/components/IAIAlertDialog';
import IAIButton from 'common/components/IAIButton';
import { clearCanvasHistory } from 'features/canvas/store/canvasSlice';

View File

@@ -1,6 +1,6 @@
import { Box, chakra, Flex } from '@chakra-ui/react';
import { createSelector } from '@reduxjs/toolkit';
import { useAppSelector } from 'app/storeHooks';
import { useAppSelector } from 'app/store/storeHooks';
import {
canvasSelector,
isStagingSelector,
@@ -8,7 +8,7 @@ import {
import Konva from 'konva';
import { KonvaEventObject } from 'konva/lib/Node';
import { Vector2d } from 'konva/lib/types';
import { isEqual } from 'lodash';
import { isEqual } from 'lodash-es';
import { useCallback, useRef } from 'react';
import { Layer, Stage } from 'react-konva';

View File

@@ -1,6 +1,6 @@
import { createSelector } from '@reduxjs/toolkit';
import { useAppSelector } from 'app/storeHooks';
import { isEqual } from 'lodash';
import { useAppSelector } from 'app/store/storeHooks';
import { isEqual } from 'lodash-es';
import { Group, Rect } from 'react-konva';
import { canvasSelector } from '../store/canvasSelectors';

View File

@@ -2,10 +2,10 @@
import { useToken } from '@chakra-ui/react';
import { createSelector } from '@reduxjs/toolkit';
import { RootState } from 'app/store';
import { useAppSelector } from 'app/storeHooks';
import { RootState } from 'app/store/store';
import { useAppSelector } from 'app/store/storeHooks';
import { canvasSelector } from 'features/canvas/store/canvasSelectors';
import { isEqual, range } from 'lodash';
import { isEqual, range } from 'lodash-es';
import { ReactNode, useCallback, useLayoutEffect, useState } from 'react';
import { Group, Line as KonvaLine } from 'react-konva';

View File

@@ -1,10 +1,10 @@
import { createSelector } from '@reduxjs/toolkit';
import { RootState } from 'app/store';
import { useAppSelector } from 'app/storeHooks';
import { RootState } from 'app/store/store';
import { useAppSelector } from 'app/store/storeHooks';
import { useGetUrl } from 'common/util/getUrl';
import { GalleryState } from 'features/gallery/store/gallerySlice';
import { ImageConfig } from 'konva/lib/shapes/Image';
import { isEqual } from 'lodash';
import { isEqual } from 'lodash-es';
import { useEffect, useState } from 'react';
import { Image as KonvaImage } from 'react-konva';

View File

@@ -1,12 +1,12 @@
import { createSelector } from '@reduxjs/toolkit';
import { useAppSelector } from 'app/storeHooks';
import { useAppSelector } from 'app/store/storeHooks';
import { canvasSelector } from 'features/canvas/store/canvasSelectors';
import { RectConfig } from 'konva/lib/shapes/Rect';
import { Rect } from 'react-konva';
import { rgbaColorToString } from 'features/canvas/util/colorToString';
import Konva from 'konva';
import { isNumber } from 'lodash';
import { isNumber } from 'lodash-es';
import { useCallback, useEffect, useRef, useState } from 'react';
export const canvasMaskCompositerSelector = createSelector(

View File

@@ -1,8 +1,8 @@
import { createSelector } from '@reduxjs/toolkit';
import { useAppSelector } from 'app/storeHooks';
import { useAppSelector } from 'app/store/storeHooks';
import { canvasSelector } from 'features/canvas/store/canvasSelectors';
import { GroupConfig } from 'konva/lib/Group';
import { isEqual } from 'lodash';
import { isEqual } from 'lodash-es';
import { Group, Line } from 'react-konva';
import { isCanvasMaskLine } from '../store/canvasTypes';

View File

@@ -1,9 +1,9 @@
import { createSelector } from '@reduxjs/toolkit';
import { useAppSelector } from 'app/storeHooks';
import { useAppSelector } from 'app/store/storeHooks';
import { useGetUrl } from 'common/util/getUrl';
import { canvasSelector } from 'features/canvas/store/canvasSelectors';
import { rgbaColorToString } from 'features/canvas/util/colorToString';
import { isEqual } from 'lodash';
import { isEqual } from 'lodash-es';
import { Group, Line, Rect } from 'react-konva';
import {

View File

@@ -1,6 +1,6 @@
import { Flex, Spinner } from '@chakra-ui/react';
import { createSelector } from '@reduxjs/toolkit';
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import {
canvasSelector,
initialCanvasImageSelector,

View File

@@ -1,9 +1,9 @@
import { createSelector } from '@reduxjs/toolkit';
import { useAppSelector } from 'app/storeHooks';
import { useAppSelector } from 'app/store/storeHooks';
import { useGetUrl } from 'common/util/getUrl';
import { canvasSelector } from 'features/canvas/store/canvasSelectors';
import { GroupConfig } from 'konva/lib/Group';
import { isEqual } from 'lodash';
import { isEqual } from 'lodash-es';
import { Group, Rect } from 'react-konva';
import IAICanvasImage from './IAICanvasImage';

View File

@@ -1,7 +1,7 @@
import { ButtonGroup, Flex } from '@chakra-ui/react';
import { createSelector } from '@reduxjs/toolkit';
import { saveStagingAreaImageToGallery } from 'app/socketio/actions';
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
// import { saveStagingAreaImageToGallery } from 'app/socketio/actions';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import IAIIconButton from 'common/components/IAIIconButton';
import { canvasSelector } from 'features/canvas/store/canvasSelectors';
import {
@@ -12,7 +12,7 @@ import {
setShouldShowStagingImage,
setShouldShowStagingOutline,
} from 'features/canvas/store/canvasSlice';
import { isEqual } from 'lodash';
import { isEqual } from 'lodash-es';
import { useCallback } from 'react';
import { useHotkeys } from 'react-hotkeys-hook';

View File

@@ -1,8 +1,8 @@
import { Box, Flex } from '@chakra-ui/react';
import { createSelector } from '@reduxjs/toolkit';
import { useAppSelector } from 'app/storeHooks';
import { useAppSelector } from 'app/store/storeHooks';
import { canvasSelector } from 'features/canvas/store/canvasSelectors';
import { isEqual } from 'lodash';
import { isEqual } from 'lodash-es';
import { useTranslation } from 'react-i18next';
import roundToHundreth from '../util/roundToHundreth';

View File

@@ -1,9 +1,9 @@
import { Box } from '@chakra-ui/react';
import { createSelector } from '@reduxjs/toolkit';
import { useAppSelector } from 'app/storeHooks';
import { useAppSelector } from 'app/store/storeHooks';
import { canvasSelector } from 'features/canvas/store/canvasSelectors';
import roundToHundreth from 'features/canvas/util/roundToHundreth';
import { isEqual } from 'lodash';
import { isEqual } from 'lodash-es';
import { useTranslation } from 'react-i18next';

View File

@@ -1,9 +1,9 @@
import { createSelector } from '@reduxjs/toolkit';
import { useAppSelector } from 'app/storeHooks';
import { useAppSelector } from 'app/store/storeHooks';
import { canvasSelector } from 'features/canvas/store/canvasSelectors';
import { rgbaColorToString } from 'features/canvas/util/colorToString';
import { GroupConfig } from 'konva/lib/Group';
import { isEqual } from 'lodash';
import { isEqual } from 'lodash-es';
import { Circle, Group } from 'react-konva';
import {

View File

@@ -1,5 +1,5 @@
import { createSelector } from '@reduxjs/toolkit';
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import {
roundDownToMultiple,
roundToMultiple,
@@ -16,7 +16,7 @@ import Konva from 'konva';
import { GroupConfig } from 'konva/lib/Group';
import { KonvaEventObject } from 'konva/lib/Node';
import { Vector2d } from 'konva/lib/types';
import { isEqual } from 'lodash';
import { isEqual } from 'lodash-es';
import { useCallback, useEffect, useRef, useState } from 'react';
import { Group, Rect, Transformer } from 'react-konva';

View File

@@ -1,6 +1,6 @@
import { ButtonGroup, Flex } from '@chakra-ui/react';
import { createSelector } from '@reduxjs/toolkit';
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import IAIButton from 'common/components/IAIButton';
import IAICheckbox from 'common/components/IAICheckbox';
import IAIColorPicker from 'common/components/IAIColorPicker';
@@ -18,7 +18,7 @@ import {
setShouldPreserveMaskedArea,
} from 'features/canvas/store/canvasSlice';
import { rgbaColorToString } from 'features/canvas/util/colorToString';
import { isEqual } from 'lodash';
import { isEqual } from 'lodash-es';
import { useHotkeys } from 'react-hotkeys-hook';
import { useTranslation } from 'react-i18next';

View File

@@ -1,5 +1,5 @@
import { createSelector } from '@reduxjs/toolkit';
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import IAIIconButton from 'common/components/IAIIconButton';
import { canvasSelector } from 'features/canvas/store/canvasSelectors';
import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
@@ -9,7 +9,7 @@ import { FaRedo } from 'react-icons/fa';
import { redo } from 'features/canvas/store/canvasSlice';
import { systemSelector } from 'features/system/store/systemSelectors';
import { isEqual } from 'lodash';
import { isEqual } from 'lodash-es';
import { useTranslation } from 'react-i18next';
const canvasRedoSelector = createSelector(

View File

@@ -1,6 +1,6 @@
import { Flex } from '@chakra-ui/react';
import { createSelector } from '@reduxjs/toolkit';
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import IAICheckbox from 'common/components/IAICheckbox';
import IAIIconButton from 'common/components/IAIIconButton';
import IAIPopover from 'common/components/IAIPopover';
@@ -16,7 +16,7 @@ import {
setShouldSnapToGrid,
} from 'features/canvas/store/canvasSlice';
import EmptyTempFolderButtonModal from 'features/system/components/ClearTempFolderButtonModal';
import { isEqual } from 'lodash';
import { isEqual } from 'lodash-es';
import { ChangeEvent } from 'react';
import { useHotkeys } from 'react-hotkeys-hook';

View File

@@ -1,6 +1,6 @@
import { ButtonGroup, Flex } from '@chakra-ui/react';
import { createSelector } from '@reduxjs/toolkit';
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import IAIColorPicker from 'common/components/IAIColorPicker';
import IAIIconButton from 'common/components/IAIIconButton';
import IAIPopover from 'common/components/IAIPopover';
@@ -17,7 +17,7 @@ import {
setTool,
} from 'features/canvas/store/canvasSlice';
import { systemSelector } from 'features/system/store/systemSelectors';
import { clamp, isEqual } from 'lodash';
import { clamp, isEqual } from 'lodash-es';
import { useHotkeys } from 'react-hotkeys-hook';
import { useTranslation } from 'react-i18next';

View File

@@ -1,6 +1,6 @@
import { ButtonGroup, Flex } from '@chakra-ui/react';
import { createSelector } from '@reduxjs/toolkit';
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import IAIIconButton from 'common/components/IAIIconButton';
import IAISelect from 'common/components/IAISelect';
import useImageUploader from 'common/hooks/useImageUploader';
@@ -24,7 +24,7 @@ import {
import { mergeAndUploadCanvas } from 'features/canvas/store/thunks/mergeAndUploadCanvas';
import { getCanvasBaseLayer } from 'features/canvas/util/konvaInstanceProvider';
import { systemSelector } from 'features/system/store/systemSelectors';
import { isEqual } from 'lodash';
import { isEqual } from 'lodash-es';
import { ChangeEvent } from 'react';
import { useHotkeys } from 'react-hotkeys-hook';

View File

@@ -1,5 +1,5 @@
import { createSelector } from '@reduxjs/toolkit';
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import IAIIconButton from 'common/components/IAIIconButton';
import { canvasSelector } from 'features/canvas/store/canvasSelectors';
import { useHotkeys } from 'react-hotkeys-hook';
@@ -9,7 +9,7 @@ import { undo } from 'features/canvas/store/canvasSlice';
import { systemSelector } from 'features/system/store/systemSelectors';
import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
import { isEqual } from 'lodash';
import { isEqual } from 'lodash-es';
import { useTranslation } from 'react-i18next';
const canvasUndoSelector = createSelector(

View File

@@ -1,5 +1,5 @@
import { createSelector } from '@reduxjs/toolkit';
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import {
canvasSelector,
isStagingSelector,
@@ -9,7 +9,7 @@ import {
setStageCoordinates,
} from 'features/canvas/store/canvasSlice';
import { KonvaEventObject } from 'konva/lib/Node';
import { isEqual } from 'lodash';
import { isEqual } from 'lodash-es';
import { useCallback } from 'react';

View File

@@ -1,5 +1,5 @@
import { createSelector } from '@reduxjs/toolkit';
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import {
canvasSelector,
isStagingSelector,
@@ -13,7 +13,7 @@ import {
setTool,
} from 'features/canvas/store/canvasSlice';
import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
import { isEqual } from 'lodash';
import { isEqual } from 'lodash-es';
import { useRef } from 'react';
import { useHotkeys } from 'react-hotkeys-hook';

View File

@@ -1,5 +1,5 @@
import { createSelector } from '@reduxjs/toolkit';
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import {
canvasSelector,
isStagingSelector,
@@ -12,7 +12,7 @@ import {
import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
import Konva from 'konva';
import { KonvaEventObject } from 'konva/lib/Node';
import { isEqual } from 'lodash';
import { isEqual } from 'lodash-es';
import { MutableRefObject, useCallback } from 'react';
import getScaledCursorPosition from '../util/getScaledCursorPosition';

View File

@@ -1,5 +1,5 @@
import { createSelector } from '@reduxjs/toolkit';
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import {
canvasSelector,
isStagingSelector,
@@ -11,7 +11,7 @@ import {
import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
import Konva from 'konva';
import { Vector2d } from 'konva/lib/types';
import { isEqual } from 'lodash';
import { isEqual } from 'lodash-es';
import { MutableRefObject, useCallback } from 'react';
import getScaledCursorPosition from '../util/getScaledCursorPosition';

View File

@@ -1,4 +1,4 @@
import { useAppDispatch } from 'app/storeHooks';
import { useAppDispatch } from 'app/store/storeHooks';
import { mouseLeftCanvas } from 'features/canvas/store/canvasSlice';
import { useCallback } from 'react';

View File

@@ -1,5 +1,5 @@
import { createSelector } from '@reduxjs/toolkit';
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import {
canvasSelector,
isStagingSelector,
@@ -12,7 +12,7 @@ import {
} from 'features/canvas/store/canvasSlice';
import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
import Konva from 'konva';
import { isEqual } from 'lodash';
import { isEqual } from 'lodash-es';
import { MutableRefObject, useCallback } from 'react';
import getScaledCursorPosition from '../util/getScaledCursorPosition';

View File

@@ -1,5 +1,5 @@
import { createSelector } from '@reduxjs/toolkit';
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { canvasSelector } from 'features/canvas/store/canvasSelectors';
import {
setStageCoordinates,
@@ -7,7 +7,7 @@ import {
} from 'features/canvas/store/canvasSlice';
import Konva from 'konva';
import { KonvaEventObject } from 'konva/lib/Node';
import { clamp, isEqual } from 'lodash';
import { clamp, isEqual } from 'lodash-es';
import { MutableRefObject, useCallback } from 'react';
import {

View File

@@ -1,4 +1,4 @@
import { useAppDispatch } from 'app/storeHooks';
import { useAppDispatch } from 'app/store/storeHooks';
import Konva from 'konva';
import {
commitColorPickerColor,

View File

@@ -1,5 +1,5 @@
import { createSelector } from '@reduxjs/toolkit';
import { RootState } from 'app/store';
import { RootState } from 'app/store/store';
import { systemSelector } from 'features/system/store/systemSelectors';
import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
import { CanvasImage, CanvasState, isCanvasBaseImage } from './canvasTypes';

View File

@@ -1,12 +1,12 @@
import type { PayloadAction } from '@reduxjs/toolkit';
import { createSlice } from '@reduxjs/toolkit';
import * as InvokeAI from 'app/invokeai';
import * as InvokeAI from 'app/types/invokeai';
import {
roundDownToMultiple,
roundToMultiple,
} from 'common/util/roundDownToMultiple';
import { IRect, Vector2d } from 'konva/lib/types';
import { clamp, cloneDeep } from 'lodash';
import { clamp, cloneDeep } from 'lodash-es';
//
import { RgbaColor } from 'react-colorful';
import calculateCoordinates from '../util/calculateCoordinates';

View File

@@ -1,4 +1,4 @@
import * as InvokeAI from 'app/invokeai';
import * as InvokeAI from 'app/types/invokeai';
import { IRect, Vector2d } from 'konva/lib/types';
import { RgbaColor } from 'react-colorful';

View File

@@ -1,7 +1,7 @@
import { AnyAction, ThunkAction } from '@reduxjs/toolkit';
import * as InvokeAI from 'app/invokeai';
import { RootState } from 'app/store';
import { addImage } from 'features/gallery/store/gallerySlice';
import * as InvokeAI from 'app/types/invokeai';
import { RootState } from 'app/store/store';
// import { addImage } from 'features/gallery/store/gallerySlice';
import {
addToast,
setCurrentStatus,

View File

@@ -1,6 +1,6 @@
import { AppDispatch, AppGetState } from 'app/store';
import { AppDispatch, AppGetState } from 'app/store/store';
import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
import { debounce } from 'lodash';
import { debounce } from 'lodash-es';
import { setDoesCanvasNeedScaling } from '../canvasSlice';
const debouncedCanvasScale = debounce((dispatch: AppDispatch) => {

View File

@@ -1,5 +1,5 @@
import { createSelector } from '@reduxjs/toolkit';
import { isEqual } from 'lodash';
import { get, isEqual, isNumber, isString } from 'lodash-es';
import {
ButtonGroup,
@@ -10,8 +10,8 @@ import {
useDisclosure,
useToast,
} from '@chakra-ui/react';
import { runESRGAN, runFacetool } from 'app/socketio/actions';
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
// import { runESRGAN, runFacetool } from 'app/socketio/actions';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import IAIButton from 'common/components/IAIButton';
import IAIIconButton from 'common/components/IAIIconButton';
import IAIPopover from 'common/components/IAIPopover';
@@ -63,11 +63,11 @@ import {
} from '../store/gallerySelectors';
import DeleteImageModal from './DeleteImageModal';
import { useCallback } from 'react';
import useSetBothPrompts from 'features/parameters/hooks/usePrompt';
import { requestCanvasRescale } from 'features/canvas/store/thunks/requestCanvasScale';
import { useGetUrl } from 'common/util/getUrl';
import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus';
import { imageDeleted } from 'services/thunks/image';
import { useParameters } from 'features/parameters/hooks/useParameters';
const currentImageButtonsSelector = createSelector(
[
@@ -112,6 +112,8 @@ const currentImageButtonsSelector = createSelector(
isLightboxOpen,
shouldHidePreview,
image,
seed: image?.metadata?.invokeai?.node?.seed,
prompt: image?.metadata?.invokeai?.node?.prompt,
};
},
{
@@ -161,18 +163,10 @@ const CurrentImageButtons = (props: CurrentImageButtonsProps) => {
const toast = useToast();
const { t } = useTranslation();
const setBothPrompts = useSetBothPrompts();
const handleClickUseAsInitialImage = () => {
if (!image) return;
if (isLightboxOpen) dispatch(setIsLightboxOpen(false));
dispatch(initialImageSelected(image.name));
// dispatch(setInitialImage(currentImage));
const { recallPrompt, recallSeed, sendToImageToImage } = useParameters();
// dispatch(setActiveTab('img2img'));
};
const handleCopyImage = async () => {
const handleCopyImage = useCallback(async () => {
if (!image?.url) {
return;
}
@@ -194,9 +188,9 @@ const CurrentImageButtons = (props: CurrentImageButtonsProps) => {
duration: 2500,
isClosable: true,
});
};
}, [getUrl, t, image?.url, toast]);
const handleCopyImageLink = () => {
const handleCopyImageLink = useCallback(() => {
const url = image
? shouldTransformUrls
? getUrl(image.url)
@@ -215,37 +209,13 @@ const CurrentImageButtons = (props: CurrentImageButtonsProps) => {
isClosable: true,
});
});
};
}, [toast, shouldTransformUrls, getUrl, t, image]);
useHotkeys(
'shift+i',
() => {
if (image) {
handleClickUseAsInitialImage();
toast({
title: t('toast.sentToImageToImage'),
status: 'success',
duration: 2500,
isClosable: true,
});
} else {
toast({
title: t('toast.imageNotLoaded'),
description: t('toast.imageNotLoadedDesc'),
status: 'error',
duration: 2500,
isClosable: true,
});
}
},
[image]
);
const handlePreviewVisibility = () => {
const handlePreviewVisibility = useCallback(() => {
dispatch(setShouldHidePreview(!shouldHidePreview));
};
}, [dispatch, shouldHidePreview]);
const handleClickUseAllParameters = () => {
const handleClickUseAllParameters = useCallback(() => {
if (!image) return;
// selectedImage.metadata &&
// dispatch(setAllParameters(selectedImage.metadata));
@@ -254,12 +224,13 @@ const CurrentImageButtons = (props: CurrentImageButtonsProps) => {
// } else if (selectedImage.metadata?.image.type === 'txt2img') {
// dispatch(setActiveTab('txt2img'));
// }
};
}, [image]);
useHotkeys(
'a',
() => {
if (['txt2img', 'img2img'].includes(image?.metadata?.sd_metadata?.type)) {
const type = image?.metadata?.invokeai?.node?.types;
if (isString(type) && ['txt2img', 'img2img'].includes(type)) {
handleClickUseAllParameters();
toast({
title: t('toast.parametersSet'),
@@ -280,67 +251,27 @@ const CurrentImageButtons = (props: CurrentImageButtonsProps) => {
[image]
);
const handleClickUseSeed = () => {
image?.metadata && dispatch(setSeed(image.metadata.sd_metadata.seed));
};
const handleUseSeed = useCallback(() => {
recallSeed(image?.metadata?.invokeai?.node?.seed);
}, [image, recallSeed]);
useHotkeys(
's',
() => {
if (image?.metadata?.sd_metadata?.seed) {
handleClickUseSeed();
toast({
title: t('toast.seedSet'),
status: 'success',
duration: 2500,
isClosable: true,
});
} else {
toast({
title: t('toast.seedNotSet'),
description: t('toast.seedNotSetDesc'),
status: 'error',
duration: 2500,
isClosable: true,
});
}
},
[image]
);
useHotkeys('s', handleUseSeed, [image]);
const handleClickUsePrompt = useCallback(() => {
if (image?.metadata?.sd_metadata?.prompt) {
setBothPrompts(image?.metadata?.sd_metadata?.prompt);
}
}, [image?.metadata?.sd_metadata?.prompt, setBothPrompts]);
const handleUsePrompt = useCallback(() => {
recallPrompt(image?.metadata?.invokeai?.node?.prompt);
}, [image, recallPrompt]);
useHotkeys(
'p',
() => {
if (image?.metadata?.sd_metadata?.prompt) {
handleClickUsePrompt();
toast({
title: t('toast.promptSet'),
status: 'success',
duration: 2500,
isClosable: true,
});
} else {
toast({
title: t('toast.promptNotSet'),
description: t('toast.promptNotSetDesc'),
status: 'error',
duration: 2500,
isClosable: true,
});
}
},
[image]
);
useHotkeys('p', handleUsePrompt, [image]);
const handleClickUpscale = () => {
const handleSendToImageToImage = useCallback(() => {
sendToImageToImage(image);
}, [image, sendToImageToImage]);
useHotkeys('shift+i', handleSendToImageToImage, [image]);
const handleClickUpscale = useCallback(() => {
// selectedImage && dispatch(runESRGAN(selectedImage));
};
}, []);
useHotkeys(
'Shift+U',
@@ -369,9 +300,9 @@ const CurrentImageButtons = (props: CurrentImageButtonsProps) => {
]
);
const handleClickFixFaces = () => {
const handleClickFixFaces = useCallback(() => {
// selectedImage && dispatch(runFacetool(selectedImage));
};
}, []);
useHotkeys(
'Shift+R',
@@ -401,10 +332,12 @@ const CurrentImageButtons = (props: CurrentImageButtonsProps) => {
]
);
const handleClickShowImageDetails = () =>
dispatch(setShouldShowImageDetails(!shouldShowImageDetails));
const handleClickShowImageDetails = useCallback(
() => dispatch(setShouldShowImageDetails(!shouldShowImageDetails)),
[dispatch, shouldShowImageDetails]
);
const handleSendToCanvas = () => {
const handleSendToCanvas = useCallback(() => {
if (!image) return;
if (isLightboxOpen) dispatch(setIsLightboxOpen(false));
@@ -421,7 +354,7 @@ const CurrentImageButtons = (props: CurrentImageButtonsProps) => {
duration: 2500,
isClosable: true,
});
};
}, [image, isLightboxOpen, dispatch, activeTabName, toast, t]);
useHotkeys(
'i',
@@ -440,19 +373,19 @@ const CurrentImageButtons = (props: CurrentImageButtonsProps) => {
[image, shouldShowImageDetails]
);
const handleInitiateDelete = () => {
const handleDelete = useCallback(() => {
if (canDeleteImage && image) {
dispatch(imageDeleted({ imageType: image.type, imageName: image.name }));
}
}, [image, canDeleteImage, dispatch]);
const handleInitiateDelete = useCallback(() => {
if (shouldConfirmOnDelete) {
onDeleteDialogOpen();
} else {
handleDelete();
}
};
const handleDelete = () => {
if (canDeleteImage && image) {
dispatch(imageDeleted({ imageType: image.type, imageName: image.name }));
}
};
}, [shouldConfirmOnDelete, onDeleteDialogOpen, handleDelete]);
useHotkeys('delete', handleInitiateDelete, [
image,
@@ -461,9 +394,9 @@ const CurrentImageButtons = (props: CurrentImageButtonsProps) => {
isProcessing,
]);
const handleLightBox = () => {
const handleLightBox = useCallback(() => {
dispatch(setIsLightboxOpen(!isLightboxOpen));
};
}, [dispatch, isLightboxOpen]);
return (
<>
@@ -494,7 +427,7 @@ const CurrentImageButtons = (props: CurrentImageButtonsProps) => {
>
<IAIButton
size="sm"
onClick={handleClickUseAsInitialImage}
onClick={handleSendToImageToImage}
leftIcon={<FaShare />}
>
{t('parameters.sendToImg2Img')}
@@ -568,16 +501,16 @@ const CurrentImageButtons = (props: CurrentImageButtonsProps) => {
icon={<FaQuoteRight />}
tooltip={`${t('parameters.usePrompt')} (P)`}
aria-label={`${t('parameters.usePrompt')} (P)`}
isDisabled={!image?.metadata?.sd_metadata?.prompt}
onClick={handleClickUsePrompt}
isDisabled={!image?.metadata?.invokeai?.node?.prompt}
onClick={handleUsePrompt}
/>
<IAIIconButton
icon={<FaSeedling />}
tooltip={`${t('parameters.useSeed')} (S)`}
aria-label={`${t('parameters.useSeed')} (S)`}
isDisabled={!image?.metadata?.sd_metadata?.seed}
onClick={handleClickUseSeed}
isDisabled={!image?.metadata?.invokeai?.node?.seed}
onClick={handleUseSeed}
/>
<IAIIconButton

View File

@@ -1,8 +1,8 @@
import { Flex, Icon } from '@chakra-ui/react';
import { createSelector } from '@reduxjs/toolkit';
import { useAppSelector } from 'app/storeHooks';
import { useAppSelector } from 'app/store/storeHooks';
import { systemSelector } from 'features/system/store/systemSelectors';
import { isEqual } from 'lodash';
import { isEqual } from 'lodash-es';
import { MdPhoto } from 'react-icons/md';
import { selectedImageSelector } from '../store/gallerySelectors';

View File

@@ -1,46 +1,27 @@
import { Box, Flex, Image } from '@chakra-ui/react';
import { createSelector } from '@reduxjs/toolkit';
import { useAppSelector } from 'app/storeHooks';
import { useAppSelector } from 'app/store/storeHooks';
import { useGetUrl } from 'common/util/getUrl';
import { systemSelector } from 'features/system/store/systemSelectors';
import { uiSelector } from 'features/ui/store/uiSelectors';
import { isEqual } from 'lodash';
import { isEqual } from 'lodash-es';
import { selectedImageSelector } from '../store/gallerySelectors';
import CurrentImageFallback from './CurrentImageFallback';
import ImageMetadataViewer from './ImageMetaDataViewer/ImageMetadataViewer';
import NextPrevImageButtons from './NextPrevImageButtons';
import CurrentImageHidden from './CurrentImageHidden';
import { memo } from 'react';
export const imagesSelector = createSelector(
[uiSelector, selectedImageSelector, systemSelector],
(ui, selectedImage, system) => {
const { shouldShowImageDetails, shouldHidePreview } = ui;
const { progressImage } = system;
// TODO: Clean this up, this is really gross
const imageToDisplay = progressImage
? {
url: progressImage.dataURL,
width: progressImage.width,
height: progressImage.height,
isProgressImage: true,
image: progressImage,
}
: selectedImage
? {
url: selectedImage.url,
width: selectedImage.metadata.width,
height: selectedImage.metadata.height,
isProgressImage: false,
image: selectedImage,
}
: null;
return {
shouldShowImageDetails,
shouldHidePreview,
imageToDisplay,
image: selectedImage,
};
},
{
@@ -50,8 +31,8 @@ export const imagesSelector = createSelector(
}
);
export default function CurrentImagePreview() {
const { shouldShowImageDetails, imageToDisplay, shouldHidePreview } =
const CurrentImagePreview = () => {
const { shouldShowImageDetails, image, shouldHidePreview } =
useAppSelector(imagesSelector);
const { getUrl } = useGetUrl();
@@ -65,54 +46,39 @@ export default function CurrentImagePreview() {
height: '100%',
}}
>
{imageToDisplay && (
{image && (
<Image
src={
shouldHidePreview
? undefined
: imageToDisplay.isProgressImage
? imageToDisplay.url
: getUrl(imageToDisplay.url)
}
width={imageToDisplay.width}
height={imageToDisplay.height}
fallback={
shouldHidePreview ? (
<CurrentImageHidden />
) : !imageToDisplay.isProgressImage ? (
<CurrentImageFallback />
) : undefined
}
src={shouldHidePreview ? undefined : getUrl(image.url)}
width={image.metadata.width}
height={image.metadata.height}
fallback={shouldHidePreview ? <CurrentImageHidden /> : undefined}
sx={{
objectFit: 'contain',
maxWidth: '100%',
maxHeight: '100%',
height: 'auto',
position: 'absolute',
imageRendering: imageToDisplay.isProgressImage
? 'pixelated'
: 'initial',
borderRadius: 'base',
}}
/>
)}
{shouldShowImageDetails && image && 'metadata' in image && (
<Box
sx={{
position: 'absolute',
top: '0',
width: '100%',
height: '100%',
borderRadius: 'base',
overflow: 'scroll',
}}
>
<ImageMetadataViewer image={image} />
</Box>
)}
{!shouldShowImageDetails && <NextPrevImageButtons />}
{shouldShowImageDetails &&
imageToDisplay &&
'metadata' in imageToDisplay.image && (
<Box
sx={{
position: 'absolute',
top: '0',
width: '100%',
height: '100%',
borderRadius: 'base',
overflow: 'scroll',
}}
>
<ImageMetadataViewer image={imageToDisplay.image} />
</Box>
)}
</Flex>
);
}
};
export default memo(CurrentImagePreview);

View File

@@ -9,13 +9,13 @@ import {
Text,
} from '@chakra-ui/react';
import { createSelector } from '@reduxjs/toolkit';
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import IAIButton from 'common/components/IAIButton';
import IAISwitch from 'common/components/IAISwitch';
import { configSelector } from 'features/system/store/configSelectors';
import { systemSelector } from 'features/system/store/systemSelectors';
import { setShouldConfirmOnDelete } from 'features/system/store/systemSlice';
import { isEqual } from 'lodash';
import { isEqual } from 'lodash-es';
import { ChangeEvent, memo, useCallback, useRef } from 'react';
import { useTranslation } from 'react-i18next';

View File

@@ -5,65 +5,38 @@ import {
Image,
MenuItem,
MenuList,
Text,
Skeleton,
useDisclosure,
useTheme,
useToast,
} from '@chakra-ui/react';
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
import {
imageSelected,
setCurrentImage,
} from 'features/gallery/store/gallerySlice';
import {
initialImageSelected,
setAllImageToImageParameters,
setAllParameters,
setSeed,
} from 'features/parameters/store/generationSlice';
import { DragEvent, memo, useState } from 'react';
import {
FaCheck,
FaExpand,
FaLink,
FaShare,
FaTrash,
FaTrashAlt,
} from 'react-icons/fa';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { imageSelected } from 'features/gallery/store/gallerySlice';
import { DragEvent, memo, useCallback, useState } from 'react';
import { FaCheck, FaExpand, FaImage, FaShare, FaTrash } from 'react-icons/fa';
import DeleteImageModal from './DeleteImageModal';
import { ContextMenu } from 'chakra-ui-contextmenu';
import * as InvokeAI from 'app/invokeai';
import {
resizeAndScaleCanvas,
setInitialCanvasImage,
} from 'features/canvas/store/canvasSlice';
import * as InvokeAI from 'app/types/invokeai';
import { resizeAndScaleCanvas } from 'features/canvas/store/canvasSlice';
import { gallerySelector } from 'features/gallery/store/gallerySelectors';
import { setActiveTab } from 'features/ui/store/uiSlice';
import { useTranslation } from 'react-i18next';
import useSetBothPrompts from 'features/parameters/hooks/usePrompt';
import { setIsLightboxOpen } from 'features/lightbox/store/lightboxSlice';
import IAIIconButton from 'common/components/IAIIconButton';
import { useGetUrl } from 'common/util/getUrl';
import { ExternalLinkIcon } from '@chakra-ui/icons';
import { BiZoomIn } from 'react-icons/bi';
import { IoArrowUndoCircleOutline } from 'react-icons/io5';
import { imageDeleted } from 'services/thunks/image';
import { createSelector } from '@reduxjs/toolkit';
import { systemSelector } from 'features/system/store/systemSelectors';
import { configSelector } from 'features/system/store/configSelectors';
import { lightboxSelector } from 'features/lightbox/store/lightboxSelectors';
import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
import { isEqual } from 'lodash';
import { isEqual } from 'lodash-es';
import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus';
import { useParameters } from 'features/parameters/hooks/useParameters';
export const selector = createSelector(
[
gallerySelector,
systemSelector,
configSelector,
lightboxSelector,
activeTabNameSelector,
],
(gallery, system, config, lightbox, activeTabName) => {
[gallerySelector, systemSelector, lightboxSelector, activeTabNameSelector],
(gallery, system, lightbox, activeTabName) => {
const {
galleryImageObjectFit,
galleryImageMinimumWidth,
@@ -71,7 +44,6 @@ export const selector = createSelector(
} = gallery;
const { isLightboxOpen } = lightbox;
const { disabledFeatures } = config;
const { isConnected, isProcessing, shouldConfirmOnDelete } = system;
return {
@@ -82,7 +54,6 @@ export const selector = createSelector(
shouldUseSingleGalleryColumn,
activeTabName,
isLightboxOpen,
disabledFeatures,
};
},
{
@@ -113,14 +84,15 @@ const HoverableImage = memo((props: HoverableImageProps) => {
galleryImageMinimumWidth,
canDeleteImage,
shouldUseSingleGalleryColumn,
disabledFeatures,
shouldConfirmOnDelete,
} = useAppSelector(selector);
const {
isOpen: isDeleteDialogOpen,
onOpen: onDeleteDialogOpen,
onClose: onDeleteDialogClose,
} = useDisclosure();
const { image, isSelected } = props;
const { url, thumbnail, name, metadata } = image;
const { getUrl } = useGetUrl();
@@ -130,53 +102,62 @@ const HoverableImage = memo((props: HoverableImageProps) => {
const toast = useToast();
const { direction } = useTheme();
const { t } = useTranslation();
const setBothPrompts = useSetBothPrompts();
const { isFeatureEnabled: isLightboxEnabled } = useFeatureStatus('lightbox');
const { recallSeed, recallPrompt, sendToImageToImage, recallInitialImage } =
useParameters();
const handleMouseOver = () => setIsHovered(true);
const handleMouseOut = () => setIsHovered(false);
const handleInitiateDelete = () => {
// Immediately deletes an image
const handleDelete = useCallback(() => {
if (canDeleteImage && image) {
dispatch(imageDeleted({ imageType: image.type, imageName: image.name }));
}
}, [dispatch, image, canDeleteImage]);
// Opens the alert dialog to check if user is sure they want to delete
const handleInitiateDelete = useCallback(() => {
if (shouldConfirmOnDelete) {
onDeleteDialogOpen();
} else {
handleDelete();
}
};
}, [handleDelete, onDeleteDialogOpen, shouldConfirmOnDelete]);
const handleDelete = () => {
if (canDeleteImage && image) {
dispatch(imageDeleted({ imageType: image.type, imageName: image.name }));
}
};
const handleSelectImage = useCallback(() => {
dispatch(imageSelected(image));
}, [image, dispatch]);
const handleUsePrompt = () => {
if (image.metadata?.sd_metadata?.prompt) {
setBothPrompts(image.metadata?.sd_metadata?.prompt);
}
toast({
title: t('toast.promptSet'),
status: 'success',
duration: 2500,
isClosable: true,
});
};
const handleDragStart = useCallback(
(e: DragEvent<HTMLDivElement>) => {
e.dataTransfer.setData('invokeai/imageName', image.name);
e.dataTransfer.setData('invokeai/imageType', image.type);
e.dataTransfer.effectAllowed = 'move';
},
[image]
);
const handleUseSeed = () => {
image.metadata.sd_metadata &&
dispatch(setSeed(image.metadata.sd_metadata.image.seed));
toast({
title: t('toast.seedSet'),
status: 'success',
duration: 2500,
isClosable: true,
});
};
// Recall parameters handlers
const handleRecallPrompt = useCallback(() => {
recallPrompt(image.metadata?.invokeai?.node?.prompt);
}, [image, recallPrompt]);
const handleSendToImageToImage = () => {
dispatch(initialImageSelected(image.name));
};
const handleRecallSeed = useCallback(() => {
recallSeed(image.metadata.invokeai?.node?.seed);
}, [image, recallSeed]);
const handleSendToImageToImage = useCallback(() => {
sendToImageToImage(image);
}, [image, sendToImageToImage]);
const handleRecallInitialImage = useCallback(() => {
recallInitialImage(image.metadata.invokeai?.node?.image);
}, [image, recallInitialImage]);
/**
* TODO: the rest of these
*/
const handleSendToCanvas = () => {
// dispatch(setInitialCanvasImage(image));
@@ -195,48 +176,14 @@ const HoverableImage = memo((props: HoverableImageProps) => {
};
const handleUseAllParameters = () => {
metadata.sd_metadata && dispatch(setAllParameters(metadata.sd_metadata));
toast({
title: t('toast.parametersSet'),
status: 'success',
duration: 2500,
isClosable: true,
});
};
const handleUseInitialImage = async () => {
if (metadata.sd_metadata?.image?.init_image_path) {
const response = await fetch(
metadata.sd_metadata?.image?.init_image_path
);
if (response.ok) {
dispatch(setAllImageToImageParameters(metadata?.sd_metadata));
toast({
title: t('toast.initialImageSet'),
status: 'success',
duration: 2500,
isClosable: true,
});
return;
}
}
toast({
title: t('toast.initialImageNotSet'),
description: t('toast.initialImageNotSetDesc'),
status: 'error',
duration: 2500,
isClosable: true,
});
};
const handleSelectImage = () => {
dispatch(imageSelected(image.name));
};
const handleDragStart = (e: DragEvent<HTMLDivElement>) => {
e.dataTransfer.setData('invokeai/imageName', image.name);
e.dataTransfer.setData('invokeai/imageType', image.type);
e.dataTransfer.effectAllowed = 'move';
// metadata.invokeai?.node &&
// dispatch(setAllParameters(metadata.invokeai?.node));
// toast({
// title: t('toast.parametersSet'),
// status: 'success',
// duration: 2500,
// isClosable: true,
// });
};
const handleLightBox = () => {
@@ -253,37 +200,37 @@ const HoverableImage = memo((props: HoverableImageProps) => {
<ContextMenu<HTMLDivElement>
menuProps={{ size: 'sm', isLazy: true }}
renderMenu={() => (
<MenuList>
<MenuList sx={{ visibility: 'visible !important' }}>
<MenuItem
icon={<ExternalLinkIcon />}
onClickCapture={handleOpenInNewTab}
>
{t('common.openInNewTab')}
</MenuItem>
{!disabledFeatures.includes('lightbox') && (
{isLightboxEnabled && (
<MenuItem icon={<FaExpand />} onClickCapture={handleLightBox}>
{t('parameters.openInViewer')}
</MenuItem>
)}
<MenuItem
icon={<IoArrowUndoCircleOutline />}
onClickCapture={handleUsePrompt}
isDisabled={image?.metadata?.sd_metadata?.prompt === undefined}
onClickCapture={handleRecallPrompt}
isDisabled={image?.metadata?.invokeai?.node?.prompt === undefined}
>
{t('parameters.usePrompt')}
</MenuItem>
<MenuItem
icon={<IoArrowUndoCircleOutline />}
onClickCapture={handleUseSeed}
isDisabled={image?.metadata?.sd_metadata?.seed === undefined}
onClickCapture={handleRecallSeed}
isDisabled={image?.metadata?.invokeai?.node?.seed === undefined}
>
{t('parameters.useSeed')}
</MenuItem>
<MenuItem
icon={<IoArrowUndoCircleOutline />}
onClickCapture={handleUseInitialImage}
isDisabled={image?.metadata?.sd_metadata?.type !== 'img2img'}
onClickCapture={handleRecallInitialImage}
isDisabled={image?.metadata?.invokeai?.node?.type !== 'img2img'}
>
{t('parameters.useInitImg')}
</MenuItem>
@@ -292,7 +239,7 @@ const HoverableImage = memo((props: HoverableImageProps) => {
onClickCapture={handleUseAllParameters}
isDisabled={
!['txt2img', 'img2img'].includes(
image?.metadata?.sd_metadata?.type
String(image?.metadata?.invokeai?.node?.type)
)
}
>
@@ -322,58 +269,48 @@ const HoverableImage = memo((props: HoverableImageProps) => {
userSelect="none"
draggable={true}
onDragStart={handleDragStart}
onClick={handleSelectImage}
ref={ref}
sx={{
padding: 2,
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
w: 'full',
h: 'full',
transition: 'transform 0.2s ease-out',
_hover: {
cursor: 'pointer',
zIndex: 2,
},
_before: {
content: '""',
display: 'block',
paddingBottom: '100%',
},
aspectRatio: '1/1',
}}
>
<Image
loading="lazy"
objectFit={
shouldUseSingleGalleryColumn ? 'contain' : galleryImageObjectFit
}
rounded="md"
src={getUrl(thumbnail || url)}
loading="lazy"
fallback={<FaImage />}
sx={{
position: 'absolute',
width: '100%',
height: '100%',
maxWidth: '100%',
maxHeight: '100%',
top: '50%',
transform: 'translate(-50%,-50%)',
...(direction === 'rtl'
? { insetInlineEnd: '50%' }
: { insetInlineStart: '50%' }),
}}
/>
<Flex
onClick={handleSelectImage}
sx={{
position: 'absolute',
top: '0',
insetInlineStart: '0',
width: '100%',
height: '100%',
alignItems: 'center',
justifyContent: 'center',
}}
>
{isSelected && (
{isSelected && (
<Flex
sx={{
position: 'absolute',
top: '0',
insetInlineStart: '0',
width: '100%',
height: '100%',
alignItems: 'center',
justifyContent: 'center',
pointerEvents: 'none',
}}
>
<Icon
filter={'drop-shadow(0px 0px 1rem black)'}
as={FaCheck}
sx={{
width: '50%',
@@ -381,9 +318,9 @@ const HoverableImage = memo((props: HoverableImageProps) => {
fill: 'ok.500',
}}
/>
)}
</Flex>
{isHovered && galleryImageMinimumWidth >= 64 && (
</Flex>
)}
{isHovered && galleryImageMinimumWidth >= 100 && (
<Box
sx={{
position: 'absolute',

View File

@@ -10,7 +10,7 @@
// useTheme,
// } from '@chakra-ui/react';
// import { requestImages } from 'app/socketio/actions';
// import { useAppDispatch, useAppSelector } from 'app/storeHooks';
// import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
// import IAIButton from 'common/components/IAIButton';
// import IAICheckbox from 'common/components/IAICheckbox';
// import IAIIconButton from 'common/components/IAIIconButton';
@@ -35,7 +35,7 @@
// } from 'features/ui/store/uiSlice';
// import { InvokeTabName } from 'features/ui/store/tabMap';
// import { clamp } from 'lodash';
// import { clamp } from 'lodash-es';
// import { Direction } from 're-resizable/lib/resizer';
// import React, {
// ChangeEvent,

View File

@@ -1,6 +1,14 @@
import { ButtonGroup, Flex, Grid, Icon, Image, Text } from '@chakra-ui/react';
import { requestImages } from 'app/socketio/actions';
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
import {
Box,
ButtonGroup,
Flex,
FlexProps,
Grid,
Icon,
Text,
forwardRef,
} from '@chakra-ui/react';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import IAIButton from 'common/components/IAIButton';
import IAICheckbox from 'common/components/IAICheckbox';
import IAIIconButton from 'common/components/IAIIconButton';
@@ -15,28 +23,33 @@ import {
setShouldUseSingleGalleryColumn,
} from 'features/gallery/store/gallerySlice';
import { togglePinGalleryPanel } from 'features/ui/store/uiSlice';
import { useOverlayScrollbars } from 'overlayscrollbars-react';
import { ChangeEvent, useEffect, useRef, useState } from 'react';
import {
ChangeEvent,
PropsWithChildren,
memo,
useCallback,
useEffect,
useRef,
useState,
} from 'react';
import { useTranslation } from 'react-i18next';
import { BsPinAngle, BsPinAngleFill } from 'react-icons/bs';
import { FaImage, FaUser, FaWrench } from 'react-icons/fa';
import { MdPhotoLibrary } from 'react-icons/md';
import HoverableImage from './HoverableImage';
import Scrollable from 'features/ui/components/common/Scrollable';
import { requestCanvasRescale } from 'features/canvas/store/thunks/requestCanvasScale';
import {
resultsAdapter,
selectResultsAll,
selectResultsTotal,
} from '../store/resultsSlice';
import { resultsAdapter } from '../store/resultsSlice';
import {
receivedResultImagesPage,
receivedUploadImagesPage,
} from 'services/thunks/gallery';
import { selectUploadsAll, uploadsAdapter } from '../store/uploadsSlice';
import { uploadsAdapter } from '../store/uploadsSlice';
import { createSelector } from '@reduxjs/toolkit';
import { RootState } from 'app/store';
import { RootState } from 'app/store/store';
import { Virtuoso, VirtuosoGrid } from 'react-virtuoso';
const GALLERY_SHOW_BUTTONS_MIN_WIDTH = 290;
@@ -49,7 +62,7 @@ const gallerySelector = createSelector(
(uploads, results, gallery) => {
const { currentCategory } = gallery;
return currentCategory === 'result'
return currentCategory === 'results'
? {
images: resultsAdapter.getSelectors().selectAll(results),
isLoading: results.isLoading,
@@ -68,32 +81,41 @@ const ImageGalleryContent = () => {
const { t } = useTranslation();
const resizeObserverRef = useRef<HTMLDivElement>(null);
const [shouldShouldIconButtons, setShouldShouldIconButtons] = useState(true);
const rootRef = useRef(null);
const [scroller, setScroller] = useState<HTMLElement | null>(null);
const [initialize, osInstance] = useOverlayScrollbars({
defer: true,
options: {
scrollbars: {
visibility: 'auto',
autoHide: 'leave',
autoHideDelay: 1300,
theme: 'os-theme-dark',
},
overflow: { x: 'hidden' },
},
});
const {
// images,
currentCategory,
currentImageUuid,
shouldPinGallery,
galleryImageMinimumWidth,
galleryGridTemplateColumns,
galleryImageObjectFit,
shouldAutoSwitchToNewImages,
// areMoreImagesAvailable,
shouldUseSingleGalleryColumn,
selectedImage,
} = useAppSelector(imageGallerySelector);
const { images, areMoreImagesAvailable, isLoading } =
useAppSelector(gallerySelector);
// const handleClickLoadMore = () => {
// dispatch(requestImages(currentCategory));
// };
const handleClickLoadMore = () => {
if (currentCategory === 'result') {
if (currentCategory === 'results') {
dispatch(receivedResultImagesPage());
}
if (currentCategory === 'user') {
if (currentCategory === 'uploads') {
dispatch(receivedUploadImagesPage());
}
};
@@ -129,6 +151,25 @@ const ImageGalleryContent = () => {
return () => resizeObserver.disconnect(); // clean up
}, []);
useEffect(() => {
const { current: root } = rootRef;
if (scroller && root) {
initialize({
target: root,
elements: {
viewport: scroller,
},
});
}
return () => osInstance()?.destroy();
}, [scroller, initialize, osInstance]);
const setScrollerRef = useCallback((ref: HTMLElement | Window | null) => {
if (ref instanceof HTMLElement) {
setScroller(ref);
}
}, []);
return (
<Flex flexDirection="column" w="full" h="full" gap={4}>
<Flex
@@ -147,34 +188,34 @@ const ImageGalleryContent = () => {
<IAIIconButton
aria-label={t('gallery.showGenerations')}
tooltip={t('gallery.showGenerations')}
isChecked={currentCategory === 'result'}
isChecked={currentCategory === 'results'}
role="radio"
icon={<FaImage />}
onClick={() => dispatch(setCurrentCategory('result'))}
onClick={() => dispatch(setCurrentCategory('results'))}
/>
<IAIIconButton
aria-label={t('gallery.showUploads')}
tooltip={t('gallery.showUploads')}
role="radio"
isChecked={currentCategory === 'user'}
isChecked={currentCategory === 'uploads'}
icon={<FaUser />}
onClick={() => dispatch(setCurrentCategory('user'))}
onClick={() => dispatch(setCurrentCategory('uploads'))}
/>
</>
) : (
<>
<IAIButton
size="sm"
isChecked={currentCategory === 'result'}
onClick={() => dispatch(setCurrentCategory('result'))}
isChecked={currentCategory === 'results'}
onClick={() => dispatch(setCurrentCategory('results'))}
flexGrow={1}
>
{t('gallery.generations')}
</IAIButton>
<IAIButton
size="sm"
isChecked={currentCategory === 'user'}
onClick={() => dispatch(setCurrentCategory('user'))}
isChecked={currentCategory === 'uploads'}
onClick={() => dispatch(setCurrentCategory('uploads'))}
flexGrow={1}
>
{t('gallery.uploads')}
@@ -241,65 +282,119 @@ const ImageGalleryContent = () => {
/>
</Flex>
</Flex>
<Scrollable>
<Flex direction="column" gap={2} h="full">
{images.length || areMoreImagesAvailable ? (
<>
<Grid
gap={2}
style={{ gridTemplateColumns: galleryGridTemplateColumns }}
>
{images.map((image) => {
const { name } = image;
const isSelected = currentImageUuid === name;
return (
<HoverableImage
key={`${name}-${image.thumbnail}`}
image={image}
isSelected={isSelected}
/>
);
})}
</Grid>
<IAIButton
onClick={handleClickLoadMore}
isDisabled={!areMoreImagesAvailable}
isLoading={isLoading}
flexShrink={0}
>
{areMoreImagesAvailable
? t('gallery.loadMore')
: t('gallery.allImagesLoaded')}
</IAIButton>
</>
) : (
<Flex
sx={{
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
gap: 2,
padding: 8,
h: '100%',
w: '100%',
color: 'base.500',
}}
<Flex direction="column" gap={2} h="full">
{images.length || areMoreImagesAvailable ? (
<>
<Box ref={rootRef} data-overlayscrollbars="" h="100%">
{shouldUseSingleGalleryColumn ? (
<Virtuoso
style={{ height: '100%' }}
data={images}
scrollerRef={(ref) => setScrollerRef(ref)}
itemContent={(index, image) => {
const { name } = image;
const isSelected = selectedImage?.name === name;
return (
<Flex sx={{ pb: 2 }}>
<HoverableImage
key={`${name}-${image.thumbnail}`}
image={image}
isSelected={isSelected}
/>
</Flex>
);
}}
/>
) : (
<VirtuosoGrid
style={{ height: '100%' }}
data={images}
components={{
Item: ItemContainer,
List: ListContainer,
}}
scrollerRef={setScroller}
itemContent={(index, image) => {
const { name } = image;
const isSelected = selectedImage?.name === name;
return (
<HoverableImage
key={`${name}-${image.thumbnail}`}
image={image}
isSelected={isSelected}
/>
);
}}
/>
)}
</Box>
<IAIButton
onClick={handleClickLoadMore}
isDisabled={!areMoreImagesAvailable}
isLoading={isLoading}
flexShrink={0}
>
<Icon
as={MdPhotoLibrary}
sx={{
w: 16,
h: 16,
}}
/>
<Text textAlign="center">{t('gallery.noImagesInGallery')}</Text>
</Flex>
)}
</Flex>
</Scrollable>
{areMoreImagesAvailable
? t('gallery.loadMore')
: t('gallery.allImagesLoaded')}
</IAIButton>
</>
) : (
<Flex
sx={{
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
gap: 2,
padding: 8,
h: '100%',
w: '100%',
color: 'base.500',
}}
>
<Icon
as={MdPhotoLibrary}
sx={{
w: 16,
h: 16,
}}
/>
<Text textAlign="center">{t('gallery.noImagesInGallery')}</Text>
</Flex>
)}
</Flex>
</Flex>
);
};
ImageGalleryContent.displayName = 'ImageGalleryContent';
export default ImageGalleryContent;
type ItemContainerProps = PropsWithChildren & FlexProps;
const ItemContainer = forwardRef((props: ItemContainerProps, ref) => (
<Box className="item-container" ref={ref}>
{props.children}
</Box>
));
type ListContainerProps = PropsWithChildren & FlexProps;
const ListContainer = forwardRef((props: ListContainerProps, ref) => {
const galleryImageMinimumWidth = useAppSelector(
(state: RootState) => state.gallery.galleryImageMinimumWidth
);
return (
<Grid
{...props}
className="list-container"
ref={ref}
sx={{
gap: 2,
gridTemplateColumns: `repeat(auto-fit, minmax(${galleryImageMinimumWidth}px, 1fr));`,
}}
>
{props.children}
</Grid>
);
});
export default memo(ImageGalleryContent);

View File

@@ -1,13 +1,13 @@
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { gallerySelector } from 'features/gallery/store/gallerySelectors';
import {
selectNextImage,
selectPrevImage,
// selectNextImage,
// selectPrevImage,
setGalleryImageMinimumWidth,
} from 'features/gallery/store/gallerySlice';
import { InvokeTabName } from 'features/ui/store/tabMap';
import { clamp, isEqual } from 'lodash';
import { clamp, isEqual } from 'lodash-es';
import { useHotkeys } from 'react-hotkeys-hook';
import './ImageGallery.css';
@@ -28,6 +28,7 @@ import { requestCanvasRescale } from 'features/canvas/store/thunks/requestCanvas
import { lightboxSelector } from 'features/lightbox/store/lightboxSelectors';
import useResolution from 'common/hooks/useResolution';
import { Flex } from '@chakra-ui/react';
import { memo } from 'react';
const GALLERY_TAB_WIDTHS: Record<
InvokeTabName,
@@ -72,7 +73,7 @@ const galleryPanelSelector = createSelector(
}
);
export default function ImageGalleryPanel() {
export const ImageGalleryPanel = () => {
const dispatch = useAppDispatch();
const {
shouldPinGallery,
@@ -109,28 +110,6 @@ export default function ImageGalleryPanel() {
[shouldPinGallery]
);
useHotkeys(
'left',
() => {
dispatch(selectPrevImage());
},
{
enabled: !isStaging || activeTabName !== 'unifiedCanvas',
},
[isStaging, activeTabName]
);
useHotkeys(
'right',
() => {
dispatch(selectNextImage());
},
{
enabled: !isStaging || activeTabName !== 'unifiedCanvas',
},
[isStaging, activeTabName]
);
useHotkeys(
'shift+g',
() => {
@@ -232,4 +211,6 @@ export default function ImageGalleryPanel() {
};
return renderImageGallery();
}
};
export default memo(ImageGalleryPanel);

View File

@@ -9,8 +9,8 @@ import {
Text,
Tooltip,
} from '@chakra-ui/react';
import * as InvokeAI from 'app/invokeai';
import { useAppDispatch } from 'app/storeHooks';
import * as InvokeAI from 'app/types/invokeai';
import { useAppDispatch } from 'app/store/storeHooks';
import { useGetUrl } from 'common/util/getUrl';
import promptToString from 'common/util/promptToString';
import { seedWeightsToString } from 'common/util/seedWeightPairs';
@@ -159,6 +159,7 @@ const ImageMetadataViewer = memo(({ image }: ImageMetadataViewerProps) => {
_dark: {
bg: 'blackAlpha.600',
},
overflow: 'scroll',
}}
>
<Flex gap={2}>

View File

@@ -9,8 +9,8 @@ import {
Text,
Tooltip,
} from '@chakra-ui/react';
import * as InvokeAI from 'app/invokeai';
import { useAppDispatch } from 'app/storeHooks';
import * as InvokeAI from 'app/types/invokeai';
import { useAppDispatch } from 'app/store/storeHooks';
import { useGetUrl } from 'common/util/getUrl';
import promptToString from 'common/util/promptToString';
import { seedWeightsToString } from 'common/util/seedWeightPairs';

View File

@@ -1,16 +1,14 @@
import { ChakraProps, Flex, Grid, IconButton } from '@chakra-ui/react';
import { createSelector } from '@reduxjs/toolkit';
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
import { isEqual } from 'lodash';
import { useState } from 'react';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { clamp, isEqual } from 'lodash-es';
import { useCallback, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { FaAngleLeft, FaAngleRight } from 'react-icons/fa';
import { gallerySelector } from '../store/gallerySelectors';
import {
GalleryCategory,
selectNextImage,
selectPrevImage,
} from '../store/gallerySlice';
import { RootState } from 'app/store/store';
import { imageSelected } from '../store/gallerySlice';
import { useHotkeys } from 'react-hotkeys-hook';
const nextPrevButtonTriggerAreaStyles: ChakraProps['sx'] = {
height: '100%',
@@ -23,24 +21,47 @@ const nextPrevButtonStyles: ChakraProps['sx'] = {
};
export const nextPrevImageButtonsSelector = createSelector(
gallerySelector,
(gallery) => {
const { currentImage } = gallery;
[(state: RootState) => state, gallerySelector],
(state, gallery) => {
const { selectedImage, currentCategory } = gallery;
const tempImages =
gallery.categories[
currentImage ? (currentImage.category as GalleryCategory) : 'result'
].images;
if (!selectedImage) {
return {
isOnFirstImage: true,
isOnLastImage: true,
};
}
const currentImageIndex = tempImages.findIndex(
(i) => i.uuid === gallery?.currentImage?.uuid
const currentImageIndex = state[currentCategory].ids.findIndex(
(i) => i === selectedImage.name
);
const imagesLength = tempImages.length;
const nextImageIndex = clamp(
currentImageIndex + 1,
0,
state[currentCategory].ids.length - 1
);
const prevImageIndex = clamp(
currentImageIndex - 1,
0,
state[currentCategory].ids.length - 1
);
const nextImageId = state[currentCategory].ids[nextImageIndex];
const prevImageId = state[currentCategory].ids[prevImageIndex];
const nextImage = state[currentCategory].entities[nextImageId];
const prevImage = state[currentCategory].entities[prevImageId];
const imagesLength = state[currentCategory].ids.length;
return {
isOnFirstImage: currentImageIndex === 0,
isOnLastImage:
!isNaN(currentImageIndex) && currentImageIndex === imagesLength - 1,
nextImage,
prevImage,
};
},
{
@@ -54,34 +75,48 @@ const NextPrevImageButtons = () => {
const dispatch = useAppDispatch();
const { t } = useTranslation();
const { isOnFirstImage, isOnLastImage } = useAppSelector(
nextPrevImageButtonsSelector
);
const { isOnFirstImage, isOnLastImage, nextImage, prevImage } =
useAppSelector(nextPrevImageButtonsSelector);
const [shouldShowNextPrevButtons, setShouldShowNextPrevButtons] =
useState<boolean>(false);
const handleCurrentImagePreviewMouseOver = () => {
const handleCurrentImagePreviewMouseOver = useCallback(() => {
setShouldShowNextPrevButtons(true);
};
}, []);
const handleCurrentImagePreviewMouseOut = () => {
const handleCurrentImagePreviewMouseOut = useCallback(() => {
setShouldShowNextPrevButtons(false);
};
}, []);
const handleClickPrevButton = () => {
dispatch(selectPrevImage());
};
const handlePrevImage = useCallback(() => {
dispatch(imageSelected(prevImage));
}, [dispatch, prevImage]);
const handleClickNextButton = () => {
dispatch(selectNextImage());
};
const handleNextImage = useCallback(() => {
dispatch(imageSelected(nextImage));
}, [dispatch, nextImage]);
useHotkeys(
'left',
() => {
handlePrevImage();
},
[prevImage]
);
useHotkeys(
'right',
() => {
handleNextImage();
},
[nextImage]
);
return (
<Flex
sx={{
justifyContent: 'space-between',
zIndex: 1,
height: '100%',
width: '100%',
pointerEvents: 'none',
@@ -100,7 +135,7 @@ const NextPrevImageButtons = () => {
aria-label={t('accessibility.previousImage')}
icon={<FaAngleLeft size={64} />}
variant="unstyled"
onClick={handleClickPrevButton}
onClick={handlePrevImage}
boxSize={16}
sx={nextPrevButtonStyles}
/>
@@ -119,7 +154,7 @@ const NextPrevImageButtons = () => {
aria-label={t('accessibility.nextImage')}
icon={<FaAngleRight size={64} />}
variant="unstyled"
onClick={handleClickNextButton}
onClick={handleNextImage}
boxSize={16}
sx={nextPrevButtonStyles}
/>

View File

@@ -1,5 +1,5 @@
import { createSelector } from '@reduxjs/toolkit';
import { useAppSelector } from 'app/storeHooks';
import { useAppSelector } from 'app/store/storeHooks';
import { ImageType } from 'services/api';
import { selectResultsEntities } from '../store/resultsSlice';
import { selectUploadsEntities } from '../store/uploadsSlice';

View File

@@ -1,5 +1,5 @@
import { createSelector } from '@reduxjs/toolkit';
import { useAppSelector } from 'app/storeHooks';
import { useAppSelector } from 'app/store/storeHooks';
import { gallerySelector } from '../store/gallerySelectors';
const selector = createSelector(gallerySelector, (gallery) => ({

View File

@@ -1,5 +1,5 @@
import { createSelector } from '@reduxjs/toolkit';
import { RootState } from 'app/store';
import { RootState } from 'app/store/store';
import { lightboxSelector } from 'features/lightbox/store/lightboxSelectors';
import { configSelector } from 'features/system/store/configSelectors';
import { systemSelector } from 'features/system/store/systemSelectors';
@@ -7,7 +7,7 @@ import {
activeTabNameSelector,
uiSelector,
} from 'features/ui/store/uiSelectors';
import { isEqual } from 'lodash';
import { isEqual } from 'lodash-es';
import {
selectResultsAll,
selectResultsById,
@@ -22,17 +22,22 @@ import {
export const gallerySelector = (state: RootState) => state.gallery;
export const imageGallerySelector = createSelector(
[gallerySelector, uiSelector, lightboxSelector, activeTabNameSelector],
(gallery, ui, lightbox, activeTabName) => {
[
(state: RootState) => state,
gallerySelector,
uiSelector,
lightboxSelector,
activeTabNameSelector,
],
(state, gallery, ui, lightbox, activeTabName) => {
const {
categories,
currentCategory,
currentImageUuid,
galleryImageMinimumWidth,
galleryImageObjectFit,
shouldAutoSwitchToNewImages,
galleryWidth,
shouldUseSingleGalleryColumn,
selectedImage,
} = gallery;
const { shouldPinGallery } = ui;
@@ -40,7 +45,6 @@ export const imageGallerySelector = createSelector(
const { isLightboxOpen } = lightbox;
return {
currentImageUuid,
shouldPinGallery,
galleryImageMinimumWidth,
galleryImageObjectFit,
@@ -49,9 +53,7 @@ export const imageGallerySelector = createSelector(
: `repeat(auto-fill, minmax(${galleryImageMinimumWidth}px, auto))`,
shouldAutoSwitchToNewImages,
currentCategory,
images: categories[currentCategory].images,
areMoreImagesAvailable:
categories[currentCategory].areMoreImagesAvailable,
images: state[currentCategory].entities,
galleryWidth,
shouldEnableResize:
isLightboxOpen ||
@@ -59,6 +61,7 @@ export const imageGallerySelector = createSelector(
? false
: true,
shouldUseSingleGalleryColumn,
selectedImage,
};
},
{
@@ -69,16 +72,16 @@ export const imageGallerySelector = createSelector(
);
export const selectedImageSelector = createSelector(
[gallerySelector, selectResultsEntities, selectUploadsEntities],
(gallery, allResults, allUploads) => {
const selectedImageName = gallery.selectedImageName;
[(state: RootState) => state, gallerySelector],
(state, gallery) => {
const selectedImage = gallery.selectedImage;
if (selectedImageName in allResults) {
return allResults[selectedImageName];
if (selectedImage?.type === 'results') {
return selectResultsById(state, selectedImage.name);
}
if (selectedImageName in allUploads) {
return allUploads[selectedImageName];
if (selectedImage?.type === 'uploads') {
return selectUploadsById(state, selectedImage.name);
}
}
);

View File

@@ -1,259 +1,47 @@
import type { PayloadAction } from '@reduxjs/toolkit';
import { createSlice } from '@reduxjs/toolkit';
import * as InvokeAI from 'app/invokeai';
import { invocationComplete } from 'services/events/actions';
import { InvokeTabName } from 'features/ui/store/tabMap';
import { IRect } from 'konva/lib/types';
import { clamp } from 'lodash';
import { isImageOutput } from 'services/types/guards';
import { deserializeImageResponse } from 'services/util/deserializeImageResponse';
import { imageUploaded } from 'services/thunks/image';
export type GalleryCategory = 'user' | 'result';
export type AddImagesPayload = {
images: Array<InvokeAI._Image>;
areMoreImagesAvailable: boolean;
category: GalleryCategory;
};
import { SelectedImage } from 'features/parameters/store/generationSlice';
type GalleryImageObjectFitType = 'contain' | 'cover';
export type Gallery = {
images: InvokeAI._Image[];
latest_mtime?: number;
earliest_mtime?: number;
areMoreImagesAvailable: boolean;
};
export interface GalleryState {
/**
* The selected image's unique name
* Use `selectedImageSelector` to access the image
* The selected image
*/
selectedImageName: string;
/**
* The currently selected image
* @deprecated See `state.gallery.selectedImageName`
*/
currentImage?: InvokeAI._Image;
/**
* The currently selected image's uuid.
* @deprecated See `state.gallery.selectedImageName`, use `selectedImageSelector` to access the image
*/
currentImageUuid: string;
/**
* The current progress image
* @deprecated See `state.system.progressImage`
*/
intermediateImage?: InvokeAI._Image & {
boundingBox?: IRect;
generationMode?: InvokeTabName;
};
selectedImage?: SelectedImage;
galleryImageMinimumWidth: number;
galleryImageObjectFit: GalleryImageObjectFitType;
shouldAutoSwitchToNewImages: boolean;
categories: {
user: Gallery;
result: Gallery;
};
currentCategory: GalleryCategory;
galleryWidth: number;
shouldUseSingleGalleryColumn: boolean;
currentCategory: 'results' | 'uploads';
}
const initialState: GalleryState = {
selectedImageName: '',
currentImageUuid: '',
selectedImage: undefined,
galleryImageMinimumWidth: 64,
galleryImageObjectFit: 'cover',
shouldAutoSwitchToNewImages: true,
currentCategory: 'result',
categories: {
user: {
images: [],
latest_mtime: undefined,
earliest_mtime: undefined,
areMoreImagesAvailable: true,
},
result: {
images: [],
latest_mtime: undefined,
earliest_mtime: undefined,
areMoreImagesAvailable: true,
},
},
galleryWidth: 300,
shouldUseSingleGalleryColumn: false,
currentCategory: 'results',
};
export const gallerySlice = createSlice({
name: 'gallery',
initialState,
reducers: {
imageSelected: (state, action: PayloadAction<string>) => {
state.selectedImageName = action.payload;
},
setCurrentImage: (state, action: PayloadAction<InvokeAI._Image>) => {
state.currentImage = action.payload;
state.currentImageUuid = action.payload.uuid;
},
removeImage: (
imageSelected: (
state,
action: PayloadAction<InvokeAI.ImageDeletedResponse>
action: PayloadAction<SelectedImage | undefined>
) => {
const { uuid, category } = action.payload;
const tempImages = state.categories[category as GalleryCategory].images;
const newImages = tempImages.filter((image) => image.uuid !== uuid);
if (uuid === state.currentImageUuid) {
/**
* We are deleting the currently selected image.
*
* We want the new currentl selected image to be under the cursor in the
* gallery, so we need to do some fanagling. The currently selected image
* is set by its UUID, not its index in the image list.
*
* Get the currently selected image's index.
*/
const imageToDeleteIndex = tempImages.findIndex(
(image) => image.uuid === uuid
);
/**
* New current image needs to be in the same spot, but because the gallery
* is sorted in reverse order, the new current image's index will actuall be
* one less than the deleted image's index.
*
* Clamp the new index to ensure it is valid..
*/
const newCurrentImageIndex = clamp(
imageToDeleteIndex,
0,
newImages.length - 1
);
state.currentImage = newImages.length
? newImages[newCurrentImageIndex]
: undefined;
state.currentImageUuid = newImages.length
? newImages[newCurrentImageIndex].uuid
: '';
}
state.categories[category as GalleryCategory].images = newImages;
},
addImage: (
state,
action: PayloadAction<{
image: InvokeAI._Image;
category: GalleryCategory;
}>
) => {
const { image: newImage, category } = action.payload;
const { uuid, url, mtime } = newImage;
const tempCategory = state.categories[category as GalleryCategory];
// Do not add duplicate images
if (tempCategory.images.find((i) => i.url === url && i.mtime === mtime)) {
return;
}
tempCategory.images.unshift(newImage);
if (state.shouldAutoSwitchToNewImages) {
state.currentImageUuid = uuid;
state.currentImage = newImage;
state.currentCategory = category;
}
state.intermediateImage = undefined;
tempCategory.latest_mtime = mtime;
},
setIntermediateImage: (
state,
action: PayloadAction<
InvokeAI._Image & {
boundingBox?: IRect;
generationMode?: InvokeTabName;
}
>
) => {
state.intermediateImage = action.payload;
},
clearIntermediateImage: (state) => {
state.intermediateImage = undefined;
},
selectNextImage: (state) => {
const { currentImage } = state;
if (!currentImage) return;
const tempImages =
state.categories[currentImage.category as GalleryCategory].images;
if (currentImage) {
const currentImageIndex = tempImages.findIndex(
(i) => i.uuid === currentImage.uuid
);
if (currentImageIndex < tempImages.length - 1) {
const newCurrentImage = tempImages[currentImageIndex + 1];
state.currentImage = newCurrentImage;
state.currentImageUuid = newCurrentImage.uuid;
}
}
},
selectPrevImage: (state) => {
const { currentImage } = state;
if (!currentImage) return;
const tempImages =
state.categories[currentImage.category as GalleryCategory].images;
if (currentImage) {
const currentImageIndex = tempImages.findIndex(
(i) => i.uuid === currentImage.uuid
);
if (currentImageIndex > 0) {
const newCurrentImage = tempImages[currentImageIndex - 1];
state.currentImage = newCurrentImage;
state.currentImageUuid = newCurrentImage.uuid;
}
}
},
addGalleryImages: (state, action: PayloadAction<AddImagesPayload>) => {
const { images, areMoreImagesAvailable, category } = action.payload;
const tempImages = state.categories[category].images;
// const prevImages = category === 'user' ? state.userImages : state.resultImages
if (images.length > 0) {
// Filter images that already exist in the gallery
const newImages = images.filter(
(newImage) =>
!tempImages.find(
(i) => i.url === newImage.url && i.mtime === newImage.mtime
)
);
state.categories[category].images = tempImages
.concat(newImages)
.sort((a, b) => b.mtime - a.mtime);
if (!state.currentImage) {
const newCurrentImage = images[0];
state.currentImage = newCurrentImage;
state.currentImageUuid = newCurrentImage.uuid;
}
// keep track of the timestamps of latest and earliest images received
state.categories[category].latest_mtime = images[0].mtime;
state.categories[category].earliest_mtime =
images[images.length - 1].mtime;
}
if (areMoreImagesAvailable !== undefined) {
state.categories[category].areMoreImagesAvailable =
areMoreImagesAvailable;
}
state.selectedImage = action.payload;
// TODO: if the user selects an image, disable the auto switch?
// state.shouldAutoSwitchToNewImages = false;
},
setGalleryImageMinimumWidth: (state, action: PayloadAction<number>) => {
state.galleryImageMinimumWidth = action.payload;
@@ -267,7 +55,10 @@ export const gallerySlice = createSlice({
setShouldAutoSwitchToNewImages: (state, action: PayloadAction<boolean>) => {
state.shouldAutoSwitchToNewImages = action.payload;
},
setCurrentCategory: (state, action: PayloadAction<GalleryCategory>) => {
setCurrentCategory: (
state,
action: PayloadAction<'results' | 'uploads'>
) => {
state.currentCategory = action.payload;
},
setGalleryWidth: (state, action: PayloadAction<number>) => {
@@ -286,9 +77,11 @@ export const gallerySlice = createSlice({
*/
builder.addCase(invocationComplete, (state, action) => {
const { data } = action.payload;
if (isImageOutput(data.result)) {
state.selectedImageName = data.result.image.image_name;
state.intermediateImage = undefined;
if (isImageOutput(data.result) && state.shouldAutoSwitchToNewImages) {
state.selectedImage = {
name: data.result.image.image_name,
type: 'results',
};
}
});
@@ -299,27 +92,19 @@ export const gallerySlice = createSlice({
const { response } = action.payload;
const uploadedImage = deserializeImageResponse(response);
state.selectedImageName = uploadedImage.name;
state.selectedImage = { name: uploadedImage.name, type: 'uploads' };
});
},
});
export const {
imageSelected,
addImage,
clearIntermediateImage,
removeImage,
setCurrentImage,
addGalleryImages,
setIntermediateImage,
selectNextImage,
selectPrevImage,
setGalleryImageMinimumWidth,
setGalleryImageObjectFit,
setShouldAutoSwitchToNewImages,
setCurrentCategory,
setGalleryWidth,
setShouldUseSingleGalleryColumn,
setCurrentCategory,
} = gallerySlice.actions;
export default gallerySlice.reducer;

View File

@@ -5,7 +5,7 @@ import { ResultsState } from './resultsSlice';
*
* Currently denylisting results slice entirely, see persist config in store.ts
*/
const itemsToDenylist: (keyof ResultsState)[] = [];
const itemsToDenylist: (keyof ResultsState)[] = ['isLoading'];
export const resultsDenylist = itemsToDenylist.map(
(denylistItem) => `results.${denylistItem}`

View File

@@ -1,8 +1,8 @@
import { createEntityAdapter, createSlice } from '@reduxjs/toolkit';
import { Image } from 'app/invokeai';
import { Image } from 'app/types/invokeai';
import { invocationComplete } from 'services/events/actions';
import { RootState } from 'app/store';
import { RootState } from 'app/store/store';
import {
receivedResultImagesPage,
IMAGES_PER_PAGE,
@@ -65,7 +65,7 @@ const resultsSlice = createSlice({
deserializeImageResponse(image)
);
resultsAdapter.addMany(state, resultImages);
resultsAdapter.setMany(state, resultImages);
state.page = page;
state.pages = pages;
@@ -107,7 +107,7 @@ const resultsSlice = createSlice({
},
};
resultsAdapter.addOne(state, image);
resultsAdapter.setOne(state, image);
}
});

View File

@@ -5,7 +5,7 @@ import { UploadsState } from './uploadsSlice';
*
* Currently denylisting uploads slice entirely, see persist config in store.ts
*/
const itemsToDenylist: (keyof UploadsState)[] = [];
const itemsToDenylist: (keyof UploadsState)[] = ['isLoading'];
export const uploadsDenylist = itemsToDenylist.map(
(denylistItem) => `uploads.${denylistItem}`

View File

@@ -1,7 +1,7 @@
import { createEntityAdapter, createSlice } from '@reduxjs/toolkit';
import { Image } from 'app/invokeai';
import { Image } from 'app/types/invokeai';
import { RootState } from 'app/store';
import { RootState } from 'app/store/store';
import {
receivedUploadImagesPage,
IMAGES_PER_PAGE,
@@ -53,7 +53,7 @@ const uploadsSlice = createSlice({
const images = items.map((image) => deserializeImageResponse(image));
uploadsAdapter.addMany(state, images);
uploadsAdapter.setMany(state, images);
state.page = page;
state.pages = pages;
@@ -69,7 +69,7 @@ const uploadsSlice = createSlice({
const uploadedImage = deserializeImageResponse(response);
uploadsAdapter.addOne(state, uploadedImage);
uploadsAdapter.setOne(state, uploadedImage);
});
/**

View File

@@ -1,7 +1,7 @@
import { Box, Flex } from '@chakra-ui/react';
import { createSelector } from '@reduxjs/toolkit';
import { RootState } from 'app/store';
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
import { RootState } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import IAIIconButton from 'common/components/IAIIconButton';
import CurrentImageButtons from 'features/gallery/components/CurrentImageButtons';
import ImageMetadataViewer from 'features/gallery/components/ImageMetaDataViewer/ImageMetadataViewer';
@@ -10,7 +10,7 @@ import { gallerySelector } from 'features/gallery/store/gallerySelectors';
import { setIsLightboxOpen } from 'features/lightbox/store/lightboxSlice';
import { uiSelector } from 'features/ui/store/uiSelectors';
import { AnimatePresence, motion } from 'framer-motion';
import { isEqual } from 'lodash';
import { isEqual } from 'lodash-es';
import { useHotkeys } from 'react-hotkeys-hook';
import { BiExit } from 'react-icons/bi';
import { TransformWrapper } from 'react-zoom-pan-pinch';

View File

@@ -1,6 +1,6 @@
import * as React from 'react';
import { TransformComponent, useTransformContext } from 'react-zoom-pan-pinch';
import * as InvokeAI from 'app/invokeai';
import * as InvokeAI from 'app/types/invokeai';
import { useGetUrl } from 'common/util/getUrl';
type ReactPanZoomProps = {

View File

@@ -1,6 +1,6 @@
import { createSelector } from '@reduxjs/toolkit';
import { RootState } from 'app/store';
import { isEqual } from 'lodash';
import { RootState } from 'app/store/store';
import { isEqual } from 'lodash-es';
export const lightboxSelector = createSelector(
(state: RootState) => state.lightbox,

View File

@@ -11,15 +11,15 @@ import {
IconButton,
} from '@chakra-ui/react';
import { FaEllipsisV, FaPlus } from 'react-icons/fa';
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { nodeAdded } from '../store/nodesSlice';
import { cloneDeep, map } from 'lodash';
import { RootState } from 'app/store';
import { cloneDeep, map } from 'lodash-es';
import { RootState } from 'app/store/store';
import { useBuildInvocation } from '../hooks/useBuildInvocation';
import { addToast } from 'features/system/store/systemSlice';
import { makeToast } from 'features/system/hooks/useToastWatcher';
import { IAIIconButton } from 'exports';
import { AnyInvocationType } from 'services/events/types';
import IAIIconButton from 'common/components/IAIIconButton';
const AddNodeMenu = () => {
const dispatch = useAppDispatch();

View File

@@ -1,6 +1,6 @@
import 'reactflow/dist/style.css';
import { Tooltip, Badge, Flex } from '@chakra-ui/react';
import { map } from 'lodash';
import { map } from 'lodash-es';
import { FIELDS } from '../types/constants';
import { memo } from 'react';

View File

@@ -7,8 +7,8 @@ import {
OnConnectStart,
OnConnectEnd,
} from 'reactflow';
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
import { RootState } from 'app/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { RootState } from 'app/store/store';
import {
connectionEnded,
connectionMade,

View File

@@ -4,9 +4,9 @@ import {
InvocationTemplate,
} from 'features/nodes/types/types';
import { memo, ReactNode, useCallback } from 'react';
import { map } from 'lodash';
import { useAppSelector } from 'app/storeHooks';
import { RootState } from 'app/store';
import { map } from 'lodash-es';
import { useAppSelector } from 'app/store/storeHooks';
import { RootState } from 'app/store/store';
import {
Box,
Flex,

View File

@@ -4,9 +4,9 @@ import {
OutputFieldValue,
} from 'features/nodes/types/types';
import { memo, ReactNode, useCallback } from 'react';
import { map } from 'lodash';
import { useAppSelector } from 'app/storeHooks';
import { RootState } from 'app/store';
import { map } from 'lodash-es';
import { useAppSelector } from 'app/store/storeHooks';
import { RootState } from 'app/store/store';
import { Box, Flex, FormControl, FormLabel, HStack } from '@chakra-ui/react';
import FieldHandle from '../FieldHandle';
import { useIsValidConnection } from 'features/nodes/hooks/useIsValidConnection';

View File

@@ -8,10 +8,10 @@ import IAINodeOutputs from './IAINode/IAINodeOutputs';
import IAINodeInputs from './IAINode/IAINodeInputs';
import IAINodeHeader from './IAINode/IAINodeHeader';
import IAINodeResizer from './IAINode/IAINodeResizer';
import { RootState } from 'app/store';
import { RootState } from 'app/store/store';
import { AnyInvocationType } from 'services/events/types';
import { createSelector } from '@reduxjs/toolkit';
import { useAppSelector } from 'app/storeHooks';
import { useAppSelector } from 'app/store/storeHooks';
import { NODE_MIN_WIDTH } from 'app/constants';
type InvocationComponentWrapperProps = PropsWithChildren & {

View File

@@ -1,6 +1,6 @@
import { Box } from '@chakra-ui/react';
import { RootState } from 'app/store';
import { useAppSelector } from 'app/storeHooks';
import { RootState } from 'app/store/store';
import { useAppSelector } from 'app/store/storeHooks';
import { memo } from 'react';
import { buildNodesGraph } from '../util/nodesGraphBuilder/buildNodesGraph';

View File

@@ -1,6 +1,6 @@
import { ButtonGroup } from '@chakra-ui/react';
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
import { IAIIconButton } from 'exports';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import IAIIconButton from 'common/components/IAIIconButton';
import { memo, useCallback } from 'react';
import { FaCode, FaExpand, FaMinus, FaPlus } from 'react-icons/fa';
import { useReactFlow } from 'reactflow';

View File

@@ -1,5 +1,5 @@
import { Switch } from '@chakra-ui/react';
import { useAppDispatch } from 'app/storeHooks';
import { useAppDispatch } from 'app/store/storeHooks';
import { fieldValueChanged } from 'features/nodes/store/nodesSlice';
import {
BooleanInputFieldTemplate,

View File

@@ -1,5 +1,5 @@
import { Select } from '@chakra-ui/react';
import { useAppDispatch } from 'app/storeHooks';
import { useAppDispatch } from 'app/store/storeHooks';
import { fieldValueChanged } from 'features/nodes/store/nodesSlice';
import {
EnumInputFieldTemplate,

View File

@@ -1,5 +1,5 @@
import { Box, Image, Icon, Flex } from '@chakra-ui/react';
import { useAppDispatch } from 'app/storeHooks';
import { useAppDispatch } from 'app/store/storeHooks';
import SelectImagePlaceholder from 'common/components/SelectImagePlaceholder';
import { useGetUrl } from 'common/util/getUrl';
import useGetImageByNameAndType from 'features/gallery/hooks/useGetImageByName';

View File

@@ -1,7 +1,7 @@
import { Select } from '@chakra-ui/react';
import { createSelector } from '@reduxjs/toolkit';
import { RootState } from 'app/store';
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
import { RootState } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { fieldValueChanged } from 'features/nodes/store/nodesSlice';
import {
ModelInputFieldTemplate,
@@ -11,7 +11,7 @@ import {
selectModelsById,
selectModelsIds,
} from 'features/system/store/modelSlice';
import { isEqual, map } from 'lodash';
import { isEqual, map } from 'lodash-es';
import { ChangeEvent, memo } from 'react';
import { FieldComponentProps } from './types';

View File

@@ -5,7 +5,7 @@ import {
NumberInputField,
NumberInputStepper,
} from '@chakra-ui/react';
import { useAppDispatch } from 'app/storeHooks';
import { useAppDispatch } from 'app/store/storeHooks';
import { fieldValueChanged } from 'features/nodes/store/nodesSlice';
import {
FloatInputFieldTemplate,

View File

@@ -1,5 +1,5 @@
import { Input } from '@chakra-ui/react';
import { useAppDispatch } from 'app/storeHooks';
import { useAppDispatch } from 'app/store/storeHooks';
import { fieldValueChanged } from 'features/nodes/store/nodesSlice';
import {
StringInputFieldTemplate,

View File

@@ -1,5 +1,5 @@
import { RootState } from 'app/store';
import { useAppSelector } from 'app/storeHooks';
import { RootState } from 'app/store/store';
import { useAppSelector } from 'app/store/storeHooks';
import { CSSProperties, memo } from 'react';
import { MiniMap } from 'reactflow';

View File

@@ -1,5 +1,5 @@
import { HStack } from '@chakra-ui/react';
import { useAppDispatch } from 'app/storeHooks';
import { useAppDispatch } from 'app/store/storeHooks';
import IAIButton from 'common/components/IAIButton';
import { memo, useCallback } from 'react';
import { Panel } from 'reactflow';

View File

@@ -1,5 +1,5 @@
import { RootState } from 'app/store';
import { useAppSelector } from 'app/storeHooks';
import { RootState } from 'app/store/store';
import { useAppSelector } from 'app/store/storeHooks';
import { memo } from 'react';
import { Panel } from 'reactflow';
import FieldTypeLegend from '../FieldTypeLegend';

View File

@@ -1,9 +1,9 @@
import { Box, Flex } from '@chakra-ui/layout';
import { RootState } from 'app/store';
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
import { RootState } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import IAIInput from 'common/components/IAIInput';
import { Panel } from 'reactflow';
import { map } from 'lodash';
import { map } from 'lodash-es';
import {
ChangeEvent,
FocusEvent,

View File

@@ -1,7 +1,7 @@
import { createSelector } from '@reduxjs/toolkit';
import { RootState } from 'app/store';
import { useAppSelector } from 'app/storeHooks';
import { reduce } from 'lodash';
import { RootState } from 'app/store/store';
import { useAppSelector } from 'app/store/storeHooks';
import { reduce } from 'lodash-es';
import { useCallback } from 'react';
import { Node, useReactFlow } from 'reactflow';
import { AnyInvocationType } from 'services/events/types';

View File

@@ -14,77 +14,77 @@ export const useIsValidConnection = () => {
return true;
// Connection must have valid targets
if (!(source && sourceHandle && target && targetHandle)) {
return false;
}
// // Connection must have valid targets
// if (!(source && sourceHandle && target && targetHandle)) {
// return false;
// }
// Connection is invalid if target already has a connection
if (
edges.find((edge) => {
return edge.target === target && edge.targetHandle === targetHandle;
})
) {
return false;
}
// // Connection is invalid if target already has a connection
// if (
// edges.find((edge) => {
// return edge.target === target && edge.targetHandle === targetHandle;
// })
// ) {
// return false;
// }
// Find the source and target nodes
const sourceNode = flow.getNode(source) as Node<InvocationValue>;
// // Find the source and target nodes
// const sourceNode = flow.getNode(source) as Node<InvocationValue>;
const targetNode = flow.getNode(target) as Node<InvocationValue>;
// const targetNode = flow.getNode(target) as Node<InvocationValue>;
// Conditional guards against undefined nodes/handles
if (!(sourceNode && targetNode && sourceNode.data && targetNode.data)) {
return false;
}
// // Conditional guards against undefined nodes/handles
// if (!(sourceNode && targetNode && sourceNode.data && targetNode.data)) {
// return false;
// }
// Connection types must be the same for a connection
if (
sourceNode.data.outputs[sourceHandle].type !==
targetNode.data.inputs[targetHandle].type
) {
return false;
}
// // Connection types must be the same for a connection
// if (
// sourceNode.data.outputs[sourceHandle].type !==
// targetNode.data.inputs[targetHandle].type
// ) {
// return false;
// }
// Graphs much be acyclic (no loops!)
// // Graphs much be acyclic (no loops!)
/**
* TODO: use `graphlib.alg.findCycles()` to identify strong connections
*
* this validation func only runs when the cursor hits the second handle of the connection,
* and only on that second handle - so it cannot tell us exhaustively which connections
* are valid.
*
* ideally, we check when the connection starts to calculate all invalid handles at once.
*
* requires making a new graphlib graph - and calling `findCycles()` - for each potential
* handle. instead of using the `isValidConnection` prop, it would use the `onConnectStart`
* prop.
*
* the strong connections should be stored in global state.
*
* then, `isValidConnection` would simple loop through the strong connections and if the
* source and target are in a single strong connection, return false.
*
* and also, we can use this knowledge to style every handle when a connection starts,
* which is otherwise not possible.
*/
// /**
// * TODO: use `graphlib.alg.findCycles()` to identify strong connections
// *
// * this validation func only runs when the cursor hits the second handle of the connection,
// * and only on that second handle - so it cannot tell us exhaustively which connections
// * are valid.
// *
// * ideally, we check when the connection starts to calculate all invalid handles at once.
// *
// * requires making a new graphlib graph - and calling `findCycles()` - for each potential
// * handle. instead of using the `isValidConnection` prop, it would use the `onConnectStart`
// * prop.
// *
// * the strong connections should be stored in global state.
// *
// * then, `isValidConnection` would simple loop through the strong connections and if the
// * source and target are in a single strong connection, return false.
// *
// * and also, we can use this knowledge to style every handle when a connection starts,
// * which is otherwise not possible.
// */
// build a graphlib graph
const g = new graphlib.Graph();
// // build a graphlib graph
// const g = new graphlib.Graph();
nodes.forEach((n) => {
g.setNode(n.id);
});
// nodes.forEach((n) => {
// g.setNode(n.id);
// });
edges.forEach((e) => {
g.setEdge(e.source, e.target);
});
// edges.forEach((e) => {
// g.setEdge(e.source, e.target);
// });
// Add the candidate edge to the graph
g.setEdge(source, target);
// // Add the candidate edge to the graph
// g.setEdge(source, target);
return graphlib.alg.isAcyclic(g);
// return graphlib.alg.isAcyclic(g);
},
[flow]
);

View File

@@ -16,6 +16,8 @@ import { receivedOpenAPISchema } from 'services/thunks/schema';
import { isFulfilledAnyGraphBuilt } from 'services/thunks/session';
import { InvocationTemplate, InvocationValue } from '../types/types';
import { parseSchema } from '../util/parseSchema';
import { log } from 'app/logging/useLogger';
import { size } from 'lodash-es';
export type NodesState = {
nodes: Node<InvocationValue>[];
@@ -82,10 +84,24 @@ const nodesSlice = createSlice({
shouldShowGraphOverlayChanged: (state, action: PayloadAction<boolean>) => {
state.shouldShowGraphOverlay = action.payload;
},
parsedOpenAPISchema: (state, action: PayloadAction<OpenAPIV3.Document>) => {
try {
const parsedSchema = parseSchema(action.payload);
// TODO: Achtung! Side effect in a reducer!
log.info(
{ namespace: 'schema', nodes: parsedSchema },
`Parsed ${size(parsedSchema)} nodes`
);
state.invocationTemplates = parsedSchema;
} catch (err) {
console.error(err);
}
},
},
extraReducers(builder) {
builder.addCase(receivedOpenAPISchema.fulfilled, (state, action) => {
state.invocationTemplates = action.payload;
state.schema = action.payload;
});
builder.addMatcher(isFulfilledAnyGraphBuilt, (state, action) => {
@@ -103,6 +119,7 @@ export const {
connectionStarted,
connectionEnded,
shouldShowGraphOverlayChanged,
parsedOpenAPISchema,
} = nodesSlice.actions;
export default nodesSlice.reducer;

View File

@@ -1,5 +1,5 @@
import { createSelector } from '@reduxjs/toolkit';
import { RootState } from 'app/store';
import { RootState } from 'app/store/store';
export const invocationTemplatesSelector = createSelector(
(state: RootState) => state.nodes,

View File

@@ -1,4 +1,4 @@
import { reduce } from 'lodash';
import { reduce } from 'lodash-es';
import { OpenAPIV3 } from 'openapi-types';
import { FIELD_TYPE_MAP } from '../types/constants';
import { isSchemaObject } from '../types/typeGuards';

View File

@@ -1,11 +1,11 @@
import { v4 as uuidv4 } from 'uuid';
import { RootState } from 'app/store';
import { RootState } from 'app/store/store';
import {
Edge,
ImageToImageInvocation,
TextToImageInvocation,
} from 'services/api';
import { _Image } from 'app/invokeai';
import { _Image } from 'app/types/invokeai';
import { initialImageSelector } from 'features/parameters/store/generationSelectors';
export const buildImg2ImgNode = (state: RootState): ImageToImageInvocation => {
@@ -16,6 +16,7 @@ export const buildImg2ImgNode = (state: RootState): ImageToImageInvocation => {
const {
prompt,
negativePrompt,
seed,
steps,
width,
@@ -38,7 +39,7 @@ export const buildImg2ImgNode = (state: RootState): ImageToImageInvocation => {
const imageToImageNode: ImageToImageInvocation = {
id: nodeId,
type: 'img2img',
prompt,
prompt: `${prompt} [${negativePrompt}]`,
steps,
width,
height,

View File

@@ -1,4 +1,4 @@
import { RootState } from 'app/store';
import { RootState } from 'app/store/store';
import { Graph } from 'services/api';
import { buildImg2ImgNode } from './buildImageToImageNode';
import { buildTxt2ImgNode } from './buildTextToImageNode';

View File

@@ -1,6 +1,6 @@
import { v4 as uuidv4 } from 'uuid';
import { RootState } from 'app/store';
import { RootState } from 'app/store/store';
import { RandomRangeInvocation, RangeInvocation } from 'services/api';
export const buildRangeNode = (

View File

@@ -1,5 +1,5 @@
import { v4 as uuidv4 } from 'uuid';
import { RootState } from 'app/store';
import { RootState } from 'app/store/store';
import { TextToImageInvocation } from 'services/api';
export const buildTxt2ImgNode = (state: RootState): TextToImageInvocation => {
@@ -10,6 +10,7 @@ export const buildTxt2ImgNode = (state: RootState): TextToImageInvocation => {
const {
prompt,
negativePrompt,
seed,
steps,
width,
@@ -23,7 +24,7 @@ export const buildTxt2ImgNode = (state: RootState): TextToImageInvocation => {
const textToImageNode: NonNullable<TextToImageInvocation> = {
id: nodeId,
type: 'txt2img',
prompt,
prompt: `${prompt} [${negativePrompt}]`,
steps,
width,
height,

View File

@@ -1,7 +1,7 @@
import { Graph } from 'services/api';
import { v4 as uuidv4 } from 'uuid';
import { reduce } from 'lodash';
import { RootState } from 'app/store';
import { reduce } from 'lodash-es';
import { RootState } from 'app/store/store';
import { AnyInvocation } from 'services/events/types';
/**

View File

@@ -1,4 +1,4 @@
import { filter, reduce } from 'lodash';
import { filter, reduce } from 'lodash-es';
import { OpenAPIV3 } from 'openapi-types';
import { isSchemaObject } from '../types/typeGuards';
import {

View File

@@ -1,10 +1,10 @@
import { Box, VStack } from '@chakra-ui/react';
import { createSelector } from '@reduxjs/toolkit';
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import IAISlider from 'common/components/IAISlider';
import { canvasSelector } from 'features/canvas/store/canvasSelectors';
import { setBoundingBoxDimensions } from 'features/canvas/store/canvasSlice';
import { isEqual } from 'lodash';
import { isEqual } from 'lodash-es';
import { useTranslation } from 'react-i18next';

View File

@@ -1,6 +1,6 @@
import { VStack } from '@chakra-ui/react';
import { createSelector } from '@reduxjs/toolkit';
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import IAISelect from 'common/components/IAISelect';
import IAISlider from 'common/components/IAISlider';
import { canvasSelector } from 'features/canvas/store/canvasSelectors';
@@ -18,7 +18,7 @@ import {
setTileSize,
} from 'features/parameters/store/generationSlice';
import { systemSelector } from 'features/system/store/systemSelectors';
import { isEqual } from 'lodash';
import { isEqual } from 'lodash-es';
import { ChangeEvent } from 'react';
import { useTranslation } from 'react-i18next';

View File

@@ -1,5 +1,5 @@
import type { RootState } from 'app/store';
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
import type { RootState } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import IAISlider from 'common/components/IAISlider';
import { setSeamBlur } from 'features/parameters/store/generationSlice';
import { useTranslation } from 'react-i18next';

View File

@@ -1,5 +1,5 @@
import type { RootState } from 'app/store';
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
import type { RootState } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import IAISlider from 'common/components/IAISlider';
import { setSeamSize } from 'features/parameters/store/generationSlice';
import { useTranslation } from 'react-i18next';

View File

@@ -1,5 +1,5 @@
import type { RootState } from 'app/store';
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
import type { RootState } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import IAISlider from 'common/components/IAISlider';
import { setSeamSteps } from 'features/parameters/store/generationSlice';
import { useTranslation } from 'react-i18next';

View File

@@ -1,5 +1,5 @@
import { RootState } from 'app/store';
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
import { RootState } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import IAISlider from 'common/components/IAISlider';
import { setSeamStrength } from 'features/parameters/store/generationSlice';
import { useTranslation } from 'react-i18next';

View File

@@ -1,5 +1,5 @@
import type { RootState } from 'app/store';
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
import type { RootState } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import IAISlider from 'common/components/IAISlider';
import { setCodeformerFidelity } from 'features/parameters/store/postprocessingSlice';
import { useTranslation } from 'react-i18next';

View File

@@ -1,6 +1,6 @@
import { VStack } from '@chakra-ui/react';
import { useAppSelector } from 'app/storeHooks';
import type { RootState } from 'app/store';
import { useAppSelector } from 'app/store/storeHooks';
import type { RootState } from 'app/store/store';
import FaceRestoreType from './FaceRestoreType';
import FaceRestoreStrength from './FaceRestoreStrength';
import CodeformerFidelity from './CodeformerFidelity';

View File

@@ -1,5 +1,5 @@
import { RootState } from 'app/store';
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
import { RootState } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import IAISlider from 'common/components/IAISlider';
import { setFacetoolStrength } from 'features/parameters/store/postprocessingSlice';
import { useTranslation } from 'react-i18next';

View File

@@ -1,5 +1,5 @@
import { RootState } from 'app/store';
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
import { RootState } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import IAISwitch from 'common/components/IAISwitch';
import { setShouldRunFacetool } from 'features/parameters/store/postprocessingSlice';
import { ChangeEvent } from 'react';

View File

@@ -1,6 +1,6 @@
import { FACETOOL_TYPES } from 'app/constants';
import { RootState } from 'app/store';
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
import { RootState } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import IAISelect from 'common/components/IAISelect';
import {
FacetoolType,

View File

@@ -1,5 +1,5 @@
import { RootState } from 'app/store';
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
import { RootState } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import IAISwitch from 'common/components/IAISwitch';
import { setShouldFitToWidthHeight } from 'features/parameters/store/generationSlice';
import { ChangeEvent } from 'react';

View File

@@ -29,7 +29,7 @@ export default function ImageToImageSettings() {
<VStack gap={2} w="full" alignItems="stretch">
<ImageToImageSettingsHeader />
<InitialImagePreview />
<ImageToImageStrength label={t('parameters.img2imgStrength')} />
<ImageToImageStrength />
<ImageFit />
</VStack>
);

View File

@@ -1,5 +1,5 @@
import { createSelector } from '@reduxjs/toolkit';
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import IAISlider from 'common/components/IAISlider';
import { generationSelector } from 'features/parameters/store/generationSelectors';
import { setImg2imgStrength } from 'features/parameters/store/generationSlice';

View File

@@ -1,6 +1,6 @@
import { Flex } from '@chakra-ui/react';
import { RootState } from 'app/store';
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
import { RootState } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import IAISwitch from 'common/components/IAISwitch';
import { isImageToImageEnabledChanged } from 'features/parameters/store/generationSlice';
import { ChangeEvent } from 'react';

Some files were not shown because too many files have changed in this diff Show More