mirror of
https://github.com/invoke-ai/InvokeAI.git
synced 2026-04-23 03:00:31 -04:00
workflow tab (#5680)
* new workflow tab UI - still using shared state with workflow editor tab * polish workflow details * remove workflow tab, add edit/view mode to workflow slice and get that working to switch between within editor tab * UI updates for view/edit mode * cleanup * add warning to view mode * lint * start with isTouched false * working on styling mode toggle * more UX iteration * lint * cleanup * save original field values to state, add indicator if they have been changed and give user choice to reset * lint * fix import and commit translation * dont switch to view mode when loading a workflow * warns before clearing editor * use folder icon * fix(ui): track do not erase value when resetting field value - When adding an exposed field, we need to add it to originalExposedFieldValues - When removing an exposed field, we need to remove it from originalExposedFieldValues - add `useFieldValue` and `useOriginalFieldValue` hooks to encapsulate related logic * feat(ui): use IconButton for workflow view/edit button * feat(ui): change icon for new workflow It was the same as the workflow tab icon, confusing bc you think it's going to somehow take you to the tab. * feat(ui): use render props for NewWorkflowConfirmationAlertDialog There was a lot of potentially sensitive logic shared between the new workflow button and menu items. Also, two instances of ConfirmationAlertDialog. Using a render prop deduplicates the logic & components * fix(ui): do not mark workflow touched when loading workflow This was occurring because the `nodesChanged` action is called by reactflow when loading a workflow. Specifically, it calculates and sets the node dimensions as it loads. The existing logic set `isTouched` whenever this action was called. The changes reactflow emits have types, and we can use the change types and data to determine if a change should result in the workflow being marked as touched. * chore(ui): lint * chore(ui): lint * delete empty file --------- Co-authored-by: Mary Hipp <maryhipp@Marys-MacBook-Air.local> Co-authored-by: psychedelicious <4822129+psychedelicious@users.noreply.github.com>
This commit is contained in:
@@ -0,0 +1,26 @@
|
||||
import { IconButton } from '@invoke-ai/ui-library';
|
||||
import { NewWorkflowConfirmationAlertDialog } from 'features/workflowLibrary/components/NewWorkflowConfirmationAlertDialog';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { PiFilePlusBold } from 'react-icons/pi';
|
||||
|
||||
export const NewWorkflowButton = memo(() => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const renderButton = useCallback(
|
||||
(onClick: () => void) => (
|
||||
<IconButton
|
||||
aria-label={t('nodes.newWorkflow')}
|
||||
tooltip={t('nodes.newWorkflow')}
|
||||
icon={<PiFilePlusBold />}
|
||||
onClick={onClick}
|
||||
pointerEvents="auto"
|
||||
/>
|
||||
),
|
||||
[t]
|
||||
);
|
||||
|
||||
return <NewWorkflowConfirmationAlertDialog renderButton={renderButton} />;
|
||||
});
|
||||
|
||||
NewWorkflowButton.displayName = 'NewWorkflowButton';
|
||||
@@ -0,0 +1,63 @@
|
||||
import { ConfirmationAlertDialog, Flex, Text, useDisclosure } from '@invoke-ai/ui-library';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { nodeEditorReset } from 'features/nodes/store/nodesSlice';
|
||||
import { workflowModeChanged } from 'features/nodes/store/workflowSlice';
|
||||
import { addToast } from 'features/system/store/systemSlice';
|
||||
import { makeToast } from 'features/system/util/makeToast';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
type Props = {
|
||||
renderButton: (onClick: () => void) => JSX.Element;
|
||||
};
|
||||
|
||||
export const NewWorkflowConfirmationAlertDialog = memo((props: Props) => {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useAppDispatch();
|
||||
const { isOpen, onOpen, onClose } = useDisclosure();
|
||||
const isTouched = useAppSelector((s) => s.workflow.isTouched);
|
||||
|
||||
const handleNewWorkflow = useCallback(() => {
|
||||
dispatch(nodeEditorReset());
|
||||
dispatch(workflowModeChanged('edit'));
|
||||
|
||||
dispatch(
|
||||
addToast(
|
||||
makeToast({
|
||||
title: t('workflows.newWorkflowCreated'),
|
||||
status: 'success',
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
onClose();
|
||||
}, [dispatch, onClose, t]);
|
||||
|
||||
const onClick = useCallback(() => {
|
||||
if (!isTouched) {
|
||||
handleNewWorkflow();
|
||||
return;
|
||||
}
|
||||
onOpen();
|
||||
}, [handleNewWorkflow, isTouched, onOpen]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{props.renderButton(onClick)}
|
||||
|
||||
<ConfirmationAlertDialog
|
||||
isOpen={isOpen}
|
||||
onClose={onClose}
|
||||
title={t('nodes.newWorkflow')}
|
||||
acceptCallback={handleNewWorkflow}
|
||||
>
|
||||
<Flex flexDir="column" gap={2}>
|
||||
<Text>{t('nodes.newWorkflowDesc')}</Text>
|
||||
<Text variant="subtext">{t('nodes.newWorkflowDesc2')}</Text>
|
||||
</Flex>
|
||||
</ConfirmationAlertDialog>
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
NewWorkflowConfirmationAlertDialog.displayName = 'NewWorkflowConfirmationAlertDialog';
|
||||
@@ -2,7 +2,7 @@ import { IconButton, useDisclosure } from '@invoke-ai/ui-library';
|
||||
import { WorkflowLibraryModalContext } from 'features/workflowLibrary/context/WorkflowLibraryModalContext';
|
||||
import { memo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { PiBooksBold } from 'react-icons/pi';
|
||||
import { PiFolderOpenBold } from 'react-icons/pi';
|
||||
|
||||
import WorkflowLibraryModal from './WorkflowLibraryModal';
|
||||
|
||||
@@ -15,7 +15,7 @@ const WorkflowLibraryButton = () => {
|
||||
<IconButton
|
||||
aria-label={t('workflows.workflowLibrary')}
|
||||
tooltip={t('workflows.workflowLibrary')}
|
||||
icon={<PiBooksBold />}
|
||||
icon={<PiFolderOpenBold />}
|
||||
onClick={disclosure.onOpen}
|
||||
pointerEvents="auto"
|
||||
/>
|
||||
|
||||
@@ -1,60 +1,22 @@
|
||||
import { ConfirmationAlertDialog, Flex, MenuItem, Text, useDisclosure } from '@invoke-ai/ui-library';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { nodeEditorReset } from 'features/nodes/store/nodesSlice';
|
||||
import { addToast } from 'features/system/store/systemSlice';
|
||||
import { makeToast } from 'features/system/util/makeToast';
|
||||
import { MenuItem } from '@invoke-ai/ui-library';
|
||||
import { NewWorkflowConfirmationAlertDialog } from 'features/workflowLibrary/components/NewWorkflowConfirmationAlertDialog';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { PiFlowArrowBold } from 'react-icons/pi';
|
||||
import { PiFilePlusBold } from 'react-icons/pi';
|
||||
|
||||
const NewWorkflowMenuItem = () => {
|
||||
export const NewWorkflowMenuItem = memo(() => {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useAppDispatch();
|
||||
const { isOpen, onOpen, onClose } = useDisclosure();
|
||||
const isTouched = useAppSelector((s) => s.workflow.isTouched);
|
||||
|
||||
const handleNewWorkflow = useCallback(() => {
|
||||
dispatch(nodeEditorReset());
|
||||
|
||||
dispatch(
|
||||
addToast(
|
||||
makeToast({
|
||||
title: t('workflows.newWorkflowCreated'),
|
||||
status: 'success',
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
onClose();
|
||||
}, [dispatch, onClose, t]);
|
||||
|
||||
const onClick = useCallback(() => {
|
||||
if (!isTouched) {
|
||||
handleNewWorkflow();
|
||||
return;
|
||||
}
|
||||
onOpen();
|
||||
}, [handleNewWorkflow, isTouched, onOpen]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<MenuItem as="button" icon={<PiFlowArrowBold />} onClick={onClick}>
|
||||
const renderButton = useCallback(
|
||||
(onClick: () => void) => (
|
||||
<MenuItem as="button" icon={<PiFilePlusBold />} onClick={onClick}>
|
||||
{t('nodes.newWorkflow')}
|
||||
</MenuItem>
|
||||
|
||||
<ConfirmationAlertDialog
|
||||
isOpen={isOpen}
|
||||
onClose={onClose}
|
||||
title={t('nodes.newWorkflow')}
|
||||
acceptCallback={handleNewWorkflow}
|
||||
>
|
||||
<Flex flexDir="column" gap={2}>
|
||||
<Text>{t('nodes.newWorkflowDesc')}</Text>
|
||||
<Text variant="subtext">{t('nodes.newWorkflowDesc2')}</Text>
|
||||
</Flex>
|
||||
</ConfirmationAlertDialog>
|
||||
</>
|
||||
),
|
||||
[t]
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(NewWorkflowMenuItem);
|
||||
return <NewWorkflowConfirmationAlertDialog renderButton={renderButton} />;
|
||||
});
|
||||
|
||||
NewWorkflowMenuItem.displayName = 'NewWorkflowMenuItem';
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
useGlobalMenuClose,
|
||||
} from '@invoke-ai/ui-library';
|
||||
import DownloadWorkflowMenuItem from 'features/workflowLibrary/components/WorkflowLibraryMenu/DownloadWorkflowMenuItem';
|
||||
import NewWorkflowMenuItem from 'features/workflowLibrary/components/WorkflowLibraryMenu/NewWorkflowMenuItem';
|
||||
import { NewWorkflowMenuItem } from 'features/workflowLibrary/components/WorkflowLibraryMenu/NewWorkflowMenuItem';
|
||||
import SaveWorkflowAsMenuItem from 'features/workflowLibrary/components/WorkflowLibraryMenu/SaveWorkflowAsMenuItem';
|
||||
import SaveWorkflowMenuItem from 'features/workflowLibrary/components/WorkflowLibraryMenu/SaveWorkflowMenuItem';
|
||||
import SettingsMenuItem from 'features/workflowLibrary/components/WorkflowLibraryMenu/SettingsMenuItem';
|
||||
|
||||
Reference in New Issue
Block a user