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