From 51a8ab65f3cb1a1c60392474d7b6b1fbde8a431e Mon Sep 17 00:00:00 2001 From: Rene <1595098+ReneCode@users.noreply.github.com> Date: Thu, 9 Jul 2020 22:32:27 +0200 Subject: [PATCH] Group / ungroup should not always be present in the context menu (#1890) Co-authored-by: rene_mbp <harryloveslearning@googlemail.com> Co-authored-by: dwelle <luzar.david@gmail.com> --- src/actions/actionGroup.ts | 4 + src/actions/manager.tsx | 8 + src/actions/types.ts | 4 + .../regressionTests.test.tsx.snap | 490 ++++++++++++++++++ src/tests/regressionTests.test.tsx | 87 ++++ 5 files changed, 593 insertions(+) diff --git a/src/actions/actionGroup.ts b/src/actions/actionGroup.ts index 746c6b3d..7755a41d 100644 --- a/src/actions/actionGroup.ts +++ b/src/actions/actionGroup.ts @@ -90,6 +90,8 @@ export const actionGroup = register({ }, contextMenuOrder: 4, contextItemLabel: "labels.group", + contextItemPredicate: (elements, appState) => + getSelectedElements(getNonDeletedElements(elements), appState).length > 1, keyTest: (event) => { return ( !event.shiftKey && @@ -136,4 +138,6 @@ export const actionUngroup = register({ }, contextMenuOrder: 5, contextItemLabel: "labels.ungroup", + contextItemPredicate: (elements, appState) => + getSelectedGroupIds(appState).length > 0, }); diff --git a/src/actions/manager.tsx b/src/actions/manager.tsx index ece81d90..2c4350ec 100644 --- a/src/actions/manager.tsx +++ b/src/actions/manager.tsx @@ -82,6 +82,14 @@ export class ActionManager implements ActionsManagerInterface { return Object.values(this.actions) .filter(actionFilter) .filter((action) => "contextItemLabel" in action) + .filter((action) => + action.contextItemPredicate + ? action.contextItemPredicate( + this.getElementsIncludingDeleted(), + this.getAppState(), + ) + : true, + ) .sort( (a, b) => (a.contextMenuOrder !== undefined ? a.contextMenuOrder : 999) - diff --git a/src/actions/types.ts b/src/actions/types.ts index 4f160eee..f8e295bb 100644 --- a/src/actions/types.ts +++ b/src/actions/types.ts @@ -81,6 +81,10 @@ export interface Action { ) => boolean; contextItemLabel?: string; contextMenuOrder?: number; + contextItemPredicate?: ( + elements: readonly ExcalidrawElement[], + appState: AppState, + ) => boolean; } export interface ActionsManagerInterface { diff --git a/src/tests/__snapshots__/regressionTests.test.tsx.snap b/src/tests/__snapshots__/regressionTests.test.tsx.snap index e2f12669..f13cf401 100644 --- a/src/tests/__snapshots__/regressionTests.test.tsx.snap +++ b/src/tests/__snapshots__/regressionTests.test.tsx.snap @@ -15375,6 +15375,496 @@ exports[`regression tests shift-click to multiselect, then drag: [end of test] n exports[`regression tests shift-click to multiselect, then drag: [end of test] number of renders 1`] = `17`; +exports[`regression tests shows 'Group selection' in context menu for multiple selected elements: [end of test] appState 1`] = ` +Object { + "collaborators": Map {}, + "currentItemBackgroundColor": "transparent", + "currentItemFillStyle": "hachure", + "currentItemFontFamily": 1, + "currentItemFontSize": 20, + "currentItemOpacity": 100, + "currentItemRoughness": 1, + "currentItemStrokeColor": "#000000", + "currentItemStrokeStyle": "solid", + "currentItemStrokeWidth": 1, + "currentItemTextAlign": "left", + "cursorButton": "up", + "cursorX": 0, + "cursorY": 0, + "draggingElement": null, + "editingElement": null, + "editingGroupId": null, + "editingLinearElement": null, + "elementLocked": false, + "elementType": "selection", + "errorMessage": null, + "exportBackground": true, + "gridSize": null, + "height": 768, + "isCollaborating": false, + "isLoading": false, + "isResizing": false, + "isRotating": false, + "lastPointerDownWith": "mouse", + "multiElement": null, + "name": "Untitled-201933152653", + "openMenu": null, + "previousSelectedElementIds": Object { + "id0": true, + "id2": true, + }, + "resizingElement": null, + "scrollX": 0, + "scrollY": 0, + "scrolledOutside": false, + "selectedElementIds": Object { + "id0": true, + "id1": true, + "id2": true, + "id3": true, + }, + "selectedGroupIds": Object {}, + "selectionElement": null, + "shouldAddWatermark": false, + "shouldCacheIgnoreZoom": false, + "showShortcutsDialog": false, + "username": "", + "viewBackgroundColor": "#ffffff", + "width": 1024, + "zenModeEnabled": false, + "zoom": 1, +} +`; + +exports[`regression tests shows 'Group selection' in context menu for multiple selected elements: [end of test] element 0 1`] = ` +Object { + "angle": 0, + "backgroundColor": "transparent", + "fillStyle": "hachure", + "groupIds": Array [], + "height": 10, + "id": "id0", + "isDeleted": false, + "opacity": 100, + "roughness": 1, + "seed": 337897, + "strokeColor": "#000000", + "strokeStyle": "solid", + "strokeWidth": 1, + "type": "rectangle", + "version": 2, + "versionNonce": 1278240551, + "width": 10, + "x": 10, + "y": 10, +} +`; + +exports[`regression tests shows 'Group selection' in context menu for multiple selected elements: [end of test] element 1 1`] = ` +Object { + "angle": 0, + "backgroundColor": "transparent", + "fillStyle": "hachure", + "groupIds": Array [], + "height": 10, + "id": "id1", + "isDeleted": false, + "opacity": 100, + "roughness": 1, + "seed": 449462985, + "strokeColor": "#000000", + "strokeStyle": "solid", + "strokeWidth": 1, + "type": "rectangle", + "version": 2, + "versionNonce": 453191, + "width": 10, + "x": 30, + "y": 10, +} +`; + +exports[`regression tests shows 'Group selection' in context menu for multiple selected elements: [end of test] history 1`] = ` +Object { + "recording": false, + "redoStack": Array [], + "stateHistory": Array [ + Object { + "appState": Object { + "editingGroupId": null, + "editingLinearElement": null, + "name": "Untitled-201933152653", + "selectedElementIds": Object { + "id0": true, + }, + "viewBackgroundColor": "#ffffff", + }, + "elements": Array [ + Object { + "angle": 0, + "backgroundColor": "transparent", + "fillStyle": "hachure", + "groupIds": Array [], + "height": 10, + "id": "id0", + "isDeleted": false, + "opacity": 100, + "roughness": 1, + "seed": 337897, + "strokeColor": "#000000", + "strokeStyle": "solid", + "strokeWidth": 1, + "type": "rectangle", + "version": 2, + "versionNonce": 1278240551, + "width": 10, + "x": 10, + "y": 10, + }, + ], + }, + Object { + "appState": Object { + "editingGroupId": null, + "editingLinearElement": null, + "name": "Untitled-201933152653", + "selectedElementIds": Object { + "id1": true, + }, + "viewBackgroundColor": "#ffffff", + }, + "elements": Array [ + Object { + "angle": 0, + "backgroundColor": "transparent", + "fillStyle": "hachure", + "groupIds": Array [], + "height": 10, + "id": "id0", + "isDeleted": false, + "opacity": 100, + "roughness": 1, + "seed": 337897, + "strokeColor": "#000000", + "strokeStyle": "solid", + "strokeWidth": 1, + "type": "rectangle", + "version": 2, + "versionNonce": 1278240551, + "width": 10, + "x": 10, + "y": 10, + }, + Object { + "angle": 0, + "backgroundColor": "transparent", + "fillStyle": "hachure", + "groupIds": Array [], + "height": 10, + "id": "id1", + "isDeleted": false, + "opacity": 100, + "roughness": 1, + "seed": 449462985, + "strokeColor": "#000000", + "strokeStyle": "solid", + "strokeWidth": 1, + "type": "rectangle", + "version": 2, + "versionNonce": 453191, + "width": 10, + "x": 30, + "y": 10, + }, + ], + }, + ], +} +`; + +exports[`regression tests shows 'Group selection' in context menu for multiple selected elements: [end of test] number of elements 1`] = `2`; + +exports[`regression tests shows 'Group selection' in context menu for multiple selected elements: [end of test] number of renders 1`] = `15`; + +exports[`regression tests shows 'Ungroup selection' in context menu for group inside selected elements: [end of test] appState 1`] = ` +Object { + "collaborators": Map {}, + "currentItemBackgroundColor": "transparent", + "currentItemFillStyle": "hachure", + "currentItemFontFamily": 1, + "currentItemFontSize": 20, + "currentItemOpacity": 100, + "currentItemRoughness": 1, + "currentItemStrokeColor": "#000000", + "currentItemStrokeStyle": "solid", + "currentItemStrokeWidth": 1, + "currentItemTextAlign": "left", + "cursorButton": "up", + "cursorX": 0, + "cursorY": 0, + "draggingElement": null, + "editingElement": null, + "editingGroupId": null, + "editingLinearElement": null, + "elementLocked": false, + "elementType": "selection", + "errorMessage": null, + "exportBackground": true, + "gridSize": null, + "height": 768, + "isCollaborating": false, + "isLoading": false, + "isResizing": false, + "isRotating": false, + "lastPointerDownWith": "mouse", + "multiElement": null, + "name": "Untitled-201933152653", + "openMenu": null, + "previousSelectedElementIds": Object { + "id0": true, + "id2": true, + }, + "resizingElement": null, + "scrollX": 0, + "scrollY": 0, + "scrolledOutside": false, + "selectedElementIds": Object { + "id0": true, + "id1": true, + "id2": true, + "id3": true, + }, + "selectedGroupIds": Object { + "id4": true, + }, + "selectionElement": null, + "shouldAddWatermark": false, + "shouldCacheIgnoreZoom": false, + "showShortcutsDialog": false, + "username": "", + "viewBackgroundColor": "#ffffff", + "width": 1024, + "zenModeEnabled": false, + "zoom": 1, +} +`; + +exports[`regression tests shows 'Ungroup selection' in context menu for group inside selected elements: [end of test] element 0 1`] = ` +Object { + "angle": 0, + "backgroundColor": "transparent", + "fillStyle": "hachure", + "groupIds": Array [ + "id4", + ], + "height": 10, + "id": "id0", + "isDeleted": false, + "opacity": 100, + "roughness": 1, + "seed": 337897, + "strokeColor": "#000000", + "strokeStyle": "solid", + "strokeWidth": 1, + "type": "rectangle", + "version": 3, + "versionNonce": 1150084233, + "width": 10, + "x": 10, + "y": 10, +} +`; + +exports[`regression tests shows 'Ungroup selection' in context menu for group inside selected elements: [end of test] element 1 1`] = ` +Object { + "angle": 0, + "backgroundColor": "transparent", + "fillStyle": "hachure", + "groupIds": Array [ + "id4", + ], + "height": 10, + "id": "id1", + "isDeleted": false, + "opacity": 100, + "roughness": 1, + "seed": 449462985, + "strokeColor": "#000000", + "strokeStyle": "solid", + "strokeWidth": 1, + "type": "rectangle", + "version": 3, + "versionNonce": 1116226695, + "width": 10, + "x": 30, + "y": 10, +} +`; + +exports[`regression tests shows 'Ungroup selection' in context menu for group inside selected elements: [end of test] history 1`] = ` +Object { + "recording": false, + "redoStack": Array [], + "stateHistory": Array [ + Object { + "appState": Object { + "editingGroupId": null, + "editingLinearElement": null, + "name": "Untitled-201933152653", + "selectedElementIds": Object { + "id0": true, + }, + "viewBackgroundColor": "#ffffff", + }, + "elements": Array [ + Object { + "angle": 0, + "backgroundColor": "transparent", + "fillStyle": "hachure", + "groupIds": Array [], + "height": 10, + "id": "id0", + "isDeleted": false, + "opacity": 100, + "roughness": 1, + "seed": 337897, + "strokeColor": "#000000", + "strokeStyle": "solid", + "strokeWidth": 1, + "type": "rectangle", + "version": 2, + "versionNonce": 1278240551, + "width": 10, + "x": 10, + "y": 10, + }, + ], + }, + Object { + "appState": Object { + "editingGroupId": null, + "editingLinearElement": null, + "name": "Untitled-201933152653", + "selectedElementIds": Object { + "id1": true, + }, + "viewBackgroundColor": "#ffffff", + }, + "elements": Array [ + Object { + "angle": 0, + "backgroundColor": "transparent", + "fillStyle": "hachure", + "groupIds": Array [], + "height": 10, + "id": "id0", + "isDeleted": false, + "opacity": 100, + "roughness": 1, + "seed": 337897, + "strokeColor": "#000000", + "strokeStyle": "solid", + "strokeWidth": 1, + "type": "rectangle", + "version": 2, + "versionNonce": 1278240551, + "width": 10, + "x": 10, + "y": 10, + }, + Object { + "angle": 0, + "backgroundColor": "transparent", + "fillStyle": "hachure", + "groupIds": Array [], + "height": 10, + "id": "id1", + "isDeleted": false, + "opacity": 100, + "roughness": 1, + "seed": 449462985, + "strokeColor": "#000000", + "strokeStyle": "solid", + "strokeWidth": 1, + "type": "rectangle", + "version": 2, + "versionNonce": 453191, + "width": 10, + "x": 30, + "y": 10, + }, + ], + }, + Object { + "appState": Object { + "editingGroupId": null, + "editingLinearElement": null, + "name": "Untitled-201933152653", + "selectedElementIds": Object { + "id0": true, + "id1": true, + "id2": true, + "id3": true, + }, + "viewBackgroundColor": "#ffffff", + }, + "elements": Array [ + Object { + "angle": 0, + "backgroundColor": "transparent", + "fillStyle": "hachure", + "groupIds": Array [ + "id4", + ], + "height": 10, + "id": "id0", + "isDeleted": false, + "opacity": 100, + "roughness": 1, + "seed": 337897, + "strokeColor": "#000000", + "strokeStyle": "solid", + "strokeWidth": 1, + "type": "rectangle", + "version": 3, + "versionNonce": 1150084233, + "width": 10, + "x": 10, + "y": 10, + }, + Object { + "angle": 0, + "backgroundColor": "transparent", + "fillStyle": "hachure", + "groupIds": Array [ + "id4", + ], + "height": 10, + "id": "id1", + "isDeleted": false, + "opacity": 100, + "roughness": 1, + "seed": 449462985, + "strokeColor": "#000000", + "strokeStyle": "solid", + "strokeWidth": 1, + "type": "rectangle", + "version": 3, + "versionNonce": 1116226695, + "width": 10, + "x": 30, + "y": 10, + }, + ], + }, + ], +} +`; + +exports[`regression tests shows 'Ungroup selection' in context menu for group inside selected elements: [end of test] number of elements 1`] = `2`; + +exports[`regression tests shows 'Ungroup selection' in context menu for group inside selected elements: [end of test] number of renders 1`] = `16`; + exports[`regression tests shows context menu for canvas: [end of test] appState 1`] = ` Object { "collaborators": Map {}, diff --git a/src/tests/regressionTests.test.tsx b/src/tests/regressionTests.test.tsx index 9c07532d..fed5c440 100644 --- a/src/tests/regressionTests.test.tsx +++ b/src/tests/regressionTests.test.tsx @@ -878,6 +878,93 @@ describe("regression tests", () => { mouse.down(10, 10); mouse.up(20, 20); fireEvent.contextMenu(canvas, { button: 2, clientX: 1, clientY: 1 }); + const contextMenu = document.querySelector(".context-menu"); + const options = contextMenu?.querySelectorAll(".context-menu-option"); + const expectedOptions = [ + "Copy styles", + "Paste styles", + "Delete", + "Send backward", + "Bring forward", + "Send to back", + "Bring to front", + "Duplicate", + ]; + + expect(contextMenu).not.toBeNull(); + expect(contextMenu?.children.length).toBe(8); + options?.forEach((opt, i) => { + expect(opt.textContent).toBe(expectedOptions[i]); + }); + }); + + it("shows 'Group selection' in context menu for multiple selected elements", () => { + fireEvent.change(document.querySelector(".dropdown-select__language")!, { + target: { value: "en" }, + }); + + clickTool("rectangle"); + mouse.down(10, 10); + mouse.up(10, 10); + + clickTool("rectangle"); + mouse.down(10, -10); + mouse.up(10, 10); + + mouse.reset(); + mouse.click(10, 10); + withModifierKeys({ shift: true }, () => { + mouse.click(20, 0); + }); + + fireEvent.contextMenu(canvas, { button: 2, clientX: 1, clientY: 1 }); + + const contextMenu = document.querySelector(".context-menu"); + const options = contextMenu?.querySelectorAll(".context-menu-option"); + const expectedOptions = [ + "Copy styles", + "Paste styles", + "Delete", + "Group selection", + "Send backward", + "Bring forward", + "Send to back", + "Bring to front", + "Duplicate", + ]; + + expect(contextMenu).not.toBeNull(); + expect(contextMenu?.children.length).toBe(9); + options?.forEach((opt, i) => { + expect(opt.textContent).toBe(expectedOptions[i]); + }); + }); + + it("shows 'Ungroup selection' in context menu for group inside selected elements", () => { + fireEvent.change(document.querySelector(".dropdown-select__language")!, { + target: { value: "en" }, + }); + + clickTool("rectangle"); + mouse.down(10, 10); + mouse.up(10, 10); + + clickTool("rectangle"); + mouse.down(10, -10); + mouse.up(10, 10); + + mouse.reset(); + mouse.click(10, 10); + withModifierKeys({ shift: true }, () => { + mouse.click(20, 0); + }); + + withModifierKeys({ ctrl: true }, () => { + keyPress("g"); + }); + + fireEvent.contextMenu(canvas, { button: 2, clientX: 1, clientY: 1 }); + const contextMenu = document.querySelector(".context-menu"); const options = contextMenu?.querySelectorAll(".context-menu-option"); const expectedOptions = [