mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
Adds user/result galleries, refactors workarea CSS
This commit is contained in:
parent
3a7b495167
commit
0855ab4173
@ -443,10 +443,10 @@ class InvokeAIWebServer:
|
|||||||
print("\n")
|
print("\n")
|
||||||
|
|
||||||
# TODO: I think this needs a safety mechanism.
|
# TODO: I think this needs a safety mechanism.
|
||||||
@socketio.on("uploadInitialImage")
|
@socketio.on("uploadImage")
|
||||||
def handle_upload_initial_image(bytes, name):
|
def handle_upload_image(bytes, name, destination):
|
||||||
try:
|
try:
|
||||||
print(f'>> Init image upload requested "{name}"')
|
print(f'>> Image upload requested "{name}"')
|
||||||
file_path = self.save_file_unique_uuid_name(
|
file_path = self.save_file_unique_uuid_name(
|
||||||
bytes=bytes, name=name, path=self.init_image_path
|
bytes=bytes, name=name, path=self.init_image_path
|
||||||
)
|
)
|
||||||
@ -454,13 +454,14 @@ class InvokeAIWebServer:
|
|||||||
(width, height) = Image.open(file_path).size
|
(width, height) = Image.open(file_path).size
|
||||||
print(file_path)
|
print(file_path)
|
||||||
socketio.emit(
|
socketio.emit(
|
||||||
"initialImageUploaded",
|
"imageUploaded",
|
||||||
{
|
{
|
||||||
"url": self.get_url_from_image_path(file_path),
|
"url": self.get_url_from_image_path(file_path),
|
||||||
"mtime": mtime,
|
"mtime": mtime,
|
||||||
"width": width,
|
"width": width,
|
||||||
"height": height,
|
"height": height,
|
||||||
"category": "user",
|
"category": "user",
|
||||||
|
"destination": destination,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
@ -7,12 +7,11 @@
|
|||||||
# was trained on.
|
# was trained on.
|
||||||
stable-diffusion-1.4:
|
stable-diffusion-1.4:
|
||||||
config: configs/stable-diffusion/v1-inference.yaml
|
config: configs/stable-diffusion/v1-inference.yaml
|
||||||
weights: models/ldm/stable-diffusion-v1/model.ckpt
|
weights: models/ldm/stable-diffusion-v1/sd-v1-4.ckpt
|
||||||
# vae: models/ldm/stable-diffusion-v1/vae-ft-mse-840000-ema-pruned.ckpt
|
# vae: models/ldm/stable-diffusion-v1/vae-ft-mse-840000-ema-pruned.ckpt
|
||||||
description: Stable Diffusion inference model version 1.4
|
description: Stable Diffusion inference model version 1.4
|
||||||
width: 512
|
width: 512
|
||||||
height: 512
|
height: 512
|
||||||
default: true
|
|
||||||
inpainting-1.5:
|
inpainting-1.5:
|
||||||
description: runwayML tuned inpainting model v1.5
|
description: runwayML tuned inpainting model v1.5
|
||||||
weights: models/ldm/stable-diffusion-v1/sd-v1-5-inpainting.ckpt
|
weights: models/ldm/stable-diffusion-v1/sd-v1-5-inpainting.ckpt
|
||||||
@ -27,3 +26,4 @@ stable-diffusion-1.5:
|
|||||||
description: Stable Diffusion inference model version 1.5
|
description: Stable Diffusion inference model version 1.5
|
||||||
width: 512
|
width: 512
|
||||||
height: 512
|
height: 512
|
||||||
|
default: true
|
||||||
|
9
frontend/src/app/invokeai.d.ts
vendored
9
frontend/src/app/invokeai.d.ts
vendored
@ -172,7 +172,9 @@ export declare type SystemConfigResponse = SystemConfig;
|
|||||||
|
|
||||||
export declare type ImageResultResponse = Omit<Image, 'uuid'>;
|
export declare type ImageResultResponse = Omit<Image, 'uuid'>;
|
||||||
|
|
||||||
export declare type ImageUploadResponse = Omit<Image, 'uuid' | 'metadata'>;
|
export declare type ImageUploadResponse = Omit<Image, 'uuid' | 'metadata'> & {
|
||||||
|
destination: 'img2img' | 'inpainting';
|
||||||
|
};
|
||||||
|
|
||||||
export declare type ErrorResponse = {
|
export declare type ErrorResponse = {
|
||||||
message: string;
|
message: string;
|
||||||
@ -194,3 +196,8 @@ export declare type ImageDeletedResponse = {
|
|||||||
export declare type ImageUrlResponse = {
|
export declare type ImageUrlResponse = {
|
||||||
url: string;
|
url: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export declare type UploadImagePayload = {
|
||||||
|
file: File;
|
||||||
|
destination: 'img2img' | 'inpainting';
|
||||||
|
};
|
||||||
|
@ -3,6 +3,7 @@ import { GalleryCategory } from '../../features/gallery/gallerySlice';
|
|||||||
import { InvokeTabName } from '../../features/tabs/InvokeTabs';
|
import { InvokeTabName } from '../../features/tabs/InvokeTabs';
|
||||||
import * as InvokeAI from '../invokeai';
|
import * as InvokeAI from '../invokeai';
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* We can't use redux-toolkit's createSlice() to make these actions,
|
* We can't use redux-toolkit's createSlice() to make these actions,
|
||||||
* because they have no associated reducer. They only exist to dispatch
|
* because they have no associated reducer. They only exist to dispatch
|
||||||
@ -16,20 +17,22 @@ export const generateImage = createAction<InvokeTabName>(
|
|||||||
export const runESRGAN = createAction<InvokeAI.Image>('socketio/runESRGAN');
|
export const runESRGAN = createAction<InvokeAI.Image>('socketio/runESRGAN');
|
||||||
export const runFacetool = createAction<InvokeAI.Image>('socketio/runFacetool');
|
export const runFacetool = createAction<InvokeAI.Image>('socketio/runFacetool');
|
||||||
export const deleteImage = createAction<InvokeAI.Image>('socketio/deleteImage');
|
export const deleteImage = createAction<InvokeAI.Image>('socketio/deleteImage');
|
||||||
export const requestImages = createAction<GalleryCategory>('socketio/requestImages');
|
export const requestImages = createAction<GalleryCategory>(
|
||||||
|
'socketio/requestImages'
|
||||||
|
);
|
||||||
export const requestNewImages = createAction<GalleryCategory>(
|
export const requestNewImages = createAction<GalleryCategory>(
|
||||||
'socketio/requestNewImages'
|
'socketio/requestNewImages'
|
||||||
);
|
);
|
||||||
export const cancelProcessing = createAction<undefined>(
|
export const cancelProcessing = createAction<undefined>(
|
||||||
'socketio/cancelProcessing'
|
'socketio/cancelProcessing'
|
||||||
);
|
);
|
||||||
export const uploadInitialImage = createAction<File>(
|
export const uploadImage = createAction<InvokeAI.UploadImagePayload>('socketio/uploadImage');
|
||||||
'socketio/uploadInitialImage'
|
|
||||||
);
|
|
||||||
export const uploadMaskImage = createAction<File>('socketio/uploadMaskImage');
|
export const uploadMaskImage = createAction<File>('socketio/uploadMaskImage');
|
||||||
|
|
||||||
export const requestSystemConfig = createAction<undefined>(
|
export const requestSystemConfig = createAction<undefined>(
|
||||||
'socketio/requestSystemConfig'
|
'socketio/requestSystemConfig'
|
||||||
);
|
);
|
||||||
|
|
||||||
export const requestModelChange = createAction<string>('socketio/requestModelChange');
|
export const requestModelChange = createAction<string>(
|
||||||
|
'socketio/requestModelChange'
|
||||||
|
);
|
||||||
|
@ -178,8 +178,9 @@ const makeSocketIOEmitters = (
|
|||||||
emitCancelProcessing: () => {
|
emitCancelProcessing: () => {
|
||||||
socketio.emit('cancel');
|
socketio.emit('cancel');
|
||||||
},
|
},
|
||||||
emitUploadInitialImage: (file: File) => {
|
emitUploadImage: (payload: InvokeAI.UploadImagePayload) => {
|
||||||
socketio.emit('uploadInitialImage', file, file.name);
|
const { file, destination } = payload;
|
||||||
|
socketio.emit('uploadImage', file, file.name, destination);
|
||||||
},
|
},
|
||||||
emitUploadMaskImage: (file: File) => {
|
emitUploadMaskImage: (file: File) => {
|
||||||
socketio.emit('uploadMaskImage', file, file.name);
|
socketio.emit('uploadMaskImage', file, file.name);
|
||||||
|
@ -32,6 +32,7 @@ import {
|
|||||||
setMaskPath,
|
setMaskPath,
|
||||||
} from '../../features/options/optionsSlice';
|
} from '../../features/options/optionsSlice';
|
||||||
import { requestImages, requestNewImages } from './actions';
|
import { requestImages, requestNewImages } from './actions';
|
||||||
|
import { setImageToInpaint } from '../../features/tabs/Inpainting/inpaintingSlice';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns an object containing listener callbacks for socketio events.
|
* Returns an object containing listener callbacks for socketio events.
|
||||||
@ -285,15 +286,26 @@ const makeSocketIOListeners = (
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
onInitialImageUploaded: (data: InvokeAI.ImageUploadResponse) => {
|
onImageUploaded: (data: InvokeAI.ImageUploadResponse) => {
|
||||||
|
const { destination, ...rest } = data;
|
||||||
const image = {
|
const image = {
|
||||||
uuid: uuidv4(),
|
uuid: uuidv4(),
|
||||||
...data,
|
...rest,
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
dispatch(addImage({ image, category: 'user' }));
|
dispatch(addImage({ image, category: 'user' }));
|
||||||
|
|
||||||
|
switch (destination) {
|
||||||
|
case 'img2img': {
|
||||||
dispatch(setInitialImage(image));
|
dispatch(setInitialImage(image));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'inpainting': {
|
||||||
|
dispatch(setImageToInpaint(image));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
dispatch(
|
dispatch(
|
||||||
addLogEntry({
|
addLogEntry({
|
||||||
|
@ -43,7 +43,7 @@ export const socketioMiddleware = () => {
|
|||||||
onGalleryImages,
|
onGalleryImages,
|
||||||
onProcessingCanceled,
|
onProcessingCanceled,
|
||||||
onImageDeleted,
|
onImageDeleted,
|
||||||
onInitialImageUploaded,
|
onImageUploaded,
|
||||||
onMaskImageUploaded,
|
onMaskImageUploaded,
|
||||||
onSystemConfig,
|
onSystemConfig,
|
||||||
onModelChanged,
|
onModelChanged,
|
||||||
@ -58,7 +58,7 @@ export const socketioMiddleware = () => {
|
|||||||
emitRequestImages,
|
emitRequestImages,
|
||||||
emitRequestNewImages,
|
emitRequestNewImages,
|
||||||
emitCancelProcessing,
|
emitCancelProcessing,
|
||||||
emitUploadInitialImage,
|
emitUploadImage,
|
||||||
emitUploadMaskImage,
|
emitUploadMaskImage,
|
||||||
emitRequestSystemConfig,
|
emitRequestSystemConfig,
|
||||||
emitRequestModelChange,
|
emitRequestModelChange,
|
||||||
@ -104,14 +104,10 @@ export const socketioMiddleware = () => {
|
|||||||
onImageDeleted(data);
|
onImageDeleted(data);
|
||||||
});
|
});
|
||||||
|
|
||||||
// socketio.on('initialImageUploaded', (data: InvokeAI.ImageUrlResponse) => {
|
|
||||||
// onInitialImageUploaded(data);
|
|
||||||
// });
|
|
||||||
|
|
||||||
socketio.on(
|
socketio.on(
|
||||||
'initialImageUploaded',
|
'imageUploaded',
|
||||||
(data: InvokeAI.ImageUploadResponse) => {
|
(data: InvokeAI.ImageUploadResponse) => {
|
||||||
onInitialImageUploaded(data);
|
onImageUploaded(data);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -173,8 +169,8 @@ export const socketioMiddleware = () => {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'socketio/uploadInitialImage': {
|
case 'socketio/uploadImage': {
|
||||||
emitUploadInitialImage(action.payload);
|
emitUploadImage(action.payload);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
30
frontend/src/common/components/InvokeImageUploader.scss
Normal file
30
frontend/src/common/components/InvokeImageUploader.scss
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
.image-upload-zone {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
cursor: pointer;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
color: var(--subtext-color-bright);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: var(--inpaint-bg-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-upload-child-wrapper {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-upload-child {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
row-gap: 3rem;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
@ -1,29 +1,23 @@
|
|||||||
import { Button, useToast } from '@chakra-ui/react';
|
import { Button, Heading, useToast } from '@chakra-ui/react';
|
||||||
import React, { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
import { FileRejection } from 'react-dropzone';
|
import { FileRejection } from 'react-dropzone';
|
||||||
import { useAppDispatch } from '../../app/store';
|
import { FaUpload } from 'react-icons/fa';
|
||||||
import ImageUploader from '../../features/options/ImageUploader';
|
import ImageUploader from '../../features/options/ImageUploader';
|
||||||
|
|
||||||
interface InvokeImageUploaderProps {
|
interface InvokeImageUploaderProps {
|
||||||
label?: string;
|
handleFile: (file: File) => void;
|
||||||
icon?: any;
|
|
||||||
onMouseOver?: any;
|
|
||||||
OnMouseout?: any;
|
|
||||||
dispatcher: any;
|
|
||||||
styleClass?: string;
|
styleClass?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function InvokeImageUploader(props: InvokeImageUploaderProps) {
|
export default function InvokeImageUploader(props: InvokeImageUploaderProps) {
|
||||||
const { label, icon, dispatcher, styleClass, onMouseOver, OnMouseout } =
|
const { handleFile, styleClass } = props;
|
||||||
props;
|
|
||||||
|
|
||||||
const toast = useToast();
|
const toast = useToast();
|
||||||
const dispatch = useAppDispatch();
|
|
||||||
|
|
||||||
// Callbacks to for handling file upload attempts
|
// Callbacks to for handling file upload attempts
|
||||||
const fileAcceptedCallback = useCallback(
|
const fileAcceptedCallback = useCallback(
|
||||||
(file: File) => dispatch(dispatcher(file)),
|
(file: File) => handleFile(file),
|
||||||
[dispatch, dispatcher]
|
[handleFile]
|
||||||
);
|
);
|
||||||
|
|
||||||
const fileRejectionCallback = useCallback(
|
const fileRejectionCallback = useCallback(
|
||||||
@ -44,22 +38,17 @@ export default function InvokeImageUploader(props: InvokeImageUploaderProps) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<div className="image-upload-zone">
|
||||||
<ImageUploader
|
<ImageUploader
|
||||||
fileAcceptedCallback={fileAcceptedCallback}
|
fileAcceptedCallback={fileAcceptedCallback}
|
||||||
fileRejectionCallback={fileRejectionCallback}
|
fileRejectionCallback={fileRejectionCallback}
|
||||||
styleClass={styleClass}
|
styleClass={`${styleClass} image-upload-child-wrapper`}
|
||||||
>
|
>
|
||||||
<Button
|
<div className="image-upload-child">
|
||||||
size={'sm'}
|
<FaUpload size={'7rem'} />
|
||||||
fontSize={'md'}
|
<Heading size={'lg'}>Upload or Drop Image Here</Heading>
|
||||||
fontWeight={'normal'}
|
</div>
|
||||||
onMouseOver={onMouseOver}
|
|
||||||
onMouseOut={OnMouseout}
|
|
||||||
leftIcon={icon}
|
|
||||||
width={'100%'}
|
|
||||||
>
|
|
||||||
{label ? label : null}
|
|
||||||
</Button>
|
|
||||||
</ImageUploader>
|
</ImageUploader>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,28 +1,23 @@
|
|||||||
@use '../../styles/Mixins/' as *;
|
@use '../../styles/Mixins/' as *;
|
||||||
|
|
||||||
.current-image-display {
|
.current-image-area {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
row-gap: 1rem;
|
||||||
background-color: var(--background-color-secondary);
|
background-color: var(--background-color-secondary);
|
||||||
border-radius: 0.5rem;
|
border-radius: 0.5rem;
|
||||||
|
&[data-tab-name='txt2img'] {
|
||||||
|
height: $app-text-to-image-height;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.current-image-options {
|
.current-image-options {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
column-gap: 0.5rem;
|
column-gap: 0.5rem;
|
||||||
padding: 1rem;
|
|
||||||
|
|
||||||
button {
|
|
||||||
@include Button(
|
|
||||||
$btn-width: 3rem,
|
|
||||||
$icon-size: 22px,
|
|
||||||
$btn-color: var(--btn-grey),
|
|
||||||
$btn-color-hover: var(--btn-grey-hover)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.current-image-viewer {
|
.current-image-viewer {
|
||||||
@ -32,25 +27,18 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.current-image-preview {
|
.current-image-preview {
|
||||||
position: absolute;
|
|
||||||
top:0;
|
|
||||||
grid-area: current-image-preview;
|
|
||||||
position: relative;
|
position: relative;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
display: grid;
|
display: flex;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
// height: 100%;
|
||||||
grid-template-areas: 'current-image-content';
|
height: $app-text-to-image-height;
|
||||||
|
|
||||||
img {
|
img {
|
||||||
grid-area: current-image-content;
|
|
||||||
background-color: var(--img2img-img-bg-color);
|
background-color: var(--img2img-img-bg-color);
|
||||||
border-radius: 0.5rem;
|
border-radius: 0.5rem;
|
||||||
object-fit: contain;
|
object-fit: contain;
|
||||||
// width: auto;
|
|
||||||
// height: $app-gallery-height;
|
|
||||||
max-height: $app-gallery-height;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,26 +3,50 @@ import CurrentImageButtons from './CurrentImageButtons';
|
|||||||
import { MdPhoto } from 'react-icons/md';
|
import { MdPhoto } from 'react-icons/md';
|
||||||
import CurrentImagePreview from './CurrentImagePreview';
|
import CurrentImagePreview from './CurrentImagePreview';
|
||||||
import ImageMetadataViewer from './ImageMetaDataViewer/ImageMetadataViewer';
|
import ImageMetadataViewer from './ImageMetaDataViewer/ImageMetadataViewer';
|
||||||
|
import { tabMap } from '../tabs/InvokeTabs';
|
||||||
|
import { GalleryState } from './gallerySlice';
|
||||||
|
import { OptionsState } from '../options/optionsSlice';
|
||||||
|
import _ from 'lodash';
|
||||||
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
|
|
||||||
|
export const currentImageDisplaySelector = createSelector(
|
||||||
|
[(state: RootState) => state.gallery, (state: RootState) => state.options],
|
||||||
|
(gallery: GalleryState, options: OptionsState) => {
|
||||||
|
const { currentImage, intermediateImage } = gallery;
|
||||||
|
const { activeTab, shouldShowImageDetails } = options;
|
||||||
|
|
||||||
|
return {
|
||||||
|
currentImage,
|
||||||
|
intermediateImage,
|
||||||
|
activeTabName: tabMap[activeTab],
|
||||||
|
shouldShowImageDetails,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
{
|
||||||
|
memoizeOptions: {
|
||||||
|
resultEqualityCheck: _.isEqual,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Displays the current image if there is one, plus associated actions.
|
* Displays the current image if there is one, plus associated actions.
|
||||||
*/
|
*/
|
||||||
const CurrentImageDisplay = () => {
|
const CurrentImageDisplay = () => {
|
||||||
const { currentImage, intermediateImage } = useAppSelector(
|
const {
|
||||||
(state: RootState) => state.gallery
|
currentImage,
|
||||||
);
|
intermediateImage,
|
||||||
|
activeTabName,
|
||||||
const shouldShowImageDetails = useAppSelector(
|
shouldShowImageDetails,
|
||||||
(state: RootState) => state.options.shouldShowImageDetails
|
} = useAppSelector(currentImageDisplaySelector);
|
||||||
);
|
|
||||||
|
|
||||||
const imageToDisplay = intermediateImage || currentImage;
|
const imageToDisplay = intermediateImage || currentImage;
|
||||||
|
|
||||||
return imageToDisplay ? (
|
return (
|
||||||
<div className="current-image-display">
|
<div className="current-image-area" data-tab-name={activeTabName}>
|
||||||
<div className="current-image-tools">
|
{imageToDisplay ? (
|
||||||
|
<>
|
||||||
<CurrentImageButtons image={imageToDisplay} />
|
<CurrentImageButtons image={imageToDisplay} />
|
||||||
</div>
|
|
||||||
<div className="current-image-viewer">
|
<div className="current-image-viewer">
|
||||||
<CurrentImagePreview imageToDisplay={imageToDisplay} />
|
<CurrentImagePreview imageToDisplay={imageToDisplay} />
|
||||||
{shouldShowImageDetails && (
|
{shouldShowImageDetails && (
|
||||||
@ -32,11 +56,13 @@ const CurrentImageDisplay = () => {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<div className="current-image-display-placeholder">
|
<div className="current-image-display-placeholder">
|
||||||
<MdPhoto />
|
<MdPhoto />
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -6,11 +6,13 @@ import { GalleryState, selectNextImage, selectPrevImage } from './gallerySlice';
|
|||||||
import * as InvokeAI from '../../app/invokeai';
|
import * as InvokeAI from '../../app/invokeai';
|
||||||
import { createSelector } from '@reduxjs/toolkit';
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
|
import { OptionsState } from '../options/optionsSlice';
|
||||||
|
|
||||||
const imagesSelector = createSelector(
|
export const imagesSelector = createSelector(
|
||||||
(state: RootState) => state.gallery,
|
[(state: RootState) => state.gallery, (state: RootState) => state.options],
|
||||||
(gallery: GalleryState) => {
|
(gallery: GalleryState, options: OptionsState) => {
|
||||||
const { currentCategory } = gallery;
|
const { currentCategory } = gallery;
|
||||||
|
const { shouldShowImageDetails } = options;
|
||||||
|
|
||||||
const tempImages = gallery.categories[currentCategory].images;
|
const tempImages = gallery.categories[currentCategory].images;
|
||||||
const currentImageIndex = tempImages.findIndex(
|
const currentImageIndex = tempImages.findIndex(
|
||||||
@ -22,6 +24,7 @@ const imagesSelector = createSelector(
|
|||||||
isOnFirstImage: currentImageIndex === 0,
|
isOnFirstImage: currentImageIndex === 0,
|
||||||
isOnLastImage:
|
isOnLastImage:
|
||||||
!isNaN(currentImageIndex) && currentImageIndex === imagesLength - 1,
|
!isNaN(currentImageIndex) && currentImageIndex === imagesLength - 1,
|
||||||
|
shouldShowImageDetails,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -39,11 +42,12 @@ export default function CurrentImagePreview(props: CurrentImagePreviewProps) {
|
|||||||
const { imageToDisplay } = props;
|
const { imageToDisplay } = props;
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
const { isOnFirstImage, isOnLastImage, currentCategory } = useAppSelector(imagesSelector);
|
const {
|
||||||
|
isOnFirstImage,
|
||||||
const shouldShowImageDetails = useAppSelector(
|
isOnLastImage,
|
||||||
(state: RootState) => state.options.shouldShowImageDetails
|
currentCategory,
|
||||||
);
|
shouldShowImageDetails,
|
||||||
|
} = useAppSelector(imagesSelector);
|
||||||
|
|
||||||
const [shouldShowNextPrevButtons, setShouldShowNextPrevButtons] =
|
const [shouldShowNextPrevButtons, setShouldShowNextPrevButtons] =
|
||||||
useState<boolean>(false);
|
useState<boolean>(false);
|
||||||
@ -65,7 +69,7 @@ export default function CurrentImagePreview(props: CurrentImagePreviewProps) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="current-image-preview">
|
<div className={'current-image-preview'}>
|
||||||
<Image
|
<Image
|
||||||
src={imageToDisplay.url}
|
src={imageToDisplay.url}
|
||||||
fit="contain"
|
fit="contain"
|
||||||
|
@ -1,5 +1,11 @@
|
|||||||
import { Box } from '@chakra-ui/react';
|
import { Box } from '@chakra-ui/react';
|
||||||
import { cloneElement, ReactElement, SyntheticEvent, useCallback } from 'react';
|
import {
|
||||||
|
cloneElement,
|
||||||
|
ReactElement,
|
||||||
|
ReactNode,
|
||||||
|
SyntheticEvent,
|
||||||
|
useCallback,
|
||||||
|
} from 'react';
|
||||||
import { FileRejection, useDropzone } from 'react-dropzone';
|
import { FileRejection, useDropzone } from 'react-dropzone';
|
||||||
|
|
||||||
type ImageUploaderProps = {
|
type ImageUploaderProps = {
|
||||||
@ -55,7 +61,7 @@ const ImageUploader = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box {...getRootProps()} flexGrow={3} className={`${styleClass}`}>
|
<Box {...getRootProps()} className={styleClass}>
|
||||||
<input {...getInputProps({ multiple: false })} />
|
<input {...getInputProps({ multiple: false })} />
|
||||||
{cloneElement(children, {
|
{cloneElement(children, {
|
||||||
onClick: handleClickUploadIcon,
|
onClick: handleClickUploadIcon,
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
@use '../../../styles/Mixins/' as *;
|
@use '../../../styles/Mixins/' as *;
|
||||||
|
|
||||||
.image-to-image-workarea {
|
.image-to-image-area {
|
||||||
display: grid;
|
display: flex;
|
||||||
grid-template-columns: max-content auto;
|
flex-direction: column;
|
||||||
column-gap: 1rem;
|
row-gap: 1rem;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.image-to-image-panel {
|
.image-to-image-panel {
|
||||||
@ -15,16 +17,6 @@
|
|||||||
@include HideScrollbar;
|
@include HideScrollbar;
|
||||||
}
|
}
|
||||||
|
|
||||||
.image-to-image-display-area {
|
|
||||||
display: grid;
|
|
||||||
column-gap: 0.5rem;
|
|
||||||
|
|
||||||
.image-gallery-area {
|
|
||||||
z-index: 2;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.image-to-image-strength-main-option {
|
.image-to-image-strength-main-option {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: none !important;
|
grid-template-columns: none !important;
|
||||||
@ -34,113 +26,29 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.image-to-image-display {
|
.init-image-preview-header {
|
||||||
border-radius: 0.5rem;
|
display: flex;
|
||||||
background-color: var(--background-color-secondary);
|
align-items: center;
|
||||||
display: grid;
|
justify-content: space-between;
|
||||||
height: $app-content-height;
|
width: 100%;
|
||||||
|
|
||||||
.current-image-options {
|
h2 {
|
||||||
grid-auto-columns: max-content;
|
font-weight: bold;
|
||||||
justify-self: center;
|
font-size: 0.9rem;
|
||||||
align-self: start;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.image-to-image-single-preview {
|
|
||||||
display: grid;
|
|
||||||
column-gap: 0.5rem;
|
|
||||||
padding: 0 1rem;
|
|
||||||
place-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.image-to-image-dual-preview-container {
|
|
||||||
display: grid;
|
|
||||||
grid-template-areas: 'img2img-preview';
|
|
||||||
}
|
|
||||||
|
|
||||||
.image-to-image-dual-preview {
|
|
||||||
grid-area: img2img-preview;
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 1fr 1fr;
|
|
||||||
column-gap: 0.5rem;
|
|
||||||
padding: 0 1rem;
|
|
||||||
place-content: center;
|
|
||||||
|
|
||||||
.current-image-preview {
|
|
||||||
img {
|
|
||||||
height: calc($app-gallery-height - 2rem);
|
|
||||||
max-height: calc($app-gallery-height - 2rem);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.img2img-metadata {
|
|
||||||
grid-area: img2img-preview;
|
|
||||||
z-index: 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
.init-image-preview {
|
.init-image-preview {
|
||||||
display: grid;
|
height: 100%;
|
||||||
grid-template-areas: 'init-image-content';
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
height: $app-text-to-image-height;
|
||||||
border-radius: 0.5rem;
|
|
||||||
|
|
||||||
.init-image-preview-header {
|
|
||||||
grid-area: init-image-content;
|
|
||||||
z-index: 2;
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: auto max-content;
|
|
||||||
height: max-content;
|
|
||||||
align-items: center;
|
|
||||||
align-self: start;
|
|
||||||
padding: 1rem;
|
|
||||||
border-radius: 0.5rem;
|
|
||||||
|
|
||||||
h1 {
|
|
||||||
padding: 0.2rem 0.6rem;
|
|
||||||
border-radius: 0.4rem;
|
|
||||||
background-color: var(--tab-hover-color);
|
|
||||||
width: max-content;
|
|
||||||
font-weight: bold;
|
|
||||||
font-size: 0.85rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.init-image-image {
|
|
||||||
grid-area: init-image-content;
|
|
||||||
|
|
||||||
img {
|
img {
|
||||||
border-radius: 0.5rem;
|
border-radius: 0.5rem;
|
||||||
object-fit: contain;
|
object-fit: contain;
|
||||||
background-color: var(--img2img-img-bg-color);
|
|
||||||
width: auto;
|
width: auto;
|
||||||
height: calc($app-gallery-height - 2rem);
|
|
||||||
max-height: calc($app-gallery-height - 2rem);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
.image-to-image-upload-btn {
|
|
||||||
display: grid;
|
|
||||||
width: 100%;
|
|
||||||
height: $app-content-height;
|
|
||||||
|
|
||||||
button {
|
|
||||||
overflow: hidden;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
font-size: 1.5rem;
|
|
||||||
color: var(--text-color-secondary);
|
|
||||||
background-color: var(--background-color-secondary);
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background-color: var(--img2img-img-bg-color);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.image-to-image-current-image-display {
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
@ -1,74 +1,40 @@
|
|||||||
import React from 'react';
|
import { uploadImage } from '../../../app/socketio/actions';
|
||||||
import { FaUpload } from 'react-icons/fa';
|
import { RootState, useAppDispatch, useAppSelector } from '../../../app/store';
|
||||||
import { uploadInitialImage } from '../../../app/socketio/actions';
|
|
||||||
import { RootState, useAppSelector } from '../../../app/store';
|
|
||||||
import InvokeImageUploader from '../../../common/components/InvokeImageUploader';
|
import InvokeImageUploader from '../../../common/components/InvokeImageUploader';
|
||||||
import CurrentImageButtons from '../../gallery/CurrentImageButtons';
|
import CurrentImageDisplay from '../../gallery/CurrentImageDisplay';
|
||||||
import CurrentImagePreview from '../../gallery/CurrentImagePreview';
|
|
||||||
import ImageMetadataViewer from '../../gallery/ImageMetaDataViewer/ImageMetadataViewer';
|
|
||||||
|
|
||||||
import InitImagePreview from './InitImagePreview';
|
import InitImagePreview from './InitImagePreview';
|
||||||
|
|
||||||
export default function ImageToImageDisplay() {
|
const ImageToImageDisplay = () => {
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
const initialImage = useAppSelector(
|
const initialImage = useAppSelector(
|
||||||
(state: RootState) => state.options.initialImage
|
(state: RootState) => state.options.initialImage
|
||||||
);
|
);
|
||||||
|
|
||||||
const { currentImage, intermediateImage } = useAppSelector(
|
const { currentImage } = useAppSelector((state: RootState) => state.gallery);
|
||||||
(state: RootState) => state.gallery
|
|
||||||
);
|
|
||||||
|
|
||||||
const shouldShowImageDetails = useAppSelector(
|
const imageToImageComponent = initialImage ? (
|
||||||
(state: RootState) => state.options.shouldShowImageDetails
|
<div className="image-to-image-area">
|
||||||
|
<InitImagePreview />
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<InvokeImageUploader
|
||||||
|
handleFile={(file: File) =>
|
||||||
|
dispatch(uploadImage({ file, destination: 'img2img' }))
|
||||||
|
}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
const imageToDisplay = intermediateImage || currentImage;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div className="workarea-split-view">
|
||||||
className="image-to-image-display"
|
<div className="workarea-split-view-left">{imageToImageComponent} </div>
|
||||||
style={
|
{currentImage && (
|
||||||
imageToDisplay
|
<div className="workarea-split-view-right">
|
||||||
? { gridAutoRows: 'max-content auto' }
|
<CurrentImageDisplay />
|
||||||
: { gridAutoRows: 'auto' }
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{initialImage ? (
|
|
||||||
<>
|
|
||||||
{imageToDisplay ? (
|
|
||||||
<>
|
|
||||||
<CurrentImageButtons image={imageToDisplay} />
|
|
||||||
<div className="image-to-image-dual-preview-container">
|
|
||||||
<div className="image-to-image-dual-preview">
|
|
||||||
<InitImagePreview />
|
|
||||||
<div className="image-to-image-current-image-display">
|
|
||||||
<CurrentImagePreview imageToDisplay={imageToDisplay} />
|
|
||||||
{shouldShowImageDetails && (
|
|
||||||
<ImageMetadataViewer
|
|
||||||
image={imageToDisplay}
|
|
||||||
styleClass="img2img-metadata"
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<div className="image-to-image-single-preview">
|
|
||||||
<InitImagePreview />
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<div className="upload-image">
|
|
||||||
<InvokeImageUploader
|
|
||||||
label="Upload or Drop Image Here"
|
|
||||||
icon={<FaUpload />}
|
|
||||||
styleClass="image-to-image-upload-btn"
|
|
||||||
dispatcher={uploadInitialImage}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
|
export default ImageToImageDisplay;
|
||||||
|
@ -27,29 +27,29 @@ export default function InitImagePreview() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="init-image-preview">
|
<>
|
||||||
<div className="init-image-preview-header">
|
<div className="init-image-preview-header">
|
||||||
<h1>Initial Image</h1>
|
<h2>Initial Image</h2>
|
||||||
<IconButton
|
<IconButton
|
||||||
isDisabled={!initialImage}
|
isDisabled={!initialImage}
|
||||||
size={'sm'}
|
|
||||||
aria-label={'Reset Initial Image'}
|
aria-label={'Reset Initial Image'}
|
||||||
onClick={handleClickResetInitialImage}
|
onClick={handleClickResetInitialImage}
|
||||||
icon={<MdClear />}
|
icon={<MdClear />}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{initialImage && (
|
{initialImage && (
|
||||||
<div className="init-image-image">
|
<div className="init-image-preview">
|
||||||
<Image
|
<Image
|
||||||
fit={'contain'}
|
fit={'contain'}
|
||||||
|
maxWidth={'100%'}
|
||||||
|
maxHeight={'100%'}
|
||||||
src={
|
src={
|
||||||
typeof initialImage === 'string' ? initialImage : initialImage.url
|
typeof initialImage === 'string' ? initialImage : initialImage.url
|
||||||
}
|
}
|
||||||
rounded={'md'}
|
|
||||||
onError={alertMissingInitImage}
|
onError={alertMissingInitImage}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,62 +1,47 @@
|
|||||||
@use '../../../styles/Mixins/' as *;
|
@use '../../../styles/Mixins/' as *;
|
||||||
|
|
||||||
.brush-preview-wrapper {
|
.inpainting-main-area {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
row-gap: 1rem;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
.inpainting-settings {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.brush-preview {
|
|
||||||
border-radius: 50%;
|
|
||||||
border: 1px black solid;
|
|
||||||
}
|
|
||||||
|
|
||||||
.inpainting-workarea {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: max-content auto;
|
|
||||||
column-gap: 1rem;
|
column-gap: 1rem;
|
||||||
}
|
|
||||||
|
|
||||||
.inpainting-display-area {
|
.inpainting-buttons-group {
|
||||||
display: grid;
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
column-gap: 0.5rem;
|
column-gap: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.inpainting-display {
|
.inpainting-slider-numberinput {
|
||||||
border-radius: 0.5rem;
|
display: flex;
|
||||||
background-color: var(--background-color-secondary);
|
column-gap: 1rem;
|
||||||
display: grid;
|
align-items: center;
|
||||||
grid-template-columns: 1fr 1fr;
|
}
|
||||||
// column-gap: 1rem;
|
|
||||||
height: $app-content-height;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.inpainting-toolkit {
|
.inpainting-canvas-area {
|
||||||
// display: grid;
|
|
||||||
// grid-template-rows: auto auto;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
background-color: var(--inpaint-bg-color);
|
align-items: center;
|
||||||
border-radius: 0.5rem;
|
row-gap: 1rem;
|
||||||
}
|
|
||||||
|
|
||||||
.inpainting-canvas-container {
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
padding-left: 1rem;
|
}
|
||||||
padding-right: 1rem;
|
|
||||||
padding-bottom: 1rem;
|
.inpainting-canvas-wrapper {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
|
||||||
.inpainting-canvas-wrapper {
|
|
||||||
position: relative;
|
position: relative;
|
||||||
width: min-content;
|
height: 100%;
|
||||||
height: min-content;
|
width: 100%;
|
||||||
border-radius: 0.5rem;
|
border-radius: 0.5rem;
|
||||||
|
|
||||||
.inpainting-alerts {
|
.inpainting-alerts {
|
||||||
@ -80,6 +65,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.inpainting-canvas-stage {
|
.inpainting-canvas-stage {
|
||||||
|
border-radius: 0.5rem;
|
||||||
canvas {
|
canvas {
|
||||||
border-radius: 0.5rem;
|
border-radius: 0.5rem;
|
||||||
}
|
}
|
||||||
@ -87,61 +73,8 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// .canvas-scale-calculator {
|
|
||||||
// width: calc(100% - 1rem);
|
|
||||||
// height: calc(100% - 1rem);
|
|
||||||
// display: flex;
|
|
||||||
// align-items: center;
|
|
||||||
// justify-content: center;
|
|
||||||
// }
|
|
||||||
|
|
||||||
.inpainting-canvas-scale-wrapper {
|
|
||||||
width: calc(100% - 1rem);
|
|
||||||
height: calc(100% - 1rem);
|
|
||||||
// width: max-content;
|
|
||||||
// height: max-content;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.inpainting-settings {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
row-gap: 1rem;
|
|
||||||
padding: 1rem;
|
|
||||||
|
|
||||||
.inpainting-buttons {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
column-gap: 0.8rem;
|
|
||||||
|
|
||||||
button {
|
|
||||||
height: 2.4rem;
|
|
||||||
svg {
|
|
||||||
width: 15px;
|
|
||||||
height: 15px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.inpainting-buttons-group {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
column-gap: 0.5rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.inpainting-slider-numberinput {
|
|
||||||
display: flex;
|
|
||||||
column-gap: 1rem;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Overrides
|
// Overrides
|
||||||
.inpainting-workarea-container {
|
.inpainting-workarea-overrides {
|
||||||
.image-gallery-area {
|
.image-gallery-area {
|
||||||
.chakra-popover__popper {
|
.chakra-popover__popper {
|
||||||
inset: 0 auto auto -75px !important;
|
inset: 0 auto auto -75px !important;
|
||||||
@ -149,21 +82,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.current-image-options {
|
.current-image-options {
|
||||||
button {
|
|
||||||
@include Button(
|
|
||||||
$btn-width: 2.5rem,
|
|
||||||
$icon-size: 18px,
|
|
||||||
$btn-color: var(--btn-grey),
|
|
||||||
$btn-color-hover: var(--btn-grey-hover)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
.chakra-popover__popper {
|
.chakra-popover__popper {
|
||||||
z-index: 11;
|
z-index: 11;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.current-image-preview {
|
|
||||||
padding: 0 1rem 1rem 1rem;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -218,7 +218,7 @@ const InpaintingCanvas = () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="inpainting-canvas-wrapper checkerboard" tabIndex={1}>
|
<div className="inpainting-canvas-wrapper" tabIndex={1}>
|
||||||
<div className="inpainting-alerts">
|
<div className="inpainting-alerts">
|
||||||
{!shouldShowMask && (
|
{!shouldShowMask && (
|
||||||
<div style={{ pointerEvents: 'none' }}>Mask Hidden (H)</div>
|
<div style={{ pointerEvents: 'none' }}>Mask Hidden (H)</div>
|
||||||
@ -245,7 +245,7 @@ const InpaintingCanvas = () => {
|
|||||||
onMouseOut={handleMouseOutCanvas}
|
onMouseOut={handleMouseOutCanvas}
|
||||||
onMouseLeave={handleMouseOutCanvas}
|
onMouseLeave={handleMouseOutCanvas}
|
||||||
style={{ cursor: shouldShowMask ? 'none' : 'default' }}
|
style={{ cursor: shouldShowMask ? 'none' : 'default' }}
|
||||||
className="inpainting-canvas-stage"
|
className="inpainting-canvas-stage checkerboard"
|
||||||
ref={stageRef}
|
ref={stageRef}
|
||||||
>
|
>
|
||||||
{!shouldInvertMask && !shouldShowCheckboardTransparency && (
|
{!shouldInvertMask && !shouldShowCheckboardTransparency && (
|
||||||
|
@ -25,7 +25,7 @@ const InpaintingCanvasPlaceholder = () => {
|
|||||||
}, [dispatch, imageToInpaint, needsRepaint]);
|
}, [dispatch, imageToInpaint, needsRepaint]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div ref={ref} className="inpainting-canvas-container">
|
<div ref={ref} className="inpainting-canvas-area">
|
||||||
<Spinner thickness="2px" speed="1s" size="xl" />
|
<Spinner thickness="2px" speed="1s" size="xl" />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -280,7 +280,6 @@ const InpaintingControls = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="inpainting-settings">
|
<div className="inpainting-settings">
|
||||||
<div className="inpainting-buttons">
|
|
||||||
<div className="inpainting-buttons-group">
|
<div className="inpainting-buttons-group">
|
||||||
<IAIPopover
|
<IAIPopover
|
||||||
trigger="hover"
|
trigger="hover"
|
||||||
@ -398,7 +397,6 @@ const InpaintingControls = () => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
import { createSelector } from '@reduxjs/toolkit';
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import { useLayoutEffect } from 'react';
|
import { useLayoutEffect } from 'react';
|
||||||
|
import { uploadImage } from '../../../app/socketio/actions';
|
||||||
import { RootState, useAppDispatch, useAppSelector } from '../../../app/store';
|
import { RootState, useAppDispatch, useAppSelector } from '../../../app/store';
|
||||||
|
import InvokeImageUploader from '../../../common/components/InvokeImageUploader';
|
||||||
import CurrentImageDisplay from '../../gallery/CurrentImageDisplay';
|
import CurrentImageDisplay from '../../gallery/CurrentImageDisplay';
|
||||||
import { OptionsState } from '../../options/optionsSlice';
|
import { OptionsState } from '../../options/optionsSlice';
|
||||||
import InpaintingCanvas from './InpaintingCanvas';
|
import InpaintingCanvas from './InpaintingCanvas';
|
||||||
@ -12,11 +14,12 @@ import { InpaintingState, setNeedsRepaint } from './inpaintingSlice';
|
|||||||
const inpaintingDisplaySelector = createSelector(
|
const inpaintingDisplaySelector = createSelector(
|
||||||
[(state: RootState) => state.inpainting, (state: RootState) => state.options],
|
[(state: RootState) => state.inpainting, (state: RootState) => state.options],
|
||||||
(inpainting: InpaintingState, options: OptionsState) => {
|
(inpainting: InpaintingState, options: OptionsState) => {
|
||||||
const { needsRepaint } = inpainting;
|
const { needsRepaint, imageToInpaint } = inpainting;
|
||||||
const { showDualDisplay } = options;
|
const { showDualDisplay } = options;
|
||||||
return {
|
return {
|
||||||
needsRepaint,
|
needsRepaint,
|
||||||
showDualDisplay,
|
showDualDisplay,
|
||||||
|
imageToInpaint,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -28,7 +31,7 @@ const inpaintingDisplaySelector = createSelector(
|
|||||||
|
|
||||||
const InpaintingDisplay = () => {
|
const InpaintingDisplay = () => {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const { showDualDisplay, needsRepaint } = useAppSelector(
|
const { showDualDisplay, needsRepaint, imageToInpaint } = useAppSelector(
|
||||||
inpaintingDisplaySelector
|
inpaintingDisplaySelector
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -41,28 +44,34 @@ const InpaintingDisplay = () => {
|
|||||||
return () => window.removeEventListener('resize', resizeCallback);
|
return () => window.removeEventListener('resize', resizeCallback);
|
||||||
}, [dispatch]);
|
}, [dispatch]);
|
||||||
|
|
||||||
|
const inpaintingComponent = imageToInpaint ? (
|
||||||
|
<div className="inpainting-main-area">
|
||||||
|
<InpaintingControls />
|
||||||
|
<div className="inpainting-canvas-area">
|
||||||
|
{needsRepaint ? <InpaintingCanvasPlaceholder /> : <InpaintingCanvas />}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<InvokeImageUploader
|
||||||
|
handleFile={(file: File) =>
|
||||||
|
dispatch(uploadImage({ file, destination: 'inpainting' }))
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className="inpainting-display"
|
className={
|
||||||
style={
|
showDualDisplay ? 'workarea-split-view' : 'workarea-single-view'
|
||||||
showDualDisplay
|
|
||||||
? { gridTemplateColumns: '1fr 1fr' }
|
|
||||||
: { gridTemplateColumns: 'auto' }
|
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<div className="inpainting-toolkit">
|
<div className="workarea-split-view-left">{inpaintingComponent} </div>
|
||||||
<InpaintingControls />
|
{showDualDisplay && (
|
||||||
|
<div className="workarea-split-view-right">
|
||||||
<div className="inpainting-canvas-container">
|
<CurrentImageDisplay />
|
||||||
{needsRepaint ? (
|
</div>
|
||||||
<InpaintingCanvasPlaceholder />
|
|
||||||
) : (
|
|
||||||
<InpaintingCanvas />
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
{showDualDisplay && <CurrentImageDisplay />}
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -6,7 +6,7 @@ export default function InpaintingWorkarea() {
|
|||||||
return (
|
return (
|
||||||
<InvokeWorkarea
|
<InvokeWorkarea
|
||||||
optionsPanel={<InpaintingPanel />}
|
optionsPanel={<InpaintingPanel />}
|
||||||
className="inpainting-workarea-container"
|
className="inpainting-workarea-overrides"
|
||||||
>
|
>
|
||||||
<InpaintingDisplay />
|
<InpaintingDisplay />
|
||||||
</InvokeWorkarea>
|
</InvokeWorkarea>
|
||||||
|
@ -156,10 +156,12 @@ export const inpaintingSlice = createSlice({
|
|||||||
setMaskColor: (state, action: PayloadAction<RgbaColor>) => {
|
setMaskColor: (state, action: PayloadAction<RgbaColor>) => {
|
||||||
state.maskColor = action.payload;
|
state.maskColor = action.payload;
|
||||||
},
|
},
|
||||||
// },
|
|
||||||
setCursorPosition: (state, action: PayloadAction<Vector2d | null>) => {
|
setCursorPosition: (state, action: PayloadAction<Vector2d | null>) => {
|
||||||
state.cursorPosition = action.payload;
|
state.cursorPosition = action.payload;
|
||||||
},
|
},
|
||||||
|
clearImageToInpaint: (state) => {
|
||||||
|
state.imageToInpaint = undefined;
|
||||||
|
},
|
||||||
setImageToInpaint: (state, action: PayloadAction<InvokeAI.Image>) => {
|
setImageToInpaint: (state, action: PayloadAction<InvokeAI.Image>) => {
|
||||||
const { width: imageWidth, height: imageHeight } = action.payload;
|
const { width: imageWidth, height: imageHeight } = action.payload;
|
||||||
const { width: boundingBoxWidth, height: boundingBoxHeight } =
|
const { width: boundingBoxWidth, height: boundingBoxHeight } =
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
@use '../../styles/Mixins/' as *;
|
@use '../../styles/Mixins/' as *;
|
||||||
|
|
||||||
.workarea-container {
|
.workarea-wrapper {
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
|
||||||
.workarea {
|
.workarea-main {
|
||||||
display: flex;
|
display: flex;
|
||||||
column-gap: 1rem;
|
column-gap: 1rem;
|
||||||
|
|
||||||
@ -15,8 +15,40 @@
|
|||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.workarea-content {
|
.workarea-split-view {
|
||||||
width: 100%;
|
width: 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;
|
||||||
|
background-color: var(--background-color-secondary);
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.workarea-split-view-left,
|
||||||
|
.workarea-split-view-right {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
row-gap: 1rem;
|
||||||
|
background-color: var(--background-color-secondary);
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.workarea-split-view-left {
|
||||||
|
padding-right: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.workarea-split-view-right {
|
||||||
|
padding-left: 0.5rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,25 +5,20 @@ import ShowHideGalleryButton from '../gallery/ShowHideGalleryButton';
|
|||||||
|
|
||||||
type InvokeWorkareaProps = {
|
type InvokeWorkareaProps = {
|
||||||
optionsPanel: ReactNode;
|
optionsPanel: ReactNode;
|
||||||
className?: string;
|
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
};
|
};
|
||||||
|
|
||||||
const InvokeWorkarea = (props: InvokeWorkareaProps) => {
|
const InvokeWorkarea = (props: InvokeWorkareaProps) => {
|
||||||
const { optionsPanel, className, children } = props;
|
const { optionsPanel, children } = props;
|
||||||
|
|
||||||
const { shouldShowGallery, shouldHoldGalleryOpen, shouldPinGallery } =
|
const { shouldShowGallery, shouldHoldGalleryOpen, shouldPinGallery } =
|
||||||
useAppSelector((state: RootState) => state.gallery);
|
useAppSelector((state: RootState) => state.gallery);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div className="workarea-wrapper">
|
||||||
className={
|
<div className="workarea-main">
|
||||||
className ? `workarea-container ${className}` : `workarea-container`
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<div className="workarea">
|
|
||||||
<div className="workarea-options-panel">{optionsPanel}</div>
|
<div className="workarea-options-panel">{optionsPanel}</div>
|
||||||
<div className="workarea-content">{children}</div>
|
{children}
|
||||||
<ImageGallery />
|
<ImageGallery />
|
||||||
</div>
|
</div>
|
||||||
{!(shouldShowGallery || (shouldHoldGalleryOpen && !shouldPinGallery)) && (
|
{!(shouldShowGallery || (shouldHoldGalleryOpen && !shouldPinGallery)) && (
|
||||||
|
@ -1,9 +1,7 @@
|
|||||||
@use '../../../styles/Mixins/' as *;
|
@use '../../../styles/Mixins/' as *;
|
||||||
|
|
||||||
.text-to-image-workarea {
|
.text-to-image-area {
|
||||||
display: grid;
|
padding: 1rem;
|
||||||
grid-template-columns: max-content auto;
|
|
||||||
column-gap: 1rem;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.text-to-image-panel {
|
.text-to-image-panel {
|
||||||
@ -14,27 +12,3 @@
|
|||||||
overflow-y: scroll;
|
overflow-y: scroll;
|
||||||
@include HideScrollbar;
|
@include HideScrollbar;
|
||||||
}
|
}
|
||||||
|
|
||||||
.text-to-image-display {
|
|
||||||
display: grid;
|
|
||||||
grid-template-areas: 'text-to-image-display';
|
|
||||||
column-gap: 0.5rem;
|
|
||||||
|
|
||||||
.current-image-display,
|
|
||||||
.current-image-display-placeholder {
|
|
||||||
grid-area: text-to-image-display;
|
|
||||||
}
|
|
||||||
|
|
||||||
.image-gallery-area {
|
|
||||||
height: 100%;
|
|
||||||
z-index: 2;
|
|
||||||
place-self: end;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Overrides
|
|
||||||
.txt-to-image-workarea-container {
|
|
||||||
.current-image-preview {
|
|
||||||
padding: 0 1rem 1rem 1rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -0,0 +1,13 @@
|
|||||||
|
import CurrentImageDisplay from '../../gallery/CurrentImageDisplay';
|
||||||
|
|
||||||
|
const TextToImageDisplay = () => {
|
||||||
|
return (
|
||||||
|
<div className="workarea-single-view">
|
||||||
|
<div className="text-to-image-area">
|
||||||
|
<CurrentImageDisplay />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default TextToImageDisplay;
|
@ -1,14 +1,11 @@
|
|||||||
import TextToImagePanel from './TextToImagePanel';
|
import TextToImagePanel from './TextToImagePanel';
|
||||||
import CurrentImageDisplay from '../../gallery/CurrentImageDisplay';
|
|
||||||
import InvokeWorkarea from '../InvokeWorkarea';
|
import InvokeWorkarea from '../InvokeWorkarea';
|
||||||
|
import TextToImageDisplay from './TextToImageDisplay';
|
||||||
|
|
||||||
export default function TextToImageWorkarea() {
|
export default function TextToImageWorkarea() {
|
||||||
return (
|
return (
|
||||||
<InvokeWorkarea
|
<InvokeWorkarea optionsPanel={<TextToImagePanel />}>
|
||||||
optionsPanel={<TextToImagePanel />}
|
<TextToImageDisplay />
|
||||||
className="txt-to-image-workarea-container"
|
|
||||||
>
|
|
||||||
<CurrentImageDisplay />
|
|
||||||
</InvokeWorkarea>
|
</InvokeWorkarea>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -7,9 +7,11 @@ $app-content-height-cutoff: 7rem; // default: 7rem
|
|||||||
$app-width: calc(100vw - $app-cutoff);
|
$app-width: calc(100vw - $app-cutoff);
|
||||||
$app-height: calc(100vh - $app-cutoff);
|
$app-height: calc(100vh - $app-cutoff);
|
||||||
$app-content-height: calc(100vh - $app-content-height-cutoff);
|
$app-content-height: calc(100vh - $app-content-height-cutoff);
|
||||||
$app-gallery-height: calc(100vh - ($app-content-height-cutoff + 6rem));
|
$app-gallery-height: calc(100vh - ($app-content-height-cutoff + 5.5rem));
|
||||||
$app-gallery-popover-height: calc(100vh - ($app-content-height-cutoff + 6rem));
|
$app-gallery-popover-height: calc(100vh - ($app-content-height-cutoff + 6rem));
|
||||||
$app-metadata-height: calc(100vh - ($app-content-height-cutoff + 4.4rem));
|
$app-metadata-height: calc(100vh - ($app-content-height-cutoff + 4.4rem));
|
||||||
|
// $app-text-to-image-height: calc(100vh - 9.4375rem);
|
||||||
|
$app-text-to-image-height: calc(100vh - 9.4375rem - 1.925rem - 1.35rem);
|
||||||
|
|
||||||
// option bar
|
// option bar
|
||||||
$options-bar-max-width: 22.5rem;
|
$options-bar-max-width: 22.5rem;
|
||||||
|
@ -51,6 +51,7 @@
|
|||||||
@use '../common/components/IAICheckbox.scss';
|
@use '../common/components/IAICheckbox.scss';
|
||||||
@use '../common/components/IAIPopover.scss';
|
@use '../common/components/IAIPopover.scss';
|
||||||
@use '../common/components/IAIColorPicker.scss';
|
@use '../common/components/IAIColorPicker.scss';
|
||||||
|
@use '../common/components/InvokeImageUploader.scss';
|
||||||
@use '../common/components/WorkInProgress/WorkInProgress.scss';
|
@use '../common/components/WorkInProgress/WorkInProgress.scss';
|
||||||
@use '../common/components/GuidePopover.scss';
|
@use '../common/components/GuidePopover.scss';
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user