From 43bc55efcd877de4ed8de04e82bf9f98eac01689 Mon Sep 17 00:00:00 2001 From: Baptiste Devessier Date: Mon, 16 Jun 2025 09:58:31 +0200 Subject: [PATCH] Move the viewport of the workflow visualizer on the show page when side panel is opened (#12605) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The viewport was moved before the flow finished initializing. I created a state to prevent moving the viewport before the flow has been initialized, allowing us to compute the bounds of the nodes correctly. ## Before https://github.com/user-attachments/assets/0f034daf-c29c-4d54-905b-191eb60477a9 ## After https://github.com/user-attachments/assets/1f9018ad-ff97-4cf2-997e-d6b7dadf1f30 ## Bonus 🎉 The viewport is no longer progressively zoomed out, as it was before: https://github.com/user-attachments/assets/0b985c22-ef06-4226-92a0-e5da569876ff --- .../components/WorkflowDiagramCanvasBase.tsx | 119 ++++++++++++------ 1 file changed, 83 insertions(+), 36 deletions(-) diff --git a/packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowDiagramCanvasBase.tsx b/packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowDiagramCanvasBase.tsx index ba69cea7c..40b69ea38 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowDiagramCanvasBase.tsx +++ b/packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowDiagramCanvasBase.tsx @@ -1,3 +1,5 @@ +import { ActionMenuContext } from '@/action-menu/contexts/ActionMenuContext'; +import { CommandMenuAnimationVariant } from '@/command-menu/types/CommandMenuAnimationVariant'; import { useListenRightDrawerClose } from '@/ui/layout/right-drawer/hooks/useListenRightDrawerClose'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2'; @@ -26,7 +28,14 @@ import { useReactFlow, } from '@xyflow/react'; import '@xyflow/react/dist/style.css'; -import React, { useEffect, useMemo, useRef } from 'react'; +import React, { + useCallback, + useContext, + useEffect, + useMemo, + useRef, + useState, +} from 'react'; import { isDefined } from 'twenty-shared/utils'; import { Tag, TagColor } from 'twenty-ui/components'; import { THEME_COMMON } from 'twenty-ui/theme'; @@ -124,6 +133,10 @@ export const WorkflowDiagramCanvasBase = ({ const workflowDiagram = useRecoilComponentValueV2( workflowDiagramComponentState, ); + const [ + workflowDiagramFlowInitializationStatus, + setWorkflowDiagramFlowInitializationStatus, + ] = useState<'not-initialized' | 'initialized'>('not-initialized'); const { nodes, edges } = useMemo( () => @@ -134,10 +147,7 @@ export const WorkflowDiagramCanvasBase = ({ ); const { rightDrawerState } = useRightDrawerState(); - - const rightDrawerWidth = Number( - THEME_COMMON.rightDrawerWidth.replace('px', ''), - ); + const { isInRightDrawer } = useContext(ActionMenuContext); const setWorkflowDiagram = useSetRecoilComponentStateV2( workflowDiagramComponentState, @@ -168,32 +178,67 @@ export const WorkflowDiagramCanvasBase = ({ const containerRef = useRef(null); + const setFlowViewport = useCallback( + ({ + rightDrawerState, + noAnimation, + workflowDiagramFlowInitializationStatus, + isInRightDrawer, + }: { + rightDrawerState: CommandMenuAnimationVariant; + noAnimation?: boolean; + workflowDiagramFlowInitializationStatus: + | 'not-initialized' + | 'initialized'; + isInRightDrawer: boolean; + }) => { + if ( + !isDefined(containerRef.current) || + workflowDiagramFlowInitializationStatus !== 'initialized' + ) { + return; + } + + const currentViewport = reactflow.getViewport(); + const flowBounds = reactflow.getNodesBounds(reactflow.getNodes()); + + let visibleRightDrawerWidth = 0; + if (rightDrawerState === 'normal' && !isInRightDrawer) { + const rightDrawerWidth = Number( + THEME_COMMON.rightDrawerWidth.replace('px', ''), + ); + + visibleRightDrawerWidth = rightDrawerWidth; + } + + const viewportX = + (containerRef.current.offsetWidth + visibleRightDrawerWidth) / 2 - + flowBounds.width / 2; + + reactflow.setViewport( + { + ...currentViewport, + x: viewportX - visibleRightDrawerWidth, + zoom: defaultFitViewOptions.maxZoom, + }, + { duration: noAnimation ? 0 : 300 }, + ); + }, + [reactflow], + ); + useEffect(() => { - if (!isDefined(containerRef.current) || !reactflow.viewportInitialized) { - return; - } - - const currentViewport = reactflow.getViewport(); - - const flowBounds = reactflow.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]); + setFlowViewport({ + rightDrawerState, + isInRightDrawer, + workflowDiagramFlowInitializationStatus, + }); + }, [ + isInRightDrawer, + rightDrawerState, + setFlowViewport, + workflowDiagramFlowInitializationStatus, + ]); const handleNodesChanges = (changes: NodeChange[]) => { setWorkflowDiagram((diagram) => { @@ -213,14 +258,15 @@ export const WorkflowDiagramCanvasBase = ({ return; } - const flowBounds = reactflow.getNodesBounds(reactflow.getNodes()); - - reactflow.setViewport({ - x: containerRef.current.offsetWidth / 2 - flowBounds.width / 2, - y: 150, - zoom: defaultFitViewOptions.maxZoom, + setFlowViewport({ + rightDrawerState, + noAnimation: true, + isInRightDrawer, + workflowDiagramFlowInitializationStatus: 'initialized', }); + setWorkflowDiagramFlowInitializationStatus('initialized'); + onInit?.(); }; @@ -232,6 +278,7 @@ export const WorkflowDiagramCanvasBase = ({ onInit={handleInit} minZoom={defaultFitViewOptions.minZoom} maxZoom={defaultFitViewOptions.maxZoom} + defaultViewport={{ x: 0, y: 150, zoom: defaultFitViewOptions.maxZoom }} nodeTypes={nodeTypes} edgeTypes={edgeTypes} nodes={nodes}