mirror of
https://github.com/invoke-ai/InvokeAI.git
synced 2026-01-15 08:28:14 -05:00
Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
70ac58e64a | ||
|
|
e653837236 | ||
|
|
2bbfcc2f13 | ||
|
|
d6e0e439c5 | ||
|
|
26aab60f81 |
@@ -6,7 +6,7 @@ import { IAINoContentFallback } from 'common/components/IAIImageFallback';
|
||||
import { DndImage } from 'features/dnd/DndImage';
|
||||
import NextPrevImageButtons from 'features/gallery/components/NextPrevImageButtons';
|
||||
import { selectLastSelectedImage } from 'features/gallery/store/gallerySelectors';
|
||||
import NodeWrapper from 'features/nodes/components/flow/nodes/common/NodeWrapper';
|
||||
import NonInvocationNodeWrapper from 'features/nodes/components/flow/nodes/common/NonInvocationNodeWrapper';
|
||||
import { DRAG_HANDLE_CLASSNAME } from 'features/nodes/types/constants';
|
||||
import type { AnimationProps } from 'framer-motion';
|
||||
import { motion } from 'framer-motion';
|
||||
@@ -58,13 +58,14 @@ const Wrapper = (props: PropsWithChildren<{ nodeProps: NodeProps }>) => {
|
||||
}, []);
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<NodeWrapper nodeId={props.nodeProps.id} selected={props.nodeProps.selected} width={384}>
|
||||
<NonInvocationNodeWrapper nodeId={props.nodeProps.id} selected={props.nodeProps.selected} width={384}>
|
||||
<Flex
|
||||
onMouseEnter={handleMouseEnter}
|
||||
onMouseLeave={handleMouseLeave}
|
||||
className={DRAG_HANDLE_CLASSNAME}
|
||||
position="relative"
|
||||
flexDirection="column"
|
||||
aspectRatio="1/1"
|
||||
>
|
||||
<Flex layerStyle="nodeHeader" borderTopRadius="base" alignItems="center" justifyContent="center" h={8}>
|
||||
<Text fontSize="sm" fontWeight="semibold" color="base.200">
|
||||
@@ -80,7 +81,7 @@ const Wrapper = (props: PropsWithChildren<{ nodeProps: NodeProps }>) => {
|
||||
)}
|
||||
</Flex>
|
||||
</Flex>
|
||||
</NodeWrapper>
|
||||
</NonInvocationNodeWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -3,8 +3,8 @@ import { createSelector } from '@reduxjs/toolkit';
|
||||
import type { Node, NodeProps } from '@xyflow/react';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import NodeCollapseButton from 'features/nodes/components/flow/nodes/common/NodeCollapseButton';
|
||||
import NodeTitle from 'features/nodes/components/flow/nodes/common/NodeTitle';
|
||||
import NodeWrapper from 'features/nodes/components/flow/nodes/common/NodeWrapper';
|
||||
import NonInvocationNodeTitle from 'features/nodes/components/flow/nodes/common/NonInvocationNodeTitle';
|
||||
import NonInvocationNodeWrapper from 'features/nodes/components/flow/nodes/common/NonInvocationNodeWrapper';
|
||||
import { notesNodeValueChanged } from 'features/nodes/store/nodesSlice';
|
||||
import { selectNodes } from 'features/nodes/store/selectors';
|
||||
import { NO_DRAG_CLASS, NO_PAN_CLASS } from 'features/nodes/types/constants';
|
||||
@@ -34,7 +34,7 @@ const NotesNode = (props: NodeProps<Node<NotesNodeData>>) => {
|
||||
}
|
||||
|
||||
return (
|
||||
<NodeWrapper nodeId={nodeId} selected={selected}>
|
||||
<NonInvocationNodeWrapper nodeId={nodeId} selected={selected}>
|
||||
<Flex
|
||||
layerStyle="nodeHeader"
|
||||
borderTopRadius="base"
|
||||
@@ -44,7 +44,7 @@ const NotesNode = (props: NodeProps<Node<NotesNodeData>>) => {
|
||||
h={8}
|
||||
>
|
||||
<NodeCollapseButton nodeId={nodeId} isOpen={isOpen} />
|
||||
<NodeTitle nodeId={nodeId} title="Notes" />
|
||||
<NonInvocationNodeTitle nodeId={nodeId} title="Notes" />
|
||||
<Box minW={8} />
|
||||
</Flex>
|
||||
{isOpen && (
|
||||
@@ -73,7 +73,7 @@ const NotesNode = (props: NodeProps<Node<NotesNodeData>>) => {
|
||||
</Flex>
|
||||
</>
|
||||
)}
|
||||
</NodeWrapper>
|
||||
</NonInvocationNodeWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { ChakraProps, SystemStyleObject } from '@invoke-ai/ui-library';
|
||||
import type { ChakraProps } from '@invoke-ai/ui-library';
|
||||
import { Box, useGlobalMenuClose } from '@invoke-ai/ui-library';
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import { useInvocationNodeContext } from 'features/nodes/components/flow/nodes/Invocation/context';
|
||||
@@ -12,6 +12,8 @@ import { zNodeStatus } from 'features/nodes/types/invocation';
|
||||
import type { MouseEvent, PropsWithChildren } from 'react';
|
||||
import { memo, useCallback } from 'react';
|
||||
|
||||
import { containerSx, inProgressSx, shadowsSx } from './shared';
|
||||
|
||||
type NodeWrapperProps = PropsWithChildren & {
|
||||
nodeId: string;
|
||||
selected: boolean;
|
||||
@@ -19,100 +21,6 @@ type NodeWrapperProps = PropsWithChildren & {
|
||||
isMissingTemplate?: boolean;
|
||||
};
|
||||
|
||||
// Certain CSS transitions are disabled as a performance optimization - they can cause massive slowdowns in large
|
||||
// workflows even when the animations are GPU-accelerated CSS.
|
||||
|
||||
const containerSx: SystemStyleObject = {
|
||||
h: 'full',
|
||||
position: 'relative',
|
||||
borderRadius: 'base',
|
||||
transitionProperty: 'none',
|
||||
cursor: 'grab',
|
||||
'--border-color': 'var(--invoke-colors-base-500)',
|
||||
'--border-color-selected': 'var(--invoke-colors-blue-300)',
|
||||
'--header-bg-color': 'var(--invoke-colors-base-900)',
|
||||
'&[data-status="warning"]': {
|
||||
'--border-color': 'var(--invoke-colors-warning-500)',
|
||||
'--border-color-selected': 'var(--invoke-colors-warning-500)',
|
||||
'--header-bg-color': 'var(--invoke-colors-warning-700)',
|
||||
},
|
||||
'&[data-status="error"]': {
|
||||
'--border-color': 'var(--invoke-colors-error-500)',
|
||||
'--border-color-selected': 'var(--invoke-colors-error-500)',
|
||||
'--header-bg-color': 'var(--invoke-colors-error-700)',
|
||||
},
|
||||
// The action buttons are hidden by default and shown on hover
|
||||
'& .node-selection-overlay': {
|
||||
display: 'block',
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
insetInlineEnd: 0,
|
||||
bottom: 0,
|
||||
insetInlineStart: 0,
|
||||
borderRadius: 'base',
|
||||
transitionProperty: 'none',
|
||||
pointerEvents: 'none',
|
||||
shadow: '0 0 0 1px var(--border-color)',
|
||||
},
|
||||
'&[data-is-mouse-over-node="true"] .node-selection-overlay': {
|
||||
display: 'block',
|
||||
},
|
||||
'&[data-is-mouse-over-form-field="true"] .node-selection-overlay': {
|
||||
display: 'block',
|
||||
bg: 'invokeBlueAlpha.100',
|
||||
},
|
||||
_hover: {
|
||||
'& .node-selection-overlay': {
|
||||
display: 'block',
|
||||
shadow: '0 0 0 1px var(--border-color-selected)',
|
||||
},
|
||||
'&[data-is-selected="true"] .node-selection-overlay': {
|
||||
display: 'block',
|
||||
shadow: '0 0 0 2px var(--border-color-selected)',
|
||||
},
|
||||
},
|
||||
'&[data-is-selected="true"] .node-selection-overlay': {
|
||||
display: 'block',
|
||||
shadow: '0 0 0 2px var(--border-color-selected)',
|
||||
},
|
||||
'&[data-is-editor-locked="true"]': {
|
||||
'& *': {
|
||||
cursor: 'not-allowed',
|
||||
pointerEvents: 'none',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const shadowsSx: SystemStyleObject = {
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
insetInlineEnd: 0,
|
||||
bottom: 0,
|
||||
insetInlineStart: 0,
|
||||
borderRadius: 'base',
|
||||
pointerEvents: 'none',
|
||||
zIndex: -1,
|
||||
shadow: 'var(--invoke-shadows-xl), var(--invoke-shadows-base), var(--invoke-shadows-base)',
|
||||
};
|
||||
|
||||
const inProgressSx: SystemStyleObject = {
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
insetInlineEnd: 0,
|
||||
bottom: 0,
|
||||
insetInlineStart: 0,
|
||||
borderRadius: 'md',
|
||||
pointerEvents: 'none',
|
||||
transitionProperty: 'none',
|
||||
opacity: 0.7,
|
||||
zIndex: -1,
|
||||
display: 'none',
|
||||
shadow: '0 0 0 2px var(--invoke-colors-yellow-400), 0 0 20px 2px var(--invoke-colors-orange-700)',
|
||||
'&[data-is-in-progress="true"]': {
|
||||
display: 'block',
|
||||
},
|
||||
};
|
||||
|
||||
const NodeWrapper = (props: NodeWrapperProps) => {
|
||||
const { nodeId, width, children, isMissingTemplate, selected } = props;
|
||||
const ctx = useInvocationNodeContext();
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
import { Flex, Input, Text } from '@invoke-ai/ui-library';
|
||||
import { createSelector } from '@reduxjs/toolkit';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { useEditable } from 'common/hooks/useEditable';
|
||||
import { nodeLabelChanged } from 'features/nodes/store/nodesSlice';
|
||||
import { selectNodes } from 'features/nodes/store/selectors';
|
||||
import { NO_FIT_ON_DOUBLE_CLICK_CLASS } from 'features/nodes/types/constants';
|
||||
import { memo, useCallback, useMemo, useRef } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
type Props = {
|
||||
nodeId: string;
|
||||
title: string;
|
||||
};
|
||||
|
||||
const NonInvocationNodeTitle = ({ nodeId, title }: Props) => {
|
||||
const dispatch = useAppDispatch();
|
||||
const selectNodeLabel = useMemo(
|
||||
() =>
|
||||
createSelector(selectNodes, (nodes) => {
|
||||
const node = nodes.find((n) => n.id === nodeId);
|
||||
return node?.data?.label ?? '';
|
||||
}),
|
||||
[nodeId]
|
||||
);
|
||||
const label = useAppSelector(selectNodeLabel);
|
||||
const { t } = useTranslation();
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
const onChange = useCallback(
|
||||
(label: string) => {
|
||||
dispatch(nodeLabelChanged({ nodeId, label }));
|
||||
},
|
||||
[dispatch, nodeId]
|
||||
);
|
||||
|
||||
const editable = useEditable({
|
||||
value: label || title || t('nodes.problemSettingTitle'),
|
||||
defaultValue: title || t('nodes.problemSettingTitle'),
|
||||
onChange,
|
||||
inputRef,
|
||||
});
|
||||
|
||||
return (
|
||||
<Flex overflow="hidden" w="full" h="full" alignItems="center" justifyContent="center">
|
||||
{!editable.isEditing && (
|
||||
<Text
|
||||
className={NO_FIT_ON_DOUBLE_CLICK_CLASS}
|
||||
fontWeight="semibold"
|
||||
color="base.200"
|
||||
onDoubleClick={editable.startEditing}
|
||||
noOfLines={1}
|
||||
>
|
||||
{editable.value}
|
||||
</Text>
|
||||
)}
|
||||
{editable.isEditing && (
|
||||
<Input
|
||||
ref={inputRef}
|
||||
{...editable.inputProps}
|
||||
variant="outline"
|
||||
_focusVisible={{ borderRadius: 'base', h: 'unset' }}
|
||||
/>
|
||||
)}
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(NonInvocationNodeTitle);
|
||||
@@ -0,0 +1,80 @@
|
||||
import type { ChakraProps } from '@invoke-ai/ui-library';
|
||||
import { Box, useGlobalMenuClose } from '@invoke-ai/ui-library';
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import { useIsWorkflowEditorLocked } from 'features/nodes/hooks/useIsWorkflowEditorLocked';
|
||||
import { useMouseOverNode } from 'features/nodes/hooks/useMouseOverNode';
|
||||
import { useNodeExecutionState } from 'features/nodes/hooks/useNodeExecutionState';
|
||||
import { useZoomToNode } from 'features/nodes/hooks/useZoomToNode';
|
||||
import { selectNodeOpacity } from 'features/nodes/store/workflowSettingsSlice';
|
||||
import { DRAG_HANDLE_CLASSNAME, NO_FIT_ON_DOUBLE_CLICK_CLASS, NODE_WIDTH } from 'features/nodes/types/constants';
|
||||
import { zNodeStatus } from 'features/nodes/types/invocation';
|
||||
import type { MouseEvent, PropsWithChildren } from 'react';
|
||||
import { memo, useCallback } from 'react';
|
||||
|
||||
import { containerSx, inProgressSx, shadowsSx } from './shared';
|
||||
|
||||
type NonInvocationNodeWrapperProps = PropsWithChildren & {
|
||||
nodeId: string;
|
||||
selected: boolean;
|
||||
width?: ChakraProps['w'];
|
||||
};
|
||||
|
||||
const NonInvocationNodeWrapper = (props: NonInvocationNodeWrapperProps) => {
|
||||
const { nodeId, width, children, selected } = props;
|
||||
const mouseOverNode = useMouseOverNode(nodeId);
|
||||
const zoomToNode = useZoomToNode(nodeId);
|
||||
const isLocked = useIsWorkflowEditorLocked();
|
||||
|
||||
const executionState = useNodeExecutionState(nodeId);
|
||||
const isInProgress = executionState?.status === zNodeStatus.enum.IN_PROGRESS;
|
||||
|
||||
const opacity = useAppSelector(selectNodeOpacity);
|
||||
const globalMenu = useGlobalMenuClose();
|
||||
|
||||
const onDoubleClick = useCallback(
|
||||
(e: MouseEvent) => {
|
||||
if (!(e.target instanceof HTMLElement)) {
|
||||
// We have to manually narrow the type here thanks to a TS quirk
|
||||
return;
|
||||
}
|
||||
if (
|
||||
e.target instanceof HTMLInputElement ||
|
||||
e.target instanceof HTMLTextAreaElement ||
|
||||
e.target instanceof HTMLSelectElement ||
|
||||
e.target instanceof HTMLButtonElement ||
|
||||
e.target instanceof HTMLAnchorElement
|
||||
) {
|
||||
// Don't fit the view if the user is editing a text field, select, button, or link
|
||||
return;
|
||||
}
|
||||
if (e.target.closest(`.${NO_FIT_ON_DOUBLE_CLICK_CLASS}`) !== null) {
|
||||
// This target is marked as not fitting the view on double click
|
||||
return;
|
||||
}
|
||||
zoomToNode();
|
||||
},
|
||||
[zoomToNode]
|
||||
);
|
||||
|
||||
return (
|
||||
<Box
|
||||
onClick={globalMenu.onCloseGlobal}
|
||||
onDoubleClick={onDoubleClick}
|
||||
onMouseOver={mouseOverNode.handleMouseOver}
|
||||
onMouseOut={mouseOverNode.handleMouseOut}
|
||||
className={DRAG_HANDLE_CLASSNAME}
|
||||
sx={containerSx}
|
||||
width={width || NODE_WIDTH}
|
||||
opacity={opacity}
|
||||
data-is-editor-locked={isLocked}
|
||||
data-is-selected={selected}
|
||||
>
|
||||
<Box sx={shadowsSx} />
|
||||
<Box sx={inProgressSx} data-is-in-progress={isInProgress} />
|
||||
{children}
|
||||
<Box className="node-selection-overlay" />
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(NonInvocationNodeWrapper);
|
||||
@@ -0,0 +1,95 @@
|
||||
// Certain CSS transitions are disabled as a performance optimization - they can cause massive slowdowns in large
|
||||
// workflows even when the animations are GPU-accelerated CSS.
|
||||
|
||||
import type { SystemStyleObject } from '@invoke-ai/ui-library';
|
||||
|
||||
export const containerSx: SystemStyleObject = {
|
||||
h: 'full',
|
||||
position: 'relative',
|
||||
borderRadius: 'base',
|
||||
transitionProperty: 'none',
|
||||
cursor: 'grab',
|
||||
'--border-color': 'var(--invoke-colors-base-500)',
|
||||
'--border-color-selected': 'var(--invoke-colors-blue-300)',
|
||||
'--header-bg-color': 'var(--invoke-colors-base-900)',
|
||||
'&[data-status="warning"]': {
|
||||
'--border-color': 'var(--invoke-colors-warning-500)',
|
||||
'--border-color-selected': 'var(--invoke-colors-warning-500)',
|
||||
'--header-bg-color': 'var(--invoke-colors-warning-700)',
|
||||
},
|
||||
'&[data-status="error"]': {
|
||||
'--border-color': 'var(--invoke-colors-error-500)',
|
||||
'--border-color-selected': 'var(--invoke-colors-error-500)',
|
||||
'--header-bg-color': 'var(--invoke-colors-error-700)',
|
||||
},
|
||||
// The action buttons are hidden by default and shown on hover
|
||||
'& .node-selection-overlay': {
|
||||
display: 'block',
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
insetInlineEnd: 0,
|
||||
bottom: 0,
|
||||
insetInlineStart: 0,
|
||||
borderRadius: 'base',
|
||||
transitionProperty: 'none',
|
||||
pointerEvents: 'none',
|
||||
shadow: '0 0 0 1px var(--border-color)',
|
||||
},
|
||||
'&[data-is-mouse-over-node="true"] .node-selection-overlay': {
|
||||
display: 'block',
|
||||
},
|
||||
'&[data-is-mouse-over-form-field="true"] .node-selection-overlay': {
|
||||
display: 'block',
|
||||
bg: 'invokeBlueAlpha.100',
|
||||
},
|
||||
_hover: {
|
||||
'& .node-selection-overlay': {
|
||||
display: 'block',
|
||||
shadow: '0 0 0 1px var(--border-color-selected)',
|
||||
},
|
||||
'&[data-is-selected="true"] .node-selection-overlay': {
|
||||
display: 'block',
|
||||
shadow: '0 0 0 2px var(--border-color-selected)',
|
||||
},
|
||||
},
|
||||
'&[data-is-selected="true"] .node-selection-overlay': {
|
||||
display: 'block',
|
||||
shadow: '0 0 0 2px var(--border-color-selected)',
|
||||
},
|
||||
'&[data-is-editor-locked="true"]': {
|
||||
'& *': {
|
||||
cursor: 'not-allowed',
|
||||
pointerEvents: 'none',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const shadowsSx: SystemStyleObject = {
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
insetInlineEnd: 0,
|
||||
bottom: 0,
|
||||
insetInlineStart: 0,
|
||||
borderRadius: 'base',
|
||||
pointerEvents: 'none',
|
||||
zIndex: -1,
|
||||
shadow: 'var(--invoke-shadows-xl), var(--invoke-shadows-base), var(--invoke-shadows-base)',
|
||||
};
|
||||
|
||||
export const inProgressSx: SystemStyleObject = {
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
insetInlineEnd: 0,
|
||||
bottom: 0,
|
||||
insetInlineStart: 0,
|
||||
borderRadius: 'md',
|
||||
pointerEvents: 'none',
|
||||
transitionProperty: 'none',
|
||||
opacity: 0.7,
|
||||
zIndex: -1,
|
||||
display: 'none',
|
||||
shadow: '0 0 0 2px var(--invoke-colors-yellow-400), 0 0 20px 2px var(--invoke-colors-orange-700)',
|
||||
'&[data-is-in-progress="true"]': {
|
||||
display: 'block',
|
||||
},
|
||||
};
|
||||
@@ -18,6 +18,7 @@ import ScrollableContent from 'common/components/OverlayScrollbars/ScrollableCon
|
||||
import { withResultAsync } from 'common/util/result';
|
||||
import { parseify } from 'common/util/serialize';
|
||||
import { ExternalLink } from 'features/gallery/components/ImageViewer/NoContentForViewer';
|
||||
import { InvocationNodeContextProvider } from 'features/nodes/components/flow/nodes/Invocation/context';
|
||||
import { NodeFieldElementOverlay } from 'features/nodes/components/sidePanel/builder/NodeFieldElementEditMode';
|
||||
import { useDoesWorkflowHaveUnsavedChanges } from 'features/nodes/components/sidePanel/workflow/IsolatedWorkflowBuilderWatcher';
|
||||
import {
|
||||
@@ -89,7 +90,11 @@ const OutputFields = memo(() => {
|
||||
{t('workflows.builder.noOutputNodeSelected')}
|
||||
</Text>
|
||||
)}
|
||||
{outputNodeId && <OutputFieldsContent outputNodeId={outputNodeId} />}
|
||||
{outputNodeId && (
|
||||
<InvocationNodeContextProvider nodeId={outputNodeId}>
|
||||
<OutputFieldsContent outputNodeId={outputNodeId} />
|
||||
</InvocationNodeContextProvider>
|
||||
)}
|
||||
</Flex>
|
||||
);
|
||||
});
|
||||
@@ -127,7 +132,11 @@ const PublishableInputFields = memo(() => {
|
||||
<Text fontWeight="semibold">{t('workflows.builder.publishedWorkflowInputs')}</Text>
|
||||
<Divider />
|
||||
{inputs.publishable.map(({ nodeId, fieldName }) => {
|
||||
return <NodeInputFieldPreview key={`${nodeId}-${fieldName}`} nodeId={nodeId} fieldName={fieldName} />;
|
||||
return (
|
||||
<InvocationNodeContextProvider nodeId={nodeId} key={`${nodeId}-${fieldName}`}>
|
||||
<NodeInputFieldPreview nodeId={nodeId} fieldName={fieldName} />
|
||||
</InvocationNodeContextProvider>
|
||||
);
|
||||
})}
|
||||
</Flex>
|
||||
);
|
||||
@@ -149,7 +158,11 @@ const UnpublishableInputFields = memo(() => {
|
||||
</Text>
|
||||
<Divider />
|
||||
{inputs.unpublishable.map(({ nodeId, fieldName }) => {
|
||||
return <NodeInputFieldPreview key={`${nodeId}-${fieldName}`} nodeId={nodeId} fieldName={fieldName} />;
|
||||
return (
|
||||
<InvocationNodeContextProvider nodeId={nodeId} key={`${nodeId}-${fieldName}`}>
|
||||
<NodeInputFieldPreview key={`${nodeId}-${fieldName}`} nodeId={nodeId} fieldName={fieldName} />
|
||||
</InvocationNodeContextProvider>
|
||||
);
|
||||
})}
|
||||
</Flex>
|
||||
);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { graphlib, layout } from '@dagrejs/dagre';
|
||||
import type { Edge, NodePositionChange } from '@xyflow/react';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { useAppSelector, useAppStore } from 'app/store/storeHooks';
|
||||
import { nodesChanged } from 'features/nodes/store/nodesSlice';
|
||||
import { selectEdges, selectNodes } from 'features/nodes/store/selectors';
|
||||
import {
|
||||
@@ -36,9 +36,7 @@ const getNodeWidth = (node: AnyNode): number => {
|
||||
};
|
||||
|
||||
export const useAutoLayout = (): (() => void) => {
|
||||
const dispatch = useAppDispatch();
|
||||
const nodes = useAppSelector(selectNodes);
|
||||
const edges = useAppSelector(selectEdges);
|
||||
const store = useAppStore();
|
||||
const nodeSpacing = useAppSelector(selectNodeSpacing);
|
||||
const layerSpacing = useAppSelector(selectLayerSpacing);
|
||||
const layeringStrategy = useAppSelector(selectLayeringStrategy);
|
||||
@@ -46,6 +44,9 @@ export const useAutoLayout = (): (() => void) => {
|
||||
const nodeAlignment = useAppSelector(selectNodeAlignment);
|
||||
|
||||
const autoLayout = useCallback(() => {
|
||||
const state = store.getState();
|
||||
const nodes = selectNodes(state);
|
||||
const edges = selectEdges(state);
|
||||
// We'll do graph layout using dagre, then convert the results to reactflow position changes
|
||||
const g = new graphlib.Graph();
|
||||
|
||||
@@ -131,8 +132,8 @@ export const useAutoLayout = (): (() => void) => {
|
||||
return { id: node.id, type: 'position', position: newPosition };
|
||||
});
|
||||
|
||||
dispatch(nodesChanged(positionChanges));
|
||||
}, [dispatch, edges, nodes, nodeSpacing, layerSpacing, layeringStrategy, layoutDirection, nodeAlignment]);
|
||||
store.dispatch(nodesChanged(positionChanges));
|
||||
}, [layerSpacing, layeringStrategy, layoutDirection, nodeAlignment, nodeSpacing, store]);
|
||||
|
||||
return autoLayout;
|
||||
};
|
||||
|
||||
@@ -1 +1 @@
|
||||
__version__ = "6.1.0"
|
||||
__version__ = "6.2.0"
|
||||
|
||||
Reference in New Issue
Block a user