feat(ui): add dialog when loading workflow if unsaved changes

This commit is contained in:
psychedelicious
2025-02-21 10:38:20 +10:00
parent efb7f36f28
commit 012054acaa
4 changed files with 98 additions and 27 deletions

View File

@@ -973,6 +973,8 @@
"newWorkflow": "New Workflow",
"newWorkflowDesc": "Create a new workflow?",
"newWorkflowDesc2": "Your current workflow has unsaved changes.",
"loadWorkflowDesc": "Load workflow?",
"loadWorkflowDesc2": "Your current workflow has unsaved changes.",
"clearWorkflow": "Clear Workflow",
"clearWorkflowDesc": "Clear this workflow and start a new one?",
"clearWorkflowDesc2": "Your current workflow has unsaved changes.",

View File

@@ -36,6 +36,7 @@ import { configChanged } from 'features/system/store/configSlice';
import { selectLanguage } from 'features/system/store/systemSelectors';
import { AppContent } from 'features/ui/components/AppContent';
import { DeleteWorkflowDialog } from 'features/workflowLibrary/components/DeleteLibraryWorkflowConfirmationAlertDialog';
import { LoadWorkflowConfirmationAlertDialog } from 'features/workflowLibrary/components/LoadWorkflowConfirmationAlertDialog';
import { NewWorkflowConfirmationAlertDialog } from 'features/workflowLibrary/components/NewWorkflowConfirmationAlertDialog';
import i18n from 'i18n';
import { size } from 'lodash-es';
@@ -75,6 +76,7 @@ const App = ({ config = DEFAULT_CONFIG, studioInitAction }: Props) => {
<CancelAllExceptCurrentQueueItemConfirmationAlertDialog />
<ClearQueueConfirmationsAlertDialog />
<NewWorkflowConfirmationAlertDialog />
<LoadWorkflowConfirmationAlertDialog />
<DeleteStylePresetDialog />
<DeleteWorkflowDialog />
<ShareWorkflowModal />

View File

@@ -1,13 +1,12 @@
import { Badge, Flex, IconButton, Spacer, Text, Tooltip } from '@invoke-ai/ui-library';
import { useStore } from '@nanostores/react';
import { $projectUrl } from 'app/store/nanostores/projectId';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { useAppSelector } from 'app/store/storeHooks';
import dateFormat, { masks } from 'dateformat';
import { useWorkflowListMenu } from 'features/nodes/store/workflowListMenu';
import { selectWorkflowId, workflowModeChanged } from 'features/nodes/store/workflowSlice';
import { selectWorkflowId } from 'features/nodes/store/workflowSlice';
import { useDeleteWorkflow } from 'features/workflowLibrary/components/DeleteLibraryWorkflowConfirmationAlertDialog';
import { useLoadWorkflow } from 'features/workflowLibrary/components/LoadWorkflowConfirmationAlertDialog';
import { useDownloadWorkflow } from 'features/workflowLibrary/hooks/useDownloadWorkflow';
import { useGetAndLoadLibraryWorkflow } from 'features/workflowLibrary/hooks/useGetAndLoadLibraryWorkflow';
import type { MouseEvent } from 'react';
import { useCallback, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
@@ -19,9 +18,7 @@ import { WorkflowListItemTooltip } from './WorkflowListItemTooltip';
export const WorkflowListItem = ({ workflow }: { workflow: WorkflowRecordListItemDTO }) => {
const { t } = useTranslation();
const dispatch = useAppDispatch();
const projectUrl = useStore($projectUrl);
const workflowListMenu = useWorkflowListMenu();
const [isHovered, setIsHovered] = useState(false);
const handleMouseOver = useCallback(() => {
@@ -36,33 +33,21 @@ export const WorkflowListItem = ({ workflow }: { workflow: WorkflowRecordListIte
const downloadWorkflow = useDownloadWorkflow();
const shareWorkflow = useShareWorkflow();
const deleteWorkflow = useDeleteWorkflow();
const { getAndLoadWorkflow } = useGetAndLoadLibraryWorkflow({
onSuccess: workflowListMenu.close,
});
const loadWorkflow = useLoadWorkflow();
const isActive = useMemo(() => {
return workflowId === workflow.workflow_id;
}, [workflowId, workflow.workflow_id]);
const handleClickLoad = useCallback(
(e: MouseEvent<HTMLDivElement>) => {
e.stopPropagation();
getAndLoadWorkflow(workflow.workflow_id);
workflowListMenu.close();
},
[getAndLoadWorkflow, workflow.workflow_id, workflowListMenu]
);
const handleClickLoad = useCallback(() => {
setIsHovered(false);
loadWorkflow.loadWithDialog(workflow.workflow_id, 'view');
}, [loadWorkflow, workflow.workflow_id]);
const handleClickEdit = useCallback(
async (e: MouseEvent<HTMLButtonElement>) => {
e.stopPropagation();
setIsHovered(false);
await getAndLoadWorkflow(workflow.workflow_id);
dispatch(workflowModeChanged('edit'));
workflowListMenu.close();
},
[getAndLoadWorkflow, workflow.workflow_id, dispatch, workflowListMenu]
);
const handleClickEdit = useCallback(() => {
setIsHovered(false);
loadWorkflow.loadWithDialog(workflow.workflow_id, 'view');
}, [loadWorkflow, workflow.workflow_id]);
const handleClickDelete = useCallback(
(e: MouseEvent<HTMLButtonElement>) => {

View File

@@ -0,0 +1,82 @@
import { ConfirmationAlertDialog, Flex, Text } from '@invoke-ai/ui-library';
import { useStore } from '@nanostores/react';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { useAssertSingleton } from 'common/hooks/useAssertSingleton';
import { useWorkflowListMenu } from 'features/nodes/store/workflowListMenu';
import { selectWorkflowIsTouched, workflowModeChanged } from 'features/nodes/store/workflowSlice';
import { useGetAndLoadLibraryWorkflow } from 'features/workflowLibrary/hooks/useGetAndLoadLibraryWorkflow';
import { atom } from 'nanostores';
import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
const $workflowToLoad = atom<{ workflowId: string; mode: 'view' | 'edit'; isOpen: boolean } | null>(null);
const cleanup = () => $workflowToLoad.set(null);
export const useLoadWorkflow = () => {
const dispatch = useAppDispatch();
const workflowListMenu = useWorkflowListMenu();
const { getAndLoadWorkflow } = useGetAndLoadLibraryWorkflow();
const isTouched = useAppSelector(selectWorkflowIsTouched);
const loadImmediate = useCallback(async () => {
const workflow = $workflowToLoad.get();
if (!workflow) {
return;
}
const { workflowId, mode } = workflow;
await getAndLoadWorkflow(workflowId);
dispatch(workflowModeChanged(mode));
cleanup();
workflowListMenu.close();
}, [dispatch, getAndLoadWorkflow, workflowListMenu]);
const loadWithDialog = useCallback(
(workflowId: string, mode: 'view' | 'edit') => {
if (!isTouched) {
$workflowToLoad.set({
workflowId,
mode,
isOpen: false,
});
loadImmediate();
} else {
$workflowToLoad.set({
workflowId,
mode,
isOpen: true,
});
}
},
[loadImmediate, isTouched]
);
return {
loadImmediate,
loadWithDialog,
} as const;
};
export const LoadWorkflowConfirmationAlertDialog = memo(() => {
useAssertSingleton('LoadWorkflowConfirmationAlertDialog');
const { t } = useTranslation();
const workflow = useStore($workflowToLoad);
const loadWorkflow = useLoadWorkflow();
return (
<ConfirmationAlertDialog
isOpen={!!workflow?.isOpen}
onClose={cleanup}
title={t('nodes.loadWorkflow')}
acceptCallback={loadWorkflow.loadImmediate}
useInert={false}
>
<Flex flexDir="column" gap={2}>
<Text>{t('nodes.loadWorkflowDesc')}</Text>
<Text variant="subtext">{t('nodes.loadWorkflowDesc2')}</Text>
</Flex>
</ConfirmationAlertDialog>
);
});
LoadWorkflowConfirmationAlertDialog.displayName = 'LoadWorkflowConfirmationAlertDialog';