diff --git a/invokeai/frontend/web/src/app/types/invokeai.ts b/invokeai/frontend/web/src/app/types/invokeai.ts index 237d213ce5..1a540d6743 100644 --- a/invokeai/frontend/web/src/app/types/invokeai.ts +++ b/invokeai/frontend/web/src/app/types/invokeai.ts @@ -27,7 +27,8 @@ export type AppFeature = | 'bulkDownload' | 'starterModels' | 'hfToken' - | 'retryQueueItem'; + | 'retryQueueItem' + | 'cancelAndClearAll'; /** * A disable-able Stable Diffusion feature */ diff --git a/invokeai/frontend/web/src/features/queue/components/CancelAllExceptCurrentButton.tsx b/invokeai/frontend/web/src/features/queue/components/CancelAllExceptCurrentButton.tsx new file mode 100644 index 0000000000..25b518296b --- /dev/null +++ b/invokeai/frontend/web/src/features/queue/components/CancelAllExceptCurrentButton.tsx @@ -0,0 +1,32 @@ +import type { ButtonProps } from '@invoke-ai/ui-library'; +import { Button } from '@invoke-ai/ui-library'; +import { useCancelAllExceptCurrentQueueItemDialog } from 'features/queue/components/CancelAllExceptCurrentQueueItemConfirmationAlertDialog'; +import { memo } from 'react'; +import { useTranslation } from 'react-i18next'; +import { PiXCircle } from 'react-icons/pi'; + +type Props = ButtonProps; + +export const CancelAllExceptCurrentButton = memo((props: Props) => { + const { t } = useTranslation(); + const cancelAllExceptCurrent = useCancelAllExceptCurrentQueueItemDialog(); + + return ( + <> + + + ); +}); + +CancelAllExceptCurrentButton.displayName = 'CancelAllExceptCurrentButton'; diff --git a/invokeai/frontend/web/src/features/queue/components/ClearQueueIconButton.tsx b/invokeai/frontend/web/src/features/queue/components/ClearQueueIconButton.tsx index 51715d0b67..348dd38ada 100644 --- a/invokeai/frontend/web/src/features/queue/components/ClearQueueIconButton.tsx +++ b/invokeai/frontend/web/src/features/queue/components/ClearQueueIconButton.tsx @@ -1,33 +1,89 @@ import { IconButton, useShiftModifier } from '@invoke-ai/ui-library'; +import { useCancelAllExceptCurrentQueueItemDialog } from 'features/queue/components/CancelAllExceptCurrentQueueItemConfirmationAlertDialog'; import { useCancelCurrentQueueItem } from 'features/queue/hooks/useCancelCurrentQueueItem'; +import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus'; import { memo } from 'react'; import { useTranslation } from 'react-i18next'; -import { PiTrashSimpleBold, PiXBold } from 'react-icons/pi'; +import { PiTrashSimpleBold, PiXBold, PiXCircle } from 'react-icons/pi'; import { useClearQueueDialog } from './ClearQueueConfirmationAlertDialog'; -export const ClearQueueIconButton = memo((_) => { - const { t } = useTranslation(); - const clearQueue = useClearQueueDialog(); - const cancelCurrentQueueItem = useCancelCurrentQueueItem(); - - // Show the single item clear button when shift is pressed - // Otherwise show the clear queue button +export const ClearQueueIconButton = memo(() => { + const isCancelAndClearAllEnabled = useFeatureStatus('cancelAndClearAll'); const shift = useShiftModifier(); + if (!shift) { + // Shift is not pressed - show cancel current + return ; + } + + if (isCancelAndClearAllEnabled) { + // Shift is pressed and cancel and clear all is enabled - show cancel and clear all + return ; + } + + // Shift is pressed and cancel and clear all is disabled - show cancel all except current + return ; +}); + +ClearQueueIconButton.displayName = 'ClearQueueIconButton'; + +const CancelCurrentIconButton = memo(() => { + const { t } = useTranslation(); + const cancelCurrentQueueItem = useCancelCurrentQueueItem(); + return ( : } + isDisabled={cancelCurrentQueueItem.isDisabled} + isLoading={cancelCurrentQueueItem.isLoading} + aria-label={t('queue.cancel')} + tooltip={t('queue.cancelTooltip')} + icon={} colorScheme="error" - onClick={shift ? clearQueue.openDialog : cancelCurrentQueueItem.cancelQueueItem} - data-testid={shift ? t('queue.clear') : t('queue.cancel')} + onClick={cancelCurrentQueueItem.cancelQueueItem} /> ); }); -ClearQueueIconButton.displayName = 'ClearQueueIconButton'; +CancelCurrentIconButton.displayName = 'CancelCurrentIconButton'; + +const CancelAndClearAllIconButton = memo(() => { + const { t } = useTranslation(); + const clearQueue = useClearQueueDialog(); + + return ( + } + colorScheme="error" + onClick={clearQueue.openDialog} + /> + ); +}); + +CancelAndClearAllIconButton.displayName = 'CancelAndClearAllIconButton'; + +const CancelAllExceptCurrentIconButton = memo(() => { + const { t } = useTranslation(); + const cancelAllExceptCurrent = useCancelAllExceptCurrentQueueItemDialog(); + + return ( + } + colorScheme="error" + onClick={cancelAllExceptCurrent.openDialog} + /> + ); +}); + +CancelAllExceptCurrentIconButton.displayName = 'CancelAllExceptCurrentIconButton'; diff --git a/invokeai/frontend/web/src/features/queue/components/QueueActionsMenuButton.tsx b/invokeai/frontend/web/src/features/queue/components/QueueActionsMenuButton.tsx index 2a9f89b0f3..42b8d704bd 100644 --- a/invokeai/frontend/web/src/features/queue/components/QueueActionsMenuButton.tsx +++ b/invokeai/frontend/web/src/features/queue/components/QueueActionsMenuButton.tsx @@ -27,6 +27,7 @@ export const QueueActionsMenuButton = memo(() => { const { t } = useTranslation(); const isPauseEnabled = useFeatureStatus('pauseQueue'); const isResumeEnabled = useFeatureStatus('resumeQueue'); + const isCancelAndClearAllEnabled = useFeatureStatus('cancelAndClearAll'); const cancelAllExceptCurrent = useCancelAllExceptCurrentQueueItemDialog(); const cancelCurrent = useCancelCurrentQueueItem(); const clearQueue = useClearQueueDialog(); @@ -71,15 +72,17 @@ export const QueueActionsMenuButton = memo(() => { > {t('queue.cancelAllExceptCurrentTooltip')} - } - onClick={clearQueue.openDialog} - isLoading={clearQueue.isLoading} - isDisabled={clearQueue.isDisabled} - > - {t('queue.clearTooltip')} - + {isCancelAndClearAllEnabled && ( + } + onClick={clearQueue.openDialog} + isLoading={clearQueue.isLoading} + isDisabled={clearQueue.isDisabled} + > + {t('queue.clearTooltip')} + + )} {isResumeEnabled && ( } diff --git a/invokeai/frontend/web/src/features/queue/components/QueueTabQueueControls.tsx b/invokeai/frontend/web/src/features/queue/components/QueueTabQueueControls.tsx index 59bd02a737..7e679caf74 100644 --- a/invokeai/frontend/web/src/features/queue/components/QueueTabQueueControls.tsx +++ b/invokeai/frontend/web/src/features/queue/components/QueueTabQueueControls.tsx @@ -1,5 +1,6 @@ /* eslint-disable i18next/no-literal-string */ import { ButtonGroup, Flex } from '@invoke-ai/ui-library'; +import { CancelAllExceptCurrentButton } from 'features/queue/components/CancelAllExceptCurrentButton'; import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus'; import { memo } from 'react'; @@ -12,6 +13,8 @@ import ResumeProcessorButton from './ResumeProcessorButton'; const QueueTabQueueControls = () => { const isPauseEnabled = useFeatureStatus('pauseQueue'); const isResumeEnabled = useFeatureStatus('resumeQueue'); + const isCancelAndClearAllEnabled = useFeatureStatus('cancelAndClearAll'); + return ( @@ -25,7 +28,8 @@ const QueueTabQueueControls = () => { )} - + {isCancelAndClearAllEnabled && } + {!isCancelAndClearAllEnabled && } diff --git a/invokeai/frontend/web/src/features/ui/components/AppContent.tsx b/invokeai/frontend/web/src/features/ui/components/AppContent.tsx index bbcfdf2ad9..665ac99db0 100644 --- a/invokeai/frontend/web/src/features/ui/components/AppContent.tsx +++ b/invokeai/frontend/web/src/features/ui/components/AppContent.tsx @@ -141,7 +141,7 @@ export const AppContent = memo(() => { )} - {withLeftPanel && } + {withLeftPanel && } {withRightPanel && } {withRightPanel && ( diff --git a/invokeai/frontend/web/src/features/ui/components/FloatingParametersPanelButtons.tsx b/invokeai/frontend/web/src/features/ui/components/FloatingParametersPanelButtons.tsx index bf09f47c34..f0de83b2af 100644 --- a/invokeai/frontend/web/src/features/ui/components/FloatingParametersPanelButtons.tsx +++ b/invokeai/frontend/web/src/features/ui/components/FloatingParametersPanelButtons.tsx @@ -3,11 +3,12 @@ import { useAppSelector } from 'app/store/storeHooks'; import { ToolChooser } from 'features/controlLayers/components/Tool/ToolChooser'; import { CanvasManagerProviderGate } from 'features/controlLayers/contexts/CanvasManagerProviderGate'; import { useImageViewer } from 'features/gallery/components/ImageViewer/useImageViewer'; +import { useCancelAllExceptCurrentQueueItemDialog } from 'features/queue/components/CancelAllExceptCurrentQueueItemConfirmationAlertDialog'; import { useClearQueueDialog } from 'features/queue/components/ClearQueueConfirmationAlertDialog'; import { InvokeButtonTooltip } from 'features/queue/components/InvokeButtonTooltip/InvokeButtonTooltip'; import { useCancelCurrentQueueItem } from 'features/queue/hooks/useCancelCurrentQueueItem'; import { useInvoke } from 'features/queue/hooks/useInvoke'; -import type { UsePanelReturn } from 'features/ui/hooks/usePanel'; +import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus'; import { selectActiveTab } from 'features/ui/store/uiSelectors'; import { memo, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; @@ -18,22 +19,53 @@ import { PiSparkleFill, PiTrashSimpleBold, PiXBold, + PiXCircle, } from 'react-icons/pi'; import { useGetQueueStatusQuery } from 'services/api/endpoints/queue'; type Props = { - panelApi: UsePanelReturn; + togglePanel: () => void; }; -const FloatingSidePanelButtons = (props: Props) => { +const FloatingSidePanelButtons = ({ togglePanel }: Props) => { + const { t } = useTranslation(); + const tab = useAppSelector(selectActiveTab); + const imageViewer = useImageViewer(); + const isCancelAndClearAllEnabled = useFeatureStatus('cancelAndClearAll'); + + return ( + + {tab === 'canvas' && !imageViewer.isOpen && ( + + + + )} + + + } + flexGrow={1} + /> + + + + {/* Show the cancel all except current button instead of cancel and clear all when it is disabled */} + {isCancelAndClearAllEnabled && } + {!isCancelAndClearAllEnabled && } + + + ); +}; + +export default memo(FloatingSidePanelButtons); + +const InvokeIconButton = memo(() => { const { t } = useTranslation(); const queue = useInvoke(); const shift = useShiftModifier(); - const tab = useAppSelector(selectActiveTab); - const imageViewer = useImageViewer(); - const clearQueue = useClearQueueDialog(); const { data: queueStatus } = useGetQueueStatusQuery(); - const cancelCurrent = useCancelCurrentQueueItem(); const queueButtonIcon = useMemo(() => { const isProcessing = (queueStatus?.queue.in_progress ?? 0) > 0; @@ -47,59 +79,80 @@ const FloatingSidePanelButtons = (props: Props) => { }, [queue.isDisabled, queueStatus?.queue.in_progress, shift]); return ( - - {tab === 'canvas' && !imageViewer.isOpen && ( - - - - )} - - - } - flexGrow={1} - /> - - - - - - } - onClick={cancelCurrent.cancelQueueItem} - colorScheme="error" - flexGrow={1} - /> - - - - } - colorScheme="error" - onClick={clearQueue.openDialog} - data-testid={t('queue.clear')} - flexGrow={1} - /> - - - + + + ); -}; +}); +InvokeIconButton.displayName = 'InvokeIconButton'; -export default memo(FloatingSidePanelButtons); +const CancelCurrentIconButton = memo(() => { + const { t } = useTranslation(); + const cancelCurrentQueueItem = useCancelCurrentQueueItem(); + + return ( + + } + onClick={cancelCurrentQueueItem.cancelQueueItem} + colorScheme="error" + flexGrow={1} + /> + + ); +}); + +CancelCurrentIconButton.displayName = 'CancelCurrentIconButton'; + +const CancelAndClearAllIconButton = memo(() => { + const { t } = useTranslation(); + const clearQueue = useClearQueueDialog(); + + return ( + + } + colorScheme="error" + onClick={clearQueue.openDialog} + flexGrow={1} + /> + + ); +}); + +CancelAndClearAllIconButton.displayName = 'CancelAndClearAllIconButton'; + +const CancelAllExceptCurrentIconButton = memo(() => { + const { t } = useTranslation(); + const cancelAllExceptCurrent = useCancelAllExceptCurrentQueueItemDialog(); + + return ( + + } + colorScheme="error" + onClick={cancelAllExceptCurrent.openDialog} + flexGrow={1} + /> + + ); +}); + +CancelAllExceptCurrentIconButton.displayName = 'CancelAllExceptCurrentIconButton';