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 { 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),
|
||||
};
|
||||
}
|
||||
);
|
||||
|
@ -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 (
|
||||
<Tooltip label={tooltip}>
|
||||
<Button size={size} {...rest}>
|
||||
{label}
|
||||
</Button>
|
||||
<Button {...rest}>{label}</Button>
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
|
@ -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<boolean>(false);
|
||||
|
||||
|
@ -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,
|
||||
|
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 { 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;
|
||||
|
||||
|
@ -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 (
|
||||
<ContextMenu.Root
|
||||
// onOpenChange={(open: boolean) => {
|
||||
// dispatch(setShouldHoldGalleryOpen(open));
|
||||
// dispatch(setShouldShowGallery(true));
|
||||
// }}
|
||||
// onOpenChange={(open: boolean) => {
|
||||
// dispatch(setShouldHoldGalleryOpen(open));
|
||||
// dispatch(setShouldShowGallery(true));
|
||||
// }}
|
||||
>
|
||||
<ContextMenu.Trigger>
|
||||
<Box
|
||||
|
@ -33,6 +33,7 @@ import { BiReset } from 'react-icons/bi';
|
||||
import IAICheckbox from '../../common/components/IAICheckbox';
|
||||
import { setNeedsCache } from '../tabs/Inpainting/inpaintingSlice';
|
||||
import _ from 'lodash';
|
||||
import useClickOutsideWatcher from '../../common/hooks/useClickOutsideWatcher';
|
||||
|
||||
const GALLERY_SHOW_BUTTONS_MIN_WIDTH = 320;
|
||||
|
||||
@ -110,6 +111,7 @@ export default function ImageGallery() {
|
||||
};
|
||||
|
||||
const handleCloseGallery = () => {
|
||||
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 (
|
||||
<CSSTransition
|
||||
nodeRef={galleryRef}
|
||||
|
@ -143,9 +143,7 @@ export const gallerySlice = createSlice({
|
||||
if (state.shouldAutoSwitchToNewImages) {
|
||||
state.currentImageUuid = uuid;
|
||||
state.currentImage = newImage;
|
||||
if (category === 'result') {
|
||||
state.currentCategory = 'result';
|
||||
}
|
||||
state.currentCategory = category;
|
||||
}
|
||||
state.intermediateImage = undefined;
|
||||
tempCategory.latest_mtime = mtime;
|
||||
|
@ -1,12 +1,16 @@
|
||||
import { createSelector } from '@reduxjs/toolkit';
|
||||
import { RootState } from '../../app/store';
|
||||
import { activeTabNameSelector } from '../options/optionsSelectors';
|
||||
import { OptionsState } from '../options/optionsSlice';
|
||||
import { tabMap } from '../tabs/InvokeTabs';
|
||||
import { GalleryState } from './gallerySlice';
|
||||
|
||||
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 {
|
||||
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,
|
||||
};
|
||||
}
|
||||
);
|
||||
|
@ -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<HTMLSelectElement>) =>
|
||||
@ -17,7 +16,7 @@ export default function MainHeight() {
|
||||
|
||||
return (
|
||||
<IAISelect
|
||||
isDisabled={tabMap[activeTab] === 'inpainting'}
|
||||
isDisabled={activeTabName === 'inpainting'}
|
||||
label="Height"
|
||||
value={height}
|
||||
flexGrow={1}
|
||||
|
@ -6,6 +6,7 @@ import { useHotkeys } from 'react-hotkeys-hook';
|
||||
import { createSelector } from '@reduxjs/toolkit';
|
||||
import { SystemState } from '../../system/systemSlice';
|
||||
import _ from 'lodash';
|
||||
import { IAIButtonProps } from '../../../common/components/IAIButton';
|
||||
|
||||
const cancelButtonSelector = createSelector(
|
||||
(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 { isProcessing, isConnected, isCancelable } =
|
||||
useAppSelector(cancelButtonSelector);
|
||||
@ -47,6 +49,7 @@ export default function CancelButton() {
|
||||
isDisabled={!isConnected || !isProcessing || !isCancelable}
|
||||
onClick={handleClickCancel}
|
||||
styleClass="cancel-btn"
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
@ -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<IAIButtonProps, 'label'>) {
|
||||
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 (
|
||||
<IAIButton
|
||||
label="Invoke"
|
||||
@ -24,6 +36,7 @@ export default function InvokeButton() {
|
||||
isDisabled={!isReady}
|
||||
onClick={handleClickGenerate}
|
||||
className="invoke-btn"
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
@ -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
|
||||
);
|
||||
}
|
||||
|
@ -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',
|
||||
() => {
|
||||
|
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',
|
||||
desc: 'Quick toggle between brush and eraser',
|
||||
hotkey: 'Z',
|
||||
hotkey: 'X',
|
||||
},
|
||||
{
|
||||
title: 'Decrease Brush Size',
|
||||
|
@ -24,3 +24,11 @@
|
||||
align-items: center;
|
||||
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 { 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 = () => {
|
||||
<h1>
|
||||
invoke <strong>ai</strong>
|
||||
</h1>
|
||||
{shouldShowProcessButtons && (
|
||||
<div className="process-buttons process-buttons">
|
||||
<InvokeButton size={'sm'} />
|
||||
<CancelButton size={'sm'} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="site-header-right-side">
|
||||
|
@ -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;
|
||||
|
@ -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 && (
|
||||
<div style={{ pointerEvents: 'none' }}>
|
||||
Transforming Bounding Box (M)
|
||||
{`Transforming Bounding Box ${boundingBoxDimensions.width}x${boundingBoxDimensions.height} (M)`}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
@ -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));
|
||||
}
|
||||
|
@ -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,
|
||||
};
|
||||
},
|
||||
{
|
||||
|
@ -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(
|
||||
|
@ -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));
|
||||
}
|
||||
|
@ -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));
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user