Improve the layout of the Workflow Visualizer (#8372)

- Increase the dimensions of the ReactFlow nodes. This allows to ditch
scaling which made it hard to get the width of the nodes as they were
visually scaled by 1.3.
- Center the flow when the flow mounts and when the state of the right
drawer opens.
- Put the node type inside of the node so it doesn't overlap with the
arrow
- Make the edges non deletable

We'll have to make a refactor so the viewport can be animated properly:
https://github.com/twentyhq/twenty/issues/8387.


https://github.com/user-attachments/assets/69494a32-5403-4898-be75-7fc38058e263

---------

Co-authored-by: Félix Malfait <felix@twenty.com>
This commit is contained in:
Baptiste Devessier
2024-11-12 17:52:12 +01:00
committed by GitHub
parent aadcb49dcb
commit 31f03764d6
9 changed files with 131 additions and 52 deletions

View File

@ -1,3 +1,6 @@
import { isRightDrawerMinimizedState } from '@/ui/layout/right-drawer/states/isRightDrawerMinimizedState';
import { isRightDrawerOpenState } from '@/ui/layout/right-drawer/states/isRightDrawerOpenState';
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
import { WorkflowVersionStatusTag } from '@/workflow/components/WorkflowVersionStatusTag';
import { workflowDiagramState } from '@/workflow/states/workflowDiagramState';
import { WorkflowVersionStatus } from '@/workflow/types/Workflow';
@ -15,14 +18,16 @@ import {
Background,
EdgeChange,
FitViewOptions,
getNodesBounds,
NodeChange,
NodeProps,
ReactFlow,
useReactFlow,
} from '@xyflow/react';
import '@xyflow/react/dist/style.css';
import React, { useMemo } from 'react';
import { useSetRecoilState } from 'recoil';
import { GRAY_SCALE, isDefined } from 'twenty-ui';
import React, { useEffect, useMemo, useRef } from 'react';
import { useRecoilValue, useSetRecoilState } from 'recoil';
import { GRAY_SCALE, isDefined, THEME_COMMON } from 'twenty-ui';
const StyledResetReactflowStyles = styled.div`
height: 100%;
@ -35,6 +40,9 @@ const StyledResetReactflowStyles = styled.div`
.react-flow__node-output,
.react-flow__node-group {
padding: 0;
width: auto;
text-align: start;
white-space: nowrap;
}
--xy-node-border-radius: none;
@ -51,10 +59,10 @@ const StyledStatusTagContainer = styled.div`
padding: ${({ theme }) => theme.spacing(2)};
`;
const defaultFitViewOptions: FitViewOptions = {
minZoom: 1.3,
maxZoom: 1.3,
};
const defaultFitViewOptions = {
minZoom: 1,
maxZoom: 1,
} satisfies FitViewOptions;
export const WorkflowDiagramCanvasBase = ({
diagram,
@ -77,11 +85,29 @@ export const WorkflowDiagramCanvasBase = ({
>;
children?: React.ReactNode;
}) => {
const reactflow = useReactFlow();
const { nodes, edges } = useMemo(
() => getOrganizedDiagram(diagram),
[diagram],
);
const isRightDrawerOpen = useRecoilValue(isRightDrawerOpenState);
const isRightDrawerMinimized = useRecoilValue(isRightDrawerMinimizedState);
const isMobile = useIsMobile();
const rightDrawerState = !isRightDrawerOpen
? 'closed'
: isRightDrawerMinimized
? 'minimized'
: isMobile
? 'fullScreen'
: 'normal';
const rightDrawerWidth = Number(
THEME_COMMON.rightDrawerWidth.replace('px', ''),
);
const setWorkflowDiagram = useSetRecoilState(workflowDiagramState);
const handleNodesChange = (
@ -118,27 +144,68 @@ export const WorkflowDiagramCanvasBase = ({
});
};
const containerRef = useRef<HTMLDivElement>(null);
useEffect(() => {
if (!isDefined(containerRef.current) || !reactflow.viewportInitialized) {
return;
}
const currentViewport = reactflow.getViewport();
const flowBounds = getNodesBounds(reactflow.getNodes());
let visibleRightDrawerWidth = 0;
if (rightDrawerState === 'normal') {
visibleRightDrawerWidth = rightDrawerWidth;
}
const viewportX =
(containerRef.current.offsetWidth + visibleRightDrawerWidth) / 2 -
flowBounds.width / 2;
reactflow.setViewport(
{
...currentViewport,
x: viewportX - visibleRightDrawerWidth,
},
{ duration: 300 },
);
}, [reactflow, rightDrawerState, rightDrawerWidth]);
return (
<StyledResetReactflowStyles>
<StyledResetReactflowStyles ref={containerRef}>
<ReactFlow
onInit={({ fitView }) => {
fitView(defaultFitViewOptions);
onInit={() => {
if (!isDefined(containerRef.current)) {
throw new Error('Expect the container ref to be defined');
}
const flowBounds = getNodesBounds(reactflow.getNodes());
reactflow.setViewport({
x: containerRef.current.offsetWidth / 2 - flowBounds.width / 2,
y: 150,
zoom: defaultFitViewOptions.maxZoom,
});
}}
minZoom={defaultFitViewOptions.minZoom}
maxZoom={defaultFitViewOptions.maxZoom}
nodeTypes={nodeTypes}
fitView
nodes={nodes.map((node) => ({ ...node, draggable: false }))}
edges={edges}
onNodesChange={handleNodesChange}
onEdgesChange={handleEdgesChange}
proOptions={{ hideAttribution: true }}
>
<Background color={GRAY_SCALE.gray25} size={2} />
{children}
<StyledStatusTagContainer>
<WorkflowVersionStatusTag versionStatus={status} />
</StyledStatusTagContainer>
</ReactFlow>
<StyledStatusTagContainer>
<WorkflowVersionStatusTag versionStatus={status} />
</StyledStatusTagContainer>
</StyledResetReactflowStyles>
);
};