mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
feat(ui): node buttons and shadow
This commit is contained in:
parent
2ec8fd3dc7
commit
0cb886b915
@ -718,12 +718,12 @@
|
|||||||
"swapSizes": "Swap Sizes"
|
"swapSizes": "Swap Sizes"
|
||||||
},
|
},
|
||||||
"nodes": {
|
"nodes": {
|
||||||
"reloadSchema": "Reload Node Templates",
|
"reloadNodeTemplates": "Reload Node Templates",
|
||||||
"saveGraph": "Save Workflow",
|
"saveWorkflow": "Save Workflow",
|
||||||
"loadGraph": "Load Workflow",
|
"loadWorkflow": "Load Workflow",
|
||||||
"resetGraph": "Reset Workflow",
|
"resetWorkflow": "Reset Workflow",
|
||||||
"clearGraph": "Reset Graph",
|
"resetWorkflowDesc": "Are you sure you want to reset this workflow?",
|
||||||
"clearGraphDesc": "Are you sure you want to clear all nodes?",
|
"resetWorkflowDesc2": "Resetting the workflow will clear all nodes, edges and workflow details.",
|
||||||
"zoomInNodes": "Zoom In",
|
"zoomInNodes": "Zoom In",
|
||||||
"zoomOutNodes": "Zoom Out",
|
"zoomOutNodes": "Zoom Out",
|
||||||
"fitViewportNodes": "Fit View",
|
"fitViewportNodes": "Fit View",
|
||||||
|
@ -24,34 +24,40 @@ type NodeWrapperProps = PropsWithChildren & {
|
|||||||
const NodeWrapper = (props: NodeWrapperProps) => {
|
const NodeWrapper = (props: NodeWrapperProps) => {
|
||||||
const { nodeId, width, children, selected } = props;
|
const { nodeId, width, children, selected } = props;
|
||||||
|
|
||||||
const selectNodeExecutionState = useMemo(
|
const selectIsInProgress = useMemo(
|
||||||
() =>
|
() =>
|
||||||
createSelector(
|
createSelector(
|
||||||
stateSelector,
|
stateSelector,
|
||||||
({ nodes }) => nodes.nodeExecutionStates[nodeId]
|
({ nodes }) =>
|
||||||
|
nodes.nodeExecutionStates[nodeId]?.status === NodeStatus.IN_PROGRESS
|
||||||
),
|
),
|
||||||
[nodeId]
|
[nodeId]
|
||||||
);
|
);
|
||||||
|
|
||||||
const nodeExecutionState = useAppSelector(selectNodeExecutionState);
|
const isInProgress = useAppSelector(selectIsInProgress);
|
||||||
|
|
||||||
const [
|
const [
|
||||||
nodeSelectedOutlineLight,
|
nodeSelectedLight,
|
||||||
nodeSelectedOutlineDark,
|
nodeSelectedDark,
|
||||||
|
nodeInProgressLight,
|
||||||
|
nodeInProgressDark,
|
||||||
shadowsXl,
|
shadowsXl,
|
||||||
shadowsBase,
|
shadowsBase,
|
||||||
] = useToken('shadows', [
|
] = useToken('shadows', [
|
||||||
'nodeSelectedOutline.light',
|
'nodeSelected.light',
|
||||||
'nodeSelectedOutline.dark',
|
'nodeSelected.dark',
|
||||||
|
'nodeInProgress.light',
|
||||||
|
'nodeInProgress.dark',
|
||||||
'shadows.xl',
|
'shadows.xl',
|
||||||
'shadows.base',
|
'shadows.base',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
const shadow = useColorModeValue(
|
const selectedShadow = useColorModeValue(nodeSelectedLight, nodeSelectedDark);
|
||||||
nodeSelectedOutlineLight,
|
const inProgressShadow = useColorModeValue(
|
||||||
nodeSelectedOutlineDark
|
nodeInProgressLight,
|
||||||
|
nodeInProgressDark
|
||||||
);
|
);
|
||||||
|
|
||||||
const opacity = useAppSelector((state) => state.nodes.nodeOpacity);
|
const opacity = useAppSelector((state) => state.nodes.nodeOpacity);
|
||||||
@ -71,24 +77,9 @@ const NodeWrapper = (props: NodeWrapperProps) => {
|
|||||||
w: width ?? NODE_WIDTH,
|
w: width ?? NODE_WIDTH,
|
||||||
transitionProperty: 'common',
|
transitionProperty: 'common',
|
||||||
transitionDuration: '0.1s',
|
transitionDuration: '0.1s',
|
||||||
shadow: selected
|
shadow: selected ? selectedShadow : undefined,
|
||||||
? nodeExecutionState?.status === NodeStatus.IN_PROGRESS
|
|
||||||
? undefined
|
|
||||||
: shadow
|
|
||||||
: undefined,
|
|
||||||
cursor: 'grab',
|
cursor: 'grab',
|
||||||
opacity,
|
opacity,
|
||||||
borderWidth: 2,
|
|
||||||
borderColor:
|
|
||||||
nodeExecutionState?.status === NodeStatus.IN_PROGRESS
|
|
||||||
? 'warning.300'
|
|
||||||
: 'base.200',
|
|
||||||
_dark: {
|
|
||||||
borderColor:
|
|
||||||
nodeExecutionState?.status === NodeStatus.IN_PROGRESS
|
|
||||||
? 'warning.500'
|
|
||||||
: 'base.900',
|
|
||||||
},
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Box
|
<Box
|
||||||
@ -104,6 +95,22 @@ const NodeWrapper = (props: NodeWrapperProps) => {
|
|||||||
zIndex: -1,
|
zIndex: -1,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
position: 'absolute',
|
||||||
|
top: 0,
|
||||||
|
insetInlineEnd: 0,
|
||||||
|
bottom: 0,
|
||||||
|
insetInlineStart: 0,
|
||||||
|
borderRadius: 'md',
|
||||||
|
pointerEvents: 'none',
|
||||||
|
transitionProperty: 'common',
|
||||||
|
transitionDuration: 'normal',
|
||||||
|
opacity: 0.7,
|
||||||
|
shadow: isInProgress ? inProgressShadow : undefined,
|
||||||
|
zIndex: -1,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
{children}
|
{children}
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
|
@ -1,20 +1,19 @@
|
|||||||
import { ButtonGroup, Tooltip } from '@chakra-ui/react';
|
import { ButtonGroup } from '@chakra-ui/react';
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import IAIIconButton from 'common/components/IAIIconButton';
|
import IAIIconButton from 'common/components/IAIIconButton';
|
||||||
|
import {
|
||||||
|
// shouldShowFieldTypeLegendChanged,
|
||||||
|
shouldShowMinimapPanelChanged,
|
||||||
|
} from 'features/nodes/store/nodesSlice';
|
||||||
import { memo, useCallback } from 'react';
|
import { memo, useCallback } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import {
|
import {
|
||||||
FaExpand,
|
FaExpand,
|
||||||
// FaInfo,
|
// FaInfo,
|
||||||
FaMapMarkerAlt,
|
FaMapMarkerAlt,
|
||||||
FaMinus,
|
|
||||||
FaPlus,
|
|
||||||
} from 'react-icons/fa';
|
} from 'react-icons/fa';
|
||||||
|
import { FaMagnifyingGlassMinus, FaMagnifyingGlassPlus } from 'react-icons/fa6';
|
||||||
import { useReactFlow } from 'reactflow';
|
import { useReactFlow } from 'reactflow';
|
||||||
import {
|
|
||||||
// shouldShowFieldTypeLegendChanged,
|
|
||||||
shouldShowMinimapPanelChanged,
|
|
||||||
} from 'features/nodes/store/nodesSlice';
|
|
||||||
|
|
||||||
const ViewportControls = () => {
|
const ViewportControls = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@ -49,27 +48,24 @@ const ViewportControls = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<ButtonGroup isAttached orientation="vertical">
|
<ButtonGroup isAttached orientation="vertical">
|
||||||
<Tooltip label={t('nodes.zoomInNodes')}>
|
<IAIIconButton
|
||||||
<IAIIconButton
|
tooltip={t('nodes.zoomInNodes')}
|
||||||
aria-label="Zoom in "
|
aria-label={t('nodes.zoomInNodes')}
|
||||||
onClick={handleClickedZoomIn}
|
onClick={handleClickedZoomIn}
|
||||||
icon={<FaPlus />}
|
icon={<FaMagnifyingGlassPlus />}
|
||||||
/>
|
/>
|
||||||
</Tooltip>
|
<IAIIconButton
|
||||||
<Tooltip label={t('nodes.zoomOutNodes')}>
|
tooltip={t('nodes.zoomOutNodes')}
|
||||||
<IAIIconButton
|
aria-label={t('nodes.zoomOutNodes')}
|
||||||
aria-label="Zoom out"
|
onClick={handleClickedZoomOut}
|
||||||
onClick={handleClickedZoomOut}
|
icon={<FaMagnifyingGlassMinus />}
|
||||||
icon={<FaMinus />}
|
/>
|
||||||
/>
|
<IAIIconButton
|
||||||
</Tooltip>
|
tooltip={t('nodes.fitViewportNodes')}
|
||||||
<Tooltip label={t('nodes.fitViewportNodes')}>
|
aria-label={t('nodes.fitViewportNodes')}
|
||||||
<IAIIconButton
|
onClick={handleClickedFitView}
|
||||||
aria-label="Fit viewport"
|
icon={<FaExpand />}
|
||||||
onClick={handleClickedFitView}
|
/>
|
||||||
icon={<FaExpand />}
|
|
||||||
/>
|
|
||||||
</Tooltip>
|
|
||||||
{/* <Tooltip
|
{/* <Tooltip
|
||||||
label={
|
label={
|
||||||
shouldShowFieldTypeLegend
|
shouldShowFieldTypeLegend
|
||||||
@ -84,20 +80,21 @@ const ViewportControls = () => {
|
|||||||
icon={<FaInfo />}
|
icon={<FaInfo />}
|
||||||
/>
|
/>
|
||||||
</Tooltip> */}
|
</Tooltip> */}
|
||||||
<Tooltip
|
<IAIIconButton
|
||||||
label={
|
tooltip={
|
||||||
shouldShowMinimapPanel
|
shouldShowMinimapPanel
|
||||||
? t('nodes.hideMinimapnodes')
|
? t('nodes.hideMinimapnodes')
|
||||||
: t('nodes.showMinimapnodes')
|
: t('nodes.showMinimapnodes')
|
||||||
}
|
}
|
||||||
>
|
aria-label={
|
||||||
<IAIIconButton
|
shouldShowMinimapPanel
|
||||||
aria-label="Toggle minimap"
|
? t('nodes.hideMinimapnodes')
|
||||||
isChecked={shouldShowMinimapPanel}
|
: t('nodes.showMinimapnodes')
|
||||||
onClick={handleClickedToggleMiniMapPanel}
|
}
|
||||||
icon={<FaMapMarkerAlt />}
|
isChecked={shouldShowMinimapPanel}
|
||||||
/>
|
onClick={handleClickedToggleMiniMapPanel}
|
||||||
</Tooltip>
|
icon={<FaMapMarkerAlt />}
|
||||||
|
/>
|
||||||
</ButtonGroup>
|
</ButtonGroup>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -2,9 +2,11 @@ import { FileButton } from '@mantine/core';
|
|||||||
import IAIIconButton from 'common/components/IAIIconButton';
|
import IAIIconButton from 'common/components/IAIIconButton';
|
||||||
import { useLoadWorkflowFromFile } from 'features/nodes/hooks/useLoadWorkflowFromFile';
|
import { useLoadWorkflowFromFile } from 'features/nodes/hooks/useLoadWorkflowFromFile';
|
||||||
import { memo, useRef } from 'react';
|
import { memo, useRef } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import { FaUpload } from 'react-icons/fa';
|
import { FaUpload } from 'react-icons/fa';
|
||||||
|
|
||||||
const LoadWorkflowButton = () => {
|
const LoadWorkflowButton = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
const resetRef = useRef<() => void>(null);
|
const resetRef = useRef<() => void>(null);
|
||||||
const loadWorkflowFromFile = useLoadWorkflowFromFile();
|
const loadWorkflowFromFile = useLoadWorkflowFromFile();
|
||||||
return (
|
return (
|
||||||
@ -16,8 +18,8 @@ const LoadWorkflowButton = () => {
|
|||||||
{(props) => (
|
{(props) => (
|
||||||
<IAIIconButton
|
<IAIIconButton
|
||||||
icon={<FaUpload />}
|
icon={<FaUpload />}
|
||||||
tooltip="Load Workflow"
|
tooltip={t('nodes.loadWorkflow')}
|
||||||
aria-label="Load Workflow"
|
aria-label={t('nodes.loadWorkflow')}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
@ -5,7 +5,7 @@ import { useTranslation } from 'react-i18next';
|
|||||||
import { FaSyncAlt } from 'react-icons/fa';
|
import { FaSyncAlt } from 'react-icons/fa';
|
||||||
import { receivedOpenAPISchema } from 'services/api/thunks/schema';
|
import { receivedOpenAPISchema } from 'services/api/thunks/schema';
|
||||||
|
|
||||||
const ReloadSchemaButton = () => {
|
const ReloadNodeTemplatesButton = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
@ -16,13 +16,13 @@ const ReloadSchemaButton = () => {
|
|||||||
return (
|
return (
|
||||||
<IAIButton
|
<IAIButton
|
||||||
leftIcon={<FaSyncAlt />}
|
leftIcon={<FaSyncAlt />}
|
||||||
tooltip={t('nodes.reloadSchema')}
|
tooltip={t('nodes.reloadNodeTemplates')}
|
||||||
aria-label={t('nodes.reloadSchema')}
|
aria-label={t('nodes.reloadNodeTemplates')}
|
||||||
onClick={handleReloadSchema}
|
onClick={handleReloadSchema}
|
||||||
>
|
>
|
||||||
{t('nodes.reloadSchema')}
|
{t('nodes.reloadNodeTemplates')}
|
||||||
</IAIButton>
|
</IAIButton>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default memo(ReloadSchemaButton);
|
export default memo(ReloadNodeTemplatesButton);
|
||||||
|
@ -6,7 +6,10 @@ import {
|
|||||||
AlertDialogHeader,
|
AlertDialogHeader,
|
||||||
AlertDialogOverlay,
|
AlertDialogOverlay,
|
||||||
Button,
|
Button,
|
||||||
|
Divider,
|
||||||
|
Flex,
|
||||||
Text,
|
Text,
|
||||||
|
VStack,
|
||||||
useDisclosure,
|
useDisclosure,
|
||||||
} from '@chakra-ui/react';
|
} from '@chakra-ui/react';
|
||||||
import { RootState } from 'app/store/store';
|
import { RootState } from 'app/store/store';
|
||||||
@ -19,7 +22,7 @@ import { memo, useCallback, useRef } from 'react';
|
|||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { FaTrash } from 'react-icons/fa';
|
import { FaTrash } from 'react-icons/fa';
|
||||||
|
|
||||||
const ClearGraphButton = () => {
|
const ResetWorkflowButton = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const { isOpen, onOpen, onClose } = useDisclosure();
|
const { isOpen, onOpen, onClose } = useDisclosure();
|
||||||
@ -48,8 +51,8 @@ const ClearGraphButton = () => {
|
|||||||
<>
|
<>
|
||||||
<IAIIconButton
|
<IAIIconButton
|
||||||
icon={<FaTrash />}
|
icon={<FaTrash />}
|
||||||
tooltip={t('nodes.clearGraph')}
|
tooltip={t('nodes.resetWorkflow')}
|
||||||
aria-label={t('nodes.clearGraph')}
|
aria-label={t('nodes.resetWorkflow')}
|
||||||
onClick={onOpen}
|
onClick={onOpen}
|
||||||
isDisabled={!nodesCount}
|
isDisabled={!nodesCount}
|
||||||
/>
|
/>
|
||||||
@ -64,18 +67,21 @@ const ClearGraphButton = () => {
|
|||||||
|
|
||||||
<AlertDialogContent>
|
<AlertDialogContent>
|
||||||
<AlertDialogHeader fontSize="lg" fontWeight="bold">
|
<AlertDialogHeader fontSize="lg" fontWeight="bold">
|
||||||
{t('nodes.clearGraph')}
|
{t('nodes.resetWorkflow')}
|
||||||
</AlertDialogHeader>
|
</AlertDialogHeader>
|
||||||
|
|
||||||
<AlertDialogBody>
|
<AlertDialogBody py={4}>
|
||||||
<Text>{t('nodes.clearGraphDesc')}</Text>
|
<Flex flexDir="column" gap={2}>
|
||||||
|
<Text>{t('nodes.resetWorkflowDesc')}</Text>
|
||||||
|
<Text variant="subtext">{t('nodes.resetWorkflowDesc2')}</Text>
|
||||||
|
</Flex>
|
||||||
</AlertDialogBody>
|
</AlertDialogBody>
|
||||||
|
|
||||||
<AlertDialogFooter>
|
<AlertDialogFooter>
|
||||||
<Button ref={cancelRef} onClick={onClose}>
|
<Button ref={cancelRef} onClick={onClose}>
|
||||||
{t('common.cancel')}
|
{t('common.cancel')}
|
||||||
</Button>
|
</Button>
|
||||||
<Button colorScheme="red" ml={3} onClick={handleConfirmClear}>
|
<Button colorScheme="error" ml={3} onClick={handleConfirmClear}>
|
||||||
{t('common.accept')}
|
{t('common.accept')}
|
||||||
</Button>
|
</Button>
|
||||||
</AlertDialogFooter>
|
</AlertDialogFooter>
|
||||||
@ -85,4 +91,4 @@ const ClearGraphButton = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default memo(ClearGraphButton);
|
export default memo(ResetWorkflowButton);
|
@ -0,0 +1,29 @@
|
|||||||
|
import IAIIconButton from 'common/components/IAIIconButton';
|
||||||
|
import { useWorkflow } from 'features/nodes/hooks/useWorkflow';
|
||||||
|
import { memo, useCallback } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { FaSave } from 'react-icons/fa';
|
||||||
|
|
||||||
|
const SaveWorkflowButton = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const workflow = useWorkflow();
|
||||||
|
const handleSave = 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 (
|
||||||
|
<IAIIconButton
|
||||||
|
icon={<FaSave />}
|
||||||
|
tooltip={t('nodes.saveWorkflow')}
|
||||||
|
aria-label={t('nodes.saveWorkflow')}
|
||||||
|
onClick={handleSave}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default memo(SaveWorkflowButton);
|
@ -1,16 +1,8 @@
|
|||||||
import { Flex } from '@chakra-ui/react';
|
|
||||||
import { memo } from 'react';
|
import { memo } from 'react';
|
||||||
import { Panel } from 'reactflow';
|
import { Panel } from 'reactflow';
|
||||||
import WorkflowEditorControls from './WorkflowEditorControls';
|
|
||||||
|
|
||||||
const TopCenterPanel = () => {
|
const TopCenterPanel = () => {
|
||||||
return (
|
return <Panel position="top-center">{null}</Panel>;
|
||||||
<Panel position="top-center">
|
|
||||||
<Flex gap={2}>
|
|
||||||
<WorkflowEditorControls />
|
|
||||||
</Flex>
|
|
||||||
</Panel>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default memo(TopCenterPanel);
|
export default memo(TopCenterPanel);
|
||||||
|
@ -1,15 +1,17 @@
|
|||||||
import CancelButton from 'features/parameters/components/ProcessButtons/CancelButton';
|
import CancelButton from 'features/parameters/components/ProcessButtons/CancelButton';
|
||||||
import InvokeButton from 'features/parameters/components/ProcessButtons/InvokeButton';
|
import InvokeButton from 'features/parameters/components/ProcessButtons/InvokeButton';
|
||||||
import { memo } from 'react';
|
import { memo } from 'react';
|
||||||
import ClearGraphButton from './ClearGraphButton';
|
import ResetWorkflowButton from './ResetWorkflowButton';
|
||||||
import LoadWorkflowButton from './LoadWorkflowButton';
|
import LoadWorkflowButton from './LoadWorkflowButton';
|
||||||
|
import SaveWorkflowButton from './SaveWorkflowButton';
|
||||||
|
|
||||||
const WorkflowEditorControls = () => {
|
const WorkflowEditorControls = () => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<InvokeButton />
|
<InvokeButton />
|
||||||
<CancelButton />
|
<CancelButton />
|
||||||
<ClearGraphButton />
|
<ResetWorkflowButton />
|
||||||
|
<SaveWorkflowButton />
|
||||||
<LoadWorkflowButton />
|
<LoadWorkflowButton />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import { Tooltip } from '@chakra-ui/react';
|
|
||||||
import { useAppDispatch } from 'app/store/storeHooks';
|
import { useAppDispatch } from 'app/store/storeHooks';
|
||||||
import IAIButton from 'common/components/IAIButton';
|
import IAIIconButton from 'common/components/IAIIconButton';
|
||||||
import { addNodePopoverOpened } from 'features/nodes/store/nodesSlice';
|
import { addNodePopoverOpened } from 'features/nodes/store/nodesSlice';
|
||||||
import { memo, useCallback } from 'react';
|
import { memo, useCallback } from 'react';
|
||||||
|
import { FaPlus } from 'react-icons/fa';
|
||||||
import { Panel } from 'reactflow';
|
import { Panel } from 'reactflow';
|
||||||
|
|
||||||
const TopLeftPanel = () => {
|
const TopLeftPanel = () => {
|
||||||
@ -14,15 +14,12 @@ const TopLeftPanel = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Panel position="top-left">
|
<Panel position="top-left">
|
||||||
<Tooltip label="Add New Node (Shift+A, Space)">
|
<IAIIconButton
|
||||||
<IAIButton
|
tooltip="Add Node (Shift+A, Space)"
|
||||||
size="sm"
|
aria-label="Add Node"
|
||||||
aria-label="Add Node"
|
icon={<FaPlus />}
|
||||||
onClick={handleOpenAddNodePopover}
|
onClick={handleOpenAddNodePopover}
|
||||||
>
|
/>
|
||||||
Add Node
|
|
||||||
</IAIButton>
|
|
||||||
</Tooltip>
|
|
||||||
</Panel>
|
</Panel>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -27,7 +27,7 @@ import {
|
|||||||
import { ChangeEvent, memo, useCallback } from 'react';
|
import { ChangeEvent, memo, useCallback } from 'react';
|
||||||
import { FaCog } from 'react-icons/fa';
|
import { FaCog } from 'react-icons/fa';
|
||||||
import { SelectionMode } from 'reactflow';
|
import { SelectionMode } from 'reactflow';
|
||||||
import ReloadSchemaButton from '../TopCenterPanel/ReloadSchemaButton';
|
import ReloadNodeTemplatesButton from '../TopCenterPanel/ReloadSchemaButton';
|
||||||
|
|
||||||
const formLabelProps: FormLabelProps = {
|
const formLabelProps: FormLabelProps = {
|
||||||
fontWeight: 600,
|
fontWeight: 600,
|
||||||
@ -163,7 +163,7 @@ const WorkflowEditorSettings = () => {
|
|||||||
label="Validate Connections and Graph"
|
label="Validate Connections and Graph"
|
||||||
helperText="Prevent invalid connections from being made, and invalid graphs from being invoked"
|
helperText="Prevent invalid connections from being made, and invalid graphs from being invoked"
|
||||||
/>
|
/>
|
||||||
<ReloadSchemaButton />
|
<ReloadNodeTemplatesButton />
|
||||||
</Flex>
|
</Flex>
|
||||||
</ModalBody>
|
</ModalBody>
|
||||||
</ModalContent>
|
</ModalContent>
|
||||||
|
@ -1,26 +1,10 @@
|
|||||||
import { Flex } from '@chakra-ui/react';
|
import { Flex } from '@chakra-ui/react';
|
||||||
import { RootState } from 'app/store/store';
|
|
||||||
import { useAppSelector } from 'app/store/storeHooks';
|
|
||||||
import DataViewer from 'features/gallery/components/ImageMetadataViewer/DataViewer';
|
import DataViewer from 'features/gallery/components/ImageMetadataViewer/DataViewer';
|
||||||
import { buildWorkflow } from 'features/nodes/util/buildWorkflow';
|
import { useWorkflow } from 'features/nodes/hooks/useWorkflow';
|
||||||
import { memo, useMemo } from 'react';
|
import { memo } from 'react';
|
||||||
import { useDebounce } from 'use-debounce';
|
|
||||||
|
|
||||||
const useWatchWorkflow = () => {
|
|
||||||
const nodes = useAppSelector((state: RootState) => state.nodes);
|
|
||||||
const [debouncedNodes] = useDebounce(nodes, 300);
|
|
||||||
const workflow = useMemo(
|
|
||||||
() => buildWorkflow(debouncedNodes),
|
|
||||||
[debouncedNodes]
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
|
||||||
workflow,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const WorkflowJSONTab = () => {
|
const WorkflowJSONTab = () => {
|
||||||
const { workflow } = useWatchWorkflow();
|
const workflow = useWorkflow();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex
|
<Flex
|
||||||
|
@ -10,6 +10,7 @@ import { memo } from 'react';
|
|||||||
import WorkflowGeneralTab from './WorkflowGeneralTab';
|
import WorkflowGeneralTab from './WorkflowGeneralTab';
|
||||||
import WorkflowLinearTab from './WorkflowLinearTab';
|
import WorkflowLinearTab from './WorkflowLinearTab';
|
||||||
import WorkflowJSONTab from './WorkflowJSONTab';
|
import WorkflowJSONTab from './WorkflowJSONTab';
|
||||||
|
import WorkflowEditorControls from '../../flow/panels/TopCenterPanel/WorkflowEditorControls';
|
||||||
|
|
||||||
const WorkflowPanel = () => {
|
const WorkflowPanel = () => {
|
||||||
return (
|
return (
|
||||||
@ -21,8 +22,12 @@ const WorkflowPanel = () => {
|
|||||||
h: 'full',
|
h: 'full',
|
||||||
borderRadius: 'base',
|
borderRadius: 'base',
|
||||||
p: 4,
|
p: 4,
|
||||||
|
gap: 2,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
<Flex gap={2}>
|
||||||
|
<WorkflowEditorControls />
|
||||||
|
</Flex>
|
||||||
<Tabs
|
<Tabs
|
||||||
variant="line"
|
variant="line"
|
||||||
sx={{ display: 'flex', flexDir: 'column', w: 'full', h: 'full' }}
|
sx={{ display: 'flex', flexDir: 'column', w: 'full', h: 'full' }}
|
||||||
|
@ -0,0 +1,16 @@
|
|||||||
|
import { RootState } from 'app/store/store';
|
||||||
|
import { useAppSelector } from 'app/store/storeHooks';
|
||||||
|
import { buildWorkflow } from 'features/nodes/util/buildWorkflow';
|
||||||
|
import { useMemo } from 'react';
|
||||||
|
import { useDebounce } from 'use-debounce';
|
||||||
|
|
||||||
|
export const useWorkflow = () => {
|
||||||
|
const nodes = useAppSelector((state: RootState) => state.nodes);
|
||||||
|
const [debouncedNodes] = useDebounce(nodes, 300);
|
||||||
|
const workflow = useMemo(
|
||||||
|
() => buildWorkflow(debouncedNodes),
|
||||||
|
[debouncedNodes]
|
||||||
|
);
|
||||||
|
|
||||||
|
return workflow;
|
||||||
|
};
|
@ -600,9 +600,9 @@ const nodesSlice = createSlice({
|
|||||||
state.workflow.contact = action.payload;
|
state.workflow.contact = action.payload;
|
||||||
},
|
},
|
||||||
workflowLoaded: (state, action: PayloadAction<Workflow>) => {
|
workflowLoaded: (state, action: PayloadAction<Workflow>) => {
|
||||||
// TODO: validation
|
|
||||||
const { nodes, edges, ...workflow } = action.payload;
|
const { nodes, edges, ...workflow } = action.payload;
|
||||||
state.workflow = workflow;
|
state.workflow = workflow;
|
||||||
|
|
||||||
state.nodes = applyNodeChanges(
|
state.nodes = applyNodeChanges(
|
||||||
nodes.map((node) => ({
|
nodes.map((node) => ({
|
||||||
item: { ...node, dragHandle: `.${DRAG_HANDLE_CLASSNAME}` },
|
item: { ...node, dragHandle: `.${DRAG_HANDLE_CLASSNAME}` },
|
||||||
@ -614,6 +614,16 @@ const nodesSlice = createSlice({
|
|||||||
edges.map((edge) => ({ item: edge, type: 'add' })),
|
edges.map((edge) => ({ item: edge, type: 'add' })),
|
||||||
[]
|
[]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
state.nodeExecutionStates = nodes.reduce<
|
||||||
|
Record<string, NodeExecutionState>
|
||||||
|
>((acc, node) => {
|
||||||
|
acc[node.id] = {
|
||||||
|
nodeId: node.id,
|
||||||
|
...initialNodeExecutionState,
|
||||||
|
};
|
||||||
|
return acc;
|
||||||
|
}, {});
|
||||||
},
|
},
|
||||||
workflowReset: (state) => {
|
workflowReset: (state) => {
|
||||||
state.workflow = cloneDeep(initialWorkflow);
|
state.workflow = cloneDeep(initialWorkflow);
|
||||||
|
@ -807,7 +807,7 @@ export const zSemVer = z.string().refine((val) => {
|
|||||||
export type SemVer = z.infer<typeof zSemVer>;
|
export type SemVer = z.infer<typeof zSemVer>;
|
||||||
|
|
||||||
export const zWorkflow = z.object({
|
export const zWorkflow = z.object({
|
||||||
name: z.string().trim().min(1),
|
name: z.string(),
|
||||||
author: z.string(),
|
author: z.string(),
|
||||||
description: z.string(),
|
description: z.string(),
|
||||||
version: z.string(),
|
version: z.string(),
|
||||||
|
@ -27,6 +27,7 @@ import { MdCancel, MdCancelScheduleSend } from 'react-icons/md';
|
|||||||
|
|
||||||
import { ChevronDownIcon } from '@chakra-ui/icons';
|
import { ChevronDownIcon } from '@chakra-ui/icons';
|
||||||
import { sessionCanceled } from 'services/api/thunks/session';
|
import { sessionCanceled } from 'services/api/thunks/session';
|
||||||
|
import IAIButton from 'common/components/IAIButton';
|
||||||
|
|
||||||
const cancelButtonSelector = createSelector(
|
const cancelButtonSelector = createSelector(
|
||||||
systemSelector,
|
systemSelector,
|
||||||
@ -49,15 +50,14 @@ const cancelButtonSelector = createSelector(
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
interface CancelButtonProps {
|
type Props = Omit<ButtonProps, 'aria-label'> & {
|
||||||
btnGroupWidth?: string | number;
|
btnGroupWidth?: string | number;
|
||||||
}
|
asIconButton?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
const CancelButton = (
|
const CancelButton = (props: Props) => {
|
||||||
props: CancelButtonProps & Omit<ButtonProps, 'aria-label'>
|
|
||||||
) => {
|
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const { btnGroupWidth = 'auto', ...rest } = props;
|
const { btnGroupWidth = 'auto', asIconButton = false, ...rest } = props;
|
||||||
const {
|
const {
|
||||||
isProcessing,
|
isProcessing,
|
||||||
isConnected,
|
isConnected,
|
||||||
@ -124,16 +124,31 @@ const CancelButton = (
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<ButtonGroup isAttached width={btnGroupWidth}>
|
<ButtonGroup isAttached width={btnGroupWidth}>
|
||||||
<IAIIconButton
|
{asIconButton ? (
|
||||||
icon={cancelIcon}
|
<IAIIconButton
|
||||||
tooltip={cancelLabel}
|
icon={cancelIcon}
|
||||||
aria-label={cancelLabel}
|
tooltip={cancelLabel}
|
||||||
isDisabled={!isConnected || !isProcessing || !isCancelable}
|
aria-label={cancelLabel}
|
||||||
onClick={handleClickCancel}
|
isDisabled={!isConnected || !isProcessing || !isCancelable}
|
||||||
colorScheme="error"
|
onClick={handleClickCancel}
|
||||||
id="cancel-button"
|
colorScheme="error"
|
||||||
{...rest}
|
id="cancel-button"
|
||||||
/>
|
{...rest}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<IAIButton
|
||||||
|
leftIcon={cancelIcon}
|
||||||
|
tooltip={cancelLabel}
|
||||||
|
aria-label={cancelLabel}
|
||||||
|
isDisabled={!isConnected || !isProcessing || !isCancelable}
|
||||||
|
onClick={handleClickCancel}
|
||||||
|
colorScheme="error"
|
||||||
|
id="cancel-button"
|
||||||
|
{...rest}
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</IAIButton>
|
||||||
|
)}
|
||||||
<Menu closeOnSelect={false}>
|
<Menu closeOnSelect={false}>
|
||||||
<MenuButton
|
<MenuButton
|
||||||
as={IAIIconButton}
|
as={IAIIconButton}
|
||||||
|
@ -56,7 +56,7 @@ const FloatingSidePanelButtons = ({
|
|||||||
icon={<FaSlidersH />}
|
icon={<FaSlidersH />}
|
||||||
/>
|
/>
|
||||||
<InvokeButton asIconButton sx={floatingButtonStyles} />
|
<InvokeButton asIconButton sx={floatingButtonStyles} />
|
||||||
<CancelButton sx={floatingButtonStyles} />
|
<CancelButton sx={floatingButtonStyles} asIconButton />
|
||||||
</Flex>
|
</Flex>
|
||||||
</Portal>
|
</Portal>
|
||||||
);
|
);
|
||||||
|
@ -107,10 +107,15 @@ export const theme: ThemeOverride = {
|
|||||||
'0px 0px 0px 1px var(--invokeai-colors-base-150), 0px 0px 0px 3px var(--invokeai-colors-accent-500)',
|
'0px 0px 0px 1px var(--invokeai-colors-base-150), 0px 0px 0px 3px var(--invokeai-colors-accent-500)',
|
||||||
dark: '0px 0px 0px 1px var(--invokeai-colors-base-900), 0px 0px 0px 3px var(--invokeai-colors-accent-400)',
|
dark: '0px 0px 0px 1px var(--invokeai-colors-base-900), 0px 0px 0px 3px var(--invokeai-colors-accent-400)',
|
||||||
},
|
},
|
||||||
nodeSelectedOutline: {
|
nodeSelected: {
|
||||||
light: `0 0 0 2px var(--invokeai-colors-accent-400)`,
|
light: `0 0 0 2px var(--invokeai-colors-accent-400)`,
|
||||||
dark: `0 0 0 2px var(--invokeai-colors-accent-500)`,
|
dark: `0 0 0 2px var(--invokeai-colors-accent-500)`,
|
||||||
},
|
},
|
||||||
|
nodeInProgress: {
|
||||||
|
light:
|
||||||
|
'0 0 4px 2px var(--invokeai-colors-accent-500), 0 0 15px 4px var(--invokeai-colors-accent-600)',
|
||||||
|
dark: '0 0 4px 2px var(--invokeai-colors-accent-400), 0 0 15px 4px var(--invokeai-colors-accent-400)',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
colors: InvokeAIColors,
|
colors: InvokeAIColors,
|
||||||
components: {
|
components: {
|
||||||
|
Loading…
Reference in New Issue
Block a user