mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
chore(ui): lint
This commit is contained in:
parent
29c47c8be5
commit
1e134de771
@ -48,7 +48,7 @@ export const IconSwitch = memo(
|
|||||||
return (
|
return (
|
||||||
<Flex
|
<Flex
|
||||||
position="relative"
|
position="relative"
|
||||||
bg='base.800'
|
bg="base.800"
|
||||||
borderRadius="base"
|
borderRadius="base"
|
||||||
alignItems="center"
|
alignItems="center"
|
||||||
justifyContent="center"
|
justifyContent="center"
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { useAppDispatch } from 'app/store/storeHooks';
|
import { useAppDispatch } from 'app/store/storeHooks';
|
||||||
import { addScope, removeScope, setScopes } from 'common/hooks/interactionScopes';
|
import { addScope, removeScope, setScopes } from 'common/hooks/interactionScopes';
|
||||||
|
import { useClearQueue } from 'features/queue/components/ClearQueueConfirmationAlertDialog';
|
||||||
import { useCancelCurrentQueueItem } from 'features/queue/hooks/useCancelCurrentQueueItem';
|
import { useCancelCurrentQueueItem } from 'features/queue/hooks/useCancelCurrentQueueItem';
|
||||||
import { useClearQueue } from 'features/queue/hooks/useClearQueue';
|
|
||||||
import { useQueueBack } from 'features/queue/hooks/useQueueBack';
|
import { useQueueBack } from 'features/queue/hooks/useQueueBack';
|
||||||
import { useQueueFront } from 'features/queue/hooks/useQueueFront';
|
import { useQueueFront } from 'features/queue/hooks/useQueueFront';
|
||||||
import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus';
|
import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus';
|
||||||
|
@ -23,7 +23,7 @@ export const CanvasEntityDeleteButton = memo(() => {
|
|||||||
alignSelf="stretch"
|
alignSelf="stretch"
|
||||||
icon={<PiTrashSimpleFill />}
|
icon={<PiTrashSimpleFill />}
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
colorScheme='error'
|
colorScheme="error"
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -685,8 +685,6 @@ export type StagingAreaImage = {
|
|||||||
offsetY: number;
|
offsetY: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type SessionMode = 'generate' | 'compose';
|
|
||||||
|
|
||||||
export type CanvasState = {
|
export type CanvasState = {
|
||||||
_version: 3;
|
_version: 3;
|
||||||
selectedEntityIdentifier: CanvasEntityIdentifier | null;
|
selectedEntityIdentifier: CanvasEntityIdentifier | null;
|
||||||
|
@ -1,27 +1,26 @@
|
|||||||
import type { ButtonProps } from '@invoke-ai/ui-library';
|
import type { ButtonProps } from '@invoke-ai/ui-library';
|
||||||
import { Button } from '@invoke-ai/ui-library';
|
import { Button } from '@invoke-ai/ui-library';
|
||||||
import { useClearQueueConfirmationAlertDialog } from 'features/queue/components/ClearQueueConfirmationAlertDialog';
|
|
||||||
import { useClearQueue } from 'features/queue/hooks/useClearQueue';
|
|
||||||
import { memo } from 'react';
|
import { memo } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { PiTrashSimpleFill } from 'react-icons/pi';
|
import { PiTrashSimpleFill } from 'react-icons/pi';
|
||||||
|
|
||||||
|
import { useClearQueue } from './ClearQueueConfirmationAlertDialog';
|
||||||
|
|
||||||
type Props = ButtonProps;
|
type Props = ButtonProps;
|
||||||
|
|
||||||
const ClearQueueButton = (props: Props) => {
|
const ClearQueueButton = (props: Props) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const dialogState = useClearQueueConfirmationAlertDialog();
|
const clearQueue = useClearQueue();
|
||||||
const { isLoading, isDisabled } = useClearQueue();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Button
|
<Button
|
||||||
isDisabled={isDisabled}
|
isDisabled={clearQueue.isDisabled}
|
||||||
isLoading={isLoading}
|
isLoading={clearQueue.isLoading}
|
||||||
tooltip={t('queue.clearTooltip')}
|
tooltip={t('queue.clearTooltip')}
|
||||||
leftIcon={<PiTrashSimpleFill />}
|
leftIcon={<PiTrashSimpleFill />}
|
||||||
colorScheme="error"
|
colorScheme="error"
|
||||||
onClick={dialogState.setTrue}
|
onClick={clearQueue.openDialog}
|
||||||
data-testid={t('queue.clear')}
|
data-testid={t('queue.clear')}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
|
@ -1,26 +1,75 @@
|
|||||||
import { ConfirmationAlertDialog, Text } from '@invoke-ai/ui-library';
|
import { ConfirmationAlertDialog, Text } from '@invoke-ai/ui-library';
|
||||||
import { useStore } from '@nanostores/react';
|
import { useStore } from '@nanostores/react';
|
||||||
|
import { $isConnected } from 'app/hooks/useSocketIO';
|
||||||
|
import { useAppDispatch } from 'app/store/storeHooks';
|
||||||
import { buildUseBoolean } from 'common/hooks/useBoolean';
|
import { buildUseBoolean } from 'common/hooks/useBoolean';
|
||||||
import { useClearQueue } from 'features/queue/hooks/useClearQueue';
|
import { listCursorChanged, listPriorityChanged } from 'features/queue/store/queueSlice';
|
||||||
|
import { toast } from 'features/toast/toast';
|
||||||
import { atom } from 'nanostores';
|
import { atom } from 'nanostores';
|
||||||
import { memo } from 'react';
|
import { memo, useCallback, useMemo } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { useClearQueueMutation, useGetQueueStatusQuery } from 'services/api/endpoints/queue';
|
||||||
|
|
||||||
const $boolean = atom(false);
|
const $boolean = atom(false);
|
||||||
export const useClearQueueConfirmationAlertDialog = buildUseBoolean($boolean);
|
const useClearQueueConfirmationAlertDialog = buildUseBoolean($boolean);
|
||||||
|
|
||||||
|
export const useClearQueue = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
const dialog = useClearQueueConfirmationAlertDialog();
|
||||||
|
const isOpen = useStore(dialog.$boolean);
|
||||||
|
const { data: queueStatus } = useGetQueueStatusQuery();
|
||||||
|
const isConnected = useStore($isConnected);
|
||||||
|
const [trigger, { isLoading }] = useClearQueueMutation({
|
||||||
|
fixedCacheKey: 'clearQueue',
|
||||||
|
});
|
||||||
|
|
||||||
|
const clearQueue = useCallback(async () => {
|
||||||
|
if (!queueStatus?.queue.total) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await trigger().unwrap();
|
||||||
|
toast({
|
||||||
|
id: 'QUEUE_CLEAR_SUCCEEDED',
|
||||||
|
title: t('queue.clearSucceeded'),
|
||||||
|
status: 'success',
|
||||||
|
});
|
||||||
|
dispatch(listCursorChanged(undefined));
|
||||||
|
dispatch(listPriorityChanged(undefined));
|
||||||
|
} catch {
|
||||||
|
toast({
|
||||||
|
id: 'QUEUE_CLEAR_FAILED',
|
||||||
|
title: t('queue.clearFailed'),
|
||||||
|
status: 'error',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [queueStatus?.queue.total, trigger, dispatch, t]);
|
||||||
|
|
||||||
|
const isDisabled = useMemo(() => !isConnected || !queueStatus?.queue.total, [isConnected, queueStatus?.queue.total]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
clearQueue,
|
||||||
|
isOpen,
|
||||||
|
openDialog: dialog.setTrue,
|
||||||
|
closeDialog: dialog.setFalse,
|
||||||
|
isLoading,
|
||||||
|
queueStatus,
|
||||||
|
isDisabled,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
export const ClearQueueConfirmationsAlertDialog = memo(() => {
|
export const ClearQueueConfirmationsAlertDialog = memo(() => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const dialogState = useClearQueueConfirmationAlertDialog();
|
const clearQueue = useClearQueue();
|
||||||
const isOpen = useStore(dialogState.$boolean);
|
|
||||||
const { clearQueue } = useClearQueue();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ConfirmationAlertDialog
|
<ConfirmationAlertDialog
|
||||||
isOpen={isOpen}
|
isOpen={clearQueue.isOpen}
|
||||||
onClose={dialogState.setFalse}
|
onClose={clearQueue.closeDialog}
|
||||||
title={t('queue.clearTooltip')}
|
title={t('queue.clearTooltip')}
|
||||||
acceptCallback={clearQueue}
|
acceptCallback={clearQueue.clearQueue}
|
||||||
acceptButtonText={t('queue.clear')}
|
acceptButtonText={t('queue.clear')}
|
||||||
useInert={false}
|
useInert={false}
|
||||||
>
|
>
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
import { IconButton, useShiftModifier } from '@invoke-ai/ui-library';
|
import { IconButton, useShiftModifier } from '@invoke-ai/ui-library';
|
||||||
import { QueueCountBadge } from 'features/queue/components/QueueCountBadge';
|
import { QueueCountBadge } from 'features/queue/components/QueueCountBadge';
|
||||||
import { useCancelCurrentQueueItem } from 'features/queue/hooks/useCancelCurrentQueueItem';
|
import { useCancelCurrentQueueItem } from 'features/queue/hooks/useCancelCurrentQueueItem';
|
||||||
import { useClearQueue } from 'features/queue/hooks/useClearQueue';
|
|
||||||
import { memo, useRef } from 'react';
|
import { memo, useRef } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { PiTrashSimpleBold, PiXBold } from 'react-icons/pi';
|
import { PiTrashSimpleBold, PiXBold } from 'react-icons/pi';
|
||||||
|
|
||||||
|
import { useClearQueue } from './ClearQueueConfirmationAlertDialog';
|
||||||
|
|
||||||
export const ClearQueueIconButton = memo((_) => {
|
export const ClearQueueIconButton = memo((_) => {
|
||||||
const ref = useRef<HTMLDivElement>(null);
|
const ref = useRef<HTMLDivElement>(null);
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
@ -1,145 +0,0 @@
|
|||||||
import {
|
|
||||||
Badge,
|
|
||||||
IconButton,
|
|
||||||
Menu,
|
|
||||||
MenuButton,
|
|
||||||
MenuDivider,
|
|
||||||
MenuItem,
|
|
||||||
MenuList,
|
|
||||||
Portal,
|
|
||||||
useDisclosure,
|
|
||||||
} from '@invoke-ai/ui-library';
|
|
||||||
import { useStore } from '@nanostores/react';
|
|
||||||
import { useAppDispatch } from 'app/store/storeHooks';
|
|
||||||
import type { Coordinate } from 'features/controlLayers/store/types';
|
|
||||||
import { useClearQueueConfirmationAlertDialog } from 'features/queue/components/ClearQueueConfirmationAlertDialog';
|
|
||||||
import { useClearQueue } from 'features/queue/hooks/useClearQueue';
|
|
||||||
import { usePauseProcessor } from 'features/queue/hooks/usePauseProcessor';
|
|
||||||
import { useResumeProcessor } from 'features/queue/hooks/useResumeProcessor';
|
|
||||||
import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus';
|
|
||||||
import { $isParametersPanelOpen, setActiveTab } from 'features/ui/store/uiSlice';
|
|
||||||
import type { RefObject } from 'react';
|
|
||||||
import { memo, useCallback, useEffect, useRef, useState } from 'react';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
import { PiPauseFill, PiPlayFill, PiTrashSimpleBold } from 'react-icons/pi';
|
|
||||||
import { RiListCheck, RiPlayList2Fill } from 'react-icons/ri';
|
|
||||||
import { useGetQueueStatusQuery } from 'services/api/endpoints/queue';
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
containerRef: RefObject<HTMLDivElement>;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const QueueActionsMenuButton = memo(({ containerRef }: Props) => {
|
|
||||||
const { isOpen, onOpen, onClose } = useDisclosure();
|
|
||||||
const dispatch = useAppDispatch();
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const [badgePos, setBadgePos] = useState<Coordinate | null>(null);
|
|
||||||
const menuButtonRef = useRef<HTMLButtonElement>(null);
|
|
||||||
const isParametersPanelOpen = useStore($isParametersPanelOpen);
|
|
||||||
const dialogState = useClearQueueConfirmationAlertDialog();
|
|
||||||
const isPauseEnabled = useFeatureStatus('pauseQueue');
|
|
||||||
const isResumeEnabled = useFeatureStatus('resumeQueue');
|
|
||||||
const { queueSize } = useGetQueueStatusQuery(undefined, {
|
|
||||||
selectFromResult: (res) => ({
|
|
||||||
queueSize: res.data ? res.data.queue.pending + res.data.queue.in_progress : 0,
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
const { isLoading: isLoadingClearQueue, isDisabled: isDisabledClearQueue } = useClearQueue();
|
|
||||||
const {
|
|
||||||
resumeProcessor,
|
|
||||||
isLoading: isLoadingResumeProcessor,
|
|
||||||
isDisabled: isDisabledResumeProcessor,
|
|
||||||
} = useResumeProcessor();
|
|
||||||
const {
|
|
||||||
pauseProcessor,
|
|
||||||
isLoading: isLoadingPauseProcessor,
|
|
||||||
isDisabled: isDisabledPauseProcessor,
|
|
||||||
} = usePauseProcessor();
|
|
||||||
const openQueue = useCallback(() => {
|
|
||||||
dispatch(setActiveTab('queue'));
|
|
||||||
}, [dispatch]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!containerRef.current || !menuButtonRef.current) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const container = containerRef.current;
|
|
||||||
const menuButton = menuButtonRef.current;
|
|
||||||
|
|
||||||
const cb = () => {
|
|
||||||
if (!$isParametersPanelOpen.get()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const { x, y } = menuButton.getBoundingClientRect();
|
|
||||||
setBadgePos({ x: x - 10, y: y - 10 });
|
|
||||||
};
|
|
||||||
|
|
||||||
// // update badge position on resize
|
|
||||||
const resizeObserver = new ResizeObserver(cb);
|
|
||||||
resizeObserver.observe(container);
|
|
||||||
cb();
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
resizeObserver.disconnect();
|
|
||||||
};
|
|
||||||
}, [containerRef]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Menu isOpen={isOpen} onOpen={onOpen} onClose={onClose} placement="bottom-end">
|
|
||||||
<MenuButton ref={menuButtonRef} as={IconButton} aria-label="Queue Actions Menu" icon={<RiListCheck />} />
|
|
||||||
<MenuList>
|
|
||||||
<MenuItem
|
|
||||||
isDestructive
|
|
||||||
icon={<PiTrashSimpleBold size="16px" />}
|
|
||||||
onClick={dialogState.setTrue}
|
|
||||||
isLoading={isLoadingClearQueue}
|
|
||||||
isDisabled={isDisabledClearQueue}
|
|
||||||
>
|
|
||||||
{t('queue.clearTooltip')}
|
|
||||||
</MenuItem>
|
|
||||||
{isResumeEnabled && (
|
|
||||||
<MenuItem
|
|
||||||
icon={<PiPlayFill size="14px" />}
|
|
||||||
onClick={resumeProcessor}
|
|
||||||
isLoading={isLoadingResumeProcessor}
|
|
||||||
isDisabled={isDisabledResumeProcessor}
|
|
||||||
>
|
|
||||||
{t('queue.resumeTooltip')}
|
|
||||||
</MenuItem>
|
|
||||||
)}
|
|
||||||
{isPauseEnabled && (
|
|
||||||
<MenuItem
|
|
||||||
icon={<PiPauseFill size="14px" />}
|
|
||||||
onClick={pauseProcessor}
|
|
||||||
isLoading={isLoadingPauseProcessor}
|
|
||||||
isDisabled={isDisabledPauseProcessor}
|
|
||||||
>
|
|
||||||
{t('queue.pauseTooltip')}
|
|
||||||
</MenuItem>
|
|
||||||
)}
|
|
||||||
<MenuDivider />
|
|
||||||
<MenuItem icon={<RiPlayList2Fill />} onClick={openQueue}>
|
|
||||||
{t('queue.openQueue')}
|
|
||||||
</MenuItem>
|
|
||||||
</MenuList>
|
|
||||||
</Menu>
|
|
||||||
{queueSize > 0 && badgePos !== null && isParametersPanelOpen && (
|
|
||||||
<Portal>
|
|
||||||
<Badge
|
|
||||||
pos="absolute"
|
|
||||||
insetInlineStart={badgePos.x}
|
|
||||||
insetBlockStart={badgePos.y}
|
|
||||||
colorScheme="invokeYellow"
|
|
||||||
zIndex="docked"
|
|
||||||
>
|
|
||||||
{queueSize}
|
|
||||||
</Badge>
|
|
||||||
</Portal>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
QueueActionsMenuButton.displayName = 'QueueActionsMenuButton';
|
|
@ -1,47 +0,0 @@
|
|||||||
import { useStore } from '@nanostores/react';
|
|
||||||
import { $isConnected } from 'app/hooks/useSocketIO';
|
|
||||||
import { useAppDispatch } from 'app/store/storeHooks';
|
|
||||||
import { useClearQueueConfirmationAlertDialog } from 'features/queue/components/ClearQueueConfirmationAlertDialog';
|
|
||||||
import { listCursorChanged, listPriorityChanged } from 'features/queue/store/queueSlice';
|
|
||||||
import { toast } from 'features/toast/toast';
|
|
||||||
import { useCallback, useMemo } from 'react';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
import { useClearQueueMutation, useGetQueueStatusQuery } from 'services/api/endpoints/queue';
|
|
||||||
|
|
||||||
export const useClearQueue = () => {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const dispatch = useAppDispatch();
|
|
||||||
const dialog = useClearQueueConfirmationAlertDialog();
|
|
||||||
const { data: queueStatus } = useGetQueueStatusQuery();
|
|
||||||
const isConnected = useStore($isConnected);
|
|
||||||
const [trigger, { isLoading }] = useClearQueueMutation({
|
|
||||||
fixedCacheKey: 'clearQueue',
|
|
||||||
});
|
|
||||||
|
|
||||||
const clearQueue = useCallback(async () => {
|
|
||||||
if (!queueStatus?.queue.total) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
await trigger().unwrap();
|
|
||||||
toast({
|
|
||||||
id: 'QUEUE_CLEAR_SUCCEEDED',
|
|
||||||
title: t('queue.clearSucceeded'),
|
|
||||||
status: 'success',
|
|
||||||
});
|
|
||||||
dispatch(listCursorChanged(undefined));
|
|
||||||
dispatch(listPriorityChanged(undefined));
|
|
||||||
} catch {
|
|
||||||
toast({
|
|
||||||
id: 'QUEUE_CLEAR_FAILED',
|
|
||||||
title: t('queue.clearFailed'),
|
|
||||||
status: 'error',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, [queueStatus?.queue.total, trigger, dispatch, t]);
|
|
||||||
|
|
||||||
const isDisabled = useMemo(() => !isConnected || !queueStatus?.queue.total, [isConnected, queueStatus?.queue.total]);
|
|
||||||
|
|
||||||
return { clearQueue, openDialog: dialog.setTrue, isLoading, queueStatus, isDisabled };
|
|
||||||
};
|
|
@ -1,8 +1,8 @@
|
|||||||
import type { SystemStyleObject } from '@invoke-ai/ui-library';
|
import type { SystemStyleObject } from '@invoke-ai/ui-library';
|
||||||
import { ButtonGroup, Flex, Icon, IconButton, Portal, spinAnimation } from '@invoke-ai/ui-library';
|
import { ButtonGroup, Flex, Icon, IconButton, Portal, spinAnimation } from '@invoke-ai/ui-library';
|
||||||
import CancelCurrentQueueItemIconButton from 'features/queue/components/CancelCurrentQueueItemIconButton';
|
import CancelCurrentQueueItemIconButton from 'features/queue/components/CancelCurrentQueueItemIconButton';
|
||||||
|
import { useClearQueue } from 'features/queue/components/ClearQueueConfirmationAlertDialog';
|
||||||
import { QueueButtonTooltip } from 'features/queue/components/QueueButtonTooltip';
|
import { QueueButtonTooltip } from 'features/queue/components/QueueButtonTooltip';
|
||||||
import { useClearQueue } from 'features/queue/hooks/useClearQueue';
|
|
||||||
import { useQueueBack } from 'features/queue/hooks/useQueueBack';
|
import { useQueueBack } from 'features/queue/hooks/useQueueBack';
|
||||||
import type { UsePanelReturn } from 'features/ui/hooks/usePanel';
|
import type { UsePanelReturn } from 'features/ui/hooks/usePanel';
|
||||||
import { memo, useMemo } from 'react';
|
import { memo, useMemo } from 'react';
|
||||||
|
@ -96,8 +96,17 @@ export const setEventListeners = ({ socket, dispatch, getState, setIsConnected }
|
|||||||
});
|
});
|
||||||
|
|
||||||
socket.on('invocation_denoise_progress', (data) => {
|
socket.on('invocation_denoise_progress', (data) => {
|
||||||
const { invocation_source_id, invocation, step, total_steps, progress_image, origin, destination, percentage, session_id } =
|
const {
|
||||||
data;
|
invocation_source_id,
|
||||||
|
invocation,
|
||||||
|
step,
|
||||||
|
total_steps,
|
||||||
|
progress_image,
|
||||||
|
origin,
|
||||||
|
destination,
|
||||||
|
percentage,
|
||||||
|
session_id,
|
||||||
|
} = data;
|
||||||
|
|
||||||
if (cancellations.has(session_id)) {
|
if (cancellations.has(session_id)) {
|
||||||
// Do not update the progress if this session has been cancelled. This prevents a race condition where we get a
|
// Do not update the progress if this session has been cancelled. This prevents a race condition where we get a
|
||||||
|
Loading…
x
Reference in New Issue
Block a user