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