Skip to content

Commit a4d58aa

Browse files
rikublockpsychedelicious
authored andcommitted
feat(ui): add cancel all except current queue item functionality
1 parent b74fb40 commit a4d58aa

File tree

6 files changed

+141
-1
lines changed

6 files changed

+141
-1
lines changed

invokeai/frontend/web/public/locales/en.json

+4
Original file line numberDiff line numberDiff line change
@@ -219,9 +219,13 @@
219219
"pauseSucceeded": "Processor Paused",
220220
"pauseFailed": "Problem Pausing Processor",
221221
"cancel": "Cancel",
222+
"cancelAllExceptCurrentQueueItemAlertDialog": "Canceling all queue items except the current one will stop pending items but allow the in-progress one to finish.",
223+
"cancelAllExceptCurrentQueueItemAlertDialog2": "Are you sure you want to cancel all pending queue items?",
224+
"cancelAllExceptCurrentTooltip": "Cancel All Except Current Item",
222225
"cancelTooltip": "Cancel Current Item",
223226
"cancelSucceeded": "Item Canceled",
224227
"cancelFailed": "Problem Canceling Item",
228+
"confirm": "Confirm",
225229
"prune": "Prune",
226230
"pruneTooltip": "Prune {{item_count}} Completed Items",
227231
"pruneSucceeded": "Pruned {{item_count}} Completed Items from Queue",

invokeai/frontend/web/src/app/components/App.tsx

+2
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import DeleteBoardModal from 'features/gallery/components/Boards/DeleteBoardModa
2323
import { ImageContextMenu } from 'features/gallery/components/ImageContextMenu/ImageContextMenu';
2424
import { useStarterModelsToast } from 'features/modelManagerV2/hooks/useStarterModelsToast';
2525
import { ShareWorkflowModal } from 'features/nodes/components/sidePanel/WorkflowListMenu/ShareWorkflowModal';
26+
import { CancelAllExceptCurrentQueueItemConfirmationAlertDialog } from 'features/queue/components/CancelAllExceptCurrentQueueItemConfirmationAlertDialog';
2627
import { ClearQueueConfirmationsAlertDialog } from 'features/queue/components/ClearQueueConfirmationAlertDialog';
2728
import { DeleteStylePresetDialog } from 'features/stylePresets/components/DeleteStylePresetDialog';
2829
import { StylePresetModal } from 'features/stylePresets/components/StylePresetForm/StylePresetModal';
@@ -97,6 +98,7 @@ const App = ({ config = DEFAULT_CONFIG, studioInitAction }: Props) => {
9798
<ChangeBoardModal />
9899
<DynamicPromptsModal />
99100
<StylePresetModal />
101+
<CancelAllExceptCurrentQueueItemConfirmationAlertDialog />
100102
<ClearQueueConfirmationsAlertDialog />
101103
<NewWorkflowConfirmationAlertDialog />
102104
<DeleteStylePresetDialog />
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import { ConfirmationAlertDialog, Text } from '@invoke-ai/ui-library';
2+
import { useAssertSingleton } from 'common/hooks/useAssertSingleton';
3+
import { buildUseBoolean } from 'common/hooks/useBoolean';
4+
import { useCancelAllExceptCurrentQueueItem } from 'features/queue/hooks/useCancelAllExceptCurrentQueueItem';
5+
import { memo } from 'react';
6+
import { useTranslation } from 'react-i18next';
7+
8+
const [useCancelAllExceptCurrentQueueItemConfirmationAlertDialog] = buildUseBoolean(false);
9+
10+
export const useCancelAllExceptCurrentQueueItemDialog = () => {
11+
const dialog = useCancelAllExceptCurrentQueueItemConfirmationAlertDialog();
12+
const { cancelAllExceptCurrentQueueItem, isLoading, isDisabled, queueStatus } = useCancelAllExceptCurrentQueueItem();
13+
14+
return {
15+
cancelAllExceptCurrentQueueItem,
16+
isOpen: dialog.isTrue,
17+
openDialog: dialog.setTrue,
18+
closeDialog: dialog.setFalse,
19+
isLoading,
20+
queueStatus,
21+
isDisabled,
22+
};
23+
};
24+
25+
export const CancelAllExceptCurrentQueueItemConfirmationAlertDialog = memo(() => {
26+
useAssertSingleton('CancelAllExceptCurrentQueueItemConfirmationAlertDialog');
27+
const { t } = useTranslation();
28+
const cancelAllExceptCurrentQueueItem = useCancelAllExceptCurrentQueueItemDialog();
29+
30+
return (
31+
<ConfirmationAlertDialog
32+
isOpen={cancelAllExceptCurrentQueueItem.isOpen}
33+
onClose={cancelAllExceptCurrentQueueItem.closeDialog}
34+
title={t('queue.cancelAllExceptCurrentTooltip')}
35+
acceptCallback={cancelAllExceptCurrentQueueItem.cancelAllExceptCurrentQueueItem}
36+
acceptButtonText={t('queue.confirm')}
37+
useInert={false}
38+
>
39+
<Text>{t('queue.cancelAllExceptCurrentQueueItemAlertDialog')}</Text>
40+
<br />
41+
<Text>{t('queue.cancelAllExceptCurrentQueueItemAlertDialog2')}</Text>
42+
</ConfirmationAlertDialog>
43+
);
44+
});
45+
46+
CancelAllExceptCurrentQueueItemConfirmationAlertDialog.displayName =
47+
'CancelAllExceptCurrentQueueItemConfirmationAlertDialog';

invokeai/frontend/web/src/features/queue/components/QueueActionsMenuButton.tsx

+20-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { IconButton, Menu, MenuButton, MenuGroup, MenuItem, MenuList } from '@invoke-ai/ui-library';
22
import { useAppDispatch } from 'app/store/storeHooks';
33
import { SessionMenuItems } from 'common/components/SessionMenuItems';
4+
import { useCancelAllExceptCurrentQueueItemDialog } from 'features/queue/components/CancelAllExceptCurrentQueueItemConfirmationAlertDialog';
45
import { useClearQueueDialog } from 'features/queue/components/ClearQueueConfirmationAlertDialog';
56
import { QueueCountBadge } from 'features/queue/components/QueueCountBadge';
67
import { useCancelCurrentQueueItem } from 'features/queue/hooks/useCancelCurrentQueueItem';
@@ -10,14 +11,23 @@ import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus';
1011
import { setActiveTab } from 'features/ui/store/uiSlice';
1112
import { memo, useCallback, useRef } from 'react';
1213
import { useTranslation } from 'react-i18next';
13-
import { PiListBold, PiPauseFill, PiPlayFill, PiQueueBold, PiTrashSimpleBold, PiXBold } from 'react-icons/pi';
14+
import {
15+
PiListBold,
16+
PiPauseFill,
17+
PiPlayFill,
18+
PiQueueBold,
19+
PiTrashSimpleBold,
20+
PiXBold,
21+
PiXCircle,
22+
} from 'react-icons/pi';
1423

1524
export const QueueActionsMenuButton = memo(() => {
1625
const ref = useRef<HTMLDivElement>(null);
1726
const dispatch = useAppDispatch();
1827
const { t } = useTranslation();
1928
const isPauseEnabled = useFeatureStatus('pauseQueue');
2029
const isResumeEnabled = useFeatureStatus('resumeQueue');
30+
const cancelAllExceptCurrent = useCancelAllExceptCurrentQueueItemDialog();
2131
const cancelCurrent = useCancelCurrentQueueItem();
2232
const clearQueue = useClearQueueDialog();
2333
const {
@@ -52,6 +62,15 @@ export const QueueActionsMenuButton = memo(() => {
5262
>
5363
{t('queue.cancelTooltip')}
5464
</MenuItem>
65+
<MenuItem
66+
isDestructive
67+
icon={<PiXCircle />}
68+
onClick={cancelAllExceptCurrent.openDialog}
69+
isLoading={cancelAllExceptCurrent.isLoading}
70+
isDisabled={cancelAllExceptCurrent.isDisabled}
71+
>
72+
{t('queue.cancelAllExceptCurrentTooltip')}
73+
</MenuItem>
5574
<MenuItem
5675
isDestructive
5776
icon={<PiTrashSimpleBold />}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import { useStore } from '@nanostores/react';
2+
import { toast } from 'features/toast/toast';
3+
import { useCallback, useMemo } from 'react';
4+
import { useTranslation } from 'react-i18next';
5+
import { useCancelAllExceptCurrentMutation, useGetQueueStatusQuery } from 'services/api/endpoints/queue';
6+
import { $isConnected } from 'services/events/stores';
7+
8+
export const useCancelAllExceptCurrentQueueItem = () => {
9+
const { t } = useTranslation();
10+
const { data: queueStatus } = useGetQueueStatusQuery();
11+
const isConnected = useStore($isConnected);
12+
const [trigger, { isLoading }] = useCancelAllExceptCurrentMutation({
13+
fixedCacheKey: 'cancelAllExceptCurrent',
14+
});
15+
16+
const cancelAllExceptCurrentQueueItem = useCallback(async () => {
17+
if (!queueStatus?.queue.pending) {
18+
return;
19+
}
20+
21+
try {
22+
await trigger().unwrap();
23+
toast({
24+
id: 'QUEUE_CANCEL_SUCCEEDED',
25+
title: t('queue.cancelSucceeded'),
26+
status: 'success',
27+
});
28+
} catch {
29+
toast({
30+
id: 'QUEUE_CANCEL_FAILED',
31+
title: t('queue.cancelFailed'),
32+
status: 'error',
33+
});
34+
}
35+
}, [queueStatus?.queue.pending, trigger, t]);
36+
37+
const isDisabled = useMemo(
38+
() => !isConnected || !queueStatus?.queue.pending,
39+
[isConnected, queueStatus?.queue.pending]
40+
);
41+
42+
return {
43+
cancelAllExceptCurrentQueueItem,
44+
isLoading,
45+
queueStatus,
46+
isDisabled,
47+
};
48+
};

invokeai/frontend/web/src/services/api/endpoints/queue.ts

+20
Original file line numberDiff line numberDiff line change
@@ -348,6 +348,25 @@ export const queueApi = api.injectEndpoints({
348348
return ['SessionQueueStatus', 'BatchStatus', { type: 'QueueCountsByDestination', id: destination }];
349349
},
350350
}),
351+
cancelAllExceptCurrent: build.mutation<
352+
paths['/api/v1/queue/{queue_id}/cancel_all_except_current']['put']['responses']['200']['content']['application/json'],
353+
void
354+
>({
355+
query: () => ({
356+
url: buildQueueUrl('cancel_all_except_current'),
357+
method: 'PUT',
358+
}),
359+
onQueryStarted: async (arg, api) => {
360+
const { dispatch, queryFulfilled } = api;
361+
try {
362+
await queryFulfilled;
363+
resetListQueryData(dispatch);
364+
} catch {
365+
// no-op
366+
}
367+
},
368+
invalidatesTags: ['SessionQueueStatus', 'BatchStatus', 'QueueCountsByDestination'],
369+
}),
351370
listQueueItems: build.query<
352371
EntityState<components['schemas']['SessionQueueItemDTO'], string> & {
353372
has_more: boolean;
@@ -390,6 +409,7 @@ export const queueApi = api.injectEndpoints({
390409
});
391410

392411
export const {
412+
useCancelAllExceptCurrentMutation,
393413
useCancelByBatchIdsMutation,
394414
useEnqueueBatchMutation,
395415
usePauseProcessorMutation,

0 commit comments

Comments
 (0)