mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
Fixes edge cases, adds invoke button to header when options floating
This commit is contained in:
parent
e58b7a7ef9
commit
62c97dd7e6
@ -6,7 +6,7 @@ import Loading from '../Loading';
|
|||||||
import { useAppDispatch } from './store';
|
import { useAppDispatch } from './store';
|
||||||
import { requestSystemConfig } from './socketio/actions';
|
import { requestSystemConfig } from './socketio/actions';
|
||||||
import { keepGUIAlive } from './utils';
|
import { keepGUIAlive } from './utils';
|
||||||
import InvokeTabs, { tabMap } from '../features/tabs/InvokeTabs';
|
import InvokeTabs from '../features/tabs/InvokeTabs';
|
||||||
import ImageUploader from '../common/components/ImageUploader';
|
import ImageUploader from '../common/components/ImageUploader';
|
||||||
import { RootState, useAppSelector } from '../app/store';
|
import { RootState, useAppSelector } from '../app/store';
|
||||||
|
|
||||||
@ -15,19 +15,23 @@ import ShowHideOptionsPanelButton from '../features/tabs/ShowHideOptionsPanelBut
|
|||||||
import { createSelector } from '@reduxjs/toolkit';
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
import { GalleryState } from '../features/gallery/gallerySlice';
|
import { GalleryState } from '../features/gallery/gallerySlice';
|
||||||
import { OptionsState } from '../features/options/optionsSlice';
|
import { OptionsState } from '../features/options/optionsSlice';
|
||||||
|
import { activeTabNameSelector } from '../features/options/optionsSelectors';
|
||||||
|
|
||||||
keepGUIAlive();
|
keepGUIAlive();
|
||||||
|
|
||||||
const appSelector = createSelector(
|
const appSelector = createSelector(
|
||||||
[(state: RootState) => state.gallery, (state: RootState) => state.options],
|
[
|
||||||
(gallery: GalleryState, options: OptionsState) => {
|
(state: RootState) => state.gallery,
|
||||||
|
(state: RootState) => state.options,
|
||||||
|
activeTabNameSelector,
|
||||||
|
],
|
||||||
|
(gallery: GalleryState, options: OptionsState, activeTabName) => {
|
||||||
const { shouldShowGallery, shouldHoldGalleryOpen, shouldPinGallery } =
|
const { shouldShowGallery, shouldHoldGalleryOpen, shouldPinGallery } =
|
||||||
gallery;
|
gallery;
|
||||||
const {
|
const {
|
||||||
shouldShowOptionsPanel,
|
shouldShowOptionsPanel,
|
||||||
shouldHoldOptionsPanelOpen,
|
shouldHoldOptionsPanelOpen,
|
||||||
shouldPinOptionsPanel,
|
shouldPinOptionsPanel,
|
||||||
activeTab,
|
|
||||||
} = options;
|
} = options;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -39,7 +43,7 @@ const appSelector = createSelector(
|
|||||||
!(
|
!(
|
||||||
shouldShowOptionsPanel ||
|
shouldShowOptionsPanel ||
|
||||||
(shouldHoldOptionsPanelOpen && !shouldPinOptionsPanel)
|
(shouldHoldOptionsPanelOpen && !shouldPinOptionsPanel)
|
||||||
) && ['txt2img', 'img2img', 'inpainting'].includes(tabMap[activeTab]),
|
) && ['txt2img', 'img2img', 'inpainting'].includes(activeTabName),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
@ -1,22 +1,18 @@
|
|||||||
import { Button, ButtonProps, Tooltip } from '@chakra-ui/react';
|
import { Button, ButtonProps, Tooltip } from '@chakra-ui/react';
|
||||||
|
|
||||||
interface Props extends ButtonProps {
|
export interface IAIButtonProps extends ButtonProps {
|
||||||
label: string;
|
label: string;
|
||||||
tooltip?: string;
|
tooltip?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reusable customized button component. Originally was more customized - now probably unecessary.
|
* Reusable customized button component.
|
||||||
*
|
|
||||||
* TODO: Get rid of this.
|
|
||||||
*/
|
*/
|
||||||
const IAIButton = (props: Props) => {
|
const IAIButton = (props: IAIButtonProps) => {
|
||||||
const { label, tooltip = '', size = 'sm', ...rest } = props;
|
const { label, tooltip = '', ...rest } = props;
|
||||||
return (
|
return (
|
||||||
<Tooltip label={tooltip}>
|
<Tooltip label={tooltip}>
|
||||||
<Button size={size} {...rest}>
|
<Button {...rest}>{label}</Button>
|
||||||
{label}
|
|
||||||
</Button>
|
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,23 +1,11 @@
|
|||||||
import { useCallback, ReactNode, useState, useEffect } from 'react';
|
import { useCallback, ReactNode, useState, useEffect } from 'react';
|
||||||
import { RootState, useAppDispatch, useAppSelector } from '../../app/store';
|
import { useAppDispatch, useAppSelector } from '../../app/store';
|
||||||
import { tabMap } from '../../features/tabs/InvokeTabs';
|
|
||||||
import { FileRejection, useDropzone } from 'react-dropzone';
|
import { FileRejection, useDropzone } from 'react-dropzone';
|
||||||
import { Heading, Spinner, useToast } from '@chakra-ui/react';
|
import { Heading, Spinner, useToast } from '@chakra-ui/react';
|
||||||
import { createSelector } from '@reduxjs/toolkit';
|
|
||||||
import { OptionsState } from '../../features/options/optionsSlice';
|
|
||||||
import { uploadImage } from '../../app/socketio/actions';
|
import { uploadImage } from '../../app/socketio/actions';
|
||||||
import { ImageUploadDestination, UploadImagePayload } from '../../app/invokeai';
|
import { ImageUploadDestination, UploadImagePayload } from '../../app/invokeai';
|
||||||
import { ImageUploaderTriggerContext } from '../../app/contexts/ImageUploaderTriggerContext';
|
import { ImageUploaderTriggerContext } from '../../app/contexts/ImageUploaderTriggerContext';
|
||||||
|
import { activeTabNameSelector } from '../../features/options/optionsSelectors';
|
||||||
const appSelector = createSelector(
|
|
||||||
(state: RootState) => state.options,
|
|
||||||
(options: OptionsState) => {
|
|
||||||
const { activeTab } = options;
|
|
||||||
return {
|
|
||||||
activeTabName: tabMap[activeTab],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
type ImageUploaderProps = {
|
type ImageUploaderProps = {
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
@ -26,7 +14,7 @@ type ImageUploaderProps = {
|
|||||||
const ImageUploader = (props: ImageUploaderProps) => {
|
const ImageUploader = (props: ImageUploaderProps) => {
|
||||||
const { children } = props;
|
const { children } = props;
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const { activeTabName } = useAppSelector(appSelector);
|
const activeTabName = useAppSelector(activeTabNameSelector);
|
||||||
const toast = useToast({});
|
const toast = useToast({});
|
||||||
const [isHandlingUpload, setIsHandlingUpload] = useState<boolean>(false);
|
const [isHandlingUpload, setIsHandlingUpload] = useState<boolean>(false);
|
||||||
|
|
||||||
|
@ -3,11 +3,11 @@ import _ from 'lodash';
|
|||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import { useAppSelector } from '../../app/store';
|
import { useAppSelector } from '../../app/store';
|
||||||
import { RootState } from '../../app/store';
|
import { RootState } from '../../app/store';
|
||||||
|
import { activeTabNameSelector } from '../../features/options/optionsSelectors';
|
||||||
import { OptionsState } from '../../features/options/optionsSlice';
|
import { OptionsState } from '../../features/options/optionsSlice';
|
||||||
|
|
||||||
import { SystemState } from '../../features/system/systemSlice';
|
import { SystemState } from '../../features/system/systemSlice';
|
||||||
import { InpaintingState } from '../../features/tabs/Inpainting/inpaintingSlice';
|
import { InpaintingState } from '../../features/tabs/Inpainting/inpaintingSlice';
|
||||||
import { tabMap } from '../../features/tabs/InvokeTabs';
|
|
||||||
import { validateSeedWeights } from '../util/seedWeightPairs';
|
import { validateSeedWeights } from '../util/seedWeightPairs';
|
||||||
|
|
||||||
export const useCheckParametersSelector = createSelector(
|
export const useCheckParametersSelector = createSelector(
|
||||||
@ -15,8 +15,9 @@ export const useCheckParametersSelector = createSelector(
|
|||||||
(state: RootState) => state.options,
|
(state: RootState) => state.options,
|
||||||
(state: RootState) => state.system,
|
(state: RootState) => state.system,
|
||||||
(state: RootState) => state.inpainting,
|
(state: RootState) => state.inpainting,
|
||||||
|
activeTabNameSelector
|
||||||
],
|
],
|
||||||
(options: OptionsState, system: SystemState, inpainting: InpaintingState) => {
|
(options: OptionsState, system: SystemState, inpainting: InpaintingState, activeTabName) => {
|
||||||
return {
|
return {
|
||||||
// options
|
// options
|
||||||
prompt: options.prompt,
|
prompt: options.prompt,
|
||||||
@ -25,7 +26,7 @@ export const useCheckParametersSelector = createSelector(
|
|||||||
maskPath: options.maskPath,
|
maskPath: options.maskPath,
|
||||||
initialImage: options.initialImage,
|
initialImage: options.initialImage,
|
||||||
seed: options.seed,
|
seed: options.seed,
|
||||||
activeTabName: tabMap[options.activeTab],
|
activeTabName,
|
||||||
// system
|
// system
|
||||||
isProcessing: system.isProcessing,
|
isProcessing: system.isProcessing,
|
||||||
isConnected: system.isConnected,
|
isConnected: system.isConnected,
|
||||||
|
20
frontend/src/common/hooks/useClickOutsideWatcher.ts
Normal file
20
frontend/src/common/hooks/useClickOutsideWatcher.ts
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import { RefObject, useEffect } from 'react';
|
||||||
|
|
||||||
|
const useClickOutsideWatcher = (
|
||||||
|
ref: RefObject<HTMLElement>,
|
||||||
|
callback: () => void
|
||||||
|
) => {
|
||||||
|
useEffect(() => {
|
||||||
|
function handleClickOutside(e: MouseEvent) {
|
||||||
|
if (ref.current && !ref.current.contains(e.target as Node)) {
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
document.addEventListener('mousedown', handleClickOutside);
|
||||||
|
return () => {
|
||||||
|
document.removeEventListener('mousedown', handleClickOutside);
|
||||||
|
};
|
||||||
|
}, [ref, callback]);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useClickOutsideWatcher;
|
@ -2,22 +2,26 @@ import { RootState, useAppSelector } from '../../app/store';
|
|||||||
import CurrentImageButtons from './CurrentImageButtons';
|
import CurrentImageButtons from './CurrentImageButtons';
|
||||||
import { MdPhoto } from 'react-icons/md';
|
import { MdPhoto } from 'react-icons/md';
|
||||||
import CurrentImagePreview from './CurrentImagePreview';
|
import CurrentImagePreview from './CurrentImagePreview';
|
||||||
import { tabMap } from '../tabs/InvokeTabs';
|
|
||||||
import { GalleryState } from './gallerySlice';
|
import { GalleryState } from './gallerySlice';
|
||||||
import { OptionsState } from '../options/optionsSlice';
|
import { OptionsState } from '../options/optionsSlice';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import { createSelector } from '@reduxjs/toolkit';
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
|
import { activeTabNameSelector } from '../options/optionsSelectors';
|
||||||
|
|
||||||
export const currentImageDisplaySelector = createSelector(
|
export const currentImageDisplaySelector = createSelector(
|
||||||
[(state: RootState) => state.gallery, (state: RootState) => state.options],
|
[
|
||||||
(gallery: GalleryState, options: OptionsState) => {
|
(state: RootState) => state.gallery,
|
||||||
|
(state: RootState) => state.options,
|
||||||
|
activeTabNameSelector,
|
||||||
|
],
|
||||||
|
(gallery: GalleryState, options: OptionsState, activeTabName) => {
|
||||||
const { currentImage, intermediateImage } = gallery;
|
const { currentImage, intermediateImage } = gallery;
|
||||||
const { activeTab, shouldShowImageDetails } = options;
|
const { shouldShowImageDetails } = options;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
currentImage,
|
currentImage,
|
||||||
intermediateImage,
|
intermediateImage,
|
||||||
activeTabName: tabMap[activeTab],
|
activeTabName,
|
||||||
shouldShowImageDetails,
|
shouldShowImageDetails,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
@ -32,11 +36,9 @@ export const currentImageDisplaySelector = createSelector(
|
|||||||
* Displays the current image if there is one, plus associated actions.
|
* Displays the current image if there is one, plus associated actions.
|
||||||
*/
|
*/
|
||||||
const CurrentImageDisplay = () => {
|
const CurrentImageDisplay = () => {
|
||||||
const {
|
const { currentImage, intermediateImage, activeTabName } = useAppSelector(
|
||||||
currentImage,
|
currentImageDisplaySelector
|
||||||
intermediateImage,
|
);
|
||||||
activeTabName,
|
|
||||||
} = useAppSelector(currentImageDisplaySelector);
|
|
||||||
|
|
||||||
const imageToDisplay = intermediateImage || currentImage;
|
const imageToDisplay = intermediateImage || currentImage;
|
||||||
|
|
||||||
|
@ -7,11 +7,7 @@ import {
|
|||||||
useToast,
|
useToast,
|
||||||
} from '@chakra-ui/react';
|
} from '@chakra-ui/react';
|
||||||
import { useAppDispatch, useAppSelector } from '../../app/store';
|
import { useAppDispatch, useAppSelector } from '../../app/store';
|
||||||
import {
|
import { setCurrentImage } from './gallerySlice';
|
||||||
setCurrentImage,
|
|
||||||
setShouldHoldGalleryOpen,
|
|
||||||
setShouldShowGallery,
|
|
||||||
} from './gallerySlice';
|
|
||||||
import { FaCheck, FaTrashAlt } from 'react-icons/fa';
|
import { FaCheck, FaTrashAlt } from 'react-icons/fa';
|
||||||
import DeleteImageModal from './DeleteImageModal';
|
import DeleteImageModal from './DeleteImageModal';
|
||||||
import { memo, useState } from 'react';
|
import { memo, useState } from 'react';
|
||||||
@ -25,7 +21,6 @@ import {
|
|||||||
} from '../options/optionsSlice';
|
} from '../options/optionsSlice';
|
||||||
import * as InvokeAI from '../../app/invokeai';
|
import * as InvokeAI from '../../app/invokeai';
|
||||||
import * as ContextMenu from '@radix-ui/react-context-menu';
|
import * as ContextMenu from '@radix-ui/react-context-menu';
|
||||||
import { tabMap } from '../tabs/InvokeTabs';
|
|
||||||
import { setImageToInpaint } from '../tabs/Inpainting/inpaintingSlice';
|
import { setImageToInpaint } from '../tabs/Inpainting/inpaintingSlice';
|
||||||
import { hoverableImageSelector } from './gallerySliceSelectors';
|
import { hoverableImageSelector } from './gallerySliceSelectors';
|
||||||
|
|
||||||
@ -118,7 +113,7 @@ const HoverableImage = memo((props: HoverableImageProps) => {
|
|||||||
if (metadata?.image?.init_image_path) {
|
if (metadata?.image?.init_image_path) {
|
||||||
const response = await fetch(metadata.image.init_image_path);
|
const response = await fetch(metadata.image.init_image_path);
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
dispatch(setActiveTab(tabMap.indexOf('img2img')));
|
dispatch(setActiveTab('img2img'));
|
||||||
dispatch(setAllImageToImageParameters(metadata));
|
dispatch(setAllImageToImageParameters(metadata));
|
||||||
toast({
|
toast({
|
||||||
title: 'Initial Image Set',
|
title: 'Initial Image Set',
|
||||||
@ -142,10 +137,10 @@ const HoverableImage = memo((props: HoverableImageProps) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<ContextMenu.Root
|
<ContextMenu.Root
|
||||||
// onOpenChange={(open: boolean) => {
|
// onOpenChange={(open: boolean) => {
|
||||||
// dispatch(setShouldHoldGalleryOpen(open));
|
// dispatch(setShouldHoldGalleryOpen(open));
|
||||||
// dispatch(setShouldShowGallery(true));
|
// dispatch(setShouldShowGallery(true));
|
||||||
// }}
|
// }}
|
||||||
>
|
>
|
||||||
<ContextMenu.Trigger>
|
<ContextMenu.Trigger>
|
||||||
<Box
|
<Box
|
||||||
|
@ -33,6 +33,7 @@ import { BiReset } from 'react-icons/bi';
|
|||||||
import IAICheckbox from '../../common/components/IAICheckbox';
|
import IAICheckbox from '../../common/components/IAICheckbox';
|
||||||
import { setNeedsCache } from '../tabs/Inpainting/inpaintingSlice';
|
import { setNeedsCache } from '../tabs/Inpainting/inpaintingSlice';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
|
import useClickOutsideWatcher from '../../common/hooks/useClickOutsideWatcher';
|
||||||
|
|
||||||
const GALLERY_SHOW_BUTTONS_MIN_WIDTH = 320;
|
const GALLERY_SHOW_BUTTONS_MIN_WIDTH = 320;
|
||||||
|
|
||||||
@ -110,6 +111,7 @@ export default function ImageGallery() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleCloseGallery = () => {
|
const handleCloseGallery = () => {
|
||||||
|
if (shouldPinGallery) return;
|
||||||
dispatch(
|
dispatch(
|
||||||
setGalleryScrollPosition(
|
setGalleryScrollPosition(
|
||||||
galleryContainerRef.current ? galleryContainerRef.current.scrollTop : 0
|
galleryContainerRef.current ? galleryContainerRef.current.scrollTop : 0
|
||||||
@ -117,7 +119,7 @@ export default function ImageGallery() {
|
|||||||
);
|
);
|
||||||
dispatch(setShouldShowGallery(false));
|
dispatch(setShouldShowGallery(false));
|
||||||
dispatch(setShouldHoldGalleryOpen(false));
|
dispatch(setShouldHoldGalleryOpen(false));
|
||||||
shouldPinGallery && dispatch(setNeedsCache(true));
|
// dispatch(setNeedsCache(true));
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleClickLoadMore = () => {
|
const handleClickLoadMore = () => {
|
||||||
@ -255,6 +257,8 @@ export default function ImageGallery() {
|
|||||||
setShouldShowButtons(galleryWidth >= 280);
|
setShouldShowButtons(galleryWidth >= 280);
|
||||||
}, [galleryWidth]);
|
}, [galleryWidth]);
|
||||||
|
|
||||||
|
useClickOutsideWatcher(galleryRef, handleCloseGallery);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CSSTransition
|
<CSSTransition
|
||||||
nodeRef={galleryRef}
|
nodeRef={galleryRef}
|
||||||
|
@ -143,9 +143,7 @@ export const gallerySlice = createSlice({
|
|||||||
if (state.shouldAutoSwitchToNewImages) {
|
if (state.shouldAutoSwitchToNewImages) {
|
||||||
state.currentImageUuid = uuid;
|
state.currentImageUuid = uuid;
|
||||||
state.currentImage = newImage;
|
state.currentImage = newImage;
|
||||||
if (category === 'result') {
|
state.currentCategory = category;
|
||||||
state.currentCategory = 'result';
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
state.intermediateImage = undefined;
|
state.intermediateImage = undefined;
|
||||||
tempCategory.latest_mtime = mtime;
|
tempCategory.latest_mtime = mtime;
|
||||||
|
@ -1,12 +1,16 @@
|
|||||||
import { createSelector } from '@reduxjs/toolkit';
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
import { RootState } from '../../app/store';
|
import { RootState } from '../../app/store';
|
||||||
|
import { activeTabNameSelector } from '../options/optionsSelectors';
|
||||||
import { OptionsState } from '../options/optionsSlice';
|
import { OptionsState } from '../options/optionsSlice';
|
||||||
import { tabMap } from '../tabs/InvokeTabs';
|
|
||||||
import { GalleryState } from './gallerySlice';
|
import { GalleryState } from './gallerySlice';
|
||||||
|
|
||||||
export const imageGallerySelector = createSelector(
|
export const imageGallerySelector = createSelector(
|
||||||
[(state: RootState) => state.gallery, (state: RootState) => state.options],
|
[
|
||||||
(gallery: GalleryState, options: OptionsState) => {
|
(state: RootState) => state.gallery,
|
||||||
|
(state: RootState) => state.options,
|
||||||
|
activeTabNameSelector,
|
||||||
|
],
|
||||||
|
(gallery: GalleryState, options: OptionsState, activeTabName) => {
|
||||||
const {
|
const {
|
||||||
categories,
|
categories,
|
||||||
currentCategory,
|
currentCategory,
|
||||||
@ -21,8 +25,6 @@ export const imageGallerySelector = createSelector(
|
|||||||
galleryWidth,
|
galleryWidth,
|
||||||
} = gallery;
|
} = gallery;
|
||||||
|
|
||||||
const { activeTab } = options;
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
currentImageUuid,
|
currentImageUuid,
|
||||||
shouldPinGallery,
|
shouldPinGallery,
|
||||||
@ -31,7 +33,7 @@ export const imageGallerySelector = createSelector(
|
|||||||
galleryImageMinimumWidth,
|
galleryImageMinimumWidth,
|
||||||
galleryImageObjectFit,
|
galleryImageObjectFit,
|
||||||
galleryGridTemplateColumns: `repeat(auto-fill, minmax(${galleryImageMinimumWidth}px, auto))`,
|
galleryGridTemplateColumns: `repeat(auto-fill, minmax(${galleryImageMinimumWidth}px, auto))`,
|
||||||
activeTabName: tabMap[activeTab],
|
activeTabName,
|
||||||
shouldHoldGalleryOpen,
|
shouldHoldGalleryOpen,
|
||||||
shouldAutoSwitchToNewImages,
|
shouldAutoSwitchToNewImages,
|
||||||
images: categories[currentCategory].images,
|
images: categories[currentCategory].images,
|
||||||
@ -44,12 +46,12 @@ export const imageGallerySelector = createSelector(
|
|||||||
);
|
);
|
||||||
|
|
||||||
export const hoverableImageSelector = createSelector(
|
export const hoverableImageSelector = createSelector(
|
||||||
[(state: RootState) => state.options, (state: RootState) => state.gallery],
|
[(state: RootState) => state.options, (state: RootState) => state.gallery, activeTabNameSelector],
|
||||||
(options: OptionsState, gallery: GalleryState) => {
|
(options: OptionsState, gallery: GalleryState, activeTabName) => {
|
||||||
return {
|
return {
|
||||||
galleryImageObjectFit: gallery.galleryImageObjectFit,
|
galleryImageObjectFit: gallery.galleryImageObjectFit,
|
||||||
galleryImageMinimumWidth: gallery.galleryImageMinimumWidth,
|
galleryImageMinimumWidth: gallery.galleryImageMinimumWidth,
|
||||||
activeTabName: tabMap[options.activeTab],
|
activeTabName,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
@ -2,14 +2,13 @@ import React, { ChangeEvent } from 'react';
|
|||||||
import { HEIGHTS } from '../../../app/constants';
|
import { HEIGHTS } from '../../../app/constants';
|
||||||
import { RootState, useAppDispatch, useAppSelector } from '../../../app/store';
|
import { RootState, useAppDispatch, useAppSelector } from '../../../app/store';
|
||||||
import IAISelect from '../../../common/components/IAISelect';
|
import IAISelect from '../../../common/components/IAISelect';
|
||||||
import { tabMap } from '../../tabs/InvokeTabs';
|
import { activeTabNameSelector } from '../optionsSelectors';
|
||||||
import { setHeight } from '../optionsSlice';
|
import { setHeight } from '../optionsSlice';
|
||||||
import { fontSize } from './MainOptions';
|
import { fontSize } from './MainOptions';
|
||||||
|
|
||||||
export default function MainHeight() {
|
export default function MainHeight() {
|
||||||
const { activeTab, height } = useAppSelector(
|
const { height } = useAppSelector((state: RootState) => state.options);
|
||||||
(state: RootState) => state.options
|
const activeTabName = useAppSelector(activeTabNameSelector);
|
||||||
);
|
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
const handleChangeHeight = (e: ChangeEvent<HTMLSelectElement>) =>
|
const handleChangeHeight = (e: ChangeEvent<HTMLSelectElement>) =>
|
||||||
@ -17,7 +16,7 @@ export default function MainHeight() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<IAISelect
|
<IAISelect
|
||||||
isDisabled={tabMap[activeTab] === 'inpainting'}
|
isDisabled={activeTabName === 'inpainting'}
|
||||||
label="Height"
|
label="Height"
|
||||||
value={height}
|
value={height}
|
||||||
flexGrow={1}
|
flexGrow={1}
|
||||||
|
@ -6,6 +6,7 @@ import { useHotkeys } from 'react-hotkeys-hook';
|
|||||||
import { createSelector } from '@reduxjs/toolkit';
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
import { SystemState } from '../../system/systemSlice';
|
import { SystemState } from '../../system/systemSlice';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
|
import { IAIButtonProps } from '../../../common/components/IAIButton';
|
||||||
|
|
||||||
const cancelButtonSelector = createSelector(
|
const cancelButtonSelector = createSelector(
|
||||||
(state: RootState) => state.system,
|
(state: RootState) => state.system,
|
||||||
@ -23,7 +24,8 @@ const cancelButtonSelector = createSelector(
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
export default function CancelButton() {
|
export default function CancelButton(props: Omit<IAIButtonProps, 'label'>) {
|
||||||
|
const { ...rest } = props;
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const { isProcessing, isConnected, isCancelable } =
|
const { isProcessing, isConnected, isCancelable } =
|
||||||
useAppSelector(cancelButtonSelector);
|
useAppSelector(cancelButtonSelector);
|
||||||
@ -47,6 +49,7 @@ export default function CancelButton() {
|
|||||||
isDisabled={!isConnected || !isProcessing || !isCancelable}
|
isDisabled={!isConnected || !isProcessing || !isCancelable}
|
||||||
onClick={handleClickCancel}
|
onClick={handleClickCancel}
|
||||||
styleClass="cancel-btn"
|
styleClass="cancel-btn"
|
||||||
|
{...rest}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,21 +1,33 @@
|
|||||||
|
import { useHotkeys } from 'react-hotkeys-hook';
|
||||||
import { generateImage } from '../../../app/socketio/actions';
|
import { generateImage } from '../../../app/socketio/actions';
|
||||||
import { RootState, useAppDispatch, useAppSelector } from '../../../app/store';
|
import { useAppDispatch, useAppSelector } from '../../../app/store';
|
||||||
import IAIButton from '../../../common/components/IAIButton';
|
import IAIButton, {
|
||||||
|
IAIButtonProps,
|
||||||
|
} from '../../../common/components/IAIButton';
|
||||||
import useCheckParameters from '../../../common/hooks/useCheckParameters';
|
import useCheckParameters from '../../../common/hooks/useCheckParameters';
|
||||||
import { tabMap } from '../../tabs/InvokeTabs';
|
import { activeTabNameSelector } from '../optionsSelectors';
|
||||||
|
|
||||||
export default function InvokeButton() {
|
export default function InvokeButton(props: Omit<IAIButtonProps, 'label'>) {
|
||||||
|
const { ...rest } = props;
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const isReady = useCheckParameters();
|
const isReady = useCheckParameters();
|
||||||
|
|
||||||
const activeTab = useAppSelector(
|
const activeTabName = useAppSelector(activeTabNameSelector);
|
||||||
(state: RootState) => state.options.activeTab
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleClickGenerate = () => {
|
const handleClickGenerate = () => {
|
||||||
dispatch(generateImage(tabMap[activeTab]));
|
dispatch(generateImage(activeTabName));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
useHotkeys(
|
||||||
|
'ctrl+enter, cmd+enter',
|
||||||
|
() => {
|
||||||
|
if (isReady) {
|
||||||
|
dispatch(generateImage(activeTabName));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[isReady, activeTabName]
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<IAIButton
|
<IAIButton
|
||||||
label="Invoke"
|
label="Invoke"
|
||||||
@ -24,6 +36,7 @@ export default function InvokeButton() {
|
|||||||
isDisabled={!isReady}
|
isDisabled={!isReady}
|
||||||
onClick={handleClickGenerate}
|
onClick={handleClickGenerate}
|
||||||
className="invoke-btn"
|
className="invoke-btn"
|
||||||
|
{...rest}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -4,20 +4,20 @@
|
|||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: auto max-content;
|
grid-template-columns: auto max-content;
|
||||||
column-gap: 0.5rem;
|
column-gap: 0.5rem;
|
||||||
|
}
|
||||||
.invoke-btn {
|
|
||||||
@include Button(
|
.invoke-btn {
|
||||||
$btn-color: var(--accent-color),
|
@include Button(
|
||||||
$btn-color-hover: var(--accent-color-hover),
|
$btn-color: var(--accent-color),
|
||||||
$btn-width: 5rem
|
$btn-color-hover: var(--accent-color-hover),
|
||||||
);
|
// $btn-width: 5rem
|
||||||
}
|
);
|
||||||
|
}
|
||||||
.cancel-btn {
|
|
||||||
@include Button(
|
.cancel-btn {
|
||||||
$btn-color: var(--destructive-color),
|
@include Button(
|
||||||
$btn-color-hover: var(--destructive-color-hover),
|
$btn-color: var(--destructive-color),
|
||||||
$btn-width: 3rem
|
$btn-color-hover: var(--destructive-color-hover),
|
||||||
);
|
// $btn-width: 3rem
|
||||||
}
|
);
|
||||||
}
|
}
|
||||||
|
@ -8,14 +8,14 @@ import { createSelector } from '@reduxjs/toolkit';
|
|||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import useCheckParameters from '../../../common/hooks/useCheckParameters';
|
import useCheckParameters from '../../../common/hooks/useCheckParameters';
|
||||||
import { useHotkeys } from 'react-hotkeys-hook';
|
import { useHotkeys } from 'react-hotkeys-hook';
|
||||||
import { tabMap } from '../../tabs/InvokeTabs';
|
import { activeTabNameSelector } from '../optionsSelectors';
|
||||||
|
|
||||||
const promptInputSelector = createSelector(
|
const promptInputSelector = createSelector(
|
||||||
(state: RootState) => state.options,
|
[(state: RootState) => state.options, activeTabNameSelector],
|
||||||
(options: OptionsState) => {
|
(options: OptionsState, activeTabName) => {
|
||||||
return {
|
return {
|
||||||
prompt: options.prompt,
|
prompt: options.prompt,
|
||||||
activeTabName: tabMap[options.activeTab],
|
activeTabName,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -38,16 +38,6 @@ const PromptInput = () => {
|
|||||||
dispatch(setPrompt(e.target.value));
|
dispatch(setPrompt(e.target.value));
|
||||||
};
|
};
|
||||||
|
|
||||||
useHotkeys(
|
|
||||||
'ctrl+enter, cmd+enter',
|
|
||||||
() => {
|
|
||||||
if (isReady) {
|
|
||||||
dispatch(generateImage(activeTabName));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[isReady, activeTabName]
|
|
||||||
);
|
|
||||||
|
|
||||||
useHotkeys(
|
useHotkeys(
|
||||||
'alt+a',
|
'alt+a',
|
||||||
() => {
|
() => {
|
||||||
|
9
frontend/src/features/options/optionsSelectors.ts
Normal file
9
frontend/src/features/options/optionsSelectors.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
|
import { RootState } from '../../app/store';
|
||||||
|
import { tabMap } from '../tabs/InvokeTabs';
|
||||||
|
import { OptionsState } from './optionsSlice';
|
||||||
|
|
||||||
|
export const activeTabNameSelector = createSelector(
|
||||||
|
(state: RootState) => state.options,
|
||||||
|
(options: OptionsState) => tabMap[options.activeTab]
|
||||||
|
);
|
@ -134,7 +134,7 @@ export default function HotkeysModal({ children }: HotkeysModalProps) {
|
|||||||
{
|
{
|
||||||
title: 'Quick Toggle Brush/Eraser',
|
title: 'Quick Toggle Brush/Eraser',
|
||||||
desc: 'Quick toggle between brush and eraser',
|
desc: 'Quick toggle between brush and eraser',
|
||||||
hotkey: 'Z',
|
hotkey: 'X',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Decrease Brush Size',
|
title: 'Decrease Brush Size',
|
||||||
|
@ -24,3 +24,11 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
column-gap: 0.5rem;
|
column-gap: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Overrides
|
||||||
|
|
||||||
|
.process-buttons {
|
||||||
|
padding-left: 0.5rem;
|
||||||
|
button {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,19 +1,37 @@
|
|||||||
import { IconButton, Link, Tooltip, useColorMode } from '@chakra-ui/react';
|
import { IconButton, Link, Tooltip, useColorMode } from '@chakra-ui/react';
|
||||||
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
|
import _ from 'lodash';
|
||||||
import { useHotkeys } from 'react-hotkeys-hook';
|
import { useHotkeys } from 'react-hotkeys-hook';
|
||||||
|
|
||||||
import { FaSun, FaMoon, FaGithub, FaDiscord } from 'react-icons/fa';
|
import { FaSun, FaMoon, FaGithub, FaDiscord } from 'react-icons/fa';
|
||||||
import { MdHelp, MdKeyboard, MdSettings } from 'react-icons/md';
|
import { MdHelp, MdKeyboard, MdSettings } from 'react-icons/md';
|
||||||
|
import { RootState, useAppSelector } from '../../app/store';
|
||||||
|
|
||||||
import InvokeAILogo from '../../assets/images/logo.png';
|
import InvokeAILogo from '../../assets/images/logo.png';
|
||||||
|
import { OptionsState } from '../options/optionsSlice';
|
||||||
|
import CancelButton from '../options/ProcessButtons/CancelButton';
|
||||||
|
import InvokeButton from '../options/ProcessButtons/InvokeButton';
|
||||||
|
import ProcessButtons from '../options/ProcessButtons/ProcessButtons';
|
||||||
import HotkeysModal from './HotkeysModal/HotkeysModal';
|
import HotkeysModal from './HotkeysModal/HotkeysModal';
|
||||||
|
|
||||||
import SettingsModal from './SettingsModal/SettingsModal';
|
import SettingsModal from './SettingsModal/SettingsModal';
|
||||||
import StatusIndicator from './StatusIndicator';
|
import StatusIndicator from './StatusIndicator';
|
||||||
|
|
||||||
|
const siteHeaderSelector = createSelector(
|
||||||
|
(state: RootState) => state.options,
|
||||||
|
|
||||||
|
(options: OptionsState) => {
|
||||||
|
const { shouldPinOptionsPanel } = options;
|
||||||
|
return { shouldShowProcessButtons: !shouldPinOptionsPanel };
|
||||||
|
},
|
||||||
|
{ memoizeOptions: { resultEqualityCheck: _.isEqual } }
|
||||||
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Header, includes color mode toggle, settings button, status message.
|
* Header, includes color mode toggle, settings button, status message.
|
||||||
*/
|
*/
|
||||||
const SiteHeader = () => {
|
const SiteHeader = () => {
|
||||||
|
const { shouldShowProcessButtons } = useAppSelector(siteHeaderSelector);
|
||||||
const { colorMode, toggleColorMode } = useColorMode();
|
const { colorMode, toggleColorMode } = useColorMode();
|
||||||
|
|
||||||
useHotkeys(
|
useHotkeys(
|
||||||
@ -36,6 +54,12 @@ const SiteHeader = () => {
|
|||||||
<h1>
|
<h1>
|
||||||
invoke <strong>ai</strong>
|
invoke <strong>ai</strong>
|
||||||
</h1>
|
</h1>
|
||||||
|
{shouldShowProcessButtons && (
|
||||||
|
<div className="process-buttons process-buttons">
|
||||||
|
<InvokeButton size={'sm'} />
|
||||||
|
<CancelButton size={'sm'} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="site-header-right-side">
|
<div className="site-header-right-side">
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
padding: 0;
|
padding: 0;
|
||||||
min-width: 2rem !important;
|
min-width: 2rem !important;
|
||||||
|
|
||||||
filter: drop-shadow(0 0 1rem var(--text-color-a3));
|
filter: var(--floating-button-drop-shadow);
|
||||||
|
|
||||||
&.left {
|
&.left {
|
||||||
left: 0;
|
left: 0;
|
||||||
|
@ -57,6 +57,7 @@ const InpaintingCanvas = () => {
|
|||||||
isDrawing,
|
isDrawing,
|
||||||
shouldLockBoundingBox,
|
shouldLockBoundingBox,
|
||||||
shouldShowBoundingBox,
|
shouldShowBoundingBox,
|
||||||
|
boundingBoxDimensions,
|
||||||
} = useAppSelector(inpaintingCanvasSelector);
|
} = useAppSelector(inpaintingCanvasSelector);
|
||||||
|
|
||||||
const toast = useToast();
|
const toast = useToast();
|
||||||
@ -95,7 +96,7 @@ const InpaintingCanvas = () => {
|
|||||||
};
|
};
|
||||||
image.src = imageToInpaint.url;
|
image.src = imageToInpaint.url;
|
||||||
} else {
|
} else {
|
||||||
setCanvasBgImage(null)
|
setCanvasBgImage(null);
|
||||||
}
|
}
|
||||||
}, [imageToInpaint, dispatch, stageScale, toast]);
|
}, [imageToInpaint, dispatch, stageScale, toast]);
|
||||||
|
|
||||||
@ -243,7 +244,7 @@ const InpaintingCanvas = () => {
|
|||||||
)}
|
)}
|
||||||
{!shouldLockBoundingBox && (
|
{!shouldLockBoundingBox && (
|
||||||
<div style={{ pointerEvents: 'none' }}>
|
<div style={{ pointerEvents: 'none' }}>
|
||||||
Transforming Bounding Box (M)
|
{`Transforming Bounding Box ${boundingBoxDimensions.width}x${boundingBoxDimensions.height} (M)`}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
@ -6,8 +6,8 @@ import {
|
|||||||
useAppDispatch,
|
useAppDispatch,
|
||||||
useAppSelector,
|
useAppSelector,
|
||||||
} from '../../../../app/store';
|
} from '../../../../app/store';
|
||||||
|
import { activeTabNameSelector } from '../../../options/optionsSelectors';
|
||||||
import { OptionsState } from '../../../options/optionsSlice';
|
import { OptionsState } from '../../../options/optionsSlice';
|
||||||
import { tabMap } from '../../InvokeTabs';
|
|
||||||
import {
|
import {
|
||||||
InpaintingState,
|
InpaintingState,
|
||||||
setIsDrawing,
|
setIsDrawing,
|
||||||
@ -16,12 +16,16 @@ import {
|
|||||||
} from '../inpaintingSlice';
|
} from '../inpaintingSlice';
|
||||||
|
|
||||||
const keyboardEventManagerSelector = createSelector(
|
const keyboardEventManagerSelector = createSelector(
|
||||||
[(state: RootState) => state.options, (state: RootState) => state.inpainting],
|
[
|
||||||
(options: OptionsState, inpainting: InpaintingState) => {
|
(state: RootState) => state.options,
|
||||||
|
(state: RootState) => state.inpainting,
|
||||||
|
activeTabNameSelector,
|
||||||
|
],
|
||||||
|
(options: OptionsState, inpainting: InpaintingState, activeTabName) => {
|
||||||
const { shouldShowMask, cursorPosition, shouldLockBoundingBox } =
|
const { shouldShowMask, cursorPosition, shouldLockBoundingBox } =
|
||||||
inpainting;
|
inpainting;
|
||||||
return {
|
return {
|
||||||
activeTabName: tabMap[options.activeTab],
|
activeTabName,
|
||||||
shouldShowMask,
|
shouldShowMask,
|
||||||
isCursorOnCanvas: Boolean(cursorPosition),
|
isCursorOnCanvas: Boolean(cursorPosition),
|
||||||
shouldLockBoundingBox,
|
shouldLockBoundingBox,
|
||||||
@ -49,7 +53,7 @@ const KeyboardEventManager = () => {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const listener = (e: KeyboardEvent) => {
|
const listener = (e: KeyboardEvent) => {
|
||||||
if (
|
if (
|
||||||
!['z', ' '].includes(e.key) ||
|
!['x', ' '].includes(e.key) ||
|
||||||
activeTabName !== 'inpainting' ||
|
activeTabName !== 'inpainting' ||
|
||||||
!shouldShowMask
|
!shouldShowMask
|
||||||
) {
|
) {
|
||||||
@ -83,13 +87,13 @@ const KeyboardEventManager = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
switch (e.key) {
|
switch (e.key) {
|
||||||
case 'z': {
|
case 'x': {
|
||||||
dispatch(toggleTool());
|
dispatch(toggleTool());
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case ' ': {
|
case ' ': {
|
||||||
if (!shouldShowMask) break;
|
if (!shouldShowMask) break;
|
||||||
|
|
||||||
if (e.type === 'keydown') {
|
if (e.type === 'keydown') {
|
||||||
dispatch(setIsDrawing(false));
|
dispatch(setIsDrawing(false));
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { createSelector } from '@reduxjs/toolkit';
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import { RootState } from '../../../app/store';
|
import { RootState } from '../../../app/store';
|
||||||
|
import { activeTabNameSelector } from '../../options/optionsSelectors';
|
||||||
import { OptionsState } from '../../options/optionsSlice';
|
import { OptionsState } from '../../options/optionsSlice';
|
||||||
import { tabMap } from '../InvokeTabs';
|
import { tabMap } from '../InvokeTabs';
|
||||||
import { InpaintingState } from './inpaintingSlice';
|
import { InpaintingState } from './inpaintingSlice';
|
||||||
@ -18,8 +19,12 @@ export const inpaintingCanvasLinesSelector = createSelector(
|
|||||||
);
|
);
|
||||||
|
|
||||||
export const inpaintingControlsSelector = createSelector(
|
export const inpaintingControlsSelector = createSelector(
|
||||||
[(state: RootState) => state.inpainting, (state: RootState) => state.options],
|
[
|
||||||
(inpainting: InpaintingState, options: OptionsState) => {
|
(state: RootState) => state.inpainting,
|
||||||
|
(state: RootState) => state.options,
|
||||||
|
activeTabNameSelector,
|
||||||
|
],
|
||||||
|
(inpainting: InpaintingState, options: OptionsState, activeTabName) => {
|
||||||
const {
|
const {
|
||||||
tool,
|
tool,
|
||||||
brushSize,
|
brushSize,
|
||||||
@ -34,7 +39,7 @@ export const inpaintingControlsSelector = createSelector(
|
|||||||
shouldShowBoundingBox,
|
shouldShowBoundingBox,
|
||||||
} = inpainting;
|
} = inpainting;
|
||||||
|
|
||||||
const { activeTab, showDualDisplay } = options;
|
const { showDualDisplay } = options;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
tool,
|
tool,
|
||||||
@ -46,7 +51,7 @@ export const inpaintingControlsSelector = createSelector(
|
|||||||
canUndo: pastLines.length > 0,
|
canUndo: pastLines.length > 0,
|
||||||
canRedo: futureLines.length > 0,
|
canRedo: futureLines.length > 0,
|
||||||
isMaskEmpty: lines.length === 0,
|
isMaskEmpty: lines.length === 0,
|
||||||
activeTabName: tabMap[activeTab],
|
activeTabName,
|
||||||
showDualDisplay,
|
showDualDisplay,
|
||||||
shouldShowBoundingBoxFill,
|
shouldShowBoundingBoxFill,
|
||||||
shouldShowBoundingBox,
|
shouldShowBoundingBox,
|
||||||
@ -75,6 +80,7 @@ export const inpaintingCanvasSelector = createSelector(
|
|||||||
isDrawing,
|
isDrawing,
|
||||||
shouldLockBoundingBox,
|
shouldLockBoundingBox,
|
||||||
shouldShowBoundingBox,
|
shouldShowBoundingBox,
|
||||||
|
boundingBoxDimensions,
|
||||||
} = inpainting;
|
} = inpainting;
|
||||||
return {
|
return {
|
||||||
tool,
|
tool,
|
||||||
@ -89,6 +95,7 @@ export const inpaintingCanvasSelector = createSelector(
|
|||||||
isDrawing,
|
isDrawing,
|
||||||
shouldLockBoundingBox,
|
shouldLockBoundingBox,
|
||||||
shouldShowBoundingBox,
|
shouldShowBoundingBox,
|
||||||
|
boundingBoxDimensions,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -1,9 +1,17 @@
|
|||||||
import { createSelector } from '@reduxjs/toolkit';
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
import { MouseEvent, ReactNode, useEffect, useRef } from 'react';
|
import {
|
||||||
|
FocusEvent,
|
||||||
|
MouseEvent,
|
||||||
|
ReactNode,
|
||||||
|
useCallback,
|
||||||
|
useEffect,
|
||||||
|
useRef,
|
||||||
|
} from 'react';
|
||||||
import { BsPinAngleFill } from 'react-icons/bs';
|
import { BsPinAngleFill } from 'react-icons/bs';
|
||||||
import { CSSTransition } from 'react-transition-group';
|
import { CSSTransition } from 'react-transition-group';
|
||||||
import { RootState, useAppDispatch, useAppSelector } from '../../app/store';
|
import { RootState, useAppDispatch, useAppSelector } from '../../app/store';
|
||||||
import IAIIconButton from '../../common/components/IAIIconButton';
|
import IAIIconButton from '../../common/components/IAIIconButton';
|
||||||
|
import useClickOutsideWatcher from '../../common/hooks/useClickOutsideWatcher';
|
||||||
import {
|
import {
|
||||||
OptionsState,
|
OptionsState,
|
||||||
setOptionsPanelScrollPosition,
|
setOptionsPanelScrollPosition,
|
||||||
@ -50,7 +58,8 @@ const InvokeOptionsPanel = (props: Props) => {
|
|||||||
|
|
||||||
const { children } = props;
|
const { children } = props;
|
||||||
|
|
||||||
const handleCloseOptionsPanel = () => {
|
const handleCloseOptionsPanel = useCallback(() => {
|
||||||
|
if (shouldPinOptionsPanel) return;
|
||||||
dispatch(
|
dispatch(
|
||||||
setOptionsPanelScrollPosition(
|
setOptionsPanelScrollPosition(
|
||||||
optionsPanelContainerRef.current
|
optionsPanelContainerRef.current
|
||||||
@ -60,8 +69,10 @@ const InvokeOptionsPanel = (props: Props) => {
|
|||||||
);
|
);
|
||||||
dispatch(setShouldShowOptionsPanel(false));
|
dispatch(setShouldShowOptionsPanel(false));
|
||||||
dispatch(setShouldHoldOptionsPanelOpen(false));
|
dispatch(setShouldHoldOptionsPanelOpen(false));
|
||||||
shouldPinOptionsPanel && dispatch(setNeedsCache(true));
|
// dispatch(setNeedsCache(true));
|
||||||
};
|
}, [dispatch, shouldPinOptionsPanel]);
|
||||||
|
|
||||||
|
useClickOutsideWatcher(optionsPanelRef, handleCloseOptionsPanel);
|
||||||
|
|
||||||
const setCloseOptionsPanelTimer = () => {
|
const setCloseOptionsPanelTimer = () => {
|
||||||
timeoutIdRef.current = window.setTimeout(
|
timeoutIdRef.current = window.setTimeout(
|
||||||
|
@ -25,8 +25,6 @@
|
|||||||
--destructive-color: rgb(185, 55, 55);
|
--destructive-color: rgb(185, 55, 55);
|
||||||
--destructive-color-hover: rgb(255, 75, 75);
|
--destructive-color-hover: rgb(255, 75, 75);
|
||||||
|
|
||||||
--text-color-a3: rgba(255, 255, 255, 0.3);
|
|
||||||
|
|
||||||
// Error status colors
|
// Error status colors
|
||||||
--border-color-invalid: rgb(255, 80, 50);
|
--border-color-invalid: rgb(255, 80, 50);
|
||||||
--box-shadow-color-invalid: rgb(210, 30, 10);
|
--box-shadow-color-invalid: rgb(210, 30, 10);
|
||||||
@ -103,6 +101,6 @@
|
|||||||
--context-menu-box-shadow: none;
|
--context-menu-box-shadow: none;
|
||||||
--context-menu-bg-color-hover: rgb(30, 32, 42);
|
--context-menu-bg-color-hover: rgb(30, 32, 42);
|
||||||
|
|
||||||
// Inpainting
|
// Shadows
|
||||||
--inpaint-bg-color: rgb(30, 32, 42);
|
--floating-button-drop-shadow: drop-shadow(0 0 1rem rgba(140, 101, 255, 0.5));
|
||||||
}
|
}
|
||||||
|
@ -25,8 +25,6 @@
|
|||||||
--destructive-color: rgb(237, 51, 51);
|
--destructive-color: rgb(237, 51, 51);
|
||||||
--destructive-color-hover: rgb(255, 55, 55);
|
--destructive-color-hover: rgb(255, 55, 55);
|
||||||
|
|
||||||
--text-color-a3: rgba(0, 0, 0, 0.3);
|
|
||||||
|
|
||||||
// Error status colors
|
// Error status colors
|
||||||
--border-color-invalid: rgb(255, 80, 50);
|
--border-color-invalid: rgb(255, 80, 50);
|
||||||
--box-shadow-color-invalid: none;
|
--box-shadow-color-invalid: none;
|
||||||
@ -104,6 +102,6 @@
|
|||||||
0px 10px 20px -15px rgba(22, 23, 24, 0.2);
|
0px 10px 20px -15px rgba(22, 23, 24, 0.2);
|
||||||
--context-menu-bg-color-hover: var(--background-color-secondary);
|
--context-menu-bg-color-hover: var(--background-color-secondary);
|
||||||
|
|
||||||
// Inpainting
|
// Shadows
|
||||||
--inpaint-bg-color: rgb(180, 182, 184);
|
--floating-button-drop-shadow: drop-shadow(0 0 1rem rgba(0, 0, 0, 0.3));
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user