Adds IAIAlertDialog component

This commit is contained in:
psychedelicious 2022-11-21 19:13:25 +11:00 committed by blessedcoolant
parent 089c85a017
commit b0810e1ed7
8 changed files with 238 additions and 150 deletions

View File

@ -0,0 +1,86 @@
import {
AlertDialog,
AlertDialogBody,
AlertDialogContent,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogOverlay,
Button,
forwardRef,
useDisclosure,
} from '@chakra-ui/react';
import { cloneElement, ReactElement, ReactNode, useRef } from 'react';
type Props = {
acceptButtonText?: string;
acceptCallback: () => void;
cancelButtonText?: string;
cancelCallback?: () => void;
children: ReactNode;
title: string;
triggerComponent: ReactElement;
};
const IAIAlertDialog = forwardRef((props: Props, ref) => {
const {
acceptButtonText = 'Accept',
acceptCallback,
cancelButtonText = 'Cancel',
cancelCallback,
children,
title,
triggerComponent,
} = props;
const { isOpen, onOpen, onClose } = useDisclosure();
const cancelRef = useRef<HTMLButtonElement | null>(null);
const handleAccept = () => {
acceptCallback();
onClose();
};
const handleCancel = () => {
cancelCallback && cancelCallback();
onClose();
};
return (
<>
{cloneElement(triggerComponent, {
onClick: onOpen,
ref: ref,
})}
<AlertDialog
isOpen={isOpen}
leastDestructiveRef={cancelRef}
onClose={onClose}
>
<AlertDialogOverlay>
<AlertDialogContent className="modal">
<AlertDialogHeader fontSize="lg" fontWeight="bold">
{title}
</AlertDialogHeader>
<AlertDialogBody>{children}</AlertDialogBody>
<AlertDialogFooter>
<Button
ref={cancelRef}
onClick={handleCancel}
className="modal-close-btn"
>
{cancelButtonText}
</Button>
<Button colorScheme="red" onClick={handleAccept} ml={3}>
{acceptButtonText}
</Button>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialogOverlay>
</AlertDialog>
</>
);
});
export default IAIAlertDialog;

View File

@ -33,29 +33,28 @@ const IAISelect = (props: IAISelectProps) => {
...rest
} = props;
return (
<Tooltip label={tooltip} {...tooltipProps}>
<FormControl
isDisabled={isDisabled}
className={`invokeai__select ${styleClass}`}
onClick={(e: MouseEvent<HTMLDivElement>) => {
e.stopPropagation();
e.nativeEvent.stopImmediatePropagation();
e.nativeEvent.stopPropagation();
e.nativeEvent.cancelBubble = true;
}}
>
{label && (
<FormLabel
className="invokeai__select-label"
fontSize={fontSize}
marginBottom={1}
flexGrow={2}
whiteSpace="nowrap"
>
{label}
</FormLabel>
)}
<FormControl
isDisabled={isDisabled}
className={`invokeai__select ${styleClass}`}
onClick={(e: MouseEvent<HTMLDivElement>) => {
e.stopPropagation();
e.nativeEvent.stopImmediatePropagation();
e.nativeEvent.stopPropagation();
e.nativeEvent.cancelBubble = true;
}}
>
{label && (
<FormLabel
className="invokeai__select-label"
fontSize={fontSize}
marginBottom={1}
flexGrow={2}
whiteSpace="nowrap"
>
{label}
</FormLabel>
)}
<Tooltip label={tooltip} {...tooltipProps}>
<Select
className="invokeai__select-picker"
fontSize={fontSize}
@ -78,8 +77,8 @@ const IAISelect = (props: IAISelectProps) => {
);
})}
</Select>
</FormControl>
</Tooltip>
</Tooltip>
</FormControl>
);
};

View File

@ -0,0 +1,30 @@
import { useAppDispatch } from 'app/store';
import IAIAlertDialog from 'common/components/IAIAlertDialog';
import IAIButton from 'common/components/IAIButton';
import { clearCanvasHistory } from 'features/canvas/store/canvasSlice';
import { FaTrash } from 'react-icons/fa';
const ClearCanvasHistoryButtonModal = () => {
const dispatch = useAppDispatch();
return (
<IAIAlertDialog
title={'Clear Canvas History'}
acceptCallback={() => dispatch(clearCanvasHistory())}
acceptButtonText={'Clear History'}
triggerComponent={
<IAIButton size={'sm'} leftIcon={<FaTrash />}>
Clear Canvas History
</IAIButton>
}
>
<p>
Clearing the canvas history leaves your current canvas intact, but
irreversibly clears the undo and redo history.
</p>
<br />
<p>Are you sure you want to clear the canvas history?</p>
</IAIAlertDialog>
);
};
export default ClearCanvasHistoryButtonModal;

View File

@ -99,58 +99,41 @@ const IAICanvasMaskOptions = () => {
dispatch(setIsMaskEnabled(!isMaskEnabled));
return (
<>
<IAISelect
label={'Layer (Q)'}
tooltipProps={{ hasArrow: true, placement: 'top' }}
value={layer}
validValues={LAYER_NAMES_DICT}
onChange={(e: ChangeEvent<HTMLSelectElement>) =>
dispatch(setLayer(e.target.value as CanvasLayer))
}
/>
<IAIPopover
isOpen={layer !== 'mask' ? false : undefined}
trigger="hover"
triggerComponent={
<ButtonGroup>
<IAIIconButton
aria-label="Masking Options"
tooltip="Masking Options"
icon={<FaMask />}
isDisabled={layer !== 'mask'}
/>
</ButtonGroup>
}
>
<Flex direction={'column'} gap={'0.5rem'}>
<IAICheckbox
label="Enable Mask (H)"
isChecked={isMaskEnabled}
onChange={handleToggleEnableMask}
<IAIPopover
trigger="hover"
triggerComponent={
<ButtonGroup>
<IAIIconButton
aria-label="Masking Options"
tooltip="Masking Options"
icon={<FaMask />}
/>
<IAICheckbox
label="Preserve Masked Area"
isChecked={shouldPreserveMaskedArea}
onChange={(e) =>
dispatch(setShouldPreserveMaskedArea(e.target.checked))
}
/>
<IAIColorPicker
style={{ paddingTop: '0.5rem', paddingBottom: '0.5rem' }}
color={maskColor}
onChange={(newColor) => dispatch(setMaskColor(newColor))}
/>
<IAIButton
size={'sm'}
leftIcon={<FaTrash />}
onClick={handleClearMask}
>
Clear Mask (Shift+C)
</IAIButton>
</Flex>
</IAIPopover>
</>
</ButtonGroup>
}
>
<Flex direction={'column'} gap={'0.5rem'}>
<IAICheckbox
label="Enable Mask (H)"
isChecked={isMaskEnabled}
onChange={handleToggleEnableMask}
/>
<IAICheckbox
label="Preserve Masked Area"
isChecked={shouldPreserveMaskedArea}
onChange={(e) =>
dispatch(setShouldPreserveMaskedArea(e.target.checked))
}
/>
<IAIColorPicker
style={{ paddingTop: '0.5rem', paddingBottom: '0.5rem' }}
color={maskColor}
onChange={(newColor) => dispatch(setMaskColor(newColor))}
/>
<IAIButton size={'sm'} leftIcon={<FaTrash />} onClick={handleClearMask}>
Clear Mask (Shift+C)
</IAIButton>
</Flex>
</IAIPopover>
);
};

View File

@ -18,7 +18,8 @@ import IAIPopover from 'common/components/IAIPopover';
import IAICheckbox from 'common/components/IAICheckbox';
import { canvasSelector } from 'features/canvas/store/canvasSelectors';
import IAIButton from 'common/components/IAIButton';
import ClearTempFolderButtonModal from 'features/system/components/ClearTempFolderButtonModal';
import EmptyTempFolderButtonModal from 'features/system/components/ClearTempFolderButtonModal';
import ClearCanvasHistoryButtonModal from '../ClearCanvasHistoryButtonModal';
export const canvasControlsSelector = createSelector(
[canvasSelector],
@ -117,14 +118,8 @@ const IAICanvasSettingsButtonPopover = () => {
dispatch(setShouldShowCanvasDebugInfo(e.target.checked))
}
/>
<IAIButton
size={'sm'}
leftIcon={<FaTrash />}
onClick={() => dispatch(clearCanvasHistory())}
>
Clear Canvas History
</IAIButton>
<ClearTempFolderButtonModal />
<ClearCanvasHistoryButtonModal />
<EmptyTempFolderButtonModal />
</Flex>
</IAIPopover>
);

View File

@ -4,6 +4,8 @@ import {
resetCanvas,
resetCanvasView,
resizeAndScaleCanvas,
setIsMaskEnabled,
setLayer,
setTool,
} from 'features/canvas/store/canvasSlice';
import { useAppDispatch, useAppSelector } from 'app/store';
@ -33,17 +35,26 @@ import {
canvasSelector,
isStagingSelector,
} from 'features/canvas/store/canvasSelectors';
import IAISelect from 'common/components/IAISelect';
import {
CanvasLayer,
LAYER_NAMES_DICT,
} from 'features/canvas/store/canvasTypes';
import { ChangeEvent } from 'react';
export const selector = createSelector(
[systemSelector, canvasSelector, isStagingSelector],
(system, canvas, isStaging) => {
const { isProcessing } = system;
const { tool, shouldCropToBoundingBoxOnSave } = canvas;
const { tool, shouldCropToBoundingBoxOnSave, layer, isMaskEnabled } =
canvas;
return {
isProcessing,
isStaging,
isMaskEnabled,
tool,
layer,
shouldCropToBoundingBoxOnSave,
};
},
@ -56,8 +67,14 @@ export const selector = createSelector(
const IAICanvasOutpaintingControls = () => {
const dispatch = useAppDispatch();
const { isProcessing, isStaging, tool, shouldCropToBoundingBoxOnSave } =
useAppSelector(selector);
const {
isProcessing,
isStaging,
isMaskEnabled,
layer,
tool,
shouldCropToBoundingBoxOnSave,
} = useAppSelector(selector);
const canvasBaseLayer = getCanvasBaseLayer();
const { openUploader } = useImageUploader();
@ -193,8 +210,24 @@ const IAICanvasOutpaintingControls = () => {
);
};
const handleChangeLayer = (e: ChangeEvent<HTMLSelectElement>) => {
const newLayer = e.target.value as CanvasLayer;
dispatch(setLayer(newLayer));
if (newLayer === 'mask' && !isMaskEnabled) {
dispatch(setIsMaskEnabled(true));
}
};
return (
<div className="inpainting-settings">
<IAISelect
tooltip={'Layer (Q)'}
tooltipProps={{ hasArrow: true, placement: 'top' }}
value={layer}
validValues={LAYER_NAMES_DICT}
onChange={handleChangeLayer}
/>
<IAICanvasMaskOptions />
<IAICanvasToolChooserOptions />

View File

@ -1,79 +1,41 @@
import {
AlertDialog,
AlertDialogBody,
AlertDialogContent,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogOverlay,
Button,
Flex,
useDisclosure,
} from '@chakra-ui/react';
import { emptyTempFolder } from 'app/socketio/actions';
import { useAppDispatch } from 'app/store';
import IAIAlertDialog from 'common/components/IAIAlertDialog';
import IAIButton from 'common/components/IAIButton';
import {
clearCanvasHistory,
resetCanvas,
} from 'features/canvas/store/canvasSlice';
import { useRef } from 'react';
import { FaTrash } from 'react-icons/fa';
const ClearTempFolderButtonModal = () => {
const EmptyTempFolderButtonModal = () => {
const dispatch = useAppDispatch();
const { isOpen, onOpen, onClose } = useDisclosure();
const cancelRef = useRef<HTMLButtonElement | null>(null);
const handleClear = () => {
const acceptCallback = () => {
dispatch(emptyTempFolder());
dispatch(clearCanvasHistory());
dispatch(resetCanvas());
onClose();
dispatch(clearCanvasHistory());
};
return (
<>
<IAIButton leftIcon={<FaTrash />} size={'sm'} onClick={onOpen}>
Clear Temp Image Folder
</IAIButton>
<AlertDialog
isOpen={isOpen}
leastDestructiveRef={cancelRef}
onClose={onClose}
>
<AlertDialogOverlay>
<AlertDialogContent className="modal">
<AlertDialogHeader fontSize="lg" fontWeight="bold">
Clear Temp Image Folder
</AlertDialogHeader>
<AlertDialogBody>
<p>
Clearing the temp image folder also fully resets the Unified
Canvas. This includes all undo/redo history, images in the
staging area, and the canvas base layer.
</p>
<br />
<p>Are you sure you want to clear the temp folder?</p>
</AlertDialogBody>
<AlertDialogFooter>
<Button
ref={cancelRef}
onClick={onClose}
className="modal-close-btn"
>
Cancel
</Button>
<Button colorScheme="red" onClick={handleClear} ml={3}>
Clear
</Button>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialogOverlay>
</AlertDialog>
</>
<IAIAlertDialog
title={'Empty Temp Image Folder'}
acceptCallback={acceptCallback}
acceptButtonText={'Empty Folder'}
triggerComponent={
<IAIButton leftIcon={<FaTrash />} size={'sm'}>
Empty Temp Image Folder
</IAIButton>
}
>
<p>
Emptying the temp image folder also fully resets the Unified Canvas.
This includes all undo/redo history, images in the staging area, and the
canvas base layer.
</p>
<br />
<p>Are you sure you want to empty the temp folder?</p>
</IAIAlertDialog>
);
};
export default ClearTempFolderButtonModal;
export default EmptyTempFolderButtonModal;

View File

@ -31,7 +31,7 @@ import { IN_PROGRESS_IMAGE_TYPES } from 'app/constants';
import IAISwitch from 'common/components/IAISwitch';
import IAISelect from 'common/components/IAISelect';
import IAINumberInput from 'common/components/IAINumberInput';
import ClearTempFolderButtonModal from '../ClearTempFolderButtonModal';
import EmptyTempFolderButtonModal from '../ClearTempFolderButtonModal';
const systemSelector = createSelector(
(state: RootState) => state.system,