Skip to content

Commit 5dc911c

Browse files
fix(apollo-react): improve stage node performance
1 parent c749406 commit 5dc911c

9 files changed

Lines changed: 264 additions & 294 deletions

File tree

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
export { useNodeConfiguration } from './useNodeConfiguration';
2-
export { useNodeSelection } from './useNodeSelection';
2+
export { useNodeSelection, useSetNodeSelection } from './useNodeSelection';

packages/apollo-react/src/canvas/components/NodePropertiesPanel/hooks/useNodeSelection.ts

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,11 @@ export function useNodeSelection(nodeId?: string, maintainSelection = true) {
2929
useEffect(() => {
3030
if (maintainSelection && selectedNodeId) {
3131
setNodes((nds) =>
32-
nds.map((node) => ({
33-
...node,
34-
selected: node.id === selectedNodeId,
35-
}))
32+
nds.map((node) => {
33+
const shouldBeSelected = node.id === selectedNodeId;
34+
if (node.selected === shouldBeSelected) return node;
35+
return { ...node, selected: shouldBeSelected };
36+
})
3637
);
3738
}
3839
}, [selectedNodeId, maintainSelection, setNodes]);
@@ -47,3 +48,27 @@ export function useNodeSelection(nodeId?: string, maintainSelection = true) {
4748
selectedNode,
4849
};
4950
}
51+
52+
/**
53+
* Lightweight hook that only provides setSelectedNodeId without subscribing
54+
* to all nodes. Use this in node components that only need to trigger selection
55+
* without reading the full nodes array.
56+
*/
57+
export function useSetNodeSelection() {
58+
const { setNodes } = useReactFlow();
59+
60+
const setSelectedNodeId = useCallback(
61+
(nodeId: string) => {
62+
setNodes((nds) =>
63+
nds.map((node) => {
64+
const shouldBeSelected = node.id === nodeId;
65+
if (node.selected === shouldBeSelected) return node;
66+
return { ...node, selected: shouldBeSelected };
67+
})
68+
);
69+
},
70+
[setNodes]
71+
);
72+
73+
return { setSelectedNodeId };
74+
}

packages/apollo-react/src/canvas/components/StageNode/DraggableTask.test.tsx

Lines changed: 14 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,6 @@ const defaultProps: DraggableTaskProps = {
4848
taskExecution: undefined,
4949
isSelected: false,
5050
isParallel: false,
51-
contextMenuItems: [],
5251
onTaskClick: vi.fn(),
5352
isDragDisabled: false,
5453
zoom: 1,
@@ -62,25 +61,25 @@ describe('DraggableTask', () => {
6261
const menuItems = createMenuItems(onRemove);
6362

6463
render(
65-
<DraggableTask {...defaultProps} onMenuOpen={onMenuOpen} contextMenuItems={menuItems} />
64+
<DraggableTask {...defaultProps} onMenuOpen={onMenuOpen} getContextMenuItems={() => menuItems} />
6665
);
6766

6867
const menuButton = screen.getByTestId('stage-task-menu-task-1');
6968
expect(menuButton).toBeInTheDocument();
7069
});
7170

72-
it('renders menu button when contextMenuItems are provided', () => {
71+
it('renders menu button when getContextMenuItems are provided', () => {
7372
const onRemove = vi.fn();
7473
const menuItems = createMenuItems(onRemove);
7574

76-
render(<DraggableTask {...defaultProps} contextMenuItems={menuItems} />);
75+
render(<DraggableTask {...defaultProps} getContextMenuItems={() => menuItems} />);
7776

7877
const menuButton = screen.getByTestId('stage-task-menu-task-1');
7978
expect(menuButton).toBeInTheDocument();
8079
});
8180

82-
it('does not render menu button when contextMenuItems is empty and onMenuOpen is not provided', () => {
83-
render(<DraggableTask {...defaultProps} contextMenuItems={[]} />);
81+
it('does not render menu button when getContextMenuItems is not provided', () => {
82+
render(<DraggableTask {...defaultProps} />);
8483

8584
const menuButton = screen.queryByTestId('stage-task-menu-task-1');
8685
expect(menuButton).not.toBeInTheDocument();
@@ -92,7 +91,7 @@ describe('DraggableTask', () => {
9291
const menuItems = createMenuItems(onRemove);
9392

9493
render(
95-
<DraggableTask {...defaultProps} onMenuOpen={onMenuOpen} contextMenuItems={menuItems} />
94+
<DraggableTask {...defaultProps} onMenuOpen={onMenuOpen} getContextMenuItems={() => menuItems} />
9695
);
9796

9897
const menuButton = screen.getByTestId('stage-task-menu-task-1');
@@ -110,7 +109,7 @@ describe('DraggableTask', () => {
110109
const menuItems = createMenuItems(onRemove);
111110

112111
render(
113-
<DraggableTask {...defaultProps} onMenuOpen={onMenuOpen} contextMenuItems={menuItems} />
112+
<DraggableTask {...defaultProps} onMenuOpen={onMenuOpen} getContextMenuItems={() => menuItems} />
114113
);
115114

116115
const menuButton = screen.getByTestId('stage-task-menu-task-1');
@@ -134,7 +133,7 @@ describe('DraggableTask', () => {
134133
{...defaultProps}
135134
onTaskClick={onTaskClick}
136135
onMenuOpen={onMenuOpen}
137-
contextMenuItems={menuItems}
136+
getContextMenuItems={() => menuItems}
138137
/>
139138
);
140139

@@ -157,7 +156,7 @@ describe('DraggableTask', () => {
157156
{...defaultProps}
158157
onTaskClick={onTaskClick}
159158
onMenuOpen={onMenuOpen}
160-
contextMenuItems={menuItems}
159+
getContextMenuItems={() => menuItems}
161160
/>
162161
);
163162

@@ -189,7 +188,7 @@ describe('DraggableTask', () => {
189188
{...defaultProps}
190189
onTaskClick={onTaskClick}
191190
onMenuOpen={onMenuOpen}
192-
contextMenuItems={menuItems}
191+
getContextMenuItems={() => menuItems}
193192
/>
194193
);
195194

@@ -218,7 +217,7 @@ describe('DraggableTask', () => {
218217
const menuItems = createMenuItems(onRemove);
219218

220219
render(
221-
<DraggableTask {...defaultProps} onMenuOpen={onMenuOpen} contextMenuItems={menuItems} />
220+
<DraggableTask {...defaultProps} onMenuOpen={onMenuOpen} getContextMenuItems={() => menuItems} />
222221
);
223222

224223
// Open menu
@@ -244,7 +243,7 @@ describe('DraggableTask', () => {
244243
const menuItems = createMenuItems(onRemove);
245244

246245
render(
247-
<DraggableTask {...defaultProps} onMenuOpen={onMenuOpen} contextMenuItems={menuItems} />
246+
<DraggableTask {...defaultProps} onMenuOpen={onMenuOpen} getContextMenuItems={() => menuItems} />
248247
);
249248

250249
// Open menu
@@ -272,7 +271,7 @@ describe('DraggableTask', () => {
272271
const menuItems = createMenuItems(onRemove);
273272

274273
render(
275-
<DraggableTask {...defaultProps} onMenuOpen={onMenuOpen} contextMenuItems={menuItems} />
274+
<DraggableTask {...defaultProps} onMenuOpen={onMenuOpen} getContextMenuItems={() => menuItems} />
276275
);
277276

278277
// Open menu
@@ -315,7 +314,7 @@ describe('DraggableTask', () => {
315314
{...defaultProps}
316315
onTaskClick={onTaskClick}
317316
onMenuOpen={onMenuOpen}
318-
contextMenuItems={menuItems}
317+
getContextMenuItems={() => menuItems}
319318
/>
320319
);
321320

packages/apollo-react/src/canvas/components/StageNode/DraggableTask.tsx

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { useSortable } from '@dnd-kit/sortable';
22
import { CSS } from '@dnd-kit/utilities';
33
import { FontVariantToken, Padding, Spacing } from '@uipath/apollo-core';
44
import { Column, Row } from '@uipath/apollo-react/canvas/layouts';
5+
import { useStoreApi } from '@uipath/apollo-react/canvas/xyflow/react';
56
import {
67
ApBadge,
78
ApCircularProgress,
@@ -126,15 +127,15 @@ const DraggableTaskComponent = ({
126127
taskExecution,
127128
isSelected,
128129
isParallel,
129-
contextMenuItems,
130+
getContextMenuItems,
130131
onTaskClick,
131132
onMenuOpen,
132133
onTaskPlay,
133134
isDragDisabled,
134135
projectedDepth,
135-
zoom = 1,
136136
}: DraggableTaskProps) => {
137137
const [isMenuOpen, setIsMenuOpen] = useState(false);
138+
const storeApi = useStoreApi();
138139
const taskRef = useRef<HTMLDivElement>(null);
139140
const menuRef = useRef<TaskMenuHandle>(null);
140141
const [playLoading, setPlayLoading] = useState(false);
@@ -197,8 +198,8 @@ const DraggableTaskComponent = ({
197198
const scaledTransform = transform
198199
? {
199200
...transform,
200-
x: transform.x / zoom,
201-
y: transform.y / zoom,
201+
x: transform.x / storeApi.getState().transform[2],
202+
y: transform.y / storeApi.getState().transform[2],
202203
}
203204
: null;
204205

@@ -213,7 +214,7 @@ const DraggableTaskComponent = ({
213214
transform: CSS.Transform.toString(scaledTransform),
214215
marginLeft,
215216
};
216-
}, [transform, zoom, transition, isDragging, projectedDepth, isParallel]);
217+
}, [transform, storeApi, transition, isDragging, projectedDepth, isParallel]);
217218

218219
if (isDragging) {
219220
const isTargetParallel = projectedDepth === 1;
@@ -233,7 +234,7 @@ const DraggableTaskComponent = ({
233234
isParallel={isParallel}
234235
isDragEnabled={!isDragDisabled}
235236
onClick={handleClick}
236-
{...(contextMenuItems.length > 0 && { onContextMenu: handleContextMenu })}
237+
{...(getContextMenuItems && { onContextMenu: handleContextMenu })}
237238
>
238239
<TaskContent task={task} taskExecution={taskExecution} />
239240

@@ -270,11 +271,11 @@ const DraggableTaskComponent = ({
270271
</span>
271272
</ApTooltip>
272273
)}
273-
{contextMenuItems.length > 0 && (
274+
{getContextMenuItems && (
274275
<TaskMenu
275276
ref={menuRef}
276277
taskId={task.id}
277-
contextMenuItems={contextMenuItems}
278+
getContextMenuItems={getContextMenuItems}
278279
onMenuOpenChange={handleMenuOpenChange}
279280
onMenuOpen={onMenuOpen}
280281
taskRef={taskRef}

packages/apollo-react/src/canvas/components/StageNode/DraggableTask.types.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,10 @@ export interface DraggableTaskProps {
1212
taskExecution?: StageTaskExecution;
1313
isSelected: boolean;
1414
isParallel: boolean;
15-
contextMenuItems: NodeMenuItem[];
15+
getContextMenuItems?: () => NodeMenuItem[];
1616
onTaskClick: (e: React.MouseEvent, taskId: string) => void;
1717
onMenuOpen?: () => void;
1818
onTaskPlay?: (taskId: string) => Promise<void>;
1919
isDragDisabled?: boolean;
2020
projectedDepth?: number;
21-
zoom?: number;
2221
}

0 commit comments

Comments
 (0)