mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
feat(ui): revise workflow editor buttons
- Add menu to top-right of editor, save/saveas/download/upload/reset/settings moved in here - Add workflow name to top-center
This commit is contained in:
parent
283bb73418
commit
e4f67628c0
@ -1627,7 +1627,7 @@
|
|||||||
},
|
},
|
||||||
"workflows": {
|
"workflows": {
|
||||||
"workflows": "Workflows",
|
"workflows": "Workflows",
|
||||||
"workflowLibrary": "Workflow Library",
|
"workflowLibrary": "Library",
|
||||||
"userWorkflows": "My Workflows",
|
"userWorkflows": "My Workflows",
|
||||||
"defaultWorkflows": "Default Workflows",
|
"defaultWorkflows": "Default Workflows",
|
||||||
"openWorkflow": "Open Workflow",
|
"openWorkflow": "Open Workflow",
|
||||||
@ -1637,6 +1637,7 @@
|
|||||||
"downloadWorkflow": "Download Workflow",
|
"downloadWorkflow": "Download Workflow",
|
||||||
"saveWorkflow": "Save Workflow",
|
"saveWorkflow": "Save Workflow",
|
||||||
"saveWorkflowAs": "Save Workflow As",
|
"saveWorkflowAs": "Save Workflow As",
|
||||||
|
"savingWorkflow": "Saving Workflow...",
|
||||||
"problemSavingWorkflow": "Problem Saving Workflow",
|
"problemSavingWorkflow": "Problem Saving Workflow",
|
||||||
"workflowSaved": "Workflow Saved",
|
"workflowSaved": "Workflow Saved",
|
||||||
"noRecentWorkflows": "No Recent Workflows",
|
"noRecentWorkflows": "No Recent Workflows",
|
||||||
@ -1648,7 +1649,8 @@
|
|||||||
"searchWorkflows": "Search Workflows",
|
"searchWorkflows": "Search Workflows",
|
||||||
"clearWorkflowSearchFilter": "Clear Workflow Search Filter",
|
"clearWorkflowSearchFilter": "Clear Workflow Search Filter",
|
||||||
"workflowName": "Workflow Name",
|
"workflowName": "Workflow Name",
|
||||||
"workflowEditorReset": "Workflow Editor Reset"
|
"workflowEditorReset": "Workflow Editor Reset",
|
||||||
|
"workflowEditorMenu": "Workflow Editor Menu"
|
||||||
},
|
},
|
||||||
"app": {
|
"app": {
|
||||||
"storeNotInitialized": "Store is not initialized"
|
"storeNotInitialized": "Store is not initialized"
|
||||||
|
@ -7,12 +7,10 @@ import { MdDeviceHub } from 'react-icons/md';
|
|||||||
import 'reactflow/dist/style.css';
|
import 'reactflow/dist/style.css';
|
||||||
import AddNodePopover from './flow/AddNodePopover/AddNodePopover';
|
import AddNodePopover from './flow/AddNodePopover/AddNodePopover';
|
||||||
import { Flow } from './flow/Flow';
|
import { Flow } from './flow/Flow';
|
||||||
import TopLeftPanel from './flow/panels/TopLeftPanel/TopLeftPanel';
|
|
||||||
import TopCenterPanel from './flow/panels/TopCenterPanel/TopCenterPanel';
|
|
||||||
import TopRightPanel from './flow/panels/TopRightPanel/TopRightPanel';
|
|
||||||
import BottomLeftPanel from './flow/panels/BottomLeftPanel/BottomLeftPanel';
|
import BottomLeftPanel from './flow/panels/BottomLeftPanel/BottomLeftPanel';
|
||||||
import MinimapPanel from './flow/panels/MinimapPanel/MinimapPanel';
|
import MinimapPanel from './flow/panels/MinimapPanel/MinimapPanel';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import TopPanel from 'features/nodes/components/flow/panels/TopPanel/TopPanel';
|
||||||
|
|
||||||
const NodeEditor = () => {
|
const NodeEditor = () => {
|
||||||
const isReady = useAppSelector((state) => state.nodes.isReady);
|
const isReady = useAppSelector((state) => state.nodes.isReady);
|
||||||
@ -47,9 +45,7 @@ const NodeEditor = () => {
|
|||||||
>
|
>
|
||||||
<Flow />
|
<Flow />
|
||||||
<AddNodePopover />
|
<AddNodePopover />
|
||||||
<TopLeftPanel />
|
<TopPanel />
|
||||||
<TopCenterPanel />
|
|
||||||
<TopRightPanel />
|
|
||||||
<BottomLeftPanel />
|
<BottomLeftPanel />
|
||||||
<MinimapPanel />
|
<MinimapPanel />
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
@ -3,6 +3,25 @@ import { createSelector } from '@reduxjs/toolkit';
|
|||||||
import { stateSelector } from 'app/store/store';
|
import { stateSelector } from 'app/store/store';
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
|
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
|
||||||
|
import { useIsValidConnection } from 'features/nodes/hooks/useIsValidConnection';
|
||||||
|
import {
|
||||||
|
connectionEnded,
|
||||||
|
connectionMade,
|
||||||
|
connectionStarted,
|
||||||
|
edgeAdded,
|
||||||
|
edgeChangeStarted,
|
||||||
|
edgeDeleted,
|
||||||
|
edgesChanged,
|
||||||
|
edgesDeleted,
|
||||||
|
nodesChanged,
|
||||||
|
nodesDeleted,
|
||||||
|
selectedAll,
|
||||||
|
selectedEdgesChanged,
|
||||||
|
selectedNodesChanged,
|
||||||
|
selectionCopied,
|
||||||
|
selectionPasted,
|
||||||
|
viewportChanged,
|
||||||
|
} from 'features/nodes/store/nodesSlice';
|
||||||
import { $flow } from 'features/nodes/store/reactFlowInstance';
|
import { $flow } from 'features/nodes/store/reactFlowInstance';
|
||||||
import { bumpGlobalMenuCloseTrigger } from 'features/ui/store/uiSlice';
|
import { bumpGlobalMenuCloseTrigger } from 'features/ui/store/uiSlice';
|
||||||
import { MouseEvent, useCallback, useRef } from 'react';
|
import { MouseEvent, useCallback, useRef } from 'react';
|
||||||
@ -25,25 +44,6 @@ import {
|
|||||||
ReactFlowProps,
|
ReactFlowProps,
|
||||||
XYPosition,
|
XYPosition,
|
||||||
} from 'reactflow';
|
} from 'reactflow';
|
||||||
import { useIsValidConnection } from 'features/nodes/hooks/useIsValidConnection';
|
|
||||||
import {
|
|
||||||
connectionEnded,
|
|
||||||
connectionMade,
|
|
||||||
connectionStarted,
|
|
||||||
edgeAdded,
|
|
||||||
edgeChangeStarted,
|
|
||||||
edgeDeleted,
|
|
||||||
edgesChanged,
|
|
||||||
edgesDeleted,
|
|
||||||
nodesChanged,
|
|
||||||
nodesDeleted,
|
|
||||||
selectedAll,
|
|
||||||
selectedEdgesChanged,
|
|
||||||
selectedNodesChanged,
|
|
||||||
selectionCopied,
|
|
||||||
selectionPasted,
|
|
||||||
viewportChanged,
|
|
||||||
} from 'features/nodes/store/nodesSlice';
|
|
||||||
import CustomConnectionLine from './connectionLines/CustomConnectionLine';
|
import CustomConnectionLine from './connectionLines/CustomConnectionLine';
|
||||||
import InvocationCollapsedEdge from './edges/InvocationCollapsedEdge';
|
import InvocationCollapsedEdge from './edges/InvocationCollapsedEdge';
|
||||||
import InvocationDefaultEdge from './edges/InvocationDefaultEdge';
|
import InvocationDefaultEdge from './edges/InvocationDefaultEdge';
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { Flex } from '@chakra-ui/layout';
|
import { Flex, Heading } from '@chakra-ui/layout';
|
||||||
import { memo } from 'react';
|
import { memo } from 'react';
|
||||||
import DownloadWorkflowButton from 'features/workflowLibrary/components/DownloadWorkflowButton';
|
import DownloadWorkflowButton from 'features/workflowLibrary/components/DownloadWorkflowButton';
|
||||||
import UploadWorkflowButton from 'features/workflowLibrary/components/LoadWorkflowFromFileButton';
|
import UploadWorkflowButton from 'features/workflowLibrary/components/LoadWorkflowFromFileButton';
|
||||||
@ -6,10 +6,14 @@ import ResetWorkflowEditorButton from 'features/workflowLibrary/components/Reset
|
|||||||
import SaveWorkflowButton from 'features/workflowLibrary/components/SaveWorkflowButton';
|
import SaveWorkflowButton from 'features/workflowLibrary/components/SaveWorkflowButton';
|
||||||
import SaveWorkflowAsButton from 'features/workflowLibrary/components/SaveWorkflowAsButton';
|
import SaveWorkflowAsButton from 'features/workflowLibrary/components/SaveWorkflowAsButton';
|
||||||
import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus';
|
import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus';
|
||||||
|
import { useAppSelector } from 'app/store/storeHooks';
|
||||||
|
|
||||||
const TopCenterPanel = () => {
|
const TopCenterPanel = () => {
|
||||||
const isWorkflowLibraryEnabled =
|
const isWorkflowLibraryEnabled =
|
||||||
useFeatureStatus('workflowLibrary').isFeatureEnabled;
|
useFeatureStatus('workflowLibrary').isFeatureEnabled;
|
||||||
|
const name = useAppSelector(
|
||||||
|
(state) => state.workflow.name || 'Untitled Workflow'
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex
|
<Flex
|
||||||
@ -19,9 +23,21 @@ const TopCenterPanel = () => {
|
|||||||
top: 2,
|
top: 2,
|
||||||
insetInlineStart: '50%',
|
insetInlineStart: '50%',
|
||||||
transform: 'translate(-50%)',
|
transform: 'translate(-50%)',
|
||||||
|
alignItems: 'center',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<DownloadWorkflowButton />
|
<Heading
|
||||||
|
m={2}
|
||||||
|
size="md"
|
||||||
|
userSelect="none"
|
||||||
|
pointerEvents="none"
|
||||||
|
noOfLines={1}
|
||||||
|
wordBreak="break-all"
|
||||||
|
maxW={80}
|
||||||
|
>
|
||||||
|
{name}
|
||||||
|
</Heading>
|
||||||
|
{/* <DownloadWorkflowButton />
|
||||||
<UploadWorkflowButton />
|
<UploadWorkflowButton />
|
||||||
{isWorkflowLibraryEnabled && (
|
{isWorkflowLibraryEnabled && (
|
||||||
<>
|
<>
|
||||||
@ -29,7 +45,7 @@ const TopCenterPanel = () => {
|
|||||||
<SaveWorkflowAsButton />
|
<SaveWorkflowAsButton />
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
<ResetWorkflowEditorButton />
|
<ResetWorkflowEditorButton /> */}
|
||||||
</Flex>
|
</Flex>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -0,0 +1,26 @@
|
|||||||
|
import { useAppDispatch } from 'app/store/storeHooks';
|
||||||
|
import IAIIconButton from 'common/components/IAIIconButton';
|
||||||
|
import { addNodePopoverOpened } from 'features/nodes/store/nodesSlice';
|
||||||
|
import { memo, useCallback } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { FaPlus } from 'react-icons/fa';
|
||||||
|
|
||||||
|
const AddNodeButton = () => {
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const handleOpenAddNodePopover = useCallback(() => {
|
||||||
|
dispatch(addNodePopoverOpened());
|
||||||
|
}, [dispatch]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<IAIIconButton
|
||||||
|
tooltip={t('nodes.addNodeToolTip')}
|
||||||
|
aria-label={t('nodes.addNode')}
|
||||||
|
icon={<FaPlus />}
|
||||||
|
onClick={handleOpenAddNodePopover}
|
||||||
|
pointerEvents="auto"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default memo(AddNodeButton);
|
@ -0,0 +1,37 @@
|
|||||||
|
import { Flex, Spacer } from '@chakra-ui/layout';
|
||||||
|
import AddNodeButton from 'features/nodes/components/flow/panels/TopPanel/AddNodeButton';
|
||||||
|
import UpdateNodesButton from 'features/nodes/components/flow/panels/TopPanel/UpdateNodesButton';
|
||||||
|
import WorkflowName from 'features/nodes/components/flow/panels/TopPanel/WorkflowName';
|
||||||
|
import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus';
|
||||||
|
import WorkflowLibraryButton from 'features/workflowLibrary/components/WorkflowLibraryButton';
|
||||||
|
import WorkflowLibraryMenu from 'features/workflowLibrary/components/WorkflowLibraryMenu/WorkflowLibraryMenu';
|
||||||
|
import { memo } from 'react';
|
||||||
|
|
||||||
|
const TopCenterPanel = () => {
|
||||||
|
const isWorkflowLibraryEnabled =
|
||||||
|
useFeatureStatus('workflowLibrary').isFeatureEnabled;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Flex
|
||||||
|
sx={{
|
||||||
|
gap: 2,
|
||||||
|
top: 2,
|
||||||
|
left: 2,
|
||||||
|
right: 2,
|
||||||
|
position: 'absolute',
|
||||||
|
alignItems: 'center',
|
||||||
|
pointerEvents: 'none',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<AddNodeButton />
|
||||||
|
<UpdateNodesButton />
|
||||||
|
<Spacer />
|
||||||
|
<WorkflowName />
|
||||||
|
<Spacer />
|
||||||
|
{isWorkflowLibraryEnabled && <WorkflowLibraryButton />}
|
||||||
|
<WorkflowLibraryMenu />
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default memo(TopCenterPanel);
|
@ -0,0 +1,32 @@
|
|||||||
|
import { useAppDispatch } from 'app/store/storeHooks';
|
||||||
|
import IAIButton from 'common/components/IAIButton';
|
||||||
|
import { useGetNodesNeedUpdate } from 'features/nodes/hooks/useGetNodesNeedUpdate';
|
||||||
|
import { updateAllNodesRequested } from 'features/nodes/store/actions';
|
||||||
|
import { memo, useCallback } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { FaExclamationTriangle } from 'react-icons/fa';
|
||||||
|
|
||||||
|
const UpdateNodesButton = () => {
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const nodesNeedUpdate = useGetNodesNeedUpdate();
|
||||||
|
const handleClickUpdateNodes = useCallback(() => {
|
||||||
|
dispatch(updateAllNodesRequested());
|
||||||
|
}, [dispatch]);
|
||||||
|
|
||||||
|
if (!nodesNeedUpdate) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<IAIButton
|
||||||
|
leftIcon={<FaExclamationTriangle />}
|
||||||
|
onClick={handleClickUpdateNodes}
|
||||||
|
pointerEvents="auto"
|
||||||
|
>
|
||||||
|
{t('nodes.updateAllNodes')}
|
||||||
|
</IAIButton>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default memo(UpdateNodesButton);
|
@ -0,0 +1,25 @@
|
|||||||
|
import { Text } from '@chakra-ui/layout';
|
||||||
|
import { useAppSelector } from 'app/store/storeHooks';
|
||||||
|
import { memo } from 'react';
|
||||||
|
|
||||||
|
const TopCenterPanel = () => {
|
||||||
|
const name = useAppSelector(
|
||||||
|
(state) => state.workflow.name || 'Untitled Workflow'
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Text
|
||||||
|
m={2}
|
||||||
|
fontSize="lg"
|
||||||
|
userSelect="none"
|
||||||
|
noOfLines={1}
|
||||||
|
wordBreak="break-all"
|
||||||
|
fontWeight={600}
|
||||||
|
opacity={0.8}
|
||||||
|
>
|
||||||
|
{name}
|
||||||
|
</Text>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default memo(TopCenterPanel);
|
@ -1,8 +1,8 @@
|
|||||||
import { Flex } from '@chakra-ui/react';
|
import { Flex } from '@chakra-ui/react';
|
||||||
import WorkflowLibraryButton from 'features/workflowLibrary/components/WorkflowLibraryButton';
|
import WorkflowLibraryButton from 'features/workflowLibrary/components/WorkflowLibraryButton';
|
||||||
import { memo } from 'react';
|
import { memo } from 'react';
|
||||||
import WorkflowEditorSettings from './WorkflowEditorSettings';
|
|
||||||
import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus';
|
import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus';
|
||||||
|
import WorkflowLibraryMenu from 'features/workflowLibrary/components/WorkflowLibraryMenu/WorkflowLibraryMenu';
|
||||||
|
|
||||||
const TopRightPanel = () => {
|
const TopRightPanel = () => {
|
||||||
const isWorkflowLibraryEnabled =
|
const isWorkflowLibraryEnabled =
|
||||||
@ -11,7 +11,7 @@ const TopRightPanel = () => {
|
|||||||
return (
|
return (
|
||||||
<Flex sx={{ gap: 2, position: 'absolute', top: 2, insetInlineEnd: 2 }}>
|
<Flex sx={{ gap: 2, position: 'absolute', top: 2, insetInlineEnd: 2 }}>
|
||||||
{isWorkflowLibraryEnabled && <WorkflowLibraryButton />}
|
{isWorkflowLibraryEnabled && <WorkflowLibraryButton />}
|
||||||
<WorkflowEditorSettings />
|
<WorkflowLibraryMenu />
|
||||||
</Flex>
|
</Flex>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -9,15 +9,14 @@ import {
|
|||||||
ModalContent,
|
ModalContent,
|
||||||
ModalHeader,
|
ModalHeader,
|
||||||
ModalOverlay,
|
ModalOverlay,
|
||||||
forwardRef,
|
|
||||||
useDisclosure,
|
useDisclosure,
|
||||||
} from '@chakra-ui/react';
|
} from '@chakra-ui/react';
|
||||||
import { createSelector } from '@reduxjs/toolkit';
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
import { stateSelector } from 'app/store/store';
|
import { stateSelector } from 'app/store/store';
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
|
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
|
||||||
import IAIIconButton from 'common/components/IAIIconButton';
|
|
||||||
import IAISwitch from 'common/components/IAISwitch';
|
import IAISwitch from 'common/components/IAISwitch';
|
||||||
|
import ReloadNodeTemplatesButton from 'features/nodes/components/flow/panels/TopCenterPanel/ReloadSchemaButton';
|
||||||
import {
|
import {
|
||||||
selectionModeChanged,
|
selectionModeChanged,
|
||||||
shouldAnimateEdgesChanged,
|
shouldAnimateEdgesChanged,
|
||||||
@ -25,11 +24,9 @@ import {
|
|||||||
shouldSnapToGridChanged,
|
shouldSnapToGridChanged,
|
||||||
shouldValidateGraphChanged,
|
shouldValidateGraphChanged,
|
||||||
} from 'features/nodes/store/nodesSlice';
|
} from 'features/nodes/store/nodesSlice';
|
||||||
import { ChangeEvent, memo, useCallback } from 'react';
|
import { ChangeEvent, ReactNode, memo, useCallback } from 'react';
|
||||||
import { FaCog } from 'react-icons/fa';
|
|
||||||
import { SelectionMode } from 'reactflow';
|
|
||||||
import ReloadNodeTemplatesButton from 'features/nodes/components/flow/panels/TopCenterPanel/ReloadSchemaButton';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { SelectionMode } from 'reactflow';
|
||||||
|
|
||||||
const formLabelProps: FormLabelProps = {
|
const formLabelProps: FormLabelProps = {
|
||||||
fontWeight: 600,
|
fontWeight: 600,
|
||||||
@ -56,7 +53,11 @@ const selector = createSelector(
|
|||||||
defaultSelectorOptions
|
defaultSelectorOptions
|
||||||
);
|
);
|
||||||
|
|
||||||
const WorkflowEditorSettings = forwardRef((_, ref) => {
|
type Props = {
|
||||||
|
children: (props: { onOpen: () => void }) => ReactNode;
|
||||||
|
};
|
||||||
|
|
||||||
|
const WorkflowEditorSettings = ({ children }: Props) => {
|
||||||
const { isOpen, onOpen, onClose } = useDisclosure();
|
const { isOpen, onOpen, onClose } = useDisclosure();
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const {
|
const {
|
||||||
@ -106,13 +107,7 @@ const WorkflowEditorSettings = forwardRef((_, ref) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<IAIIconButton
|
{children({ onOpen })}
|
||||||
ref={ref}
|
|
||||||
aria-label={t('nodes.workflowSettings')}
|
|
||||||
tooltip={t('nodes.workflowSettings')}
|
|
||||||
icon={<FaCog />}
|
|
||||||
onClick={onOpen}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Modal isOpen={isOpen} onClose={onClose} size="2xl" isCentered>
|
<Modal isOpen={isOpen} onClose={onClose} size="2xl" isCentered>
|
||||||
<ModalOverlay />
|
<ModalOverlay />
|
||||||
@ -151,6 +146,7 @@ const WorkflowEditorSettings = forwardRef((_, ref) => {
|
|||||||
label={t('nodes.colorCodeEdges')}
|
label={t('nodes.colorCodeEdges')}
|
||||||
helperText={t('nodes.colorCodeEdgesHelp')}
|
helperText={t('nodes.colorCodeEdgesHelp')}
|
||||||
/>
|
/>
|
||||||
|
<Divider />
|
||||||
<IAISwitch
|
<IAISwitch
|
||||||
formLabelProps={formLabelProps}
|
formLabelProps={formLabelProps}
|
||||||
isChecked={selectionModeIsChecked}
|
isChecked={selectionModeIsChecked}
|
||||||
@ -175,6 +171,6 @@ const WorkflowEditorSettings = forwardRef((_, ref) => {
|
|||||||
</Modal>
|
</Modal>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
});
|
};
|
||||||
|
|
||||||
export default memo(WorkflowEditorSettings);
|
export default memo(WorkflowEditorSettings);
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import { useDisclosure } from '@chakra-ui/react';
|
import { useDisclosure } from '@chakra-ui/react';
|
||||||
import IAIIconButton from 'common/components/IAIIconButton';
|
import IAIButton from 'common/components/IAIButton';
|
||||||
|
import { WorkflowLibraryModalContext } from 'features/workflowLibrary/context/WorkflowLibraryModalContext';
|
||||||
import { memo } from 'react';
|
import { memo } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { FaFolderOpen } from 'react-icons/fa';
|
import { FaFolderOpen } from 'react-icons/fa';
|
||||||
import WorkflowLibraryModal from './WorkflowLibraryModal';
|
import WorkflowLibraryModal from './WorkflowLibraryModal';
|
||||||
import { WorkflowLibraryModalContext } from 'features/workflowLibrary/context/WorkflowLibraryModalContext';
|
|
||||||
|
|
||||||
const WorkflowLibraryButton = () => {
|
const WorkflowLibraryButton = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@ -12,12 +12,13 @@ const WorkflowLibraryButton = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<WorkflowLibraryModalContext.Provider value={disclosure}>
|
<WorkflowLibraryModalContext.Provider value={disclosure}>
|
||||||
<IAIIconButton
|
<IAIButton
|
||||||
icon={<FaFolderOpen />}
|
leftIcon={<FaFolderOpen />}
|
||||||
onClick={disclosure.onOpen}
|
onClick={disclosure.onOpen}
|
||||||
tooltip={t('workflows.workflowLibrary')}
|
pointerEvents="auto"
|
||||||
aria-label={t('workflows.workflowLibrary')}
|
>
|
||||||
/>
|
{t('workflows.workflowLibrary')}
|
||||||
|
</IAIButton>
|
||||||
<WorkflowLibraryModal />
|
<WorkflowLibraryModal />
|
||||||
</WorkflowLibraryModalContext.Provider>
|
</WorkflowLibraryModalContext.Provider>
|
||||||
);
|
);
|
||||||
|
@ -0,0 +1,18 @@
|
|||||||
|
import { MenuItem } from '@chakra-ui/react';
|
||||||
|
import { useDownloadWorkflow } from 'features/nodes/hooks/useDownloadWorkflow';
|
||||||
|
import { memo } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { FaDownload } from 'react-icons/fa';
|
||||||
|
|
||||||
|
const DownloadWorkflowMenuItem = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const downloadWorkflow = useDownloadWorkflow();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<MenuItem as="button" icon={<FaDownload />} onClick={downloadWorkflow}>
|
||||||
|
{t('workflows.downloadWorkflow')}
|
||||||
|
</MenuItem>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default memo(DownloadWorkflowMenuItem);
|
@ -0,0 +1,88 @@
|
|||||||
|
import {
|
||||||
|
AlertDialog,
|
||||||
|
AlertDialogBody,
|
||||||
|
AlertDialogContent,
|
||||||
|
AlertDialogFooter,
|
||||||
|
AlertDialogHeader,
|
||||||
|
AlertDialogOverlay,
|
||||||
|
Button,
|
||||||
|
Flex,
|
||||||
|
MenuItem,
|
||||||
|
Text,
|
||||||
|
useDisclosure,
|
||||||
|
} from '@chakra-ui/react';
|
||||||
|
import { useAppDispatch } 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 { memo, useCallback, useRef } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { FaTrash } from 'react-icons/fa';
|
||||||
|
|
||||||
|
const ResetWorkflowEditorMenuItem = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
const { isOpen, onOpen, onClose } = useDisclosure();
|
||||||
|
const cancelRef = useRef<HTMLButtonElement | null>(null);
|
||||||
|
|
||||||
|
const handleConfirmClear = useCallback(() => {
|
||||||
|
dispatch(nodeEditorReset());
|
||||||
|
|
||||||
|
dispatch(
|
||||||
|
addToast(
|
||||||
|
makeToast({
|
||||||
|
title: t('workflows.workflowEditorReset'),
|
||||||
|
status: 'success',
|
||||||
|
})
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
onClose();
|
||||||
|
}, [dispatch, t, onClose]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<MenuItem
|
||||||
|
as="button"
|
||||||
|
icon={<FaTrash />}
|
||||||
|
sx={{ color: 'error.600', _dark: { color: 'error.300' } }}
|
||||||
|
onClick={onOpen}
|
||||||
|
>
|
||||||
|
{t('nodes.resetWorkflow')}
|
||||||
|
</MenuItem>
|
||||||
|
|
||||||
|
<AlertDialog
|
||||||
|
isOpen={isOpen}
|
||||||
|
onClose={onClose}
|
||||||
|
leastDestructiveRef={cancelRef}
|
||||||
|
isCentered
|
||||||
|
>
|
||||||
|
<AlertDialogOverlay />
|
||||||
|
|
||||||
|
<AlertDialogContent>
|
||||||
|
<AlertDialogHeader fontSize="lg" fontWeight="bold">
|
||||||
|
{t('nodes.resetWorkflow')}
|
||||||
|
</AlertDialogHeader>
|
||||||
|
|
||||||
|
<AlertDialogBody py={4}>
|
||||||
|
<Flex flexDir="column" gap={2}>
|
||||||
|
<Text>{t('nodes.resetWorkflowDesc')}</Text>
|
||||||
|
<Text variant="subtext">{t('nodes.resetWorkflowDesc2')}</Text>
|
||||||
|
</Flex>
|
||||||
|
</AlertDialogBody>
|
||||||
|
|
||||||
|
<AlertDialogFooter>
|
||||||
|
<Button ref={cancelRef} onClick={onClose}>
|
||||||
|
{t('common.cancel')}
|
||||||
|
</Button>
|
||||||
|
<Button colorScheme="error" ml={3} onClick={handleConfirmClear}>
|
||||||
|
{t('common.accept')}
|
||||||
|
</Button>
|
||||||
|
</AlertDialogFooter>
|
||||||
|
</AlertDialogContent>
|
||||||
|
</AlertDialog>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default memo(ResetWorkflowEditorMenuItem);
|
@ -0,0 +1,85 @@
|
|||||||
|
import {
|
||||||
|
AlertDialog,
|
||||||
|
AlertDialogBody,
|
||||||
|
AlertDialogContent,
|
||||||
|
AlertDialogFooter,
|
||||||
|
AlertDialogHeader,
|
||||||
|
AlertDialogOverlay,
|
||||||
|
FormControl,
|
||||||
|
FormLabel,
|
||||||
|
Input,
|
||||||
|
MenuItem,
|
||||||
|
useDisclosure,
|
||||||
|
} from '@chakra-ui/react';
|
||||||
|
import { useAppSelector } from 'app/store/storeHooks';
|
||||||
|
import IAIButton from 'common/components/IAIButton';
|
||||||
|
import { useSaveWorkflowAs } from 'features/workflowLibrary/hooks/useSaveWorkflowAs';
|
||||||
|
import { getWorkflowCopyName } from 'features/workflowLibrary/util/getWorkflowCopyName';
|
||||||
|
import { ChangeEvent, memo, useCallback, useRef, useState } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { FaClone } from 'react-icons/fa';
|
||||||
|
|
||||||
|
const SaveWorkflowAsButton = () => {
|
||||||
|
const currentName = useAppSelector((state) => state.workflow.name);
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const { saveWorkflowAs } = useSaveWorkflowAs();
|
||||||
|
const [name, setName] = useState(getWorkflowCopyName(currentName));
|
||||||
|
const { isOpen, onOpen, onClose } = useDisclosure();
|
||||||
|
const inputRef = useRef<HTMLInputElement>(null);
|
||||||
|
|
||||||
|
const onOpenCallback = useCallback(() => {
|
||||||
|
setName(getWorkflowCopyName(currentName));
|
||||||
|
onOpen();
|
||||||
|
}, [currentName, onOpen]);
|
||||||
|
|
||||||
|
const onSave = useCallback(async () => {
|
||||||
|
saveWorkflowAs({ name, onSuccess: onClose, onError: onClose });
|
||||||
|
}, [name, onClose, saveWorkflowAs]);
|
||||||
|
|
||||||
|
const onChange = useCallback((e: ChangeEvent<HTMLInputElement>) => {
|
||||||
|
setName(e.target.value);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<MenuItem as="button" icon={<FaClone />} onClick={onOpenCallback}>
|
||||||
|
{t('workflows.saveWorkflowAs')}
|
||||||
|
</MenuItem>
|
||||||
|
<AlertDialog
|
||||||
|
isOpen={isOpen}
|
||||||
|
onClose={onClose}
|
||||||
|
leastDestructiveRef={inputRef}
|
||||||
|
isCentered
|
||||||
|
>
|
||||||
|
<AlertDialogOverlay>
|
||||||
|
<AlertDialogContent>
|
||||||
|
<AlertDialogHeader fontSize="lg" fontWeight="bold">
|
||||||
|
{t('workflows.saveWorkflowAs')}
|
||||||
|
</AlertDialogHeader>
|
||||||
|
|
||||||
|
<AlertDialogBody>
|
||||||
|
<FormControl>
|
||||||
|
<FormLabel>{t('workflows.workflowName')}</FormLabel>
|
||||||
|
<Input
|
||||||
|
ref={inputRef}
|
||||||
|
value={name}
|
||||||
|
onChange={onChange}
|
||||||
|
placeholder={t('workflows.workflowName')}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
</AlertDialogBody>
|
||||||
|
|
||||||
|
<AlertDialogFooter>
|
||||||
|
<IAIButton onClick={onClose}>{t('common.cancel')}</IAIButton>
|
||||||
|
<IAIButton colorScheme="accent" onClick={onSave} ml={3}>
|
||||||
|
{t('common.saveAs')}
|
||||||
|
</IAIButton>
|
||||||
|
</AlertDialogFooter>
|
||||||
|
</AlertDialogContent>
|
||||||
|
</AlertDialogOverlay>
|
||||||
|
</AlertDialog>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default memo(SaveWorkflowAsButton);
|
@ -0,0 +1,17 @@
|
|||||||
|
import { MenuItem } from '@chakra-ui/react';
|
||||||
|
import { useSaveLibraryWorkflow } from 'features/workflowLibrary/hooks/useSaveWorkflow';
|
||||||
|
import { memo } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { FaSave } from 'react-icons/fa';
|
||||||
|
|
||||||
|
const SaveLibraryWorkflowMenuItem = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const { saveWorkflow } = useSaveLibraryWorkflow();
|
||||||
|
return (
|
||||||
|
<MenuItem as="button" icon={<FaSave />} onClick={saveWorkflow}>
|
||||||
|
{t('workflows.saveWorkflow')}
|
||||||
|
</MenuItem>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default memo(SaveLibraryWorkflowMenuItem);
|
@ -0,0 +1,21 @@
|
|||||||
|
import { MenuItem } from '@chakra-ui/react';
|
||||||
|
import WorkflowEditorSettings from 'features/nodes/components/flow/panels/TopRightPanel/WorkflowEditorSettings';
|
||||||
|
import { memo } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { FaCog } from 'react-icons/fa';
|
||||||
|
|
||||||
|
const DownloadWorkflowMenuItem = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<WorkflowEditorSettings>
|
||||||
|
{({ onOpen }) => (
|
||||||
|
<MenuItem as="button" icon={<FaCog />} onClick={onOpen}>
|
||||||
|
{t('nodes.workflowSettings')}
|
||||||
|
</MenuItem>
|
||||||
|
)}
|
||||||
|
</WorkflowEditorSettings>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default memo(DownloadWorkflowMenuItem);
|
@ -0,0 +1,27 @@
|
|||||||
|
import { MenuItem } from '@chakra-ui/react';
|
||||||
|
import { FileButton } from '@mantine/core';
|
||||||
|
import { useLoadWorkflowFromFile } from 'features/workflowLibrary/hooks/useLoadWorkflowFromFile';
|
||||||
|
import { memo, useRef } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { FaUpload } from 'react-icons/fa';
|
||||||
|
|
||||||
|
const UploadWorkflowMenuItem = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const resetRef = useRef<() => void>(null);
|
||||||
|
const loadWorkflowFromFile = useLoadWorkflowFromFile({ resetRef });
|
||||||
|
return (
|
||||||
|
<FileButton
|
||||||
|
resetRef={resetRef}
|
||||||
|
accept="application/json"
|
||||||
|
onChange={loadWorkflowFromFile}
|
||||||
|
>
|
||||||
|
{(props) => (
|
||||||
|
<MenuItem as="button" icon={<FaUpload />} {...props}>
|
||||||
|
{t('workflows.uploadWorkflow')}
|
||||||
|
</MenuItem>
|
||||||
|
)}
|
||||||
|
</FileButton>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default memo(UploadWorkflowMenuItem);
|
@ -0,0 +1,50 @@
|
|||||||
|
import {
|
||||||
|
Menu,
|
||||||
|
MenuButton,
|
||||||
|
MenuDivider,
|
||||||
|
MenuList,
|
||||||
|
useDisclosure,
|
||||||
|
} from '@chakra-ui/react';
|
||||||
|
import IAIIconButton from 'common/components/IAIIconButton';
|
||||||
|
import { useGlobalMenuCloseTrigger } from 'common/hooks/useGlobalMenuCloseTrigger';
|
||||||
|
import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus';
|
||||||
|
import DownloadWorkflowMenuItem from 'features/workflowLibrary/components/WorkflowLibraryMenu/DownloadWorkflowMenuItem';
|
||||||
|
import ResetWorkflowEditorMenuItem from 'features/workflowLibrary/components/WorkflowLibraryMenu/ResetWorkflowEditorMenuItem';
|
||||||
|
import SaveWorkflowAsMenuItem from 'features/workflowLibrary/components/WorkflowLibraryMenu/SaveWorkflowAsMenuItem';
|
||||||
|
import SaveWorkflowMenuItem from 'features/workflowLibrary/components/WorkflowLibraryMenu/SaveWorkflowMenuItem';
|
||||||
|
import SettingsMenuItem from 'features/workflowLibrary/components/WorkflowLibraryMenu/SettingsMenuItem';
|
||||||
|
import UploadWorkflowMenuItem from 'features/workflowLibrary/components/WorkflowLibraryMenu/UploadWorkflowMenuItem';
|
||||||
|
import { memo } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { FaEllipsis } from 'react-icons/fa6';
|
||||||
|
import { menuListMotionProps } from 'theme/components/menu';
|
||||||
|
|
||||||
|
const WorkflowLibraryMenu = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const { isOpen, onOpen, onClose } = useDisclosure();
|
||||||
|
useGlobalMenuCloseTrigger(onClose);
|
||||||
|
const isWorkflowLibraryEnabled =
|
||||||
|
useFeatureStatus('workflowLibrary').isFeatureEnabled;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Menu isOpen={isOpen} onOpen={onOpen} onClose={onClose}>
|
||||||
|
<MenuButton
|
||||||
|
as={IAIIconButton}
|
||||||
|
aria-label={t('workflows.workflowEditorMenu')}
|
||||||
|
icon={<FaEllipsis />}
|
||||||
|
pointerEvents="auto"
|
||||||
|
/>
|
||||||
|
<MenuList motionProps={menuListMotionProps} pointerEvents="auto">
|
||||||
|
{isWorkflowLibraryEnabled && <SaveWorkflowMenuItem />}
|
||||||
|
{isWorkflowLibraryEnabled && <SaveWorkflowAsMenuItem />}
|
||||||
|
<DownloadWorkflowMenuItem />
|
||||||
|
<UploadWorkflowMenuItem />
|
||||||
|
<ResetWorkflowEditorMenuItem />
|
||||||
|
<MenuDivider />
|
||||||
|
<SettingsMenuItem />
|
||||||
|
</MenuList>
|
||||||
|
</Menu>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default memo(WorkflowLibraryMenu);
|
@ -0,0 +1,17 @@
|
|||||||
|
import { useWorkflow } from 'features/nodes/hooks/useWorkflow';
|
||||||
|
import { useCallback } from 'react';
|
||||||
|
|
||||||
|
export const useDownloadWorkflow = () => {
|
||||||
|
const workflow = useWorkflow();
|
||||||
|
const downloadWorkflow = useCallback(() => {
|
||||||
|
const blob = new Blob([JSON.stringify(workflow, null, 2)]);
|
||||||
|
const a = document.createElement('a');
|
||||||
|
a.href = URL.createObjectURL(blob);
|
||||||
|
a.download = `${workflow.name || 'My Workflow'}.json`;
|
||||||
|
document.body.appendChild(a);
|
||||||
|
a.click();
|
||||||
|
a.remove();
|
||||||
|
}, [workflow]);
|
||||||
|
|
||||||
|
return downloadWorkflow;
|
||||||
|
};
|
@ -45,6 +45,9 @@ const invokeAI = definePartsStyle((props) => ({
|
|||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
divider: {
|
||||||
|
borderColor: mode('base.400', 'base.700')(props),
|
||||||
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export const menuTheme = defineMultiStyleConfig({
|
export const menuTheme = defineMultiStyleConfig({
|
||||||
|
Loading…
Reference in New Issue
Block a user