Skip to content

Commit f76684b

Browse files
authored
feat(designer): Moved edge context menu to one shared component (#5321)
* Moved edge context menu to one shared component * Fixed slice issue * Added context menu e2e tests * Fixed paste conflict issue * Fixed custom menu tests
1 parent 1817950 commit f76684b

File tree

16 files changed

+344
-208
lines changed

16 files changed

+344
-208
lines changed

Localize/lang/strings.json

-2
Original file line numberDiff line numberDiff line change
@@ -644,7 +644,6 @@
644644
"MfAdfx": "Edit in advanced mode",
645645
"MirIsS": "Show code",
646646
"MmBfD1": "Unexpected error",
647-
"MmldTM": "No action to paste",
648647
"MnThTq": "Insert function",
649648
"MwMpAh": "Return to expression editor",
650649
"N0pS6Y": "Target schema",
@@ -1640,7 +1639,6 @@
16401639
"_MfAdfx.comment": "Button Label when clicked to swith to advanced editor",
16411640
"_MirIsS.comment": "Button to display the code view",
16421641
"_MmBfD1.comment": "This is the default message shown in case of an error. It can be shown in multiple contexts but generally would be a notification",
1643-
"_MmldTM.comment": "Text for tooltip when there is no action to paste",
16441642
"_MnThTq.comment": "Message to insert function",
16451643
"_MwMpAh.comment": "Text of Tooltip to return to expression editor",
16461644
"_N0pS6Y.comment": "Target schema",

e2e/designer/contextMenus.spec.ts

+45
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import { test, expect } from '@playwright/test';
2+
import { GoToMockWorkflow } from './utils/GoToWorkflow';
3+
4+
test.describe(
5+
'ContextMenu Tests',
6+
{
7+
tag: '@mock',
8+
},
9+
() => {
10+
test('Should open node context menus', async ({ page }) => {
11+
await page.goto('/');
12+
await GoToMockWorkflow(page, 'Scope');
13+
14+
// Open trigger context menu
15+
await page.getByText('manual', { exact: true }).click({ button: 'right' });
16+
expect(await page.getByText('Copy trigger', { exact: true }).isVisible()).toBeTruthy();
17+
await page.keyboard.press('Escape');
18+
// Open variable context menu
19+
await page.getByText('Initialize variable', { exact: true }).click({ button: 'right' });
20+
expect(await page.getByText('Copy action', { exact: true }).isVisible()).toBeTruthy();
21+
await page.keyboard.press('Escape');
22+
// Open scope context menu
23+
await page.getByLabel('Scope operation', { exact: true }).click({ button: 'right' });
24+
expect(await page.getByText('Copy entire action', { exact: true }).isVisible()).toBeTruthy();
25+
await page.keyboard.press('Escape');
26+
});
27+
28+
test('Should open edge context menus', async ({ page }) => {
29+
await page.goto('/');
30+
await GoToMockWorkflow(page, 'Scope');
31+
32+
// Click first edge button
33+
await page.getByLabel('Insert a new step between manual and Initialize variable').click();
34+
expect(await page.getByText('Add an action', { exact: true }).isVisible()).toBeTruthy();
35+
expect(await page.getByText('Add a parallel branch', { exact: true }).isVisible()).toBeTruthy();
36+
await page.keyboard.press('Escape');
37+
38+
// Click the last edge button
39+
await page.getByLabel('Insert a new step after Response').click();
40+
expect(await page.getByText('Add an action', { exact: true }).isVisible()).toBeTruthy();
41+
expect(await page.getByText('Add a parallel branch', { exact: true }).isVisible()).not.toBeTruthy();
42+
await page.keyboard.press('Escape');
43+
});
44+
}
45+
);

libs/designer/src/lib/core/state/designerView/designerViewInterfaces.ts

+11-2
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,19 @@ export interface DesignerViewState {
22
showMinimap?: boolean;
33
clampPan?: boolean;
44
showDeleteModalNodeId?: string;
5-
nodeContextMenuData?: ContextMenuObject;
5+
nodeContextMenuData?: NodeContextMenuObject;
6+
edgeContextMenuData?: EdgeContextMenuObject;
67
}
78

8-
export interface ContextMenuObject {
9+
export interface NodeContextMenuObject {
910
nodeId: string;
1011
location: { x: number; y: number };
1112
}
13+
14+
export interface EdgeContextMenuObject {
15+
graphId: string;
16+
parentId?: string;
17+
childId?: string;
18+
isLeaf?: boolean;
19+
location: { x: number; y: number };
20+
}

libs/designer/src/lib/core/state/designerView/designerViewSelectors.ts

+4
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,7 @@ export const useShowDeleteModalNodeId = () => {
1616
export const useNodeContextMenuData = () => {
1717
return useSelector((state: RootState) => state.designerView.nodeContextMenuData);
1818
};
19+
20+
export const useEdgeContextMenuData = () => {
21+
return useSelector((state: RootState) => state.designerView.edgeContextMenuData);
22+
};
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { resetWorkflowState } from '../global';
2-
import type { ContextMenuObject, DesignerViewState } from './designerViewInterfaces';
2+
import type { NodeContextMenuObject, EdgeContextMenuObject, DesignerViewState } from './designerViewInterfaces';
33
import type { PayloadAction } from '@reduxjs/toolkit';
44
import { createSlice } from '@reduxjs/toolkit';
55

@@ -8,30 +8,35 @@ const initialState: DesignerViewState = {
88
clampPan: true,
99
showDeleteModalNodeId: undefined,
1010
nodeContextMenuData: undefined,
11+
edgeContextMenuData: undefined,
1112
};
1213

1314
export const designerViewSlice = createSlice({
1415
name: 'designerView',
1516
initialState,
1617
reducers: {
17-
toggleMinimap: (state: DesignerViewState) => {
18+
toggleMinimap: (state) => {
1819
state.showMinimap = !state.showMinimap;
1920
},
20-
toggleClampPan: (state: DesignerViewState) => {
21+
toggleClampPan: (state) => {
2122
state.clampPan = !state.clampPan;
2223
},
23-
setShowDeleteModalNodeId: (state: DesignerViewState, action: PayloadAction<string | undefined>) => {
24+
setShowDeleteModalNodeId: (state, action: PayloadAction<string | undefined>) => {
2425
state.showDeleteModalNodeId = action.payload;
2526
},
26-
setNodeContextMenuData: (state: DesignerViewState, action: PayloadAction<ContextMenuObject>) => {
27+
setNodeContextMenuData: (state, action: PayloadAction<NodeContextMenuObject>) => {
2728
state.nodeContextMenuData = action.payload;
2829
},
30+
setEdgeContextMenuData: (state, action: PayloadAction<EdgeContextMenuObject>) => {
31+
state.edgeContextMenuData = action.payload;
32+
},
2933
},
3034
extraReducers: (builder) => {
3135
builder.addCase(resetWorkflowState, () => initialState);
3236
},
3337
});
3438

35-
export const { toggleMinimap, toggleClampPan, setShowDeleteModalNodeId, setNodeContextMenuData } = designerViewSlice.actions;
39+
export const { toggleMinimap, toggleClampPan, setShowDeleteModalNodeId, setNodeContextMenuData, setEdgeContextMenuData } =
40+
designerViewSlice.actions;
3641

3742
export default designerViewSlice.reducer;

libs/designer/src/lib/ui/Designer.tsx

+2
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ import type { BackgroundProps, EdgeTypes, NodeChange } from '@xyflow/react';
3838
import { PerformanceDebugTool } from './common/PerformanceDebug/PerformanceDebug';
3939
import { CanvasFinder } from './CanvasFinder';
4040
import { DesignerContextualMenu } from './common/DesignerContextualMenu/DesignerContextualMenu';
41+
import { EdgeContextualMenu } from './common/EdgeContextualMenu/EdgeContextualMenu';
4142

4243
export interface DesignerProps {
4344
backgroundProps?: BackgroundProps;
@@ -242,6 +243,7 @@ export const Designer = (props: DesignerProps) => {
242243
{backgroundProps ? <Background {...backgroundProps} /> : null}
243244
<DeleteModal />
244245
<DesignerContextualMenu />
246+
<EdgeContextualMenu />
245247
</ReactFlow>
246248
<div className={css('msla-designer-tools', panelLocation === PanelLocation.Left && 'left-panel')} style={copilotPadding}>
247249
<Controls />

libs/designer/src/lib/ui/common/DesignerContextualMenu/CopyTooltip.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ export interface CopyTooltipProps {
1111
}
1212

1313
export const CopyTooltip = ({ targetRef: ref, location, hideTooltip }: CopyTooltipProps) => {
14-
useOnViewportChange({ onStart: hideTooltip });
14+
useOnViewportChange({ onStart: () => hideTooltip() });
1515

1616
const intl = useIntl();
1717
const copiedText = intl.formatMessage({

libs/designer/src/lib/ui/common/DesignerContextualMenu/DesignerContextualMenu.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,8 @@ import {
2222
} from '../../../core/state/designerOptions/designerOptionsSelectors';
2323
import { copyOperation, copyScopeOperation } from '../../../core/actions/bjsworkflow/copypaste';
2424
import { CopyTooltip } from './CopyTooltip';
25-
import { CustomMenu } from '../../connections/customMenu';
26-
import { NodeMenuPriorities } from '../../CustomNodes/Priorities';
25+
import { CustomMenu } from '../EdgeContextualMenu/customMenu';
26+
import { NodeMenuPriorities } from './Priorities';
2727
import type { DropdownMenuCustomNode } from '@microsoft/logic-apps-shared/src/utils/src/lib/models/dropdownMenuCustomNode';
2828

2929
export const DesignerContextualMenu = () => {

0 commit comments

Comments
 (0)