mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
Refactor canvas buttons + more
This commit is contained in:
parent
3feb7d8922
commit
6173e3e9ca
1
frontend/dist/assets/index.28b80602.css
vendored
1
frontend/dist/assets/index.28b80602.css
vendored
File diff suppressed because one or more lines are too long
517
frontend/dist/assets/index.adcf8963.js
vendored
517
frontend/dist/assets/index.adcf8963.js
vendored
File diff suppressed because one or more lines are too long
517
frontend/dist/assets/index.bf9dd1fc.js
vendored
Normal file
517
frontend/dist/assets/index.bf9dd1fc.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
frontend/dist/assets/index.f9f4c989.css
vendored
Normal file
1
frontend/dist/assets/index.f9f4c989.css
vendored
Normal file
File diff suppressed because one or more lines are too long
4
frontend/dist/index.html
vendored
4
frontend/dist/index.html
vendored
@ -6,8 +6,8 @@
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>InvokeAI - A Stable Diffusion Toolkit</title>
|
<title>InvokeAI - A Stable Diffusion Toolkit</title>
|
||||||
<link rel="shortcut icon" type="icon" href="./assets/favicon.0d253ced.ico" />
|
<link rel="shortcut icon" type="icon" href="./assets/favicon.0d253ced.ico" />
|
||||||
<script type="module" crossorigin src="./assets/index.adcf8963.js"></script>
|
<script type="module" crossorigin src="./assets/index.bf9dd1fc.js"></script>
|
||||||
<link rel="stylesheet" href="./assets/index.28b80602.css">
|
<link rel="stylesheet" href="./assets/index.f9f4c989.css">
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
@ -21,6 +21,27 @@
|
|||||||
|
|
||||||
&[data-variant='link'] {
|
&[data-variant='link'] {
|
||||||
background: none !important;
|
background: none !important;
|
||||||
|
&:hover {
|
||||||
|
background: none !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&[data-selected='true'] {
|
||||||
|
border-color: var(--accent-color);
|
||||||
|
&:hover {
|
||||||
|
border-color: var(--accent-color-hover);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&[data-alert='true'] {
|
||||||
|
animation-name: pulseColor;
|
||||||
|
animation-duration: 1s;
|
||||||
|
animation-timing-function: ease-in-out;
|
||||||
|
animation-iteration-count: infinite;
|
||||||
|
&:hover {
|
||||||
|
animation: none;
|
||||||
|
background-color: var(--accent-color-hover);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&[data-as-checkbox='true'] {
|
&[data-as-checkbox='true'] {
|
||||||
@ -38,13 +59,17 @@
|
|||||||
fill: var(--text-color);
|
fill: var(--text-color);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
&[data-selected='true'] {
|
}
|
||||||
border-color: var(--accent-color);
|
|
||||||
background-color: var(--btn-grey);
|
@keyframes pulseColor {
|
||||||
svg {
|
0% {
|
||||||
fill: var(--text-color);
|
background-color: var(--accent-color);
|
||||||
}
|
}
|
||||||
}
|
50% {
|
||||||
|
background-color: var(--accent-color-dim);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
background-color: var(--accent-color);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
.invokeai__number-input-form-control {
|
.invokeai__number-input-form-control {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: max-content auto;
|
grid-template-columns: max-content auto;
|
||||||
column-gap: 1rem;
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
.invokeai__number-input-form-label {
|
.invokeai__number-input-form-label {
|
||||||
@ -11,6 +10,7 @@
|
|||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
flex-grow: 2;
|
flex-grow: 2;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
padding-right: 1rem;
|
||||||
|
|
||||||
&[data-focus] + .invokeai__number-input-root {
|
&[data-focus] + .invokeai__number-input-root {
|
||||||
outline: none;
|
outline: none;
|
||||||
|
@ -123,6 +123,7 @@ const IAINumberInput = (props: Props) => {
|
|||||||
}
|
}
|
||||||
{...formControlProps}
|
{...formControlProps}
|
||||||
>
|
>
|
||||||
|
{label && (
|
||||||
<FormLabel
|
<FormLabel
|
||||||
className="invokeai__number-input-form-label"
|
className="invokeai__number-input-form-label"
|
||||||
style={{ display: label ? 'block' : 'none' }}
|
style={{ display: label ? 'block' : 'none' }}
|
||||||
@ -130,6 +131,7 @@ const IAINumberInput = (props: Props) => {
|
|||||||
>
|
>
|
||||||
{label}
|
{label}
|
||||||
</FormLabel>
|
</FormLabel>
|
||||||
|
)}
|
||||||
<NumberInput
|
<NumberInput
|
||||||
className="invokeai__number-input-root"
|
className="invokeai__number-input-root"
|
||||||
value={valueAsString}
|
value={valueAsString}
|
||||||
@ -145,10 +147,8 @@ const IAINumberInput = (props: Props) => {
|
|||||||
textAlign={textAlign}
|
textAlign={textAlign}
|
||||||
{...numberInputFieldProps}
|
{...numberInputFieldProps}
|
||||||
/>
|
/>
|
||||||
<div
|
{showStepper && (
|
||||||
className="invokeai__number-input-stepper"
|
<div className="invokeai__number-input-stepper">
|
||||||
style={showStepper ? { display: 'block' } : { display: 'none' }}
|
|
||||||
>
|
|
||||||
<NumberIncrementStepper
|
<NumberIncrementStepper
|
||||||
{...numberInputStepperProps}
|
{...numberInputStepperProps}
|
||||||
className="invokeai__number-input-stepper-button"
|
className="invokeai__number-input-stepper-button"
|
||||||
@ -158,6 +158,7 @@ const IAINumberInput = (props: Props) => {
|
|||||||
className="invokeai__number-input-stepper-button"
|
className="invokeai__number-input-stepper-button"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
</NumberInput>
|
</NumberInput>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
19
frontend/src/common/components/ImageUploaderIconButton.tsx
Normal file
19
frontend/src/common/components/ImageUploaderIconButton.tsx
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import { useContext } from 'react';
|
||||||
|
import { FaUpload } from 'react-icons/fa';
|
||||||
|
import { ImageUploaderTriggerContext } from '../../app/contexts/ImageUploaderTriggerContext';
|
||||||
|
import IAIIconButton from './IAIIconButton';
|
||||||
|
|
||||||
|
const ImageUploaderIconButton = () => {
|
||||||
|
const openImageUploader = useContext(ImageUploaderTriggerContext);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<IAIIconButton
|
||||||
|
aria-label="Upload Image"
|
||||||
|
tooltip="Upload Image"
|
||||||
|
icon={<FaUpload />}
|
||||||
|
onClick={openImageUploader || undefined}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ImageUploaderIconButton;
|
@ -34,7 +34,10 @@ import {
|
|||||||
FaShareAlt,
|
FaShareAlt,
|
||||||
FaTrash,
|
FaTrash,
|
||||||
} from 'react-icons/fa';
|
} from 'react-icons/fa';
|
||||||
import { setImageToInpaint } from '../tabs/Inpainting/inpaintingSlice';
|
import {
|
||||||
|
setImageToInpaint,
|
||||||
|
setNeedsCache,
|
||||||
|
} from '../tabs/Inpainting/inpaintingSlice';
|
||||||
import { GalleryState } from './gallerySlice';
|
import { GalleryState } from './gallerySlice';
|
||||||
import { activeTabNameSelector } from '../options/optionsSelectors';
|
import { activeTabNameSelector } from '../options/optionsSelectors';
|
||||||
import IAIPopover from '../../common/components/IAIPopover';
|
import IAIPopover from '../../common/components/IAIPopover';
|
||||||
@ -95,7 +98,6 @@ const CurrentImageButtons = () => {
|
|||||||
facetoolStrength,
|
facetoolStrength,
|
||||||
shouldDisableToolbarButtons,
|
shouldDisableToolbarButtons,
|
||||||
shouldShowImageDetails,
|
shouldShowImageDetails,
|
||||||
activeTabName,
|
|
||||||
currentImage,
|
currentImage,
|
||||||
} = useAppSelector(systemSelector);
|
} = useAppSelector(systemSelector);
|
||||||
|
|
||||||
@ -108,7 +110,7 @@ const CurrentImageButtons = () => {
|
|||||||
const handleClickUseAsInitialImage = () => {
|
const handleClickUseAsInitialImage = () => {
|
||||||
if (!currentImage) return;
|
if (!currentImage) return;
|
||||||
dispatch(setInitialImage(currentImage));
|
dispatch(setInitialImage(currentImage));
|
||||||
dispatch(setActiveTab(1));
|
dispatch(setActiveTab('img2img'));
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleCopyImageLink = () => {
|
const handleCopyImageLink = () => {
|
||||||
@ -308,9 +310,10 @@ const CurrentImageButtons = () => {
|
|||||||
if (!currentImage) return;
|
if (!currentImage) return;
|
||||||
|
|
||||||
dispatch(setImageToInpaint(currentImage));
|
dispatch(setImageToInpaint(currentImage));
|
||||||
if (activeTabName !== 'inpainting') {
|
|
||||||
dispatch(setActiveTab('inpainting'));
|
dispatch(setActiveTab('inpainting'));
|
||||||
}
|
dispatch(setNeedsCache(true));
|
||||||
|
|
||||||
toast({
|
toast({
|
||||||
title: 'Sent to Inpainting',
|
title: 'Sent to Inpainting',
|
||||||
status: 'success',
|
status: 'success',
|
||||||
@ -461,7 +464,7 @@ const CurrentImageButtons = () => {
|
|||||||
icon={<FaTrash />}
|
icon={<FaTrash />}
|
||||||
tooltip="Delete Image"
|
tooltip="Delete Image"
|
||||||
aria-label="Delete Image"
|
aria-label="Delete Image"
|
||||||
isDisabled={Boolean(currentImage) || !isConnected || isProcessing}
|
isDisabled={!currentImage || !isConnected || isProcessing}
|
||||||
/>
|
/>
|
||||||
</DeleteImageModal>
|
</DeleteImageModal>
|
||||||
</ButtonGroup>
|
</ButtonGroup>
|
||||||
|
@ -38,6 +38,7 @@
|
|||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,8 +2,15 @@ import { Link, useColorMode } from '@chakra-ui/react';
|
|||||||
|
|
||||||
import { useHotkeys } from 'react-hotkeys-hook';
|
import { useHotkeys } from 'react-hotkeys-hook';
|
||||||
|
|
||||||
import { FaSun, FaMoon, FaGithub, FaDiscord, FaBug } from 'react-icons/fa';
|
import {
|
||||||
import { MdKeyboard, MdSettings } from 'react-icons/md';
|
FaSun,
|
||||||
|
FaMoon,
|
||||||
|
FaGithub,
|
||||||
|
FaDiscord,
|
||||||
|
FaBug,
|
||||||
|
FaKeyboard,
|
||||||
|
FaWrench,
|
||||||
|
} from 'react-icons/fa';
|
||||||
|
|
||||||
import InvokeAILogo from '../../assets/images/logo.png';
|
import InvokeAILogo from '../../assets/images/logo.png';
|
||||||
import IAIIconButton from '../../common/components/IAIIconButton';
|
import IAIIconButton from '../../common/components/IAIIconButton';
|
||||||
@ -27,11 +34,6 @@ const SiteHeader = () => {
|
|||||||
[colorMode, toggleColorMode]
|
[colorMode, toggleColorMode]
|
||||||
);
|
);
|
||||||
|
|
||||||
const colorModeIcon = colorMode == 'light' ? <FaMoon /> : <FaSun />;
|
|
||||||
|
|
||||||
// Make FaMoon and FaSun icon apparent size consistent
|
|
||||||
const colorModeIconFontSize = colorMode == 'light' ? 18 : 20;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="site-header">
|
<div className="site-header">
|
||||||
<div className="site-header-left-side">
|
<div className="site-header-left-side">
|
||||||
@ -48,10 +50,11 @@ const SiteHeader = () => {
|
|||||||
<IAIIconButton
|
<IAIIconButton
|
||||||
aria-label="Hotkeys"
|
aria-label="Hotkeys"
|
||||||
tooltip="Hotkeys"
|
tooltip="Hotkeys"
|
||||||
fontSize={24}
|
|
||||||
size={'sm'}
|
size={'sm'}
|
||||||
variant="link"
|
variant="link"
|
||||||
icon={<MdKeyboard />}
|
data-variant="link"
|
||||||
|
fontSize={20}
|
||||||
|
icon={<FaKeyboard />}
|
||||||
/>
|
/>
|
||||||
</HotkeysModal>
|
</HotkeysModal>
|
||||||
|
|
||||||
@ -60,16 +63,18 @@ const SiteHeader = () => {
|
|||||||
tooltip="Dark Mode"
|
tooltip="Dark Mode"
|
||||||
onClick={toggleColorMode}
|
onClick={toggleColorMode}
|
||||||
variant="link"
|
variant="link"
|
||||||
|
data-variant="link"
|
||||||
|
fontSize={20}
|
||||||
size={'sm'}
|
size={'sm'}
|
||||||
fontSize={colorModeIconFontSize}
|
icon={colorMode === 'light' ? <FaMoon /> : <FaSun />}
|
||||||
icon={colorModeIcon}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<IAIIconButton
|
<IAIIconButton
|
||||||
aria-label="Report Bug"
|
aria-label="Report Bug"
|
||||||
tooltip="Report Bug"
|
tooltip="Report Bug"
|
||||||
variant="link"
|
variant="link"
|
||||||
fontSize={19}
|
data-variant="link"
|
||||||
|
fontSize={20}
|
||||||
size={'sm'}
|
size={'sm'}
|
||||||
icon={
|
icon={
|
||||||
<Link isExternal href="http://github.com/invoke-ai/InvokeAI/issues">
|
<Link isExternal href="http://github.com/invoke-ai/InvokeAI/issues">
|
||||||
@ -82,6 +87,7 @@ const SiteHeader = () => {
|
|||||||
aria-label="Link to Github Repo"
|
aria-label="Link to Github Repo"
|
||||||
tooltip="Github"
|
tooltip="Github"
|
||||||
variant="link"
|
variant="link"
|
||||||
|
data-variant="link"
|
||||||
fontSize={20}
|
fontSize={20}
|
||||||
size={'sm'}
|
size={'sm'}
|
||||||
icon={
|
icon={
|
||||||
@ -95,6 +101,7 @@ const SiteHeader = () => {
|
|||||||
aria-label="Link to Discord Server"
|
aria-label="Link to Discord Server"
|
||||||
tooltip="Discord"
|
tooltip="Discord"
|
||||||
variant="link"
|
variant="link"
|
||||||
|
data-variant="link"
|
||||||
fontSize={20}
|
fontSize={20}
|
||||||
size={'sm'}
|
size={'sm'}
|
||||||
icon={
|
icon={
|
||||||
@ -109,9 +116,10 @@ const SiteHeader = () => {
|
|||||||
aria-label="Settings"
|
aria-label="Settings"
|
||||||
tooltip="Settings"
|
tooltip="Settings"
|
||||||
variant="link"
|
variant="link"
|
||||||
fontSize={24}
|
data-variant="link"
|
||||||
|
fontSize={20}
|
||||||
size={'sm'}
|
size={'sm'}
|
||||||
icon={<MdSettings />}
|
icon={<FaWrench />}
|
||||||
/>
|
/>
|
||||||
</SettingsModal>
|
</SettingsModal>
|
||||||
</div>
|
</div>
|
||||||
|
@ -20,7 +20,7 @@
|
|||||||
.init-image-preview-header {
|
.init-image-preview-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: center;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
h2 {
|
h2 {
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { IconButton, Image, useToast } from '@chakra-ui/react';
|
import { Image, useToast } from '@chakra-ui/react';
|
||||||
import React, { SyntheticEvent } from 'react';
|
import { SyntheticEvent } from 'react';
|
||||||
import { MdClear } from 'react-icons/md';
|
|
||||||
import { RootState, useAppDispatch, useAppSelector } from '../../../app/store';
|
import { RootState, useAppDispatch, useAppSelector } from '../../../app/store';
|
||||||
|
import ImageUploaderIconButton from '../../../common/components/ImageUploaderIconButton';
|
||||||
import { clearInitialImage } from '../../options/optionsSlice';
|
import { clearInitialImage } from '../../options/optionsSlice';
|
||||||
|
|
||||||
export default function InitImagePreview() {
|
export default function InitImagePreview() {
|
||||||
@ -13,10 +13,10 @@ export default function InitImagePreview() {
|
|||||||
|
|
||||||
const toast = useToast();
|
const toast = useToast();
|
||||||
|
|
||||||
const handleClickResetInitialImage = (e: SyntheticEvent) => {
|
// const handleClickResetInitialImage = (e: SyntheticEvent) => {
|
||||||
e.stopPropagation();
|
// e.stopPropagation();
|
||||||
dispatch(clearInitialImage());
|
// dispatch(clearInitialImage());
|
||||||
};
|
// };
|
||||||
|
|
||||||
const alertMissingInitImage = () => {
|
const alertMissingInitImage = () => {
|
||||||
toast({
|
toast({
|
||||||
@ -31,13 +31,7 @@ export default function InitImagePreview() {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="init-image-preview-header">
|
<div className="init-image-preview-header">
|
||||||
<h2>Initial Image</h2>
|
<ImageUploaderIconButton />
|
||||||
<IconButton
|
|
||||||
isDisabled={!initialImage}
|
|
||||||
aria-label={'Reset Initial Image'}
|
|
||||||
onClick={handleClickResetInitialImage}
|
|
||||||
icon={<MdClear />}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
{initialImage && (
|
{initialImage && (
|
||||||
<div className="init-image-preview">
|
<div className="init-image-preview">
|
||||||
|
@ -11,7 +11,7 @@
|
|||||||
.inpainting-settings {
|
.inpainting-settings {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
column-gap: 1rem;
|
column-gap: 0.5rem;
|
||||||
|
|
||||||
.inpainting-buttons-group {
|
.inpainting-buttons-group {
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -29,10 +29,10 @@
|
|||||||
margin-left: 1rem !important;
|
margin-left: 1rem !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.inpainting-slider-numberinput {
|
.inpainting-brush-options {
|
||||||
display: flex;
|
display: flex;
|
||||||
column-gap: 1rem;
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
column-gap: 1rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -33,9 +33,8 @@ import InpaintingBoundingBoxPreview, {
|
|||||||
InpaintingBoundingBoxPreviewOverlay,
|
InpaintingBoundingBoxPreviewOverlay,
|
||||||
} from './components/InpaintingBoundingBoxPreview';
|
} from './components/InpaintingBoundingBoxPreview';
|
||||||
import { KonvaEventObject } from 'konva/lib/Node';
|
import { KonvaEventObject } from 'konva/lib/Node';
|
||||||
import KeyboardEventManager from './components/KeyboardEventManager';
|
import KeyboardEventManager from './KeyboardEventManager';
|
||||||
import { useToast } from '@chakra-ui/react';
|
import { useToast } from '@chakra-ui/react';
|
||||||
import InpaintingCanvasStatusIcons from './InpaintingCanvasStatusIcons';
|
|
||||||
|
|
||||||
// Use a closure allow other components to use these things... not ideal...
|
// Use a closure allow other components to use these things... not ideal...
|
||||||
export let stageRef: MutableRefObject<StageType | null>;
|
export let stageRef: MutableRefObject<StageType | null>;
|
||||||
@ -57,7 +56,6 @@ const InpaintingCanvas = () => {
|
|||||||
shouldShowBoundingBox,
|
shouldShowBoundingBox,
|
||||||
shouldShowBoundingBoxFill,
|
shouldShowBoundingBoxFill,
|
||||||
isDrawing,
|
isDrawing,
|
||||||
isMouseOverBoundingBox,
|
|
||||||
isModifyingBoundingBox,
|
isModifyingBoundingBox,
|
||||||
stageCursor,
|
stageCursor,
|
||||||
} = useAppSelector(inpaintingCanvasSelector);
|
} = useAppSelector(inpaintingCanvasSelector);
|
||||||
@ -236,10 +234,8 @@ const InpaintingCanvas = () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="inpainting-canvas-container" tabIndex={1}>
|
<div className="inpainting-canvas-container">
|
||||||
<div className="inpainting-canvas-wrapper">
|
<div className="inpainting-canvas-wrapper">
|
||||||
<InpaintingCanvasStatusIcons />
|
|
||||||
|
|
||||||
{canvasBgImage && (
|
{canvasBgImage && (
|
||||||
<Stage
|
<Stage
|
||||||
width={Math.floor(canvasBgImage.width * stageScale)}
|
width={Math.floor(canvasBgImage.width * stageScale)}
|
||||||
@ -274,9 +270,7 @@ const InpaintingCanvas = () => {
|
|||||||
>
|
>
|
||||||
<InpaintingCanvasLines />
|
<InpaintingCanvasLines />
|
||||||
|
|
||||||
{!isMouseOverBoundingBox && !isModifyingBoundingBox && (
|
|
||||||
<InpaintingCanvasBrushPreview />
|
<InpaintingCanvasBrushPreview />
|
||||||
)}
|
|
||||||
|
|
||||||
{shouldInvertMask && (
|
{shouldInvertMask && (
|
||||||
<KonvaImage
|
<KonvaImage
|
||||||
@ -299,9 +293,7 @@ const InpaintingCanvas = () => {
|
|||||||
)}
|
)}
|
||||||
{shouldShowBoundingBox && <InpaintingBoundingBoxPreview />}
|
{shouldShowBoundingBox && <InpaintingBoundingBoxPreview />}
|
||||||
|
|
||||||
{!isMouseOverBoundingBox && !isModifyingBoundingBox && (
|
|
||||||
<InpaintingCanvasBrushPreviewOutline />
|
<InpaintingCanvasBrushPreviewOutline />
|
||||||
)}
|
|
||||||
</Layer>
|
</Layer>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
@ -4,7 +4,6 @@
|
|||||||
left: 0;
|
left: 0;
|
||||||
z-index: 2;
|
z-index: 2;
|
||||||
margin: 0.5rem;
|
margin: 0.5rem;
|
||||||
pointer-events: none;
|
|
||||||
|
|
||||||
button {
|
button {
|
||||||
background-color: var(--inpainting-alerts-bg);
|
background-color: var(--inpainting-alerts-bg);
|
||||||
|
@ -27,7 +27,7 @@ const inpaintingCanvasStatusIconsSelector = createSelector(
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
import { ButtonGroup, IconButton } from '@chakra-ui/react';
|
import { ButtonGroup, IconButton, Tooltip } from '@chakra-ui/react';
|
||||||
import { createSelector } from '@reduxjs/toolkit';
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
import { BiHide, BiShow } from 'react-icons/bi';
|
import { BiHide, BiShow } from 'react-icons/bi';
|
||||||
import { GiResize } from 'react-icons/gi';
|
import { GiResize } from 'react-icons/gi';
|
||||||
@ -36,6 +36,7 @@ import { FaLock, FaUnlock } from 'react-icons/fa';
|
|||||||
import { MdInvertColors, MdInvertColorsOff } from 'react-icons/md';
|
import { MdInvertColors, MdInvertColorsOff } from 'react-icons/md';
|
||||||
import { RootState, useAppSelector } from '../../../app/store';
|
import { RootState, useAppSelector } from '../../../app/store';
|
||||||
import { InpaintingState } from './inpaintingSlice';
|
import { InpaintingState } from './inpaintingSlice';
|
||||||
|
import { MouseEvent, useRef, useState } from 'react';
|
||||||
|
|
||||||
const InpaintingCanvasStatusIcons = () => {
|
const InpaintingCanvasStatusIcons = () => {
|
||||||
const {
|
const {
|
||||||
@ -46,9 +47,39 @@ const InpaintingCanvasStatusIcons = () => {
|
|||||||
isBoundingBoxTooSmall,
|
isBoundingBoxTooSmall,
|
||||||
} = useAppSelector(inpaintingCanvasStatusIconsSelector);
|
} = useAppSelector(inpaintingCanvasStatusIconsSelector);
|
||||||
|
|
||||||
|
const [shouldAcceptPointerEvents, setShouldAcceptPointerEvents] =
|
||||||
|
useState<boolean>(false);
|
||||||
|
const timeoutRef = useRef<number>(0);
|
||||||
|
|
||||||
|
const handleMouseOver = () => {
|
||||||
|
if (!shouldAcceptPointerEvents) {
|
||||||
|
timeoutRef.current = window.setTimeout(
|
||||||
|
() => setShouldAcceptPointerEvents(true),
|
||||||
|
1000
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleMouseOut = () => {
|
||||||
|
if (!shouldAcceptPointerEvents) {
|
||||||
|
setShouldAcceptPointerEvents(false);
|
||||||
|
window.clearTimeout(timeoutRef.current);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="inpainting-alerts">
|
<div
|
||||||
<ButtonGroup isAttached>
|
className="inpainting-alerts"
|
||||||
|
onMouseOver={handleMouseOver}
|
||||||
|
onMouseOut={handleMouseOut}
|
||||||
|
onMouseLeave={handleMouseOut}
|
||||||
|
onBlur={handleMouseOut}
|
||||||
|
>
|
||||||
|
<ButtonGroup
|
||||||
|
isAttached
|
||||||
|
pointerEvents={shouldAcceptPointerEvents ? 'auto' : 'none'}
|
||||||
|
>
|
||||||
|
<Tooltip label="Mask Hidden">
|
||||||
<IconButton
|
<IconButton
|
||||||
aria-label="Show/HideMask"
|
aria-label="Show/HideMask"
|
||||||
size="xs"
|
size="xs"
|
||||||
@ -57,6 +88,7 @@ const InpaintingCanvasStatusIcons = () => {
|
|||||||
data-selected={!shouldShowMask}
|
data-selected={!shouldShowMask}
|
||||||
icon={shouldShowMask ? <BiShow /> : <BiHide />}
|
icon={shouldShowMask ? <BiShow /> : <BiHide />}
|
||||||
/>
|
/>
|
||||||
|
</Tooltip>
|
||||||
<IconButton
|
<IconButton
|
||||||
aria-label="Invert Mask"
|
aria-label="Invert Mask"
|
||||||
variant={'ghost'}
|
variant={'ghost'}
|
||||||
|
@ -1,30 +1,38 @@
|
|||||||
import InpaintingBrushControl from './InpaintingControls/InpaintingBrushControl';
|
import InpaintingBrushControl from './InpaintingControls/InpaintingBrushControl';
|
||||||
import InpaintingEraserControl from './InpaintingControls/InpaintingEraserControl';
|
import InpaintingEraserControl from './InpaintingControls/InpaintingEraserControl';
|
||||||
import InpaintingMaskControl from './InpaintingControls/InpaintingMaskControl';
|
|
||||||
import InpaintingUndoControl from './InpaintingControls/InpaintingUndoControl';
|
import InpaintingUndoControl from './InpaintingControls/InpaintingUndoControl';
|
||||||
import InpaintingRedoControl from './InpaintingControls/InpaintingRedoControl';
|
import InpaintingRedoControl from './InpaintingControls/InpaintingRedoControl';
|
||||||
import InpaintingClearImageControl from './InpaintingControls/InpaintingClearImageControl';
|
import { ButtonGroup } from '@chakra-ui/react';
|
||||||
import InpaintingSplitLayoutControl from './InpaintingControls/InpaintingSplitLayoutControl';
|
import InpaintingMaskClear from './InpaintingControls/InpaintingMaskControls/InpaintingMaskClear';
|
||||||
|
import InpaintingMaskVisibilityControl from './InpaintingControls/InpaintingMaskControls/InpaintingMaskVisibilityControl';
|
||||||
|
import InpaintingMaskInvertControl from './InpaintingControls/InpaintingMaskControls/InpaintingMaskInvertControl';
|
||||||
|
import InpaintingLockBoundingBoxControl from './InpaintingControls/InpaintingLockBoundingBoxControl';
|
||||||
|
import InpaintingShowHideBoundingBoxControl from './InpaintingControls/InpaintingShowHideBoundingBoxControl';
|
||||||
|
import ImageUploaderIconButton from '../../../common/components/ImageUploaderIconButton';
|
||||||
|
|
||||||
const InpaintingControls = () => {
|
const InpaintingControls = () => {
|
||||||
return (
|
return (
|
||||||
<div className="inpainting-settings">
|
<div className="inpainting-settings">
|
||||||
<div className="inpainting-buttons-group">
|
<ButtonGroup isAttached={true}>
|
||||||
<InpaintingBrushControl />
|
<InpaintingBrushControl />
|
||||||
<InpaintingEraserControl />
|
<InpaintingEraserControl />
|
||||||
</div>
|
</ButtonGroup>
|
||||||
<div className="inpainting-buttons-group">
|
|
||||||
<InpaintingMaskControl />
|
<ButtonGroup isAttached={true}>
|
||||||
</div>
|
<InpaintingMaskVisibilityControl />
|
||||||
<div className="inpainting-buttons-group">
|
<InpaintingMaskInvertControl />
|
||||||
|
<InpaintingLockBoundingBoxControl />
|
||||||
|
<InpaintingShowHideBoundingBoxControl />
|
||||||
|
</ButtonGroup>
|
||||||
|
|
||||||
|
<ButtonGroup isAttached={true}>
|
||||||
<InpaintingUndoControl />
|
<InpaintingUndoControl />
|
||||||
<InpaintingRedoControl />
|
<InpaintingRedoControl />
|
||||||
</div>
|
<InpaintingMaskClear />
|
||||||
|
</ButtonGroup>
|
||||||
<div className="inpainting-buttons-group">
|
<ButtonGroup isAttached={true}>
|
||||||
<InpaintingClearImageControl />
|
<ImageUploaderIconButton />
|
||||||
</div>
|
</ButtonGroup>
|
||||||
<InpaintingSplitLayoutControl />
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -21,6 +21,7 @@ import {
|
|||||||
} from '../inpaintingSlice';
|
} from '../inpaintingSlice';
|
||||||
|
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
|
import InpaintingMaskColorPicker from './InpaintingMaskControls/InpaintingMaskColorPicker';
|
||||||
|
|
||||||
const inpaintingBrushSelector = createSelector(
|
const inpaintingBrushSelector = createSelector(
|
||||||
[(state: RootState) => state.inpainting, activeTabNameSelector],
|
[(state: RootState) => state.inpainting, activeTabNameSelector],
|
||||||
@ -123,7 +124,7 @@ export default function InpaintingBrushControl() {
|
|||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<div className="inpainting-slider-numberinput">
|
<div className="inpainting-brush-options">
|
||||||
<IAISlider
|
<IAISlider
|
||||||
label="Brush Size"
|
label="Brush Size"
|
||||||
value={brushSize}
|
value={brushSize}
|
||||||
@ -142,6 +143,7 @@ export default function InpaintingBrushControl() {
|
|||||||
max={999}
|
max={999}
|
||||||
isDisabled={!shouldShowMask}
|
isDisabled={!shouldShowMask}
|
||||||
/>
|
/>
|
||||||
|
<InpaintingMaskColorPicker />
|
||||||
</div>
|
</div>
|
||||||
</IAIPopover>
|
</IAIPopover>
|
||||||
);
|
);
|
||||||
|
@ -0,0 +1,29 @@
|
|||||||
|
import { FaLock, FaUnlock } from 'react-icons/fa';
|
||||||
|
import {
|
||||||
|
RootState,
|
||||||
|
useAppDispatch,
|
||||||
|
useAppSelector,
|
||||||
|
} from '../../../../app/store';
|
||||||
|
import IAIIconButton from '../../../../common/components/IAIIconButton';
|
||||||
|
import { setShouldLockBoundingBox } from '../inpaintingSlice';
|
||||||
|
|
||||||
|
const InpaintingLockBoundingBoxControl = () => {
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
const shouldLockBoundingBox = useAppSelector(
|
||||||
|
(state: RootState) => state.inpainting.shouldLockBoundingBox
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<IAIIconButton
|
||||||
|
aria-label="Lock Inpainting Box"
|
||||||
|
tooltip="Lock Inpainting Box"
|
||||||
|
icon={shouldLockBoundingBox ? <FaLock /> : <FaUnlock />}
|
||||||
|
data-selected={shouldLockBoundingBox}
|
||||||
|
onClick={() => {
|
||||||
|
dispatch(setShouldLockBoundingBox(!shouldLockBoundingBox));
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default InpaintingLockBoundingBoxControl;
|
@ -7,7 +7,6 @@ import IAIPopover from '../../../../common/components/IAIPopover';
|
|||||||
import InpaintingMaskVisibilityControl from './InpaintingMaskControls/InpaintingMaskVisibilityControl';
|
import InpaintingMaskVisibilityControl from './InpaintingMaskControls/InpaintingMaskVisibilityControl';
|
||||||
import InpaintingMaskInvertControl from './InpaintingMaskControls/InpaintingMaskInvertControl';
|
import InpaintingMaskInvertControl from './InpaintingMaskControls/InpaintingMaskInvertControl';
|
||||||
import InpaintingMaskColorPicker from './InpaintingMaskControls/InpaintingMaskColorPicker';
|
import InpaintingMaskColorPicker from './InpaintingMaskControls/InpaintingMaskColorPicker';
|
||||||
import InpaintingMaskClear from './InpaintingMaskControls/InpaintingMaskClear';
|
|
||||||
|
|
||||||
export default function InpaintingMaskControl() {
|
export default function InpaintingMaskControl() {
|
||||||
const [maskOptionsOpen, setMaskOptionsOpen] = useState<boolean>(false);
|
const [maskOptionsOpen, setMaskOptionsOpen] = useState<boolean>(false);
|
||||||
@ -34,7 +33,6 @@ export default function InpaintingMaskControl() {
|
|||||||
<InpaintingMaskColorPicker />
|
<InpaintingMaskColorPicker />
|
||||||
</div>
|
</div>
|
||||||
</IAIPopover>
|
</IAIPopover>
|
||||||
<InpaintingMaskClear />
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -62,7 +62,7 @@ export default function InpaintingMaskClear() {
|
|||||||
<IAIIconButton
|
<IAIIconButton
|
||||||
aria-label="Clear Mask (Shift+C)"
|
aria-label="Clear Mask (Shift+C)"
|
||||||
tooltip="Clear Mask (Shift+C)"
|
tooltip="Clear Mask (Shift+C)"
|
||||||
icon={<FaPlus size={18} style={{ transform: 'rotate(45deg)' }} />}
|
icon={<FaPlus size={20} style={{ transform: 'rotate(45deg)' }} />}
|
||||||
onClick={handleClearMask}
|
onClick={handleClearMask}
|
||||||
isDisabled={isMaskEmpty || !shouldShowMask}
|
isDisabled={isMaskEmpty || !shouldShowMask}
|
||||||
/>
|
/>
|
||||||
|
@ -75,12 +75,10 @@ export default function InpaintingMaskColorPicker() {
|
|||||||
return (
|
return (
|
||||||
<IAIPopover
|
<IAIPopover
|
||||||
trigger="hover"
|
trigger="hover"
|
||||||
placement="right"
|
|
||||||
styleClass="inpainting-color-picker"
|
styleClass="inpainting-color-picker"
|
||||||
triggerComponent={
|
triggerComponent={
|
||||||
<IAIIconButton
|
<IAIIconButton
|
||||||
aria-label="Mask Color"
|
aria-label="Mask Color"
|
||||||
tooltip="Mask Color"
|
|
||||||
icon={<FaPalette />}
|
icon={<FaPalette />}
|
||||||
isDisabled={!shouldShowMask}
|
isDisabled={!shouldShowMask}
|
||||||
cursor={'pointer'}
|
cursor={'pointer'}
|
||||||
|
@ -51,9 +51,9 @@ export default function InpaintingMaskVisibilityControl() {
|
|||||||
);
|
);
|
||||||
return (
|
return (
|
||||||
<IAIIconButton
|
<IAIIconButton
|
||||||
aria-label="Hide/Show Mask (H)"
|
aria-label="Hide Mask (H)"
|
||||||
tooltip="Hide/Show Mask (H)"
|
tooltip="Hide Mask (H)"
|
||||||
data-selected={!shouldShowMask}
|
data-alert={!shouldShowMask}
|
||||||
icon={shouldShowMask ? <BiShow size={22} /> : <BiHide size={22} />}
|
icon={shouldShowMask ? <BiShow size={22} /> : <BiHide size={22} />}
|
||||||
onClick={handleToggleShouldShowMask}
|
onClick={handleToggleShouldShowMask}
|
||||||
/>
|
/>
|
||||||
|
@ -0,0 +1,29 @@
|
|||||||
|
import { FaVectorSquare } from 'react-icons/fa';
|
||||||
|
import {
|
||||||
|
RootState,
|
||||||
|
useAppDispatch,
|
||||||
|
useAppSelector,
|
||||||
|
} from '../../../../app/store';
|
||||||
|
import IAIIconButton from '../../../../common/components/IAIIconButton';
|
||||||
|
import { setShouldShowBoundingBox } from '../inpaintingSlice';
|
||||||
|
|
||||||
|
const InpaintingShowHideBoundingBoxControl = () => {
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
const shouldShowBoundingBox = useAppSelector(
|
||||||
|
(state: RootState) => state.inpainting.shouldShowBoundingBox
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<IAIIconButton
|
||||||
|
aria-label="Hide Inpainting Box"
|
||||||
|
tooltip="Hide Inpainting Box"
|
||||||
|
icon={<FaVectorSquare />}
|
||||||
|
data-alert={!shouldShowBoundingBox}
|
||||||
|
onClick={() => {
|
||||||
|
dispatch(setShouldShowBoundingBox(!shouldShowBoundingBox));
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default InpaintingShowHideBoundingBoxControl;
|
@ -2,20 +2,16 @@ import { createSelector } from '@reduxjs/toolkit';
|
|||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import { useEffect, useRef } from 'react';
|
import { useEffect, useRef } from 'react';
|
||||||
import { useHotkeys } from 'react-hotkeys-hook';
|
import { useHotkeys } from 'react-hotkeys-hook';
|
||||||
import {
|
import { RootState, useAppDispatch, useAppSelector } from '../../../app/store';
|
||||||
RootState,
|
import { activeTabNameSelector } from '../../options/optionsSelectors';
|
||||||
useAppDispatch,
|
import { OptionsState } from '../../options/optionsSlice';
|
||||||
useAppSelector,
|
|
||||||
} from '../../../../app/store';
|
|
||||||
import { activeTabNameSelector } from '../../../options/optionsSelectors';
|
|
||||||
import { OptionsState } from '../../../options/optionsSlice';
|
|
||||||
import {
|
import {
|
||||||
InpaintingState,
|
InpaintingState,
|
||||||
setIsSpacebarHeld,
|
setIsSpacebarHeld,
|
||||||
setShouldLockBoundingBox,
|
setShouldLockBoundingBox,
|
||||||
toggleShouldLockBoundingBox,
|
toggleShouldLockBoundingBox,
|
||||||
toggleTool,
|
toggleTool,
|
||||||
} from '../inpaintingSlice';
|
} from './inpaintingSlice';
|
||||||
|
|
||||||
const keyboardEventManagerSelector = createSelector(
|
const keyboardEventManagerSelector = createSelector(
|
||||||
[
|
[
|
@ -35,6 +35,7 @@ const Cacher = () => {
|
|||||||
isDrawing,
|
isDrawing,
|
||||||
isTransformingBoundingBox,
|
isTransformingBoundingBox,
|
||||||
isMovingBoundingBox,
|
isMovingBoundingBox,
|
||||||
|
shouldShowBoundingBox,
|
||||||
} = useAppSelector((state: RootState) => state.inpainting);
|
} = useAppSelector((state: RootState) => state.inpainting);
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
@ -60,6 +61,7 @@ const Cacher = () => {
|
|||||||
imageToInpaint,
|
imageToInpaint,
|
||||||
shouldShowBrush,
|
shouldShowBrush,
|
||||||
shouldShowBoundingBoxFill,
|
shouldShowBoundingBoxFill,
|
||||||
|
shouldShowBoundingBox,
|
||||||
shouldLockBoundingBox,
|
shouldLockBoundingBox,
|
||||||
stageScale,
|
stageScale,
|
||||||
pastLines,
|
pastLines,
|
||||||
|
@ -280,6 +280,7 @@ const InpaintingBoundingBoxPreview = () => {
|
|||||||
const handleStartedTransforming = (e: KonvaEventObject<MouseEvent>) => {
|
const handleStartedTransforming = (e: KonvaEventObject<MouseEvent>) => {
|
||||||
e.cancelBubble = true;
|
e.cancelBubble = true;
|
||||||
e.evt.stopImmediatePropagation();
|
e.evt.stopImmediatePropagation();
|
||||||
|
console.log("Started transform")
|
||||||
dispatch(setIsTransformingBoundingBox(true));
|
dispatch(setIsTransformingBoundingBox(true));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -11,22 +11,28 @@ const inpaintingCanvasBrushPreviewSelector = createSelector(
|
|||||||
const {
|
const {
|
||||||
cursorPosition,
|
cursorPosition,
|
||||||
canvasDimensions: { width, height },
|
canvasDimensions: { width, height },
|
||||||
shouldShowBrushPreview,
|
|
||||||
brushSize,
|
brushSize,
|
||||||
maskColor,
|
maskColor,
|
||||||
tool,
|
tool,
|
||||||
shouldShowBrush,
|
shouldShowBrush,
|
||||||
|
isMovingBoundingBox,
|
||||||
|
isTransformingBoundingBox,
|
||||||
} = inpainting;
|
} = inpainting;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
cursorPosition,
|
cursorPosition,
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
shouldShowBrushPreview,
|
|
||||||
brushSize,
|
brushSize,
|
||||||
maskColorString: rgbaColorToRgbString(maskColor),
|
maskColorString: rgbaColorToRgbString(maskColor),
|
||||||
tool,
|
tool,
|
||||||
shouldShowBrush,
|
shouldShowBrush,
|
||||||
|
shouldDrawBrushPreview:
|
||||||
|
!(
|
||||||
|
isMovingBoundingBox ||
|
||||||
|
isTransformingBoundingBox ||
|
||||||
|
!cursorPosition
|
||||||
|
) && shouldShowBrush,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -40,12 +46,17 @@ const inpaintingCanvasBrushPreviewSelector = createSelector(
|
|||||||
* Draws a black circle around the canvas brush preview.
|
* Draws a black circle around the canvas brush preview.
|
||||||
*/
|
*/
|
||||||
const InpaintingCanvasBrushPreview = () => {
|
const InpaintingCanvasBrushPreview = () => {
|
||||||
const { cursorPosition, width, height, brushSize, maskColorString, tool } =
|
const {
|
||||||
useAppSelector(inpaintingCanvasBrushPreviewSelector);
|
cursorPosition,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
brushSize,
|
||||||
|
maskColorString,
|
||||||
|
tool,
|
||||||
|
shouldDrawBrushPreview,
|
||||||
|
} = useAppSelector(inpaintingCanvasBrushPreviewSelector);
|
||||||
|
|
||||||
if (!cursorPosition) {
|
if (!shouldDrawBrushPreview) return null;
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Circle
|
<Circle
|
||||||
|
@ -4,26 +4,34 @@ import { Circle } from 'react-konva';
|
|||||||
import { RootState, useAppSelector } from '../../../../app/store';
|
import { RootState, useAppSelector } from '../../../../app/store';
|
||||||
import { InpaintingState } from '../inpaintingSlice';
|
import { InpaintingState } from '../inpaintingSlice';
|
||||||
|
|
||||||
const inpaintingCanvasBrushPreviewSelector = createSelector(
|
const inpaintingCanvasBrushPrevieOutlineSelector = createSelector(
|
||||||
(state: RootState) => state.inpainting,
|
(state: RootState) => state.inpainting,
|
||||||
(inpainting: InpaintingState) => {
|
(inpainting: InpaintingState) => {
|
||||||
const {
|
const {
|
||||||
cursorPosition,
|
cursorPosition,
|
||||||
canvasDimensions: { width, height },
|
canvasDimensions: { width, height },
|
||||||
shouldShowBrushPreview,
|
|
||||||
brushSize,
|
brushSize,
|
||||||
stageScale,
|
tool,
|
||||||
shouldShowBrush,
|
shouldShowBrush,
|
||||||
|
isMovingBoundingBox,
|
||||||
|
isTransformingBoundingBox,
|
||||||
|
stageScale,
|
||||||
} = inpainting;
|
} = inpainting;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
cursorPosition,
|
cursorPosition,
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
shouldShowBrushPreview,
|
|
||||||
brushSize,
|
brushSize,
|
||||||
|
tool,
|
||||||
strokeWidth: 1 / stageScale, // scale stroke thickness
|
strokeWidth: 1 / stageScale, // scale stroke thickness
|
||||||
shouldShowBrush,
|
radius: 1 / stageScale, // scale stroke thickness
|
||||||
|
shouldDrawBrushPreview:
|
||||||
|
!(
|
||||||
|
isMovingBoundingBox ||
|
||||||
|
isTransformingBoundingBox ||
|
||||||
|
!cursorPosition
|
||||||
|
) && shouldShowBrush,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -41,15 +49,13 @@ const InpaintingCanvasBrushPreviewOutline = () => {
|
|||||||
cursorPosition,
|
cursorPosition,
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
shouldShowBrushPreview,
|
|
||||||
brushSize,
|
brushSize,
|
||||||
|
shouldDrawBrushPreview,
|
||||||
strokeWidth,
|
strokeWidth,
|
||||||
shouldShowBrush,
|
radius,
|
||||||
} = useAppSelector(inpaintingCanvasBrushPreviewSelector);
|
} = useAppSelector(inpaintingCanvasBrushPrevieOutlineSelector);
|
||||||
|
|
||||||
if (!shouldShowBrush || !(cursorPosition || shouldShowBrushPreview))
|
|
||||||
return null;
|
|
||||||
|
|
||||||
|
if (!shouldDrawBrushPreview) return null;
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Circle
|
<Circle
|
||||||
@ -64,7 +70,7 @@ const InpaintingCanvasBrushPreviewOutline = () => {
|
|||||||
<Circle
|
<Circle
|
||||||
x={cursorPosition ? cursorPosition.x : width / 2}
|
x={cursorPosition ? cursorPosition.x : width / 2}
|
||||||
y={cursorPosition ? cursorPosition.y : height / 2}
|
y={cursorPosition ? cursorPosition.y : height / 2}
|
||||||
radius={1}
|
radius={radius}
|
||||||
fill={'rgba(0,0,0,1)'}
|
fill={'rgba(0,0,0,1)'}
|
||||||
listening={false}
|
listening={false}
|
||||||
/>
|
/>
|
||||||
|
@ -112,12 +112,6 @@ const InvokeOptionsPanel = (props: Props) => {
|
|||||||
dispatch(setNeedsCache(true));
|
dispatch(setNeedsCache(true));
|
||||||
};
|
};
|
||||||
|
|
||||||
// // set gallery scroll position
|
|
||||||
// useEffect(() => {
|
|
||||||
// if (!optionsPanelContainerRef.current) return;
|
|
||||||
// optionsPanelContainerRef.current.scrollTop = optionsPanelScrollPosition;
|
|
||||||
// }, [optionsPanelScrollPosition, shouldShowOptionsPanel]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CSSTransition
|
<CSSTransition
|
||||||
nodeRef={optionsPanelRef}
|
nodeRef={optionsPanelRef}
|
||||||
|
@ -15,6 +15,7 @@ import TextToImageIcon from '../../common/icons/TextToImageIcon';
|
|||||||
import { setActiveTab } from '../options/optionsSlice';
|
import { setActiveTab } from '../options/optionsSlice';
|
||||||
import ImageToImageWorkarea from './ImageToImage';
|
import ImageToImageWorkarea from './ImageToImage';
|
||||||
import InpaintingWorkarea from './Inpainting';
|
import InpaintingWorkarea from './Inpainting';
|
||||||
|
import { setNeedsCache } from './Inpainting/inpaintingSlice';
|
||||||
import TextToImageWorkarea from './TextToImage';
|
import TextToImageWorkarea from './TextToImage';
|
||||||
|
|
||||||
export const tabDict = {
|
export const tabDict = {
|
||||||
@ -73,6 +74,7 @@ export default function InvokeTabs() {
|
|||||||
|
|
||||||
useHotkeys('3', () => {
|
useHotkeys('3', () => {
|
||||||
dispatch(setActiveTab(2));
|
dispatch(setActiveTab(2));
|
||||||
|
dispatch(setNeedsCache(true));
|
||||||
});
|
});
|
||||||
|
|
||||||
useHotkeys('4', () => {
|
useHotkeys('4', () => {
|
||||||
@ -125,6 +127,7 @@ export default function InvokeTabs() {
|
|||||||
index={activeTab}
|
index={activeTab}
|
||||||
onChange={(index: number) => {
|
onChange={(index: number) => {
|
||||||
dispatch(setActiveTab(index));
|
dispatch(setActiveTab(index));
|
||||||
|
dispatch(setNeedsCache(true));
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className="app-tabs-list">{renderTabs()}</div>
|
<div className="app-tabs-list">{renderTabs()}</div>
|
||||||
|
@ -10,18 +10,23 @@
|
|||||||
column-gap: 1rem;
|
column-gap: 1rem;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
|
||||||
|
.workarea-children-wrapper {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
.workarea-split-view {
|
.workarea-split-view {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 1fr 1fr;
|
grid-template-columns: 1fr 1fr;
|
||||||
// height: $app-content-height;
|
|
||||||
background-color: var(--background-color-secondary);
|
background-color: var(--background-color-secondary);
|
||||||
border-radius: 0.5rem;
|
border-radius: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.workarea-single-view {
|
.workarea-single-view {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
// height: $app-content-height;
|
height: 100%;
|
||||||
background-color: var(--background-color-secondary);
|
background-color: var(--background-color-secondary);
|
||||||
border-radius: 0.5rem;
|
border-radius: 0.5rem;
|
||||||
}
|
}
|
||||||
@ -45,3 +50,22 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.workarea-split-button {
|
||||||
|
position: absolute;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 0.5rem;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
z-index: 20;
|
||||||
|
|
||||||
|
&[data-selected='true'] {
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
svg {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
svg {
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,5 +1,19 @@
|
|||||||
|
import { Tooltip } from '@chakra-ui/react';
|
||||||
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
import { ReactNode } from 'react';
|
import { ReactNode } from 'react';
|
||||||
|
import { VscSplitHorizontal } from 'react-icons/vsc';
|
||||||
|
import { RootState, useAppDispatch, useAppSelector } from '../../app/store';
|
||||||
import ImageGallery from '../gallery/ImageGallery';
|
import ImageGallery from '../gallery/ImageGallery';
|
||||||
|
import { activeTabNameSelector } from '../options/optionsSelectors';
|
||||||
|
import { OptionsState, setShowDualDisplay } from '../options/optionsSlice';
|
||||||
|
|
||||||
|
const workareaSelector = createSelector(
|
||||||
|
[(state: RootState) => state.options, activeTabNameSelector],
|
||||||
|
(options: OptionsState, activeTabName) => {
|
||||||
|
const { showDualDisplay, shouldPinOptionsPanel } = options;
|
||||||
|
return { showDualDisplay, shouldPinOptionsPanel, activeTabName };
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
type InvokeWorkareaProps = {
|
type InvokeWorkareaProps = {
|
||||||
optionsPanel: ReactNode;
|
optionsPanel: ReactNode;
|
||||||
@ -8,7 +22,9 @@ type InvokeWorkareaProps = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const InvokeWorkarea = (props: InvokeWorkareaProps) => {
|
const InvokeWorkarea = (props: InvokeWorkareaProps) => {
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
const { optionsPanel, children, styleClass } = props;
|
const { optionsPanel, children, styleClass } = props;
|
||||||
|
const { showDualDisplay, activeTabName } = useAppSelector(workareaSelector);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@ -18,7 +34,20 @@ const InvokeWorkarea = (props: InvokeWorkareaProps) => {
|
|||||||
>
|
>
|
||||||
<div className="workarea-main">
|
<div className="workarea-main">
|
||||||
{optionsPanel}
|
{optionsPanel}
|
||||||
|
<div className="workarea-children-wrapper">
|
||||||
{children}
|
{children}
|
||||||
|
{activeTabName === 'inpainting' && (
|
||||||
|
<Tooltip label="Toggle Split View">
|
||||||
|
<div
|
||||||
|
className="workarea-split-button"
|
||||||
|
data-selected={showDualDisplay}
|
||||||
|
onClick={() => dispatch(setShowDualDisplay(!showDualDisplay))}
|
||||||
|
>
|
||||||
|
<VscSplitHorizontal />
|
||||||
|
</div>
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
<ImageGallery />
|
<ImageGallery />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -19,12 +19,16 @@
|
|||||||
--invalid: rgb(255, 75, 75);
|
--invalid: rgb(255, 75, 75);
|
||||||
--invalid-secondary: rgb(120, 5, 5);
|
--invalid-secondary: rgb(120, 5, 5);
|
||||||
|
|
||||||
|
--accent-color-dim: rgb(57, 25, 153);
|
||||||
--accent-color: rgb(80, 40, 200);
|
--accent-color: rgb(80, 40, 200);
|
||||||
--accent-color-hover: rgb(104, 60, 230);
|
--accent-color-hover: rgb(104, 60, 230);
|
||||||
|
|
||||||
--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);
|
||||||
|
|
||||||
|
--warning-color: rgb(200, 88, 40);
|
||||||
|
--warning-color-hover: rgb(230, 117, 60);
|
||||||
|
|
||||||
// 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);
|
||||||
|
@ -19,12 +19,16 @@
|
|||||||
--invalid: rgb(255, 75, 75);
|
--invalid: rgb(255, 75, 75);
|
||||||
--invalid-secondary: rgb(120, 5, 5);
|
--invalid-secondary: rgb(120, 5, 5);
|
||||||
|
|
||||||
|
--accent-color-dim: rgb(186, 146, 0);
|
||||||
--accent-color: rgb(235, 185, 5);
|
--accent-color: rgb(235, 185, 5);
|
||||||
--accent-color-hover: rgb(255, 200, 0);
|
--accent-color-hover: rgb(255, 200, 0);
|
||||||
|
|
||||||
--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);
|
||||||
|
|
||||||
|
--warning-color: rgb(224, 142, 42);
|
||||||
|
--warning-color-hover: rgb(255, 167, 60);
|
||||||
|
|
||||||
// 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;
|
||||||
|
Loading…
Reference in New Issue
Block a user