Skip to content

Commit 012054a

Browse files
feat(ui): add dialog when loading workflow if unsaved changes
1 parent efb7f36 commit 012054a

File tree

4 files changed

+98
-27
lines changed

4 files changed

+98
-27
lines changed

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -973,6 +973,8 @@
973973
"newWorkflow": "New Workflow",
974974
"newWorkflowDesc": "Create a new workflow?",
975975
"newWorkflowDesc2": "Your current workflow has unsaved changes.",
976+
"loadWorkflowDesc": "Load workflow?",
977+
"loadWorkflowDesc2": "Your current workflow has unsaved changes.",
976978
"clearWorkflow": "Clear Workflow",
977979
"clearWorkflowDesc": "Clear this workflow and start a new one?",
978980
"clearWorkflowDesc2": "Your current workflow has unsaved changes.",

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ import { configChanged } from 'features/system/store/configSlice';
3636
import { selectLanguage } from 'features/system/store/systemSelectors';
3737
import { AppContent } from 'features/ui/components/AppContent';
3838
import { DeleteWorkflowDialog } from 'features/workflowLibrary/components/DeleteLibraryWorkflowConfirmationAlertDialog';
39+
import { LoadWorkflowConfirmationAlertDialog } from 'features/workflowLibrary/components/LoadWorkflowConfirmationAlertDialog';
3940
import { NewWorkflowConfirmationAlertDialog } from 'features/workflowLibrary/components/NewWorkflowConfirmationAlertDialog';
4041
import i18n from 'i18n';
4142
import { size } from 'lodash-es';
@@ -75,6 +76,7 @@ const App = ({ config = DEFAULT_CONFIG, studioInitAction }: Props) => {
7576
<CancelAllExceptCurrentQueueItemConfirmationAlertDialog />
7677
<ClearQueueConfirmationsAlertDialog />
7778
<NewWorkflowConfirmationAlertDialog />
79+
<LoadWorkflowConfirmationAlertDialog />
7880
<DeleteStylePresetDialog />
7981
<DeleteWorkflowDialog />
8082
<ShareWorkflowModal />

invokeai/frontend/web/src/features/nodes/components/sidePanel/WorkflowListMenu/WorkflowListItem.tsx

Lines changed: 12 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
11
import { Badge, Flex, IconButton, Spacer, Text, Tooltip } from '@invoke-ai/ui-library';
22
import { useStore } from '@nanostores/react';
33
import { $projectUrl } from 'app/store/nanostores/projectId';
4-
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
4+
import { useAppSelector } from 'app/store/storeHooks';
55
import dateFormat, { masks } from 'dateformat';
6-
import { useWorkflowListMenu } from 'features/nodes/store/workflowListMenu';
7-
import { selectWorkflowId, workflowModeChanged } from 'features/nodes/store/workflowSlice';
6+
import { selectWorkflowId } from 'features/nodes/store/workflowSlice';
87
import { useDeleteWorkflow } from 'features/workflowLibrary/components/DeleteLibraryWorkflowConfirmationAlertDialog';
8+
import { useLoadWorkflow } from 'features/workflowLibrary/components/LoadWorkflowConfirmationAlertDialog';
99
import { useDownloadWorkflow } from 'features/workflowLibrary/hooks/useDownloadWorkflow';
10-
import { useGetAndLoadLibraryWorkflow } from 'features/workflowLibrary/hooks/useGetAndLoadLibraryWorkflow';
1110
import type { MouseEvent } from 'react';
1211
import { useCallback, useMemo, useState } from 'react';
1312
import { useTranslation } from 'react-i18next';
@@ -19,9 +18,7 @@ import { WorkflowListItemTooltip } from './WorkflowListItemTooltip';
1918

2019
export const WorkflowListItem = ({ workflow }: { workflow: WorkflowRecordListItemDTO }) => {
2120
const { t } = useTranslation();
22-
const dispatch = useAppDispatch();
2321
const projectUrl = useStore($projectUrl);
24-
const workflowListMenu = useWorkflowListMenu();
2522
const [isHovered, setIsHovered] = useState(false);
2623

2724
const handleMouseOver = useCallback(() => {
@@ -36,33 +33,21 @@ export const WorkflowListItem = ({ workflow }: { workflow: WorkflowRecordListIte
3633
const downloadWorkflow = useDownloadWorkflow();
3734
const shareWorkflow = useShareWorkflow();
3835
const deleteWorkflow = useDeleteWorkflow();
39-
const { getAndLoadWorkflow } = useGetAndLoadLibraryWorkflow({
40-
onSuccess: workflowListMenu.close,
41-
});
36+
const loadWorkflow = useLoadWorkflow();
4237

4338
const isActive = useMemo(() => {
4439
return workflowId === workflow.workflow_id;
4540
}, [workflowId, workflow.workflow_id]);
4641

47-
const handleClickLoad = useCallback(
48-
(e: MouseEvent<HTMLDivElement>) => {
49-
e.stopPropagation();
50-
getAndLoadWorkflow(workflow.workflow_id);
51-
workflowListMenu.close();
52-
},
53-
[getAndLoadWorkflow, workflow.workflow_id, workflowListMenu]
54-
);
42+
const handleClickLoad = useCallback(() => {
43+
setIsHovered(false);
44+
loadWorkflow.loadWithDialog(workflow.workflow_id, 'view');
45+
}, [loadWorkflow, workflow.workflow_id]);
5546

56-
const handleClickEdit = useCallback(
57-
async (e: MouseEvent<HTMLButtonElement>) => {
58-
e.stopPropagation();
59-
setIsHovered(false);
60-
await getAndLoadWorkflow(workflow.workflow_id);
61-
dispatch(workflowModeChanged('edit'));
62-
workflowListMenu.close();
63-
},
64-
[getAndLoadWorkflow, workflow.workflow_id, dispatch, workflowListMenu]
65-
);
47+
const handleClickEdit = useCallback(() => {
48+
setIsHovered(false);
49+
loadWorkflow.loadWithDialog(workflow.workflow_id, 'view');
50+
}, [loadWorkflow, workflow.workflow_id]);
6651

6752
const handleClickDelete = useCallback(
6853
(e: MouseEvent<HTMLButtonElement>) => {
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
import { ConfirmationAlertDialog, Flex, Text } from '@invoke-ai/ui-library';
2+
import { useStore } from '@nanostores/react';
3+
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
4+
import { useAssertSingleton } from 'common/hooks/useAssertSingleton';
5+
import { useWorkflowListMenu } from 'features/nodes/store/workflowListMenu';
6+
import { selectWorkflowIsTouched, workflowModeChanged } from 'features/nodes/store/workflowSlice';
7+
import { useGetAndLoadLibraryWorkflow } from 'features/workflowLibrary/hooks/useGetAndLoadLibraryWorkflow';
8+
import { atom } from 'nanostores';
9+
import { memo, useCallback } from 'react';
10+
import { useTranslation } from 'react-i18next';
11+
12+
const $workflowToLoad = atom<{ workflowId: string; mode: 'view' | 'edit'; isOpen: boolean } | null>(null);
13+
const cleanup = () => $workflowToLoad.set(null);
14+
15+
export const useLoadWorkflow = () => {
16+
const dispatch = useAppDispatch();
17+
const workflowListMenu = useWorkflowListMenu();
18+
const { getAndLoadWorkflow } = useGetAndLoadLibraryWorkflow();
19+
20+
const isTouched = useAppSelector(selectWorkflowIsTouched);
21+
22+
const loadImmediate = useCallback(async () => {
23+
const workflow = $workflowToLoad.get();
24+
if (!workflow) {
25+
return;
26+
}
27+
const { workflowId, mode } = workflow;
28+
await getAndLoadWorkflow(workflowId);
29+
dispatch(workflowModeChanged(mode));
30+
cleanup();
31+
workflowListMenu.close();
32+
}, [dispatch, getAndLoadWorkflow, workflowListMenu]);
33+
34+
const loadWithDialog = useCallback(
35+
(workflowId: string, mode: 'view' | 'edit') => {
36+
if (!isTouched) {
37+
$workflowToLoad.set({
38+
workflowId,
39+
mode,
40+
isOpen: false,
41+
});
42+
loadImmediate();
43+
} else {
44+
$workflowToLoad.set({
45+
workflowId,
46+
mode,
47+
isOpen: true,
48+
});
49+
}
50+
},
51+
[loadImmediate, isTouched]
52+
);
53+
54+
return {
55+
loadImmediate,
56+
loadWithDialog,
57+
} as const;
58+
};
59+
60+
export const LoadWorkflowConfirmationAlertDialog = memo(() => {
61+
useAssertSingleton('LoadWorkflowConfirmationAlertDialog');
62+
const { t } = useTranslation();
63+
const workflow = useStore($workflowToLoad);
64+
const loadWorkflow = useLoadWorkflow();
65+
66+
return (
67+
<ConfirmationAlertDialog
68+
isOpen={!!workflow?.isOpen}
69+
onClose={cleanup}
70+
title={t('nodes.loadWorkflow')}
71+
acceptCallback={loadWorkflow.loadImmediate}
72+
useInert={false}
73+
>
74+
<Flex flexDir="column" gap={2}>
75+
<Text>{t('nodes.loadWorkflowDesc')}</Text>
76+
<Text variant="subtext">{t('nodes.loadWorkflowDesc2')}</Text>
77+
</Flex>
78+
</ConfirmationAlertDialog>
79+
);
80+
});
81+
82+
LoadWorkflowConfirmationAlertDialog.displayName = 'LoadWorkflowConfirmationAlertDialog';

0 commit comments

Comments
 (0)