diff --git a/frontend/src/app/App.tsx b/frontend/src/app/App.tsx index 9544fe75b4..1321469028 100644 --- a/frontend/src/app/App.tsx +++ b/frontend/src/app/App.tsx @@ -6,7 +6,7 @@ import Loading from '../Loading'; import { useAppDispatch } from './store'; import { requestSystemConfig } from './socketio/actions'; import { keepGUIAlive } from './utils'; -import InvokeTabs, { tabMap } from '../features/tabs/InvokeTabs'; +import InvokeTabs from '../features/tabs/InvokeTabs'; import ImageUploader from '../common/components/ImageUploader'; import { RootState, useAppSelector } from '../app/store'; @@ -15,19 +15,23 @@ import ShowHideOptionsPanelButton from '../features/tabs/ShowHideOptionsPanelBut import { createSelector } from '@reduxjs/toolkit'; import { GalleryState } from '../features/gallery/gallerySlice'; import { OptionsState } from '../features/options/optionsSlice'; +import { activeTabNameSelector } from '../features/options/optionsSelectors'; keepGUIAlive(); 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 } = gallery; const { shouldShowOptionsPanel, shouldHoldOptionsPanelOpen, shouldPinOptionsPanel, - activeTab, } = options; return { @@ -39,7 +43,7 @@ const appSelector = createSelector( !( shouldShowOptionsPanel || (shouldHoldOptionsPanelOpen && !shouldPinOptionsPanel) - ) && ['txt2img', 'img2img', 'inpainting'].includes(tabMap[activeTab]), + ) && ['txt2img', 'img2img', 'inpainting'].includes(activeTabName), }; } ); diff --git a/frontend/src/common/components/IAIButton.tsx b/frontend/src/common/components/IAIButton.tsx index 84a4622e21..53db1cd8f9 100644 --- a/frontend/src/common/components/IAIButton.tsx +++ b/frontend/src/common/components/IAIButton.tsx @@ -1,22 +1,18 @@ import { Button, ButtonProps, Tooltip } from '@chakra-ui/react'; -interface Props extends ButtonProps { +export interface IAIButtonProps extends ButtonProps { label: string; tooltip?: string; } /** - * Reusable customized button component. Originally was more customized - now probably unecessary. - * - * TODO: Get rid of this. + * Reusable customized button component. */ -const IAIButton = (props: Props) => { - const { label, tooltip = '', size = 'sm', ...rest } = props; +const IAIButton = (props: IAIButtonProps) => { + const { label, tooltip = '', ...rest } = props; return ( - + ); }; diff --git a/frontend/src/common/components/ImageUploader.tsx b/frontend/src/common/components/ImageUploader.tsx index 30c5249334..cf980ed995 100644 --- a/frontend/src/common/components/ImageUploader.tsx +++ b/frontend/src/common/components/ImageUploader.tsx @@ -1,23 +1,11 @@ import { useCallback, ReactNode, useState, useEffect } from 'react'; -import { RootState, useAppDispatch, useAppSelector } from '../../app/store'; -import { tabMap } from '../../features/tabs/InvokeTabs'; +import { useAppDispatch, useAppSelector } from '../../app/store'; import { FileRejection, useDropzone } from 'react-dropzone'; 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 { ImageUploadDestination, UploadImagePayload } from '../../app/invokeai'; import { ImageUploaderTriggerContext } from '../../app/contexts/ImageUploaderTriggerContext'; - -const appSelector = createSelector( - (state: RootState) => state.options, - (options: OptionsState) => { - const { activeTab } = options; - return { - activeTabName: tabMap[activeTab], - }; - } -); +import { activeTabNameSelector } from '../../features/options/optionsSelectors'; type ImageUploaderProps = { children: ReactNode; @@ -26,7 +14,7 @@ type ImageUploaderProps = { const ImageUploader = (props: ImageUploaderProps) => { const { children } = props; const dispatch = useAppDispatch(); - const { activeTabName } = useAppSelector(appSelector); + const activeTabName = useAppSelector(activeTabNameSelector); const toast = useToast({}); const [isHandlingUpload, setIsHandlingUpload] = useState(false); diff --git a/frontend/src/common/hooks/useCheckParameters.ts b/frontend/src/common/hooks/useCheckParameters.ts index 5c23200442..4ff7743538 100644 --- a/frontend/src/common/hooks/useCheckParameters.ts +++ b/frontend/src/common/hooks/useCheckParameters.ts @@ -3,11 +3,11 @@ import _ from 'lodash'; import { useMemo } from 'react'; import { useAppSelector } from '../../app/store'; import { RootState } from '../../app/store'; +import { activeTabNameSelector } from '../../features/options/optionsSelectors'; import { OptionsState } from '../../features/options/optionsSlice'; import { SystemState } from '../../features/system/systemSlice'; import { InpaintingState } from '../../features/tabs/Inpainting/inpaintingSlice'; -import { tabMap } from '../../features/tabs/InvokeTabs'; import { validateSeedWeights } from '../util/seedWeightPairs'; export const useCheckParametersSelector = createSelector( @@ -15,8 +15,9 @@ export const useCheckParametersSelector = createSelector( (state: RootState) => state.options, (state: RootState) => state.system, (state: RootState) => state.inpainting, + activeTabNameSelector ], - (options: OptionsState, system: SystemState, inpainting: InpaintingState) => { + (options: OptionsState, system: SystemState, inpainting: InpaintingState, activeTabName) => { return { // options prompt: options.prompt, @@ -25,7 +26,7 @@ export const useCheckParametersSelector = createSelector( maskPath: options.maskPath, initialImage: options.initialImage, seed: options.seed, - activeTabName: tabMap[options.activeTab], + activeTabName, // system isProcessing: system.isProcessing, isConnected: system.isConnected, diff --git a/frontend/src/common/hooks/useClickOutsideWatcher.ts b/frontend/src/common/hooks/useClickOutsideWatcher.ts new file mode 100644 index 0000000000..6c7f4f9742 --- /dev/null +++ b/frontend/src/common/hooks/useClickOutsideWatcher.ts @@ -0,0 +1,20 @@ +import { RefObject, useEffect } from 'react'; + +const useClickOutsideWatcher = ( + ref: RefObject, + 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; diff --git a/frontend/src/features/gallery/CurrentImageDisplay.tsx b/frontend/src/features/gallery/CurrentImageDisplay.tsx index d470f2eb71..dcf4d09dd9 100644 --- a/frontend/src/features/gallery/CurrentImageDisplay.tsx +++ b/frontend/src/features/gallery/CurrentImageDisplay.tsx @@ -2,22 +2,26 @@ import { RootState, useAppSelector } from '../../app/store'; import CurrentImageButtons from './CurrentImageButtons'; import { MdPhoto } from 'react-icons/md'; import CurrentImagePreview from './CurrentImagePreview'; -import { tabMap } from '../tabs/InvokeTabs'; import { GalleryState } from './gallerySlice'; import { OptionsState } from '../options/optionsSlice'; import _ from 'lodash'; import { createSelector } from '@reduxjs/toolkit'; +import { activeTabNameSelector } from '../options/optionsSelectors'; 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 { activeTab, shouldShowImageDetails } = options; + const { shouldShowImageDetails } = options; return { currentImage, intermediateImage, - activeTabName: tabMap[activeTab], + activeTabName, shouldShowImageDetails, }; }, @@ -32,11 +36,9 @@ export const currentImageDisplaySelector = createSelector( * Displays the current image if there is one, plus associated actions. */ const CurrentImageDisplay = () => { - const { - currentImage, - intermediateImage, - activeTabName, - } = useAppSelector(currentImageDisplaySelector); + const { currentImage, intermediateImage, activeTabName } = useAppSelector( + currentImageDisplaySelector + ); const imageToDisplay = intermediateImage || currentImage; diff --git a/frontend/src/features/gallery/HoverableImage.tsx b/frontend/src/features/gallery/HoverableImage.tsx index 95ec92aaad..e3dcabb158 100644 --- a/frontend/src/features/gallery/HoverableImage.tsx +++ b/frontend/src/features/gallery/HoverableImage.tsx @@ -7,11 +7,7 @@ import { useToast, } from '@chakra-ui/react'; import { useAppDispatch, useAppSelector } from '../../app/store'; -import { - setCurrentImage, - setShouldHoldGalleryOpen, - setShouldShowGallery, -} from './gallerySlice'; +import { setCurrentImage } from './gallerySlice'; import { FaCheck, FaTrashAlt } from 'react-icons/fa'; import DeleteImageModal from './DeleteImageModal'; import { memo, useState } from 'react'; @@ -25,7 +21,6 @@ import { } from '../options/optionsSlice'; import * as InvokeAI from '../../app/invokeai'; import * as ContextMenu from '@radix-ui/react-context-menu'; -import { tabMap } from '../tabs/InvokeTabs'; import { setImageToInpaint } from '../tabs/Inpainting/inpaintingSlice'; import { hoverableImageSelector } from './gallerySliceSelectors'; @@ -118,7 +113,7 @@ const HoverableImage = memo((props: HoverableImageProps) => { if (metadata?.image?.init_image_path) { const response = await fetch(metadata.image.init_image_path); if (response.ok) { - dispatch(setActiveTab(tabMap.indexOf('img2img'))); + dispatch(setActiveTab('img2img')); dispatch(setAllImageToImageParameters(metadata)); toast({ title: 'Initial Image Set', @@ -142,10 +137,10 @@ const HoverableImage = memo((props: HoverableImageProps) => { return ( { - // dispatch(setShouldHoldGalleryOpen(open)); - // dispatch(setShouldShowGallery(true)); - // }} + // onOpenChange={(open: boolean) => { + // dispatch(setShouldHoldGalleryOpen(open)); + // dispatch(setShouldShowGallery(true)); + // }} > { + if (shouldPinGallery) return; dispatch( setGalleryScrollPosition( galleryContainerRef.current ? galleryContainerRef.current.scrollTop : 0 @@ -117,7 +119,7 @@ export default function ImageGallery() { ); dispatch(setShouldShowGallery(false)); dispatch(setShouldHoldGalleryOpen(false)); - shouldPinGallery && dispatch(setNeedsCache(true)); + // dispatch(setNeedsCache(true)); }; const handleClickLoadMore = () => { @@ -255,6 +257,8 @@ export default function ImageGallery() { setShouldShowButtons(galleryWidth >= 280); }, [galleryWidth]); + useClickOutsideWatcher(galleryRef, handleCloseGallery); + return ( 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 { categories, currentCategory, @@ -21,8 +25,6 @@ export const imageGallerySelector = createSelector( galleryWidth, } = gallery; - const { activeTab } = options; - return { currentImageUuid, shouldPinGallery, @@ -31,7 +33,7 @@ export const imageGallerySelector = createSelector( galleryImageMinimumWidth, galleryImageObjectFit, galleryGridTemplateColumns: `repeat(auto-fill, minmax(${galleryImageMinimumWidth}px, auto))`, - activeTabName: tabMap[activeTab], + activeTabName, shouldHoldGalleryOpen, shouldAutoSwitchToNewImages, images: categories[currentCategory].images, @@ -44,12 +46,12 @@ export const imageGallerySelector = createSelector( ); export const hoverableImageSelector = createSelector( - [(state: RootState) => state.options, (state: RootState) => state.gallery], - (options: OptionsState, gallery: GalleryState) => { + [(state: RootState) => state.options, (state: RootState) => state.gallery, activeTabNameSelector], + (options: OptionsState, gallery: GalleryState, activeTabName) => { return { galleryImageObjectFit: gallery.galleryImageObjectFit, galleryImageMinimumWidth: gallery.galleryImageMinimumWidth, - activeTabName: tabMap[options.activeTab], + activeTabName, }; } ); diff --git a/frontend/src/features/options/MainOptions/MainHeight.tsx b/frontend/src/features/options/MainOptions/MainHeight.tsx index 9d57cdadb1..6930f56bd8 100644 --- a/frontend/src/features/options/MainOptions/MainHeight.tsx +++ b/frontend/src/features/options/MainOptions/MainHeight.tsx @@ -2,14 +2,13 @@ import React, { ChangeEvent } from 'react'; import { HEIGHTS } from '../../../app/constants'; import { RootState, useAppDispatch, useAppSelector } from '../../../app/store'; import IAISelect from '../../../common/components/IAISelect'; -import { tabMap } from '../../tabs/InvokeTabs'; +import { activeTabNameSelector } from '../optionsSelectors'; import { setHeight } from '../optionsSlice'; import { fontSize } from './MainOptions'; export default function MainHeight() { - const { activeTab, height } = useAppSelector( - (state: RootState) => state.options - ); + const { height } = useAppSelector((state: RootState) => state.options); + const activeTabName = useAppSelector(activeTabNameSelector); const dispatch = useAppDispatch(); const handleChangeHeight = (e: ChangeEvent) => @@ -17,7 +16,7 @@ export default function MainHeight() { return ( state.system, @@ -23,7 +24,8 @@ const cancelButtonSelector = createSelector( } ); -export default function CancelButton() { +export default function CancelButton(props: Omit) { + const { ...rest } = props; const dispatch = useAppDispatch(); const { isProcessing, isConnected, isCancelable } = useAppSelector(cancelButtonSelector); @@ -47,6 +49,7 @@ export default function CancelButton() { isDisabled={!isConnected || !isProcessing || !isCancelable} onClick={handleClickCancel} styleClass="cancel-btn" + {...rest} /> ); } diff --git a/frontend/src/features/options/ProcessButtons/InvokeButton.tsx b/frontend/src/features/options/ProcessButtons/InvokeButton.tsx index 230795a19e..66590c4d0a 100644 --- a/frontend/src/features/options/ProcessButtons/InvokeButton.tsx +++ b/frontend/src/features/options/ProcessButtons/InvokeButton.tsx @@ -1,21 +1,33 @@ +import { useHotkeys } from 'react-hotkeys-hook'; import { generateImage } from '../../../app/socketio/actions'; -import { RootState, useAppDispatch, useAppSelector } from '../../../app/store'; -import IAIButton from '../../../common/components/IAIButton'; +import { useAppDispatch, useAppSelector } from '../../../app/store'; +import IAIButton, { + IAIButtonProps, +} from '../../../common/components/IAIButton'; 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) { + const { ...rest } = props; const dispatch = useAppDispatch(); const isReady = useCheckParameters(); - const activeTab = useAppSelector( - (state: RootState) => state.options.activeTab - ); + const activeTabName = useAppSelector(activeTabNameSelector); const handleClickGenerate = () => { - dispatch(generateImage(tabMap[activeTab])); + dispatch(generateImage(activeTabName)); }; + useHotkeys( + 'ctrl+enter, cmd+enter', + () => { + if (isReady) { + dispatch(generateImage(activeTabName)); + } + }, + [isReady, activeTabName] + ); + return ( ); } diff --git a/frontend/src/features/options/ProcessButtons/ProcessButtons.scss b/frontend/src/features/options/ProcessButtons/ProcessButtons.scss index 6c6c13971d..480ae28c7a 100644 --- a/frontend/src/features/options/ProcessButtons/ProcessButtons.scss +++ b/frontend/src/features/options/ProcessButtons/ProcessButtons.scss @@ -4,20 +4,20 @@ display: grid; grid-template-columns: auto max-content; column-gap: 0.5rem; - - .invoke-btn { - @include Button( - $btn-color: var(--accent-color), - $btn-color-hover: var(--accent-color-hover), - $btn-width: 5rem - ); - } - - .cancel-btn { - @include Button( - $btn-color: var(--destructive-color), - $btn-color-hover: var(--destructive-color-hover), - $btn-width: 3rem - ); - } +} + +.invoke-btn { + @include Button( + $btn-color: var(--accent-color), + $btn-color-hover: var(--accent-color-hover), + // $btn-width: 5rem + ); +} + +.cancel-btn { + @include Button( + $btn-color: var(--destructive-color), + $btn-color-hover: var(--destructive-color-hover), + // $btn-width: 3rem + ); } diff --git a/frontend/src/features/options/PromptInput/PromptInput.tsx b/frontend/src/features/options/PromptInput/PromptInput.tsx index 178bd4ff30..d4d5475ccd 100644 --- a/frontend/src/features/options/PromptInput/PromptInput.tsx +++ b/frontend/src/features/options/PromptInput/PromptInput.tsx @@ -8,14 +8,14 @@ import { createSelector } from '@reduxjs/toolkit'; import _ from 'lodash'; import useCheckParameters from '../../../common/hooks/useCheckParameters'; import { useHotkeys } from 'react-hotkeys-hook'; -import { tabMap } from '../../tabs/InvokeTabs'; +import { activeTabNameSelector } from '../optionsSelectors'; const promptInputSelector = createSelector( - (state: RootState) => state.options, - (options: OptionsState) => { + [(state: RootState) => state.options, activeTabNameSelector], + (options: OptionsState, activeTabName) => { return { prompt: options.prompt, - activeTabName: tabMap[options.activeTab], + activeTabName, }; }, { @@ -38,16 +38,6 @@ const PromptInput = () => { dispatch(setPrompt(e.target.value)); }; - useHotkeys( - 'ctrl+enter, cmd+enter', - () => { - if (isReady) { - dispatch(generateImage(activeTabName)); - } - }, - [isReady, activeTabName] - ); - useHotkeys( 'alt+a', () => { diff --git a/frontend/src/features/options/optionsSelectors.ts b/frontend/src/features/options/optionsSelectors.ts new file mode 100644 index 0000000000..4c55c2a92a --- /dev/null +++ b/frontend/src/features/options/optionsSelectors.ts @@ -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] +); diff --git a/frontend/src/features/system/HotkeysModal/HotkeysModal.tsx b/frontend/src/features/system/HotkeysModal/HotkeysModal.tsx index df89408292..fea7a566db 100644 --- a/frontend/src/features/system/HotkeysModal/HotkeysModal.tsx +++ b/frontend/src/features/system/HotkeysModal/HotkeysModal.tsx @@ -134,7 +134,7 @@ export default function HotkeysModal({ children }: HotkeysModalProps) { { title: 'Quick Toggle Brush/Eraser', desc: 'Quick toggle between brush and eraser', - hotkey: 'Z', + hotkey: 'X', }, { title: 'Decrease Brush Size', diff --git a/frontend/src/features/system/SiteHeader.scss b/frontend/src/features/system/SiteHeader.scss index f9b40912c0..549f370265 100644 --- a/frontend/src/features/system/SiteHeader.scss +++ b/frontend/src/features/system/SiteHeader.scss @@ -24,3 +24,11 @@ align-items: center; column-gap: 0.5rem; } + +// Overrides + +.process-buttons { + padding-left: 0.5rem; + button { + } +} diff --git a/frontend/src/features/system/SiteHeader.tsx b/frontend/src/features/system/SiteHeader.tsx index 515787749b..69f3a4cc5a 100644 --- a/frontend/src/features/system/SiteHeader.tsx +++ b/frontend/src/features/system/SiteHeader.tsx @@ -1,19 +1,37 @@ import { IconButton, Link, Tooltip, useColorMode } from '@chakra-ui/react'; +import { createSelector } from '@reduxjs/toolkit'; +import _ from 'lodash'; import { useHotkeys } from 'react-hotkeys-hook'; import { FaSun, FaMoon, FaGithub, FaDiscord } from 'react-icons/fa'; import { MdHelp, MdKeyboard, MdSettings } from 'react-icons/md'; +import { RootState, useAppSelector } from '../../app/store'; 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 SettingsModal from './SettingsModal/SettingsModal'; 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. */ const SiteHeader = () => { + const { shouldShowProcessButtons } = useAppSelector(siteHeaderSelector); const { colorMode, toggleColorMode } = useColorMode(); useHotkeys( @@ -36,6 +54,12 @@ const SiteHeader = () => {

invoke ai

+ {shouldShowProcessButtons && ( +
+ + +
+ )}
diff --git a/frontend/src/features/tabs/FloatingShowHideButton.scss b/frontend/src/features/tabs/FloatingShowHideButton.scss index 4fa7060411..abb8eba9de 100644 --- a/frontend/src/features/tabs/FloatingShowHideButton.scss +++ b/frontend/src/features/tabs/FloatingShowHideButton.scss @@ -8,7 +8,7 @@ padding: 0; min-width: 2rem !important; - filter: drop-shadow(0 0 1rem var(--text-color-a3)); + filter: var(--floating-button-drop-shadow); &.left { left: 0; diff --git a/frontend/src/features/tabs/Inpainting/InpaintingCanvas.tsx b/frontend/src/features/tabs/Inpainting/InpaintingCanvas.tsx index 8f306d7abf..dfe4931b82 100644 --- a/frontend/src/features/tabs/Inpainting/InpaintingCanvas.tsx +++ b/frontend/src/features/tabs/Inpainting/InpaintingCanvas.tsx @@ -57,6 +57,7 @@ const InpaintingCanvas = () => { isDrawing, shouldLockBoundingBox, shouldShowBoundingBox, + boundingBoxDimensions, } = useAppSelector(inpaintingCanvasSelector); const toast = useToast(); @@ -95,7 +96,7 @@ const InpaintingCanvas = () => { }; image.src = imageToInpaint.url; } else { - setCanvasBgImage(null) + setCanvasBgImage(null); } }, [imageToInpaint, dispatch, stageScale, toast]); @@ -243,7 +244,7 @@ const InpaintingCanvas = () => { )} {!shouldLockBoundingBox && (
- Transforming Bounding Box (M) + {`Transforming Bounding Box ${boundingBoxDimensions.width}x${boundingBoxDimensions.height} (M)`}
)}
diff --git a/frontend/src/features/tabs/Inpainting/components/KeyboardEventManager.tsx b/frontend/src/features/tabs/Inpainting/components/KeyboardEventManager.tsx index 34a1f31fd0..a4c65f28e8 100644 --- a/frontend/src/features/tabs/Inpainting/components/KeyboardEventManager.tsx +++ b/frontend/src/features/tabs/Inpainting/components/KeyboardEventManager.tsx @@ -6,8 +6,8 @@ import { useAppDispatch, useAppSelector, } from '../../../../app/store'; +import { activeTabNameSelector } from '../../../options/optionsSelectors'; import { OptionsState } from '../../../options/optionsSlice'; -import { tabMap } from '../../InvokeTabs'; import { InpaintingState, setIsDrawing, @@ -16,12 +16,16 @@ import { } from '../inpaintingSlice'; 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 } = inpainting; return { - activeTabName: tabMap[options.activeTab], + activeTabName, shouldShowMask, isCursorOnCanvas: Boolean(cursorPosition), shouldLockBoundingBox, @@ -49,7 +53,7 @@ const KeyboardEventManager = () => { useEffect(() => { const listener = (e: KeyboardEvent) => { if ( - !['z', ' '].includes(e.key) || + !['x', ' '].includes(e.key) || activeTabName !== 'inpainting' || !shouldShowMask ) { @@ -83,13 +87,13 @@ const KeyboardEventManager = () => { } switch (e.key) { - case 'z': { + case 'x': { dispatch(toggleTool()); break; } case ' ': { if (!shouldShowMask) break; - + if (e.type === 'keydown') { dispatch(setIsDrawing(false)); } diff --git a/frontend/src/features/tabs/Inpainting/inpaintingSliceSelectors.ts b/frontend/src/features/tabs/Inpainting/inpaintingSliceSelectors.ts index 3edb3eb06f..a9b6fd14ee 100644 --- a/frontend/src/features/tabs/Inpainting/inpaintingSliceSelectors.ts +++ b/frontend/src/features/tabs/Inpainting/inpaintingSliceSelectors.ts @@ -1,6 +1,7 @@ import { createSelector } from '@reduxjs/toolkit'; import _ from 'lodash'; import { RootState } from '../../../app/store'; +import { activeTabNameSelector } from '../../options/optionsSelectors'; import { OptionsState } from '../../options/optionsSlice'; import { tabMap } from '../InvokeTabs'; import { InpaintingState } from './inpaintingSlice'; @@ -18,8 +19,12 @@ export const inpaintingCanvasLinesSelector = 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 { tool, brushSize, @@ -34,7 +39,7 @@ export const inpaintingControlsSelector = createSelector( shouldShowBoundingBox, } = inpainting; - const { activeTab, showDualDisplay } = options; + const { showDualDisplay } = options; return { tool, @@ -46,7 +51,7 @@ export const inpaintingControlsSelector = createSelector( canUndo: pastLines.length > 0, canRedo: futureLines.length > 0, isMaskEmpty: lines.length === 0, - activeTabName: tabMap[activeTab], + activeTabName, showDualDisplay, shouldShowBoundingBoxFill, shouldShowBoundingBox, @@ -75,6 +80,7 @@ export const inpaintingCanvasSelector = createSelector( isDrawing, shouldLockBoundingBox, shouldShowBoundingBox, + boundingBoxDimensions, } = inpainting; return { tool, @@ -89,6 +95,7 @@ export const inpaintingCanvasSelector = createSelector( isDrawing, shouldLockBoundingBox, shouldShowBoundingBox, + boundingBoxDimensions, }; }, { diff --git a/frontend/src/features/tabs/InvokeOptionsPanel.tsx b/frontend/src/features/tabs/InvokeOptionsPanel.tsx index 85e55e953f..032eeeeb5a 100644 --- a/frontend/src/features/tabs/InvokeOptionsPanel.tsx +++ b/frontend/src/features/tabs/InvokeOptionsPanel.tsx @@ -1,9 +1,17 @@ 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 { CSSTransition } from 'react-transition-group'; import { RootState, useAppDispatch, useAppSelector } from '../../app/store'; import IAIIconButton from '../../common/components/IAIIconButton'; +import useClickOutsideWatcher from '../../common/hooks/useClickOutsideWatcher'; import { OptionsState, setOptionsPanelScrollPosition, @@ -50,7 +58,8 @@ const InvokeOptionsPanel = (props: Props) => { const { children } = props; - const handleCloseOptionsPanel = () => { + const handleCloseOptionsPanel = useCallback(() => { + if (shouldPinOptionsPanel) return; dispatch( setOptionsPanelScrollPosition( optionsPanelContainerRef.current @@ -60,8 +69,10 @@ const InvokeOptionsPanel = (props: Props) => { ); dispatch(setShouldShowOptionsPanel(false)); dispatch(setShouldHoldOptionsPanelOpen(false)); - shouldPinOptionsPanel && dispatch(setNeedsCache(true)); - }; + // dispatch(setNeedsCache(true)); + }, [dispatch, shouldPinOptionsPanel]); + + useClickOutsideWatcher(optionsPanelRef, handleCloseOptionsPanel); const setCloseOptionsPanelTimer = () => { timeoutIdRef.current = window.setTimeout( diff --git a/frontend/src/styles/_Colors_Dark.scss b/frontend/src/styles/_Colors_Dark.scss index 78fc683e6d..ae343f309b 100644 --- a/frontend/src/styles/_Colors_Dark.scss +++ b/frontend/src/styles/_Colors_Dark.scss @@ -25,8 +25,6 @@ --destructive-color: rgb(185, 55, 55); --destructive-color-hover: rgb(255, 75, 75); - --text-color-a3: rgba(255, 255, 255, 0.3); - // Error status colors --border-color-invalid: rgb(255, 80, 50); --box-shadow-color-invalid: rgb(210, 30, 10); @@ -103,6 +101,6 @@ --context-menu-box-shadow: none; --context-menu-bg-color-hover: rgb(30, 32, 42); - // Inpainting - --inpaint-bg-color: rgb(30, 32, 42); + // Shadows + --floating-button-drop-shadow: drop-shadow(0 0 1rem rgba(140, 101, 255, 0.5)); } diff --git a/frontend/src/styles/_Colors_Light.scss b/frontend/src/styles/_Colors_Light.scss index ba202bc5ef..325c0f57f5 100644 --- a/frontend/src/styles/_Colors_Light.scss +++ b/frontend/src/styles/_Colors_Light.scss @@ -25,8 +25,6 @@ --destructive-color: rgb(237, 51, 51); --destructive-color-hover: rgb(255, 55, 55); - --text-color-a3: rgba(0, 0, 0, 0.3); - // Error status colors --border-color-invalid: rgb(255, 80, 50); --box-shadow-color-invalid: none; @@ -104,6 +102,6 @@ 0px 10px 20px -15px rgba(22, 23, 24, 0.2); --context-menu-bg-color-hover: var(--background-color-secondary); - // Inpainting - --inpaint-bg-color: rgb(180, 182, 184); + // Shadows + --floating-button-drop-shadow: drop-shadow(0 0 1rem rgba(0, 0, 0, 0.3)); }