diff --git a/frontend/src/common/components/IAIAlertDialog.tsx b/frontend/src/common/components/IAIAlertDialog.tsx new file mode 100644 index 0000000000..1ea35f5881 --- /dev/null +++ b/frontend/src/common/components/IAIAlertDialog.tsx @@ -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(null); + + const handleAccept = () => { + acceptCallback(); + onClose(); + }; + + const handleCancel = () => { + cancelCallback && cancelCallback(); + onClose(); + }; + + return ( + <> + {cloneElement(triggerComponent, { + onClick: onOpen, + ref: ref, + })} + + + + + + {title} + + + {children} + + + + + + + + + + ); +}); +export default IAIAlertDialog; diff --git a/frontend/src/common/components/IAISelect.tsx b/frontend/src/common/components/IAISelect.tsx index f4f74f5c83..c21135db00 100644 --- a/frontend/src/common/components/IAISelect.tsx +++ b/frontend/src/common/components/IAISelect.tsx @@ -33,29 +33,28 @@ const IAISelect = (props: IAISelectProps) => { ...rest } = props; return ( - - ) => { - e.stopPropagation(); - e.nativeEvent.stopImmediatePropagation(); - e.nativeEvent.stopPropagation(); - e.nativeEvent.cancelBubble = true; - }} - > - {label && ( - - {label} - - )} - + ) => { + e.stopPropagation(); + e.nativeEvent.stopImmediatePropagation(); + e.nativeEvent.stopPropagation(); + e.nativeEvent.cancelBubble = true; + }} + > + {label && ( + + {label} + + )} + - - + + ); }; diff --git a/frontend/src/features/canvas/components/ClearCanvasHistoryButtonModal.tsx b/frontend/src/features/canvas/components/ClearCanvasHistoryButtonModal.tsx new file mode 100644 index 0000000000..81e220718e --- /dev/null +++ b/frontend/src/features/canvas/components/ClearCanvasHistoryButtonModal.tsx @@ -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 ( + dispatch(clearCanvasHistory())} + acceptButtonText={'Clear History'} + triggerComponent={ + }> + Clear Canvas History + + } + > +

+ Clearing the canvas history leaves your current canvas intact, but + irreversibly clears the undo and redo history. +

+
+

Are you sure you want to clear the canvas history?

+
+ ); +}; +export default ClearCanvasHistoryButtonModal; diff --git a/frontend/src/features/canvas/components/IAICanvasToolbar/IAICanvasMaskOptions.tsx b/frontend/src/features/canvas/components/IAICanvasToolbar/IAICanvasMaskOptions.tsx index 5036dfbc44..6bccd20d3b 100644 --- a/frontend/src/features/canvas/components/IAICanvasToolbar/IAICanvasMaskOptions.tsx +++ b/frontend/src/features/canvas/components/IAICanvasToolbar/IAICanvasMaskOptions.tsx @@ -99,58 +99,41 @@ const IAICanvasMaskOptions = () => { dispatch(setIsMaskEnabled(!isMaskEnabled)); return ( - <> - ) => - dispatch(setLayer(e.target.value as CanvasLayer)) - } - /> - - } - isDisabled={layer !== 'mask'} - /> - - } - > - - + } /> - - dispatch(setShouldPreserveMaskedArea(e.target.checked)) - } - /> - dispatch(setMaskColor(newColor))} - /> - } - onClick={handleClearMask} - > - Clear Mask (Shift+C) - - - - + + } + > + + + + dispatch(setShouldPreserveMaskedArea(e.target.checked)) + } + /> + dispatch(setMaskColor(newColor))} + /> + } onClick={handleClearMask}> + Clear Mask (Shift+C) + + + ); }; diff --git a/frontend/src/features/canvas/components/IAICanvasToolbar/IAICanvasSettingsButtonPopover.tsx b/frontend/src/features/canvas/components/IAICanvasToolbar/IAICanvasSettingsButtonPopover.tsx index bfe424e8cd..fbd844b53d 100644 --- a/frontend/src/features/canvas/components/IAICanvasToolbar/IAICanvasSettingsButtonPopover.tsx +++ b/frontend/src/features/canvas/components/IAICanvasToolbar/IAICanvasSettingsButtonPopover.tsx @@ -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)) } /> - } - onClick={() => dispatch(clearCanvasHistory())} - > - Clear Canvas History - - + + ); diff --git a/frontend/src/features/canvas/components/IAICanvasToolbar/IAICanvasToolbar.tsx b/frontend/src/features/canvas/components/IAICanvasToolbar/IAICanvasToolbar.tsx index e974eb0e7d..5cd18e691f 100644 --- a/frontend/src/features/canvas/components/IAICanvasToolbar/IAICanvasToolbar.tsx +++ b/frontend/src/features/canvas/components/IAICanvasToolbar/IAICanvasToolbar.tsx @@ -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) => { + const newLayer = e.target.value as CanvasLayer; + dispatch(setLayer(newLayer)); + if (newLayer === 'mask' && !isMaskEnabled) { + dispatch(setIsMaskEnabled(true)); + } + }; + return (
+ + diff --git a/frontend/src/features/system/components/ClearTempFolderButtonModal.tsx b/frontend/src/features/system/components/ClearTempFolderButtonModal.tsx index a8ff929dc1..0021af4bf0 100644 --- a/frontend/src/features/system/components/ClearTempFolderButtonModal.tsx +++ b/frontend/src/features/system/components/ClearTempFolderButtonModal.tsx @@ -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(null); - const handleClear = () => { + const acceptCallback = () => { dispatch(emptyTempFolder()); - dispatch(clearCanvasHistory()); dispatch(resetCanvas()); - onClose(); + dispatch(clearCanvasHistory()); }; return ( - <> - } size={'sm'} onClick={onOpen}> - Clear Temp Image Folder - - - - - - - Clear Temp Image Folder - - - -

- 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. -

-
-

Are you sure you want to clear the temp folder?

-
- - - - - -
-
-
- + } size={'sm'}> + Empty Temp Image Folder + + } + > +

+ 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. +

+
+

Are you sure you want to empty the temp folder?

+
); }; -export default ClearTempFolderButtonModal; +export default EmptyTempFolderButtonModal; diff --git a/frontend/src/features/system/components/SettingsModal/SettingsModal.tsx b/frontend/src/features/system/components/SettingsModal/SettingsModal.tsx index 5bf25988cc..6cb3bda431 100644 --- a/frontend/src/features/system/components/SettingsModal/SettingsModal.tsx +++ b/frontend/src/features/system/components/SettingsModal/SettingsModal.tsx @@ -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,