Reworks CurrentImageButtons.tsx

- Change all icons to FA iconset for consistency
- Refactors IAIIconButton, IAIButton, IAIPopover to handle ref forwarding
- Redesigns buttons into group
This commit is contained in:
psychedelicious 2022-11-02 19:51:38 +11:00 committed by Lincoln Stein
parent cfe567c62a
commit 620cf84d3d
19 changed files with 820 additions and 786 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

517
frontend/dist/assets/index.adcf8963.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -6,8 +6,8 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>InvokeAI - A Stable Diffusion Toolkit</title>
<link rel="shortcut icon" type="icon" href="./assets/favicon.0d253ced.ico" />
<script type="module" crossorigin src="./assets/index.552c95d8.js"></script>
<link rel="stylesheet" href="./assets/index.67342d6d.css">
<script type="module" crossorigin src="./assets/index.adcf8963.js"></script>
<link rel="stylesheet" href="./assets/index.28b80602.css">
</head>
<body>

View File

@ -0,0 +1,3 @@
.invokeai__button {
justify-content: space-between;
}

View File

@ -1,23 +1,32 @@
import { Button, ButtonProps, Tooltip } from '@chakra-ui/react';
import {
Button,
ButtonProps,
forwardRef,
Tooltip,
TooltipProps,
} from '@chakra-ui/react';
import { ReactNode } from 'react';
export interface IAIButtonProps extends ButtonProps {
label: string;
tooltip?: string;
tooltipProps?: Omit<TooltipProps, 'children'>;
styleClass?: string;
children: ReactNode;
}
/**
* Reusable customized button component.
*/
const IAIButton = (props: IAIButtonProps) => {
const { label, tooltip = '', styleClass, ...rest } = props;
const IAIButton = forwardRef((props: IAIButtonProps, forwardedRef) => {
const { children, tooltip = '', tooltipProps, styleClass, ...rest } = props;
return (
<Tooltip label={tooltip}>
<Button className={styleClass ? styleClass : ''} {...rest}>
{label}
<Tooltip label={tooltip} {...tooltipProps}>
<Button
ref={forwardedRef}
className={['invokeai__button', styleClass].join(' ')}
{...rest}
>
{children}
</Button>
</Tooltip>
);
};
});
export default IAIButton;

View File

@ -3,17 +3,18 @@ import {
IconButton,
Tooltip,
TooltipProps,
forwardRef,
} from '@chakra-ui/react';
interface Props extends IconButtonProps {
export type IAIIconButtonProps = IconButtonProps & {
styleClass?: string;
tooltip?: string;
tooltipProps?: Omit<TooltipProps, 'children'>;
asCheckbox?: boolean;
isChecked?: boolean;
}
};
const IAIIconButton = (props: Props) => {
const IAIIconButton = forwardRef((props: IAIIconButtonProps, forwardedRef) => {
const {
tooltip = '',
styleClass,
@ -26,6 +27,7 @@ const IAIIconButton = (props: Props) => {
return (
<Tooltip label={tooltip} hasArrow {...tooltipProps}>
<IconButton
ref={forwardedRef}
className={`invokeai__icon-button ${styleClass}`}
data-as-checkbox={asCheckbox}
data-selected={isChecked !== undefined ? isChecked : undefined}
@ -34,6 +36,6 @@ const IAIIconButton = (props: Props) => {
/>
</Tooltip>
);
};
});
export default IAIIconButton;

View File

@ -3,7 +3,6 @@ import {
PopoverArrow,
PopoverContent,
PopoverTrigger,
Box,
BoxProps,
} from '@chakra-ui/react';
import { PopoverProps } from '@chakra-ui/react';
@ -20,7 +19,6 @@ type IAIPopoverProps = PopoverProps & {
const IAIPopover = (props: IAIPopoverProps) => {
const {
triggerComponent,
triggerContainerProps,
children,
styleClass,
hasArrow = true,
@ -29,9 +27,7 @@ const IAIPopover = (props: IAIPopoverProps) => {
return (
<Popover {...rest}>
<PopoverTrigger>
<Box {...triggerContainerProps}>{triggerComponent}</Box>
</PopoverTrigger>
<PopoverTrigger>{triggerComponent}</PopoverTrigger>
<PopoverContent className={`invokeai__popover-content ${styleClass}`}>
{hasArrow && <PopoverArrow className={'invokeai__popover-arrow'} />}
{children}

View File

@ -0,0 +1,19 @@
.current-image-options {
width: 100%;
display: flex;
justify-content: center;
align-items: center;
column-gap: 0.5rem;
.current-image-send-to-popover,
.current-image-postprocessing-popover {
display: flex;
flex-direction: column;
row-gap: 0.5rem;
max-width: 25rem;
}
.chakra-popover__popper {
z-index: 11;
}
}

View File

@ -10,6 +10,7 @@ import {
setActiveTab,
setAllParameters,
setInitialImage,
setPrompt,
setSeed,
setShouldShowImageDetails,
} from '../options/optionsSlice';
@ -18,27 +19,27 @@ import { SystemState } from '../system/systemSlice';
import IAIButton from '../../common/components/IAIButton';
import { runESRGAN, runFacetool } from '../../app/socketio/actions';
import IAIIconButton from '../../common/components/IAIIconButton';
import { MdDelete, MdFace, MdHd, MdImage, MdInfo } from 'react-icons/md';
import InvokePopover from './InvokePopover';
import UpscaleOptions from '../options/AdvancedOptions/Upscale/UpscaleOptions';
import FaceRestoreOptions from '../options/AdvancedOptions/FaceRestore/FaceRestoreOptions';
import { useHotkeys } from 'react-hotkeys-hook';
import { useToast } from '@chakra-ui/react';
import { FaCopy, FaPaintBrush, FaSeedling } from 'react-icons/fa';
import { ButtonGroup, Link, useClipboard, useToast } from '@chakra-ui/react';
import {
FaAsterisk,
FaCode,
FaCopy,
FaDownload,
FaExpandArrowsAlt,
FaGrinStars,
FaQuoteRight,
FaSeedling,
FaShare,
FaShareAlt,
FaTrash,
} from 'react-icons/fa';
import { setImageToInpaint } from '../tabs/Inpainting/inpaintingSlice';
import { GalleryState } from './gallerySlice';
import { activeTabNameSelector } from '../options/optionsSelectors';
const intermediateImageSelector = createSelector(
(state: RootState) => state.gallery,
(gallery: GalleryState) => gallery.intermediateImage,
{
memoizeOptions: {
resultEqualityCheck: (a, b) =>
(a === undefined && b === undefined) || a.uuid === b.uuid,
},
}
);
import IAIPopover from '../../common/components/IAIPopover';
const systemSelector = createSelector(
[
@ -102,6 +103,8 @@ const CurrentImageButtons = ({ image }: CurrentImageButtonsProps) => {
activeTabName,
} = useAppSelector(systemSelector);
const { onCopy } = useClipboard(window.location.toString() + image.url);
const toast = useToast();
const handleClickUseAsInitialImage = () => {
@ -109,6 +112,16 @@ const CurrentImageButtons = ({ image }: CurrentImageButtonsProps) => {
dispatch(setActiveTab(1));
};
const handleCopyImageLink = () => {
onCopy();
toast({
title: 'Image Link Copied',
status: 'success',
duration: 2500,
isClosable: true,
});
};
useHotkeys(
'shift+i',
() => {
@ -186,6 +199,34 @@ const CurrentImageButtons = ({ image }: CurrentImageButtonsProps) => {
[image]
);
const handleClickUsePrompt = () =>
image?.metadata?.image?.prompt &&
dispatch(setPrompt(image.metadata.image.prompt));
useHotkeys(
'p',
() => {
if (image?.metadata?.image?.prompt) {
handleClickUsePrompt();
toast({
title: 'Prompt Set',
status: 'success',
duration: 2500,
isClosable: true,
});
} else {
toast({
title: 'Prompt Not Set',
description: 'Could not find prompt for this image.',
status: 'error',
duration: 2500,
isClosable: true,
});
}
},
[image]
);
const handleClickUpscale = () => dispatch(runESRGAN(image));
useHotkeys(
'u',
@ -284,108 +325,133 @@ const CurrentImageButtons = ({ image }: CurrentImageButtonsProps) => {
return (
<div className="current-image-options">
<IAIIconButton
icon={<MdImage />}
tooltip="Send To Image To Image"
aria-label="Send To Image To Image"
onClick={handleClickUseAsInitialImage}
/>
<IAIIconButton
icon={<FaPaintBrush />}
tooltip="Send To Inpainting"
aria-label="Send To Inpainting"
onClick={handleSendToInpainting}
/>
<IAIIconButton
icon={<FaCopy />}
tooltip="Use All"
aria-label="Use All"
isDisabled={
!['txt2img', 'img2img'].includes(image?.metadata?.image?.type)
}
onClick={handleClickUseAllParameters}
/>
<IAIIconButton
icon={<FaSeedling />}
tooltip="Use Seed"
aria-label="Use Seed"
isDisabled={!image?.metadata?.image?.seed}
onClick={handleClickUseSeed}
/>
{/* <IAIButton
label="Use All"
isDisabled={
!['txt2img', 'img2img'].includes(image?.metadata?.image?.type)
}
onClick={handleClickUseAllParameters}
/>
<IAIButton
label="Use Seed"
isDisabled={!image?.metadata?.image?.seed}
onClick={handleClickUseSeed}
/> */}
<InvokePopover
title="Restore Faces"
popoverOptions={<FaceRestoreOptions />}
actionButton={
<IAIButton
label={'Restore Faces'}
isDisabled={
!isGFPGANAvailable ||
Boolean(intermediateImage) ||
!(isConnected && !isProcessing) ||
!facetoolStrength
}
onClick={handleClickFixFaces}
/>
}
>
<IAIIconButton icon={<MdFace />} aria-label="Restore Faces" />
</InvokePopover>
<InvokePopover
title="Upscale"
styleClass="upscale-popover"
popoverOptions={<UpscaleOptions />}
actionButton={
<IAIButton
label={'Upscale Image'}
isDisabled={
!isESRGANAvailable ||
Boolean(intermediateImage) ||
!(isConnected && !isProcessing) ||
!upscalingLevel
}
onClick={handleClickUpscale}
/>
}
>
<IAIIconButton icon={<MdHd />} aria-label="Upscale" />
</InvokePopover>
<IAIIconButton
icon={<MdInfo />}
tooltip="Details"
aria-label="Details"
onClick={handleClickShowImageDetails}
/>
<DeleteImageModal image={image}>
<IAIIconButton
icon={<MdDelete />}
tooltip="Delete Image"
aria-label="Delete Image"
isDisabled={
Boolean(intermediateImage) || !isConnected || isProcessing
<ButtonGroup isAttached={true}>
<IAIPopover
trigger="hover"
triggerComponent={
<IAIIconButton aria-label="Send to..." icon={<FaShareAlt />} />
}
>
<div className="current-image-send-to-popover">
<IAIButton
size={'sm'}
onClick={handleClickUseAsInitialImage}
leftIcon={<FaShare />}
>
Send to Image to Image
</IAIButton>
<IAIButton
size={'sm'}
onClick={handleSendToInpainting}
leftIcon={<FaShare />}
>
Send to Inpainting
</IAIButton>
<IAIButton
size={'sm'}
onClick={handleCopyImageLink}
leftIcon={<FaCopy />}
>
Copy Link to Image
</IAIButton>
<IAIButton leftIcon={<FaDownload />} size={'sm'}>
<Link download={true} href={image.url}>
Download Image
</Link>
</IAIButton>
</div>
</IAIPopover>
<IAIIconButton
icon={<FaQuoteRight />}
tooltip="Use Prompt"
aria-label="Use Prompt"
isDisabled={!image?.metadata?.image?.prompt}
onClick={handleClickUsePrompt}
/>
</DeleteImageModal>
<IAIIconButton
icon={<FaSeedling />}
tooltip="Use Seed"
aria-label="Use Seed"
isDisabled={!image?.metadata?.image?.seed}
onClick={handleClickUseSeed}
/>
<IAIIconButton
icon={<FaAsterisk />}
tooltip="Use All"
aria-label="Use All"
isDisabled={
!['txt2img', 'img2img'].includes(image?.metadata?.image?.type)
}
onClick={handleClickUseAllParameters}
/>
<IAIPopover
trigger="hover"
triggerComponent={
<IAIIconButton icon={<FaGrinStars />} aria-label="Restore Faces" />
}
>
<div className="current-image-postprocessing-popover">
<FaceRestoreOptions />
<IAIButton
isDisabled={
!isGFPGANAvailable ||
Boolean(intermediateImage) ||
!(isConnected && !isProcessing) ||
!facetoolStrength
}
onClick={handleClickFixFaces}
>
Restore Faces
</IAIButton>
</div>
</IAIPopover>
<IAIPopover
trigger="hover"
triggerComponent={
<IAIIconButton icon={<FaExpandArrowsAlt />} aria-label="Upscale" />
}
>
<div className="current-image-postprocessing-popover">
<UpscaleOptions />
<IAIButton
isDisabled={
!isESRGANAvailable ||
Boolean(intermediateImage) ||
!(isConnected && !isProcessing) ||
!upscalingLevel
}
onClick={handleClickUpscale}
>
Upscale Image
</IAIButton>
</div>
</IAIPopover>
<IAIIconButton
icon={<FaCode />}
tooltip="Details"
aria-label="Details"
data-selected={shouldShowImageDetails}
onClick={handleClickShowImageDetails}
/>
<DeleteImageModal image={image}>
<IAIIconButton
icon={<FaTrash />}
tooltip="Delete Image"
aria-label="Delete Image"
isDisabled={
Boolean(intermediateImage) || !isConnected || isProcessing
}
/>
</DeleteImageModal>
</ButtonGroup>
</div>
);
};

View File

@ -9,18 +9,6 @@
border-radius: 0.5rem;
}
.current-image-options {
width: 100%;
display: flex;
justify-content: center;
align-items: center;
column-gap: 0.5rem;
.chakra-popover__popper {
z-index: 11;
}
}
.current-image-preview {
position: relative;
justify-content: center;

View File

@ -1,35 +0,0 @@
.popover-content {
background-color: var(--background-color-secondary) !important;
border: none !important;
border-top: 0px;
background-color: var(--tab-hover-color);
border-radius: 0 0 0.4rem 0.4rem;
}
.popover-arrow {
background: var(--tab-hover-color) !important;
box-shadow: none;
}
.popover-options {
background: var(--tab-panel-bg);
border-radius: 0 0 0.4rem 0.4rem;
border: 2px solid var(--tab-hover-color);
padding: 0.75rem 1rem 0.75rem 1rem;
display: grid;
grid-auto-rows: max-content;
grid-row-gap: 0.5rem;
justify-content: space-between;
}
.popover-header {
background: var(--tab-hover-color);
border-radius: 0.4rem 0.4rem 0 0;
font-weight: bold;
border: none;
padding-left: 1rem !important;
}
.upscale-popover {
width: 23rem !important;
}

View File

@ -1,45 +0,0 @@
import {
Box,
Popover,
PopoverArrow,
PopoverContent,
PopoverHeader,
PopoverTrigger,
} from '@chakra-ui/react';
import React, { ReactNode } from 'react';
type PopoverProps = {
title?: string;
delay?: number;
styleClass?: string;
popoverOptions?: ReactNode;
actionButton?: ReactNode;
children: ReactNode;
};
const InvokePopover = ({
title = 'Popup',
styleClass,
delay = 50,
popoverOptions,
actionButton,
children,
}: PopoverProps) => {
return (
<Popover trigger={'hover'} closeDelay={delay}>
<PopoverTrigger>
<Box>{children}</Box>
</PopoverTrigger>
<PopoverContent className={`popover-content ${styleClass}`}>
<PopoverArrow className="popover-arrow" />
<PopoverHeader className="popover-header">{title}</PopoverHeader>
<div className="popover-options">
{popoverOptions ? popoverOptions : null}
{actionButton}
</div>
</PopoverContent>
</Popover>
);
};
export default InvokePopover;

View File

@ -17,7 +17,8 @@ const clearBrushHistorySelector = createSelector(
(inpainting: InpaintingState) => {
const { pastLines, futureLines } = inpainting;
return {
mayClearBrushHistory: futureLines.length > 0 || pastLines.length > 0 ? false : true
mayClearBrushHistory:
futureLines.length > 0 || pastLines.length > 0 ? false : true,
};
},
{
@ -44,11 +45,12 @@ export default function ClearBrushHistory() {
};
return (
<IAIButton
label="Clear Brush History"
onClick={handleClearBrushHistory}
tooltip="Clears brush stroke history"
disabled={mayClearBrushHistory}
styleClass="inpainting-options-btn"
/>
>
Clear Brush History
</IAIButton>
);
}

View File

@ -1,12 +1,13 @@
import { MdCancel } from 'react-icons/md';
import { cancelProcessing } from '../../../app/socketio/actions';
import { RootState, useAppDispatch, useAppSelector } from '../../../app/store';
import IAIIconButton from '../../../common/components/IAIIconButton';
import IAIIconButton, {
IAIIconButtonProps,
} from '../../../common/components/IAIIconButton';
import { useHotkeys } from 'react-hotkeys-hook';
import { createSelector } from '@reduxjs/toolkit';
import { SystemState } from '../../system/systemSlice';
import _ from 'lodash';
import { IAIButtonProps } from '../../../common/components/IAIButton';
const cancelButtonSelector = createSelector(
(state: RootState) => state.system,
@ -24,7 +25,9 @@ const cancelButtonSelector = createSelector(
}
);
export default function CancelButton(props: Omit<IAIButtonProps, 'label'>) {
export default function CancelButton(
props: Omit<IAIIconButtonProps, 'aria-label'>
) {
const { ...rest } = props;
const dispatch = useAppDispatch();
const { isProcessing, isConnected, isCancelable } =

View File

@ -7,11 +7,14 @@ import { useAppDispatch, useAppSelector } from '../../../app/store';
import IAIButton, {
IAIButtonProps,
} from '../../../common/components/IAIButton';
import IAIIconButton from '../../../common/components/IAIIconButton';
import IAIIconButton, {
IAIIconButtonProps,
} from '../../../common/components/IAIIconButton';
import IAIPopover from '../../../common/components/IAIPopover';
import { activeTabNameSelector } from '../optionsSelectors';
interface InvokeButton extends Omit<IAIButtonProps, 'label'> {
interface InvokeButton
extends Omit<IAIButtonProps | IAIIconButtonProps, 'aria-label'> {
iconButton?: boolean;
}
@ -35,38 +38,39 @@ export default function InvokeButton(props: InvokeButton) {
[isReady, activeTabName]
);
const buttonComponent = iconButton ? (
<IAIIconButton
aria-label="Invoke"
type="submit"
icon={<FaPlay />}
isDisabled={!isReady}
onClick={handleClickGenerate}
className="invoke-btn invoke"
tooltip="Invoke"
tooltipProps={{ placement: 'bottom' }}
{...rest}
/>
) : (
<IAIButton
label="Invoke"
aria-label="Invoke"
type="submit"
isDisabled={!isReady}
onClick={handleClickGenerate}
className="invoke-btn"
{...rest}
/>
const buttonComponent = (
<div style={{ flexGrow: 4 }}>
{iconButton ? (
<IAIIconButton
aria-label="Invoke"
type="submit"
icon={<FaPlay />}
isDisabled={!isReady}
onClick={handleClickGenerate}
className="invoke-btn invoke"
tooltip="Invoke"
tooltipProps={{ placement: 'bottom' }}
{...rest}
/>
) : (
<IAIButton
aria-label="Invoke"
type="submit"
isDisabled={!isReady}
onClick={handleClickGenerate}
className="invoke-btn"
{...rest}
>
Invoke
</IAIButton>
)}
</div>
);
return isReady ? (
buttonComponent
) : (
<IAIPopover
trigger="hover"
triggerContainerProps={{ style: { flexGrow: 4 } }}
triggerComponent={buttonComponent}
>
<IAIPopover trigger="hover" triggerComponent={buttonComponent}>
{reasonsWhyNotReady && (
<UnorderedList>
{reasonsWhyNotReady.map((reason, i) => (
@ -76,4 +80,20 @@ export default function InvokeButton(props: InvokeButton) {
)}
</IAIPopover>
);
// return isReady ? (
// buttonComponent
// ) : (
// <IAIPopover trigger="hover" triggerComponent={buttonComponent}>
// {reasonsWhyNotReady ? (
// <UnorderedList>
// {reasonsWhyNotReady.map((reason, i) => (
// <ListItem key={i}>{reason}</ListItem>
// ))}
// </UnorderedList>
// ) : (
// 'test'
// )}
// </IAIPopover>
// );
}

View File

@ -73,15 +73,20 @@ export default function HotkeysModal({ children }: HotkeysModalProps) {
const generalHotkeys = [
{
title: 'Set Parameters',
desc: 'Use all parameters of the current image',
hotkey: 'A',
title: 'Set Prompt',
desc: 'Use the prompt of the current image',
hotkey: 'P',
},
{
title: 'Set Seed',
desc: 'Use the seed of the current image',
hotkey: 'S',
},
{
title: 'Set Parameters',
desc: 'Use all parameters of the current image',
hotkey: 'A',
},
{ title: 'Restore Faces', desc: 'Restore the current image', hotkey: 'R' },
{ title: 'Upscale', desc: 'Upscale the current image', hotkey: 'U' },
{

View File

@ -28,9 +28,9 @@
// gallery
@use '../features/gallery/CurrentImageDisplay.scss';
@use '../features/gallery/CurrentImageButtons.scss';
@use '../features/gallery/ImageGallery.scss';
@use '../features/gallery/HoverableImage.scss';
@use '../features/gallery/InvokePopover.scss';
@use '../features/gallery/ImageMetaDataViewer/ImageMetadataViewer.scss';
// Tabs
@ -47,6 +47,7 @@
@use '../common/components/IAINumberInput.scss';
@use '../common/components/IAIInput.scss';
@use '../common/components/IAIIconButton.scss';
@use '../common/components/IAIButton.scss';
@use '../common/components/IAISwitch.scss';
@use '../common/components/IAISelect.scss';
@use '../common/components/IAISlider.scss';