fix: on contextMenu, use selected element regardless of z-index (#3668)

This commit is contained in:
David Luzar 2021-05-29 22:33:53 +02:00 committed by GitHub
parent 60cea7a0c2
commit c819b653bf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 216 additions and 1 deletions

View File

@ -1858,9 +1858,21 @@ class App extends React.Component<AppProps, AppState> {
private getElementAtPosition(
x: number,
y: number,
opts?: {
/** if true, returns the first selected element (with highest z-index)
of all hit elements */
preferSelected?: boolean;
},
): NonDeleted<ExcalidrawElement> | null {
const allHitElements = this.getElementsAtPosition(x, y);
if (allHitElements.length > 1) {
if (opts?.preferSelected) {
for (let index = allHitElements.length - 1; index > -1; index--) {
if (this.state.selectedElementIds[allHitElements[index].id]) {
return allHitElements[index];
}
}
}
const elementWithHighestZIndex =
allHitElements[allHitElements.length - 1];
// If we're hitting element with highest z-index only on its bounding box
@ -3935,7 +3947,7 @@ class App extends React.Component<AppProps, AppState> {
event.preventDefault();
const { x, y } = viewportCoordsToSceneCoords(event, this.state);
const element = this.getElementAtPosition(x, y);
const element = this.getElementAtPosition(x, y, { preferSelected: true });
const type = element ? "element" : "canvas";

View File

@ -4235,6 +4235,84 @@ Object {
}
`;
exports[`contextMenu element shows context menu for element: [end of test] appState 2`] = `
Object {
"collaborators": Map {},
"currentChartType": "bar",
"currentItemBackgroundColor": "transparent",
"currentItemEndArrowhead": "arrow",
"currentItemFillStyle": "hachure",
"currentItemFontFamily": 1,
"currentItemFontSize": 20,
"currentItemLinearStrokeSharpness": "round",
"currentItemOpacity": 100,
"currentItemRoughness": 1,
"currentItemStartArrowhead": null,
"currentItemStrokeColor": "#000000",
"currentItemStrokeSharpness": "sharp",
"currentItemStrokeStyle": "solid",
"currentItemStrokeWidth": 1,
"currentItemTextAlign": "left",
"cursorButton": "up",
"draggingElement": null,
"editingElement": null,
"editingGroupId": null,
"editingLinearElement": null,
"elementLocked": false,
"elementType": "selection",
"errorMessage": null,
"exportBackground": true,
"exportEmbedScene": false,
"exportWithDarkMode": false,
"fileHandle": null,
"gridSize": null,
"height": 100,
"isBindingEnabled": true,
"isLibraryOpen": false,
"isLoading": false,
"isResizing": false,
"isRotating": false,
"lastPointerDownWith": "mouse",
"multiElement": null,
"name": "Untitled-201933152653",
"offsetLeft": 20,
"offsetTop": 10,
"openMenu": null,
"pasteDialog": Object {
"data": null,
"shown": false,
},
"previousSelectedElementIds": Object {},
"resizingElement": null,
"scrollX": 0,
"scrollY": 0,
"scrolledOutside": false,
"selectedElementIds": Object {
"id1": true,
},
"selectedGroupIds": Object {},
"selectionElement": null,
"shouldCacheIgnoreZoom": false,
"showHelpDialog": false,
"showStats": false,
"startBoundElement": null,
"suggestedBindings": Array [],
"theme": "light",
"toastMessage": null,
"viewBackgroundColor": "#ffffff",
"viewModeEnabled": false,
"width": 200,
"zenModeEnabled": false,
"zoom": Object {
"translation": Object {
"x": 0,
"y": 0,
},
"value": 1,
},
}
`;
exports[`contextMenu element shows context menu for element: [end of test] element 0 1`] = `
Object {
"angle": 0,
@ -4261,6 +4339,58 @@ Object {
}
`;
exports[`contextMenu element shows context menu for element: [end of test] element 0 2`] = `
Object {
"angle": 0,
"backgroundColor": "red",
"boundElementIds": null,
"fillStyle": "hachure",
"groupIds": Array [],
"height": 200,
"id": "id0",
"isDeleted": false,
"opacity": 100,
"roughness": 1,
"seed": 337897,
"strokeColor": "#000000",
"strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
"version": 1,
"versionNonce": 0,
"width": 200,
"x": 0,
"y": 0,
}
`;
exports[`contextMenu element shows context menu for element: [end of test] element 1 1`] = `
Object {
"angle": 0,
"backgroundColor": "red",
"boundElementIds": null,
"fillStyle": "hachure",
"groupIds": Array [],
"height": 200,
"id": "id1",
"isDeleted": false,
"opacity": 100,
"roughness": 1,
"seed": 1278240551,
"strokeColor": "#000000",
"strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
"version": 1,
"versionNonce": 0,
"width": 200,
"x": 0,
"y": 0,
}
`;
exports[`contextMenu element shows context menu for element: [end of test] history 1`] = `
Object {
"recording": false,
@ -4318,6 +4448,30 @@ Object {
}
`;
exports[`contextMenu element shows context menu for element: [end of test] history 2`] = `
Object {
"recording": false,
"redoStack": Array [],
"stateHistory": Array [
Object {
"appState": Object {
"editingGroupId": null,
"editingLinearElement": null,
"name": "Untitled-201933152653",
"selectedElementIds": Object {},
"selectedGroupIds": Object {},
"viewBackgroundColor": "#ffffff",
},
"elements": Array [],
},
],
}
`;
exports[`contextMenu element shows context menu for element: [end of test] number of elements 1`] = `1`;
exports[`contextMenu element shows context menu for element: [end of test] number of elements 2`] = `2`;
exports[`contextMenu element shows context menu for element: [end of test] number of renders 1`] = `9`;
exports[`contextMenu element shows context menu for element: [end of test] number of renders 2`] = `6`;

View File

@ -147,6 +147,46 @@ describe("contextMenu element", () => {
});
});
it("shows context menu for element", () => {
const rect1 = API.createElement({
type: "rectangle",
x: 0,
y: 0,
height: 200,
width: 200,
backgroundColor: "red",
});
const rect2 = API.createElement({
type: "rectangle",
x: 0,
y: 0,
height: 200,
width: 200,
backgroundColor: "red",
});
h.elements = [rect1, rect2];
API.setSelectedElements([rect1]);
// lower z-index
fireEvent.contextMenu(GlobalTestState.canvas, {
button: 2,
clientX: 100,
clientY: 100,
});
expect(queryContextMenu()).not.toBeNull();
expect(API.getSelectedElement().id).toBe(rect1.id);
// higher z-index
API.setSelectedElements([rect2]);
fireEvent.contextMenu(GlobalTestState.canvas, {
button: 2,
clientX: 100,
clientY: 100,
});
expect(queryContextMenu()).not.toBeNull();
expect(API.getSelectedElement().id).toBe(rect2.id);
});
it("shows 'Group selection' in context menu for multiple selected elements", () => {
UI.clickTool("rectangle");
mouse.down(10, 10);

View File

@ -20,6 +20,15 @@ const readFile = util.promisify(fs.readFile);
const { h } = window;
export class API {
static setSelectedElements = (elements: ExcalidrawElement[]) => {
h.setState({
selectedElementIds: elements.reduce((acc, element) => {
acc[element.id] = true;
return acc;
}, {} as Record<ExcalidrawElement["id"], true>),
});
};
static getSelectedElements = (): ExcalidrawElement[] => {
return h.elements.filter(
(element) => h.state.selectedElementIds[element.id],