mirror of
https://github.com/invoke-ai/InvokeAI.git
synced 2026-04-23 03:00:31 -04:00
Merge branch 'main' into feat/compel_node
This commit is contained in:
@@ -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';
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useAppDispatch } from 'app/storeHooks';
|
||||
import { useAppDispatch } from 'app/store/storeHooks';
|
||||
import Konva from 'konva';
|
||||
import {
|
||||
commitColorPickerColor,
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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}>
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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) => ({
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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}`
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -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}`
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
/**
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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 & {
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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]
|
||||
);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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 = (
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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';
|
||||
|
||||
/**
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user