Skip to content

Commit

Permalink
feat(ui): add cancel all except current queue item functionality
Browse files Browse the repository at this point in the history
  • Loading branch information
rikublock committed Dec 6, 2024
1 parent eae9092 commit 0f40e2a
Show file tree
Hide file tree
Showing 6 changed files with 141 additions and 1 deletion.
4 changes: 4 additions & 0 deletions invokeai/frontend/web/public/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -209,9 +209,13 @@
"pauseSucceeded": "Processor Paused",
"pauseFailed": "Problem Pausing Processor",
"cancel": "Cancel",
"cancelAllExceptCurrentQueueItemAlertDialog": "Canceling all queue items except the current one will stop pending items but allow the in-progress one to finish.",
"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",
"confirm": "Confirm",
"prune": "Prune",
"pruneTooltip": "Prune {{item_count}} Completed Items",
"pruneSucceeded": "Pruned {{item_count}} Completed Items from Queue",
Expand Down
2 changes: 2 additions & 0 deletions invokeai/frontend/web/src/app/components/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import DeleteBoardModal from 'features/gallery/components/Boards/DeleteBoardModa
import { ImageContextMenu } from 'features/gallery/components/ImageContextMenu/ImageContextMenu';
import { useStarterModelsToast } from 'features/modelManagerV2/hooks/useStarterModelsToast';
import { ShareWorkflowModal } from 'features/nodes/components/sidePanel/WorkflowListMenu/ShareWorkflowModal';
import { CancelAllExceptCurrentQueueItemConfirmationAlertDialog } from 'features/queue/components/CancelAllExceptCurrentQueueItemConfirmationAlertDialog';
import { ClearQueueConfirmationsAlertDialog } from 'features/queue/components/ClearQueueConfirmationAlertDialog';
import { DeleteStylePresetDialog } from 'features/stylePresets/components/DeleteStylePresetDialog';
import { StylePresetModal } from 'features/stylePresets/components/StylePresetForm/StylePresetModal';
Expand Down Expand Up @@ -97,6 +98,7 @@ const App = ({ config = DEFAULT_CONFIG, studioInitAction }: Props) => {
<ChangeBoardModal />
<DynamicPromptsModal />
<StylePresetModal />
<CancelAllExceptCurrentQueueItemConfirmationAlertDialog />
<ClearQueueConfirmationsAlertDialog />
<NewWorkflowConfirmationAlertDialog />
<DeleteStylePresetDialog />
Expand Down
Original file line number Diff line number Diff line change
@@ -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 (
<ConfirmationAlertDialog
isOpen={cancelAllExceptCurrentQueueItem.isOpen}
onClose={cancelAllExceptCurrentQueueItem.closeDialog}
title={t('queue.cancelAllExceptCurrentTooltip')}
acceptCallback={cancelAllExceptCurrentQueueItem.cancelAllExceptCurrentQueueItem}
acceptButtonText={t('queue.confirm')}
useInert={false}
>
<Text>{t('queue.cancelAllExceptCurrentQueueItemAlertDialog')}</Text>
<br />
<Text>{t('queue.cancelAllExceptCurrentQueueItemAlertDialog2')}</Text>
</ConfirmationAlertDialog>
);
});

CancelAllExceptCurrentQueueItemConfirmationAlertDialog.displayName =
'CancelAllExceptCurrentQueueItemConfirmationAlertDialog';
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -10,14 +11,23 @@ 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<HTMLDivElement>(null);
const dispatch = useAppDispatch();
const { t } = useTranslation();
const isPauseEnabled = useFeatureStatus('pauseQueue');
const isResumeEnabled = useFeatureStatus('resumeQueue');
const cancelAllExceptCurrent = useCancelAllExceptCurrentQueueItemDialog();
const cancelCurrent = useCancelCurrentQueueItem();
const clearQueue = useClearQueueDialog();
const {
Expand Down Expand Up @@ -52,6 +62,15 @@ export const QueueActionsMenuButton = memo(() => {
>
{t('queue.cancelTooltip')}
</MenuItem>
<MenuItem
isDestructive
icon={<PiXCircle />}
onClick={cancelAllExceptCurrent.openDialog}
isLoading={cancelAllExceptCurrent.isLoading}
isDisabled={cancelAllExceptCurrent.isDisabled}
>
{t('queue.cancelAllExceptCurrentTooltip')}
</MenuItem>
<MenuItem
isDestructive
icon={<PiTrashSimpleBold />}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
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({
fixedCacheKey: 'cancelAllExceptCurrent',
});

const cancelAllExceptCurrentQueueItem = useCallback(async () => {
if (!queueStatus?.queue.pending) {
return;
}

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.pending, trigger, t]);

const isDisabled = useMemo(
() => !isConnected || !queueStatus?.queue.pending,
[isConnected, queueStatus?.queue.pending]
);

return {
cancelAllExceptCurrentQueueItem,
isLoading,
queueStatus,
isDisabled,
};
};
20 changes: 20 additions & 0 deletions invokeai/frontend/web/src/services/api/endpoints/queue.ts
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,25 @@ 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'],
void
>({
query: () => ({
url: buildQueueUrl('cancel_all_except_current'),
method: 'PUT',
}),
onQueryStarted: async (arg, api) => {
const { dispatch, queryFulfilled } = api;
try {
await queryFulfilled;
resetListQueryData(dispatch);
} catch {
// no-op
}
},
invalidatesTags: ['SessionQueueStatus', 'BatchStatus', 'QueueCountsByDestination'],
}),
listQueueItems: build.query<
EntityState<components['schemas']['SessionQueueItemDTO'], string> & {
has_more: boolean;
Expand Down Expand Up @@ -390,6 +409,7 @@ export const queueApi = api.injectEndpoints({
});

export const {
useCancelAllExceptCurrentMutation,
useCancelByBatchIdsMutation,
useEnqueueBatchMutation,
usePauseProcessorMutation,
Expand Down

0 comments on commit 0f40e2a

Please sign in to comment.