mirror of
https://github.com/invoke-ai/InvokeAI.git
synced 2026-02-16 12:05:25 -05:00
Fixes gallery bugs & adds gallery context menu
This commit is contained in:
committed by
blessedcoolant
parent
1ca517d73b
commit
a127eeff20
@@ -26,7 +26,7 @@ const makeSocketIOEmitters = (
|
||||
|
||||
const options = { ...getState().options };
|
||||
|
||||
if (tabMap[options.activeTab] === 'txt2img') {
|
||||
if (tabMap[options.activeTab] !== 'img2img') {
|
||||
options.shouldUseInitImage = false;
|
||||
}
|
||||
|
||||
|
||||
@@ -7,12 +7,17 @@ export const PostProcessingWIP = () => {
|
||||
<p>
|
||||
Invoke AI offers a wide variety of post processing features. Image
|
||||
Upscaling and Face Restoration are already available in the WebUI. You
|
||||
can access them from the Advanced Options menu of the Text To Image tab.
|
||||
A dedicated UI will be released soon.
|
||||
can access them from the Advanced Options menu of the Text To Image and
|
||||
Image To Image tabs. You can also process images directly, using the
|
||||
image action buttons above the main image display.
|
||||
</p>
|
||||
<p>
|
||||
A dedicated UI will be released soon to facilitate more advanced post
|
||||
processing workflows.
|
||||
</p>
|
||||
<p>
|
||||
The Invoke AI Command Line Interface offers various other features
|
||||
including Embiggen, High Resolution Fixing and more.
|
||||
including Embiggen.
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
FormControl,
|
||||
FormLabel,
|
||||
Flex,
|
||||
useToast,
|
||||
} from '@chakra-ui/react';
|
||||
import { createSelector } from '@reduxjs/toolkit';
|
||||
import {
|
||||
@@ -57,6 +58,7 @@ const DeleteImageModal = forwardRef(
|
||||
const dispatch = useAppDispatch();
|
||||
const shouldConfirmOnDelete = useAppSelector(systemSelector);
|
||||
const cancelRef = useRef<HTMLButtonElement>(null);
|
||||
const toast = useToast();
|
||||
|
||||
const handleClickDelete = (e: SyntheticEvent) => {
|
||||
e.stopPropagation();
|
||||
@@ -65,6 +67,12 @@ const DeleteImageModal = forwardRef(
|
||||
|
||||
const handleDelete = () => {
|
||||
dispatch(deleteImage(image));
|
||||
toast({
|
||||
title: 'Image Deleted',
|
||||
status: 'success',
|
||||
duration: 2500,
|
||||
isClosable: true,
|
||||
});
|
||||
onClose();
|
||||
};
|
||||
|
||||
|
||||
@@ -17,6 +17,12 @@
|
||||
max-height: 100%;
|
||||
}
|
||||
|
||||
.hoverable-image-delete-button {
|
||||
position: absolute;
|
||||
top: 0.25rem;
|
||||
right: 0.25rem;
|
||||
}
|
||||
|
||||
.hoverable-image-content {
|
||||
display: flex;
|
||||
position: absolute;
|
||||
@@ -57,3 +63,39 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.hoverable-image-context-menu {
|
||||
z-index: 999;
|
||||
padding: 0.4rem;
|
||||
border-radius: 0.25rem;
|
||||
background-color: var(--context-menu-bg-color);
|
||||
box-shadow: var(--context-menu-box-shadow);
|
||||
|
||||
[role='menuitem'] {
|
||||
font-size: 0.8rem;
|
||||
line-height: 1rem;
|
||||
border-radius: 3px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 1.75rem;
|
||||
padding: 0 0.5rem;
|
||||
position: relative;
|
||||
user-select: none;
|
||||
cursor: pointer;
|
||||
outline: none;
|
||||
|
||||
&[data-disabled] {
|
||||
color: grey;
|
||||
pointer-events: none;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
&[data-warning] {
|
||||
color: var(--status-bad-color);
|
||||
}
|
||||
|
||||
&[data-highlighted] {
|
||||
background-color: var(--context-menu-bg-color-hover);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,17 +1,27 @@
|
||||
import { Box, Icon, IconButton, Image, Tooltip } from '@chakra-ui/react';
|
||||
import {
|
||||
Box,
|
||||
Icon,
|
||||
IconButton,
|
||||
Image,
|
||||
Tooltip,
|
||||
useToast,
|
||||
} from '@chakra-ui/react';
|
||||
import { RootState, useAppDispatch, useAppSelector } from '../../app/store';
|
||||
import { setCurrentImage } from './gallerySlice';
|
||||
import { FaCheck, FaImage, FaSeedling, FaTrashAlt } from 'react-icons/fa';
|
||||
import { FaCheck, FaTrashAlt } from 'react-icons/fa';
|
||||
import DeleteImageModal from './DeleteImageModal';
|
||||
import { memo, SyntheticEvent, useState } from 'react';
|
||||
import { memo, useState } from 'react';
|
||||
import {
|
||||
setActiveTab,
|
||||
setAllParameters,
|
||||
setAllImageToImageParameters,
|
||||
setAllTextToImageParameters,
|
||||
setInitialImagePath,
|
||||
setPrompt,
|
||||
setSeed,
|
||||
} from '../options/optionsSlice';
|
||||
import * as InvokeAI from '../../app/invokeai';
|
||||
import { IoArrowUndoCircleOutline } from 'react-icons/io5';
|
||||
import * as ContextMenu from '@radix-ui/react-context-menu';
|
||||
import { tabMap } from '../tabs/InvokeTabs';
|
||||
|
||||
interface HoverableImageProps {
|
||||
image: InvokeAI.Image;
|
||||
@@ -27,115 +37,174 @@ const memoEqualityCheck = (
|
||||
* Gallery image component with delete/use all/use seed buttons on hover.
|
||||
*/
|
||||
const HoverableImage = memo((props: HoverableImageProps) => {
|
||||
const [isHovered, setIsHovered] = useState<boolean>(false);
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const activeTab = useAppSelector(
|
||||
(state: RootState) => state.options.activeTab
|
||||
);
|
||||
|
||||
const [isHovered, setIsHovered] = useState<boolean>(false);
|
||||
|
||||
const toast = useToast();
|
||||
|
||||
const { image, isSelected } = props;
|
||||
const { url, uuid, metadata } = image;
|
||||
|
||||
const handleMouseOver = () => setIsHovered(true);
|
||||
|
||||
const handleMouseOut = () => setIsHovered(false);
|
||||
|
||||
const handleClickSetAllParameters = (e: SyntheticEvent) => {
|
||||
e.stopPropagation();
|
||||
dispatch(setAllParameters(metadata));
|
||||
const handleUsePrompt = () => {
|
||||
dispatch(setPrompt(image.metadata.image.prompt));
|
||||
toast({
|
||||
title: 'Prompt Set',
|
||||
status: 'success',
|
||||
duration: 2500,
|
||||
isClosable: true,
|
||||
});
|
||||
};
|
||||
|
||||
const handleClickSetSeed = (e: SyntheticEvent) => {
|
||||
e.stopPropagation();
|
||||
const handleUseSeed = () => {
|
||||
dispatch(setSeed(image.metadata.image.seed));
|
||||
toast({
|
||||
title: 'Seed Set',
|
||||
status: 'success',
|
||||
duration: 2500,
|
||||
isClosable: true,
|
||||
});
|
||||
};
|
||||
|
||||
const handleSetInitImage = (e: SyntheticEvent) => {
|
||||
e.stopPropagation();
|
||||
const handleSendToImageToImage = () => {
|
||||
dispatch(setInitialImagePath(image.url));
|
||||
if (activeTab !== 1) {
|
||||
dispatch(setActiveTab(1));
|
||||
}
|
||||
toast({
|
||||
title: 'Sent to Image To Image',
|
||||
status: 'success',
|
||||
duration: 2500,
|
||||
isClosable: true,
|
||||
});
|
||||
};
|
||||
|
||||
const handleClickImage = () => dispatch(setCurrentImage(image));
|
||||
const handleUseAllParameters = () => {
|
||||
dispatch(setAllTextToImageParameters(metadata));
|
||||
toast({
|
||||
title: 'Parameters Set',
|
||||
status: 'success',
|
||||
duration: 2500,
|
||||
isClosable: true,
|
||||
});
|
||||
};
|
||||
|
||||
const handleUseInitialImage = async () => {
|
||||
// check if the image exists before setting it as initial image
|
||||
if (metadata?.image?.init_image_path) {
|
||||
const response = await fetch(metadata.image.init_image_path);
|
||||
if (response.ok) {
|
||||
dispatch(setActiveTab(tabMap.indexOf('img2img')));
|
||||
dispatch(setAllImageToImageParameters(metadata));
|
||||
toast({
|
||||
title: 'Initial Image Set',
|
||||
status: 'success',
|
||||
duration: 2500,
|
||||
isClosable: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
toast({
|
||||
title: 'Initial Image Not Set',
|
||||
description: 'Could not load initial image.',
|
||||
status: 'error',
|
||||
duration: 2500,
|
||||
isClosable: true,
|
||||
});
|
||||
};
|
||||
|
||||
const handleSelectImage = () => dispatch(setCurrentImage(image));
|
||||
|
||||
return (
|
||||
<Box
|
||||
position={'relative'}
|
||||
key={uuid}
|
||||
className="hoverable-image"
|
||||
onMouseOver={handleMouseOver}
|
||||
onMouseOut={handleMouseOut}
|
||||
>
|
||||
<Image
|
||||
objectFit="cover"
|
||||
rounded={'md'}
|
||||
src={url}
|
||||
loading={'lazy'}
|
||||
className="hoverable-image-image"
|
||||
/>
|
||||
<div className="hoverable-image-content" onClick={handleClickImage}>
|
||||
{isSelected && (
|
||||
<Icon
|
||||
width={'50%'}
|
||||
height={'50%'}
|
||||
as={FaCheck}
|
||||
className="hoverable-image-check"
|
||||
<ContextMenu.Root>
|
||||
<ContextMenu.Trigger>
|
||||
<Box
|
||||
position={'relative'}
|
||||
key={uuid}
|
||||
className="hoverable-image"
|
||||
onMouseOver={handleMouseOver}
|
||||
onMouseOut={handleMouseOut}
|
||||
>
|
||||
<Image
|
||||
className="hoverable-image-image"
|
||||
objectFit="cover"
|
||||
rounded={'md'}
|
||||
src={url}
|
||||
loading={'lazy'}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
{isHovered && (
|
||||
<div className="hoverable-image-icons">
|
||||
<Tooltip label={'Delete image'} hasArrow>
|
||||
<DeleteImageModal image={image}>
|
||||
<IconButton
|
||||
colorScheme="red"
|
||||
aria-label="Delete image"
|
||||
icon={<FaTrashAlt />}
|
||||
size="xs"
|
||||
variant={'imageHoverIconButton'}
|
||||
fontSize={14}
|
||||
<div className="hoverable-image-content" onClick={handleSelectImage}>
|
||||
{isSelected && (
|
||||
<Icon
|
||||
width={'50%'}
|
||||
height={'50%'}
|
||||
as={FaCheck}
|
||||
className="hoverable-image-check"
|
||||
/>
|
||||
</DeleteImageModal>
|
||||
</Tooltip>
|
||||
{['txt2img', 'img2img'].includes(image?.metadata?.image?.type) && (
|
||||
<Tooltip label="Use All Parameters" hasArrow>
|
||||
<IconButton
|
||||
aria-label="Use All Parameters"
|
||||
icon={<IoArrowUndoCircleOutline />}
|
||||
size="xs"
|
||||
fontSize={18}
|
||||
variant={'imageHoverIconButton'}
|
||||
onClickCapture={handleClickSetAllParameters}
|
||||
/>
|
||||
</Tooltip>
|
||||
)}
|
||||
</div>
|
||||
{isHovered && (
|
||||
<div className="hoverable-image-delete-button">
|
||||
<Tooltip label={'Delete image'} hasArrow>
|
||||
<DeleteImageModal image={image}>
|
||||
<IconButton
|
||||
aria-label="Delete image"
|
||||
icon={<FaTrashAlt />}
|
||||
size="xs"
|
||||
variant={'imageHoverIconButton'}
|
||||
fontSize={14}
|
||||
/>
|
||||
</DeleteImageModal>
|
||||
</Tooltip>
|
||||
</div>
|
||||
)}
|
||||
{image?.metadata?.image?.seed !== undefined && (
|
||||
<Tooltip label="Use Seed" hasArrow>
|
||||
<IconButton
|
||||
aria-label="Use Seed"
|
||||
icon={<FaSeedling />}
|
||||
size="xs"
|
||||
fontSize={16}
|
||||
variant={'imageHoverIconButton'}
|
||||
onClickCapture={handleClickSetSeed}
|
||||
/>
|
||||
</Tooltip>
|
||||
)}
|
||||
<Tooltip label="Send To Image To Image" hasArrow>
|
||||
<IconButton
|
||||
aria-label="Send To Image To Image"
|
||||
icon={<FaImage />}
|
||||
size="xs"
|
||||
fontSize={16}
|
||||
variant={'imageHoverIconButton'}
|
||||
onClickCapture={handleSetInitImage}
|
||||
/>
|
||||
</Tooltip>
|
||||
</div>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
</ContextMenu.Trigger>
|
||||
<ContextMenu.Content className="hoverable-image-context-menu">
|
||||
<ContextMenu.Item
|
||||
onClickCapture={handleUsePrompt}
|
||||
disabled={image?.metadata?.image?.prompt === undefined}
|
||||
>
|
||||
Use Prompt
|
||||
</ContextMenu.Item>
|
||||
|
||||
<ContextMenu.Item
|
||||
onClickCapture={handleUseSeed}
|
||||
disabled={image?.metadata?.image?.seed === undefined}
|
||||
>
|
||||
Use Seed
|
||||
</ContextMenu.Item>
|
||||
<ContextMenu.Item
|
||||
onClickCapture={handleUseAllParameters}
|
||||
disabled={
|
||||
!['txt2img', 'img2img'].includes(image?.metadata?.image?.type)
|
||||
}
|
||||
>
|
||||
Use All Parameters
|
||||
</ContextMenu.Item>
|
||||
<Tooltip label="Load initial image used for this generation">
|
||||
<ContextMenu.Item
|
||||
onClickCapture={handleUseInitialImage}
|
||||
disabled={image?.metadata?.image?.type !== 'img2img'}
|
||||
>
|
||||
Use Initial Image
|
||||
</ContextMenu.Item>
|
||||
</Tooltip>
|
||||
<ContextMenu.Item onClickCapture={handleSendToImageToImage}>
|
||||
Send to Image To Image
|
||||
</ContextMenu.Item>
|
||||
<DeleteImageModal image={image}>
|
||||
<ContextMenu.Item data-warning>Delete Image</ContextMenu.Item>
|
||||
</DeleteImageModal>
|
||||
</ContextMenu.Content>
|
||||
</ContextMenu.Root>
|
||||
);
|
||||
}, memoEqualityCheck);
|
||||
|
||||
|
||||
@@ -55,31 +55,37 @@
|
||||
@include HideScrollbar;
|
||||
}
|
||||
|
||||
.masonry-grid {
|
||||
display: -webkit-box; /* Not needed if autoprefixing */
|
||||
display: -ms-flexbox; /* Not needed if autoprefixing */
|
||||
display: flex;
|
||||
margin-left: 0.5rem; /* gutter size offset */
|
||||
width: auto;
|
||||
}
|
||||
.masonry-grid_column {
|
||||
padding-left: 0.5rem; /* gutter size */
|
||||
background-clip: padding-box;
|
||||
}
|
||||
// from https://css-tricks.com/a-grid-of-logos-in-squares/
|
||||
.image-gallery {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(80px, auto));
|
||||
grid-gap: 0.5rem;
|
||||
.hoverable-image {
|
||||
padding: 0.5rem;
|
||||
position: relative;
|
||||
&::before {
|
||||
// for apsect ratio
|
||||
content: '';
|
||||
display: block;
|
||||
padding-bottom: 100%;
|
||||
}
|
||||
.hoverable-image-image {
|
||||
position: absolute;
|
||||
max-width: 100%;
|
||||
|
||||
/* Style your items */
|
||||
.masonry-grid_column > .hoverable-image {
|
||||
/* change div to reference your elements you put in <Masonry> */
|
||||
background: var(--tab-color);
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
// Alternate Version
|
||||
// top: 0;
|
||||
// bottom: 0;
|
||||
// right: 0;
|
||||
// left: 0;
|
||||
// margin: auto;
|
||||
|
||||
// .image-gallery {
|
||||
// display: flex;
|
||||
// grid-template-columns: repeat(auto-fill, minmax(80px, auto));
|
||||
// gap: 0.5rem;
|
||||
// justify-items: center;
|
||||
// }
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.image-gallery-load-more-btn {
|
||||
background-color: var(--btn-load-more) !important;
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import { Button, IconButton } from '@chakra-ui/button';
|
||||
import { Resizable } from 're-resizable';
|
||||
|
||||
import React, { useState } from 'react';
|
||||
import React from 'react';
|
||||
import { useHotkeys } from 'react-hotkeys-hook';
|
||||
import { MdClear, MdPhotoLibrary } from 'react-icons/md';
|
||||
import Masonry from 'react-masonry-css';
|
||||
import { requestImages } from '../../app/socketio/actions';
|
||||
import { RootState, useAppDispatch, useAppSelector } from '../../app/store';
|
||||
import IAIIconButton from '../../common/components/IAIIconButton';
|
||||
@@ -27,12 +26,6 @@ export default function ImageGallery() {
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const [column, setColumn] = useState<number | undefined>();
|
||||
|
||||
const handleResize = (event: MouseEvent | TouchEvent | any) => {
|
||||
setColumn(Math.floor((window.innerWidth - event.x) / 120));
|
||||
};
|
||||
|
||||
const handleShowGalleryToggle = () => {
|
||||
dispatch(setShouldShowGallery(!shouldShowGallery));
|
||||
};
|
||||
@@ -89,9 +82,7 @@ export default function ImageGallery() {
|
||||
minWidth={'300'}
|
||||
maxWidth={activeTab == 1 ? '300' : '600'}
|
||||
className="image-gallery-popup"
|
||||
onResize={handleResize}
|
||||
>
|
||||
{/* <div className="image-gallery-popup"></div> */}
|
||||
<div className="image-gallery-header">
|
||||
<h1>Your Invocations</h1>
|
||||
<IconButton
|
||||
@@ -104,12 +95,7 @@ export default function ImageGallery() {
|
||||
</div>
|
||||
<div className="image-gallery-container">
|
||||
{images.length ? (
|
||||
<Masonry
|
||||
className="masonry-grid"
|
||||
columnClassName="masonry-grid_column"
|
||||
breakpointCols={column}
|
||||
>
|
||||
{/* <div className="image-gallery"> */}
|
||||
<div className="image-gallery">
|
||||
{images.map((image) => {
|
||||
const { uuid } = image;
|
||||
const isSelected = currentImageUuid === uuid;
|
||||
@@ -121,8 +107,7 @@ export default function ImageGallery() {
|
||||
/>
|
||||
);
|
||||
})}
|
||||
{/* </div> */}
|
||||
</Masonry>
|
||||
</div>
|
||||
) : (
|
||||
<div className="image-gallery-container-placeholder">
|
||||
<MdPhotoLibrary />
|
||||
|
||||
@@ -183,6 +183,67 @@ export const optionsSlice = createSlice({
|
||||
setSeedWeights: (state, action: PayloadAction<string>) => {
|
||||
state.seedWeights = action.payload;
|
||||
},
|
||||
setAllTextToImageParameters: (
|
||||
state,
|
||||
action: PayloadAction<InvokeAI.Metadata>
|
||||
) => {
|
||||
const {
|
||||
sampler,
|
||||
prompt,
|
||||
seed,
|
||||
variations,
|
||||
steps,
|
||||
cfg_scale,
|
||||
threshold,
|
||||
perlin,
|
||||
seamless,
|
||||
hires_fix,
|
||||
width,
|
||||
height,
|
||||
} = action.payload.image;
|
||||
|
||||
if (variations && variations.length > 0) {
|
||||
state.seedWeights = seedWeightsToString(variations);
|
||||
state.shouldGenerateVariations = true;
|
||||
} else {
|
||||
state.shouldGenerateVariations = false;
|
||||
}
|
||||
|
||||
if (seed) {
|
||||
state.seed = seed;
|
||||
state.shouldRandomizeSeed = false;
|
||||
}
|
||||
|
||||
if (prompt) state.prompt = promptToString(prompt);
|
||||
if (sampler) state.sampler = sampler;
|
||||
if (steps) state.steps = steps;
|
||||
if (cfg_scale) state.cfgScale = cfg_scale;
|
||||
if (threshold) state.threshold = threshold;
|
||||
if (typeof threshold === 'undefined') state.threshold = 0;
|
||||
if (perlin) state.perlin = perlin;
|
||||
if (typeof perlin === 'undefined') state.perlin = 0;
|
||||
if (typeof seamless === 'boolean') state.seamless = seamless;
|
||||
if (typeof hires_fix === 'boolean') state.hiresFix = hires_fix;
|
||||
if (width) state.width = width;
|
||||
if (height) state.height = height;
|
||||
},
|
||||
setAllImageToImageParameters: (
|
||||
state,
|
||||
action: PayloadAction<InvokeAI.Metadata>
|
||||
) => {
|
||||
const { type, strength, fit, init_image_path, mask_image_path } =
|
||||
action.payload.image;
|
||||
|
||||
if (type === 'img2img') {
|
||||
if (init_image_path) state.initialImagePath = init_image_path;
|
||||
if (mask_image_path) state.maskPath = mask_image_path;
|
||||
if (strength) state.img2imgStrength = strength;
|
||||
if (typeof fit === 'boolean') state.shouldFitToWidthHeight = fit;
|
||||
state.shouldUseInitImage = true;
|
||||
} else {
|
||||
state.shouldUseInitImage = false;
|
||||
}
|
||||
},
|
||||
setAllParameters: (state, action: PayloadAction<InvokeAI.Metadata>) => {
|
||||
const {
|
||||
type,
|
||||
@@ -226,43 +287,6 @@ export const optionsSlice = createSlice({
|
||||
state.shouldRandomizeSeed = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* We support arbitrary numbers of postprocessing steps, so it
|
||||
* doesnt make sense to be include postprocessing metadata when
|
||||
* we use all parameters. Because this code needed a bit of braining
|
||||
* to figure out, I am leaving it, in case it is needed again.
|
||||
*/
|
||||
|
||||
// let postprocessingNotDone = ['gfpgan', 'esrgan'];
|
||||
// if (postprocessing && postprocessing.length > 0) {
|
||||
// postprocessing.forEach(
|
||||
// (postprocess: InvokeAI.PostProcessedImageMetadata) => {
|
||||
// if (postprocess.type === 'gfpgan') {
|
||||
// const { strength } = postprocess;
|
||||
// if (strength) state.facetoolStrength = strength;
|
||||
// state.shouldRunFacetool = true;
|
||||
// postprocessingNotDone = postprocessingNotDone.filter(
|
||||
// (p) => p !== 'gfpgan'
|
||||
// );
|
||||
// }
|
||||
// if (postprocess.type === 'esrgan') {
|
||||
// const { scale, strength } = postprocess;
|
||||
// if (scale) state.upscalingLevel = scale;
|
||||
// if (strength) state.upscalingStrength = strength;
|
||||
// state.shouldRunESRGAN = true;
|
||||
// postprocessingNotDone = postprocessingNotDone.filter(
|
||||
// (p) => p !== 'esrgan'
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
// );
|
||||
// }
|
||||
|
||||
// postprocessingNotDone.forEach((p) => {
|
||||
// if (p === 'esrgan') state.shouldRunESRGAN = false;
|
||||
// if (p === 'gfpgan') state.shouldRunFacetool = false;
|
||||
// });
|
||||
|
||||
if (prompt) state.prompt = promptToString(prompt);
|
||||
if (sampler) state.sampler = sampler;
|
||||
if (steps) state.steps = steps;
|
||||
@@ -346,6 +370,8 @@ export const {
|
||||
setActiveTab,
|
||||
setShouldShowImageDetails,
|
||||
setShouldShowGallery,
|
||||
setAllTextToImageParameters,
|
||||
setAllImageToImageParameters,
|
||||
} = optionsSlice.actions;
|
||||
|
||||
export default optionsSlice.reducer;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { IconButton, Image } from '@chakra-ui/react';
|
||||
import { IconButton, Image, useToast } from '@chakra-ui/react';
|
||||
import React, { SyntheticEvent } from 'react';
|
||||
import { MdClear } from 'react-icons/md';
|
||||
import { RootState, useAppDispatch, useAppSelector } from '../../../app/store';
|
||||
@@ -11,10 +11,23 @@ export default function InitImagePreview() {
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const toast = useToast();
|
||||
|
||||
const handleClickResetInitialImage = (e: SyntheticEvent) => {
|
||||
e.stopPropagation();
|
||||
dispatch(setInitialImagePath(null));
|
||||
};
|
||||
|
||||
const alertMissingInitImage = () => {
|
||||
toast({
|
||||
title: 'Problem loading parameters',
|
||||
description: 'Unable to load init image.',
|
||||
status: 'error',
|
||||
isClosable: true,
|
||||
});
|
||||
dispatch(setInitialImagePath(null));
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="init-image-preview">
|
||||
<div className="init-image-preview-header">
|
||||
@@ -29,7 +42,12 @@ export default function InitImagePreview() {
|
||||
</div>
|
||||
{initialImagePath && (
|
||||
<div className="init-image-image">
|
||||
<Image fit={'contain'} src={initialImagePath} rounded={'md'} />
|
||||
<Image
|
||||
fit={'contain'}
|
||||
src={initialImagePath}
|
||||
rounded={'md'}
|
||||
onError={alertMissingInitImage}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -50,8 +50,13 @@ export const tab_dict = {
|
||||
},
|
||||
};
|
||||
|
||||
// Array where index maps to the key of tab_dict
|
||||
export const tabMap = _.map(tab_dict, (tab, key) => key);
|
||||
|
||||
// Use tabMap to generate a union type of tab names
|
||||
const tabMapTypes = [...tabMap] as const;
|
||||
export type InvokeTabName = typeof tabMapTypes[number];
|
||||
|
||||
export default function InvokeTabs() {
|
||||
const activeTab = useAppSelector(
|
||||
(state: RootState) => state.options.activeTab
|
||||
|
||||
@@ -95,4 +95,9 @@
|
||||
|
||||
// Gallery
|
||||
--gallery-resizeable-color: rgb(36, 38, 48);
|
||||
|
||||
// Context Menus
|
||||
--context-menu-bg-color: rgb(46, 48, 58);
|
||||
--context-menu-box-shadow: none;
|
||||
--context-menu-bg-color-hover: rgb(30, 32, 42);
|
||||
}
|
||||
|
||||
@@ -94,4 +94,11 @@
|
||||
|
||||
// Gallery
|
||||
--gallery-resizeable-color: rgb(192, 194, 196);
|
||||
|
||||
// Context Menus
|
||||
--context-menu-bg-color: var(--background-color);
|
||||
--context-menu-box-shadow: 0px 10px 38px -10px rgba(22, 23, 24, 0.35),
|
||||
0px 10px 20px -15px rgba(22, 23, 24, 0.2);
|
||||
--context-menu-bg-color-hover: var(--background-color-secondary);
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user