From 1f0a154c374b0b87054431f9a557374cb9349b16 Mon Sep 17 00:00:00 2001 From: Riku Date: Thu, 28 Nov 2024 15:39:18 +0100 Subject: [PATCH] feat(ui): add cancel all except current queue item functionality --- invokeai/frontend/web/public/locales/en.json | 3 ++ ...urrentQueueItemConfirmationAlertDialog.tsx | 47 +++++++++++++++++++ .../components/QueueActionsMenuButton.tsx | 21 ++++++++- .../useCancelAllExceptCurrentQueueItem.ts | 44 +++++++++++++++++ .../web/src/services/api/endpoints/queue.ts | 28 +++++++++++ 5 files changed, 142 insertions(+), 1 deletion(-) create mode 100644 invokeai/frontend/web/src/features/queue/components/CancelAllExceptCurrentQueueItemConfirmationAlertDialog.tsx create mode 100644 invokeai/frontend/web/src/features/queue/hooks/useCancelAllExceptCurrentQueueItem.ts diff --git a/invokeai/frontend/web/public/locales/en.json b/invokeai/frontend/web/public/locales/en.json index d8957c0b1e2..e9576ff4e55 100644 --- a/invokeai/frontend/web/public/locales/en.json +++ b/invokeai/frontend/web/public/locales/en.json @@ -208,6 +208,9 @@ "pauseSucceeded": "Processor Paused", "pauseFailed": "Problem Pausing Processor", "cancel": "Cancel", + "cancelAllExceptCurrentQueueItemAlertDialog": "TODO Canceling the current queue item will immediately cancels any processing and the results will be lost. But will finish current in-progress items.", + "cancelAllExceptCurrentQueueItemAlertDialog2": "Are you sure you want to cancel all pending queue items?", + "cancelAllExceptCurrentTooltip": "Cancel All Except Current Item", "cancelTooltip": "Cancel Current Item", "cancelSucceeded": "Item Canceled", "cancelFailed": "Problem Canceling Item", diff --git a/invokeai/frontend/web/src/features/queue/components/CancelAllExceptCurrentQueueItemConfirmationAlertDialog.tsx b/invokeai/frontend/web/src/features/queue/components/CancelAllExceptCurrentQueueItemConfirmationAlertDialog.tsx new file mode 100644 index 00000000000..8049eef5a24 --- /dev/null +++ b/invokeai/frontend/web/src/features/queue/components/CancelAllExceptCurrentQueueItemConfirmationAlertDialog.tsx @@ -0,0 +1,47 @@ +import { ConfirmationAlertDialog, Text } from '@invoke-ai/ui-library'; +import { useAssertSingleton } from 'common/hooks/useAssertSingleton'; +import { buildUseBoolean } from 'common/hooks/useBoolean'; +import { useCancelAllExceptCurrentQueueItem } from 'features/queue/hooks/useCancelAllExceptCurrentQueueItem'; +import { memo } from 'react'; +import { useTranslation } from 'react-i18next'; + +const [useCancelAllExceptCurrentQueueItemConfirmationAlertDialog] = buildUseBoolean(false); + +export const useCancelAllExceptCurrentQueueItemDialog = () => { + const dialog = useCancelAllExceptCurrentQueueItemConfirmationAlertDialog(); + const { cancelAllExceptCurrentQueueItem, isLoading, isDisabled, queueStatus } = useCancelAllExceptCurrentQueueItem(); + + return { + cancelAllExceptCurrentQueueItem, + isOpen: dialog.isTrue, + openDialog: dialog.setTrue, + closeDialog: dialog.setFalse, + isLoading, + queueStatus, + isDisabled, + }; +}; + +export const CancelAllExceptCurrentQueueItemConfirmationAlertDialog = memo(() => { + useAssertSingleton('CancelAllExceptCurrentQueueItemConfirmationAlertDialog'); + const { t } = useTranslation(); + const cancelAllExceptCurrentQueueItem = useCancelAllExceptCurrentQueueItemDialog(); + + return ( + + {t('queue.cancelAllExceptCurrentQueueItemAlertDialog')} +
+ {t('queue.cancelAllExceptCurrentQueueItemAlertDialog2')} +
+ ); +}); + +CancelAllExceptCurrentQueueItemConfirmationAlertDialog.displayName = + 'CancelAllExceptCurrentQueueItemConfirmationAlertDialog'; diff --git a/invokeai/frontend/web/src/features/queue/components/QueueActionsMenuButton.tsx b/invokeai/frontend/web/src/features/queue/components/QueueActionsMenuButton.tsx index 27672cec839..2a9f89b0f33 100644 --- a/invokeai/frontend/web/src/features/queue/components/QueueActionsMenuButton.tsx +++ b/invokeai/frontend/web/src/features/queue/components/QueueActionsMenuButton.tsx @@ -1,6 +1,7 @@ import { IconButton, Menu, MenuButton, MenuGroup, MenuItem, MenuList } from '@invoke-ai/ui-library'; import { useAppDispatch } from 'app/store/storeHooks'; import { SessionMenuItems } from 'common/components/SessionMenuItems'; +import { useCancelAllExceptCurrentQueueItemDialog } from 'features/queue/components/CancelAllExceptCurrentQueueItemConfirmationAlertDialog'; import { useClearQueueDialog } from 'features/queue/components/ClearQueueConfirmationAlertDialog'; import { QueueCountBadge } from 'features/queue/components/QueueCountBadge'; import { useCancelCurrentQueueItem } from 'features/queue/hooks/useCancelCurrentQueueItem'; @@ -10,7 +11,15 @@ import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus'; import { setActiveTab } from 'features/ui/store/uiSlice'; import { memo, useCallback, useRef } from 'react'; import { useTranslation } from 'react-i18next'; -import { PiListBold, PiPauseFill, PiPlayFill, PiQueueBold, PiTrashSimpleBold, PiXBold } from 'react-icons/pi'; +import { + PiListBold, + PiPauseFill, + PiPlayFill, + PiQueueBold, + PiTrashSimpleBold, + PiXBold, + PiXCircle, +} from 'react-icons/pi'; export const QueueActionsMenuButton = memo(() => { const ref = useRef(null); @@ -18,6 +27,7 @@ export const QueueActionsMenuButton = memo(() => { const { t } = useTranslation(); const isPauseEnabled = useFeatureStatus('pauseQueue'); const isResumeEnabled = useFeatureStatus('resumeQueue'); + const cancelAllExceptCurrent = useCancelAllExceptCurrentQueueItemDialog(); const cancelCurrent = useCancelCurrentQueueItem(); const clearQueue = useClearQueueDialog(); const { @@ -52,6 +62,15 @@ export const QueueActionsMenuButton = memo(() => { > {t('queue.cancelTooltip')} + } + onClick={cancelAllExceptCurrent.openDialog} + isLoading={cancelAllExceptCurrent.isLoading} + isDisabled={cancelAllExceptCurrent.isDisabled} + > + {t('queue.cancelAllExceptCurrentTooltip')} + } diff --git a/invokeai/frontend/web/src/features/queue/hooks/useCancelAllExceptCurrentQueueItem.ts b/invokeai/frontend/web/src/features/queue/hooks/useCancelAllExceptCurrentQueueItem.ts new file mode 100644 index 00000000000..eb9113ee5fa --- /dev/null +++ b/invokeai/frontend/web/src/features/queue/hooks/useCancelAllExceptCurrentQueueItem.ts @@ -0,0 +1,44 @@ +import { useStore } from '@nanostores/react'; +import { toast } from 'features/toast/toast'; +import { useCallback, useMemo } from 'react'; +import { useTranslation } from 'react-i18next'; +import { useCancelAllExceptCurrentMutation, useGetQueueStatusQuery } from 'services/api/endpoints/queue'; +import { $isConnected } from 'services/events/stores'; + +export const useCancelAllExceptCurrentQueueItem = () => { + const { t } = useTranslation(); + const { data: queueStatus } = useGetQueueStatusQuery(); + const isConnected = useStore($isConnected); + const [trigger, { isLoading }] = useCancelAllExceptCurrentMutation(); + + const cancelAllExceptCurrentQueueItem = useCallback(async () => { + if (!queueStatus?.queue.total) { + return; + } + + // TODO fix trigger + try { + await trigger().unwrap(); + toast({ + id: 'QUEUE_CANCEL_SUCCEEDED', + title: t('queue.cancelSucceeded'), + status: 'success', + }); + } catch { + toast({ + id: 'QUEUE_CANCEL_FAILED', + title: t('queue.cancelFailed'), + status: 'error', + }); + } + }, [queueStatus?.queue.total, trigger, t]); + + const isDisabled = useMemo(() => !isConnected || !queueStatus?.queue.total, [isConnected, queueStatus?.queue.total]); + + return { + cancelAllExceptCurrentQueueItem, + isLoading, + queueStatus, + isDisabled, + }; +}; diff --git a/invokeai/frontend/web/src/services/api/endpoints/queue.ts b/invokeai/frontend/web/src/services/api/endpoints/queue.ts index c2af0d8aa9e..3acc038812b 100644 --- a/invokeai/frontend/web/src/services/api/endpoints/queue.ts +++ b/invokeai/frontend/web/src/services/api/endpoints/queue.ts @@ -348,6 +348,33 @@ export const queueApi = api.injectEndpoints({ return ['SessionQueueStatus', 'BatchStatus', { type: 'QueueCountsByDestination', id: destination }]; }, }), + cancelAllExceptCurrent: build.mutation< + paths['/api/v1/queue/{queue_id}/cancel_all_except_current']['put']['responses']['200']['content']['application/json'], + paths['/api/v1/queue/{queue_id}/cancel_all_except_current']['put']['parameters']['query'] + >({ + query: (params) => ({ + url: buildQueueUrl('cancel_all_except_current'), + method: 'PUT', + params, + }), + // TODO + onQueryStarted: async (arg, api) => { + const { dispatch, queryFulfilled } = api; + try { + await queryFulfilled; + resetListQueryData(dispatch); + } catch { + // no-op + } + }, + // TODO + invalidatesTags: (result, error) => { + if (!result) { + return []; + } + return ['SessionQueueStatus', 'BatchStatus']; + }, + }), listQueueItems: build.query< EntityState & { has_more: boolean; @@ -390,6 +417,7 @@ export const queueApi = api.injectEndpoints({ }); export const { + useCancelAllExceptCurrentMutation, useCancelByBatchIdsMutation, useEnqueueBatchMutation, usePauseProcessorMutation,