Move the viewport of the workflow visualizer on the show page when side panel is opened (#12605)
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
This commit is contained in:
committed by
GitHub
parent
94376e8509
commit
43bc55efcd
@ -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 { useListenRightDrawerClose } from '@/ui/layout/right-drawer/hooks/useListenRightDrawerClose';
|
||||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||||
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
|
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
|
||||||
@ -26,7 +28,14 @@ import {
|
|||||||
useReactFlow,
|
useReactFlow,
|
||||||
} from '@xyflow/react';
|
} from '@xyflow/react';
|
||||||
import '@xyflow/react/dist/style.css';
|
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 { isDefined } from 'twenty-shared/utils';
|
||||||
import { Tag, TagColor } from 'twenty-ui/components';
|
import { Tag, TagColor } from 'twenty-ui/components';
|
||||||
import { THEME_COMMON } from 'twenty-ui/theme';
|
import { THEME_COMMON } from 'twenty-ui/theme';
|
||||||
@ -124,6 +133,10 @@ export const WorkflowDiagramCanvasBase = ({
|
|||||||
const workflowDiagram = useRecoilComponentValueV2(
|
const workflowDiagram = useRecoilComponentValueV2(
|
||||||
workflowDiagramComponentState,
|
workflowDiagramComponentState,
|
||||||
);
|
);
|
||||||
|
const [
|
||||||
|
workflowDiagramFlowInitializationStatus,
|
||||||
|
setWorkflowDiagramFlowInitializationStatus,
|
||||||
|
] = useState<'not-initialized' | 'initialized'>('not-initialized');
|
||||||
|
|
||||||
const { nodes, edges } = useMemo(
|
const { nodes, edges } = useMemo(
|
||||||
() =>
|
() =>
|
||||||
@ -134,10 +147,7 @@ export const WorkflowDiagramCanvasBase = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
const { rightDrawerState } = useRightDrawerState();
|
const { rightDrawerState } = useRightDrawerState();
|
||||||
|
const { isInRightDrawer } = useContext(ActionMenuContext);
|
||||||
const rightDrawerWidth = Number(
|
|
||||||
THEME_COMMON.rightDrawerWidth.replace('px', ''),
|
|
||||||
);
|
|
||||||
|
|
||||||
const setWorkflowDiagram = useSetRecoilComponentStateV2(
|
const setWorkflowDiagram = useSetRecoilComponentStateV2(
|
||||||
workflowDiagramComponentState,
|
workflowDiagramComponentState,
|
||||||
@ -168,32 +178,67 @@ export const WorkflowDiagramCanvasBase = ({
|
|||||||
|
|
||||||
const containerRef = useRef<HTMLDivElement>(null);
|
const containerRef = useRef<HTMLDivElement>(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(() => {
|
useEffect(() => {
|
||||||
if (!isDefined(containerRef.current) || !reactflow.viewportInitialized) {
|
setFlowViewport({
|
||||||
return;
|
rightDrawerState,
|
||||||
}
|
isInRightDrawer,
|
||||||
|
workflowDiagramFlowInitializationStatus,
|
||||||
const currentViewport = reactflow.getViewport();
|
});
|
||||||
|
}, [
|
||||||
const flowBounds = reactflow.getNodesBounds(reactflow.getNodes());
|
isInRightDrawer,
|
||||||
|
rightDrawerState,
|
||||||
let visibleRightDrawerWidth = 0;
|
setFlowViewport,
|
||||||
if (rightDrawerState === 'normal') {
|
workflowDiagramFlowInitializationStatus,
|
||||||
visibleRightDrawerWidth = rightDrawerWidth;
|
]);
|
||||||
}
|
|
||||||
|
|
||||||
const viewportX =
|
|
||||||
(containerRef.current.offsetWidth + visibleRightDrawerWidth) / 2 -
|
|
||||||
flowBounds.width / 2;
|
|
||||||
|
|
||||||
reactflow.setViewport(
|
|
||||||
{
|
|
||||||
...currentViewport,
|
|
||||||
x: viewportX - visibleRightDrawerWidth,
|
|
||||||
},
|
|
||||||
{ duration: 300 },
|
|
||||||
);
|
|
||||||
}, [reactflow, rightDrawerState, rightDrawerWidth]);
|
|
||||||
|
|
||||||
const handleNodesChanges = (changes: NodeChange<WorkflowDiagramNode>[]) => {
|
const handleNodesChanges = (changes: NodeChange<WorkflowDiagramNode>[]) => {
|
||||||
setWorkflowDiagram((diagram) => {
|
setWorkflowDiagram((diagram) => {
|
||||||
@ -213,14 +258,15 @@ export const WorkflowDiagramCanvasBase = ({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const flowBounds = reactflow.getNodesBounds(reactflow.getNodes());
|
setFlowViewport({
|
||||||
|
rightDrawerState,
|
||||||
reactflow.setViewport({
|
noAnimation: true,
|
||||||
x: containerRef.current.offsetWidth / 2 - flowBounds.width / 2,
|
isInRightDrawer,
|
||||||
y: 150,
|
workflowDiagramFlowInitializationStatus: 'initialized',
|
||||||
zoom: defaultFitViewOptions.maxZoom,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
setWorkflowDiagramFlowInitializationStatus('initialized');
|
||||||
|
|
||||||
onInit?.();
|
onInit?.();
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -232,6 +278,7 @@ export const WorkflowDiagramCanvasBase = ({
|
|||||||
onInit={handleInit}
|
onInit={handleInit}
|
||||||
minZoom={defaultFitViewOptions.minZoom}
|
minZoom={defaultFitViewOptions.minZoom}
|
||||||
maxZoom={defaultFitViewOptions.maxZoom}
|
maxZoom={defaultFitViewOptions.maxZoom}
|
||||||
|
defaultViewport={{ x: 0, y: 150, zoom: defaultFitViewOptions.maxZoom }}
|
||||||
nodeTypes={nodeTypes}
|
nodeTypes={nodeTypes}
|
||||||
edgeTypes={edgeTypes}
|
edgeTypes={edgeTypes}
|
||||||
nodes={nodes}
|
nodes={nodes}
|
||||||
|
|||||||
Reference in New Issue
Block a user