remove closures from mutateElement, get rid of the element spreading (#902)

This commit is contained in:
Pete Hunt 2020-03-10 20:11:02 -07:00 committed by GitHub
parent 13b838117c
commit 83a2f5de28
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 218 additions and 196 deletions

View File

@ -19,11 +19,11 @@ export const actionFinalize = register({
if (appState.multiElement) { if (appState.multiElement) {
// pen and mouse have hover // pen and mouse have hover
if (appState.lastPointerDownWith !== "touch") { if (appState.lastPointerDownWith !== "touch") {
mutateElement(appState.multiElement, multiElement => { mutateElement(appState.multiElement, {
multiElement.points = multiElement.points.slice( points: appState.multiElement.points.slice(
0, 0,
multiElement.points.length - 1, appState.multiElement.points.length - 1,
); ),
}); });
} }
if (isInvisiblySmallElement(appState.multiElement)) { if (isInvisiblySmallElement(appState.multiElement)) {

View File

@ -11,6 +11,7 @@ import { AppState } from "../../src/types";
import { t } from "../i18n"; import { t } from "../i18n";
import { DEFAULT_FONT } from "../appState"; import { DEFAULT_FONT } from "../appState";
import { register } from "./register"; import { register } from "./register";
import { newElementWith, newTextElementWith } from "../element/mutateElement";
const changeProperty = ( const changeProperty = (
elements: readonly ExcalidrawElement[], elements: readonly ExcalidrawElement[],
@ -45,10 +46,11 @@ export const actionChangeStrokeColor = register({
name: "changeStrokeColor", name: "changeStrokeColor",
perform: (elements, appState, value) => { perform: (elements, appState, value) => {
return { return {
elements: changeProperty(elements, appState, el => ({ elements: changeProperty(elements, appState, el =>
...el, newElementWith(el, {
strokeColor: value, strokeColor: value,
})), }),
),
appState: { ...appState, currentItemStrokeColor: value }, appState: { ...appState, currentItemStrokeColor: value },
}; };
}, },
@ -75,10 +77,11 @@ export const actionChangeBackgroundColor = register({
name: "changeBackgroundColor", name: "changeBackgroundColor",
perform: (elements, appState, value) => { perform: (elements, appState, value) => {
return { return {
elements: changeProperty(elements, appState, el => ({ elements: changeProperty(elements, appState, el =>
...el, newElementWith(el, {
backgroundColor: value, backgroundColor: value,
})), }),
),
appState: { ...appState, currentItemBackgroundColor: value }, appState: { ...appState, currentItemBackgroundColor: value },
}; };
}, },
@ -105,10 +108,11 @@ export const actionChangeFillStyle = register({
name: "changeFillStyle", name: "changeFillStyle",
perform: (elements, appState, value) => { perform: (elements, appState, value) => {
return { return {
elements: changeProperty(elements, appState, el => ({ elements: changeProperty(elements, appState, el =>
...el, newElementWith(el, {
fillStyle: value, fillStyle: value,
})), }),
),
appState: { ...appState, currentItemFillStyle: value }, appState: { ...appState, currentItemFillStyle: value },
}; };
}, },
@ -141,10 +145,11 @@ export const actionChangeStrokeWidth = register({
name: "changeStrokeWidth", name: "changeStrokeWidth",
perform: (elements, appState, value) => { perform: (elements, appState, value) => {
return { return {
elements: changeProperty(elements, appState, el => ({ elements: changeProperty(elements, appState, el =>
...el, newElementWith(el, {
strokeWidth: value, strokeWidth: value,
})), }),
),
appState: { ...appState, currentItemStrokeWidth: value }, appState: { ...appState, currentItemStrokeWidth: value },
}; };
}, },
@ -175,10 +180,11 @@ export const actionChangeSloppiness = register({
name: "changeSloppiness", name: "changeSloppiness",
perform: (elements, appState, value) => { perform: (elements, appState, value) => {
return { return {
elements: changeProperty(elements, appState, el => ({ elements: changeProperty(elements, appState, el =>
...el, newElementWith(el, {
roughness: value, roughness: value,
})), }),
),
appState: { ...appState, currentItemRoughness: value }, appState: { ...appState, currentItemRoughness: value },
}; };
}, },
@ -209,10 +215,11 @@ export const actionChangeOpacity = register({
name: "changeOpacity", name: "changeOpacity",
perform: (elements, appState, value) => { perform: (elements, appState, value) => {
return { return {
elements: changeProperty(elements, appState, el => ({ elements: changeProperty(elements, appState, el =>
...el, newElementWith(el, {
opacity: value, opacity: value,
})), }),
),
appState: { ...appState, currentItemOpacity: value }, appState: { ...appState, currentItemOpacity: value },
}; };
}, },
@ -259,10 +266,9 @@ export const actionChangeFontSize = register({
return { return {
elements: changeProperty(elements, appState, el => { elements: changeProperty(elements, appState, el => {
if (isTextElement(el)) { if (isTextElement(el)) {
const element: ExcalidrawTextElement = { const element: ExcalidrawTextElement = newTextElementWith(el, {
...el,
font: `${value}px ${el.font.split("px ")[1]}`, font: `${value}px ${el.font.split("px ")[1]}`,
}; });
redrawTextBoundingBox(element); redrawTextBoundingBox(element);
return element; return element;
} }
@ -307,10 +313,9 @@ export const actionChangeFontFamily = register({
return { return {
elements: changeProperty(elements, appState, el => { elements: changeProperty(elements, appState, el => {
if (isTextElement(el)) { if (isTextElement(el)) {
const element: ExcalidrawTextElement = { const element: ExcalidrawTextElement = newTextElementWith(el, {
...el,
font: `${el.font.split("px ")[0]}px ${value}`, font: `${el.font.split("px ")[0]}px ${value}`,
}; });
redrawTextBoundingBox(element); redrawTextBoundingBox(element);
return element; return element;
} }

View File

@ -6,7 +6,7 @@ import {
import { KEYS } from "../keys"; import { KEYS } from "../keys";
import { DEFAULT_FONT } from "../appState"; import { DEFAULT_FONT } from "../appState";
import { register } from "./register"; import { register } from "./register";
import { mutateTextElement } from "../element/mutateElement"; import { mutateTextElement, newElementWith } from "../element/mutateElement";
let copiedStyles: string = "{}"; let copiedStyles: string = "{}";
@ -35,20 +35,19 @@ export const actionPasteStyles = register({
return { return {
elements: elements.map(element => { elements: elements.map(element => {
if (appState.selectedElementIds[element.id]) { if (appState.selectedElementIds[element.id]) {
const newElement = { const newElement = newElementWith(element, {
...element,
backgroundColor: pastedElement?.backgroundColor, backgroundColor: pastedElement?.backgroundColor,
strokeWidth: pastedElement?.strokeWidth, strokeWidth: pastedElement?.strokeWidth,
strokeColor: pastedElement?.strokeColor, strokeColor: pastedElement?.strokeColor,
fillStyle: pastedElement?.fillStyle, fillStyle: pastedElement?.fillStyle,
opacity: pastedElement?.opacity, opacity: pastedElement?.opacity,
roughness: pastedElement?.roughness, roughness: pastedElement?.roughness,
};
if (isTextElement(newElement)) {
mutateTextElement(newElement, newElement => {
newElement.font = pastedElement?.font || DEFAULT_FONT;
redrawTextBoundingBox(newElement);
}); });
if (isTextElement(newElement)) {
mutateTextElement(newElement, {
font: pastedElement?.font || DEFAULT_FONT,
});
redrawTextBoundingBox(newElement);
} }
return newElement; return newElement;
} }

View File

@ -88,7 +88,7 @@ import { LayerUI } from "./LayerUI";
import { ScrollBars } from "../scene/types"; import { ScrollBars } from "../scene/types";
import { invalidateShapeForElement } from "../renderer/renderElement"; import { invalidateShapeForElement } from "../renderer/renderElement";
import { generateCollaborationLink, getCollaborationLinkData } from "../data"; import { generateCollaborationLink, getCollaborationLinkData } from "../data";
import { mutateElement } from "../element/mutateElement"; import { mutateElement, newElementWith } from "../element/mutateElement";
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// TEST HOOKS // TEST HOOKS
@ -473,17 +473,17 @@ export class App extends React.Component<any, AppState> {
: ELEMENT_TRANSLATE_AMOUNT; : ELEMENT_TRANSLATE_AMOUNT;
elements = elements.map(el => { elements = elements.map(el => {
if (this.state.selectedElementIds[el.id]) { if (this.state.selectedElementIds[el.id]) {
const element = { ...el }; const update: { x?: number; y?: number } = {};
if (event.key === KEYS.ARROW_LEFT) { if (event.key === KEYS.ARROW_LEFT) {
element.x -= step; update.x = el.x - step;
} else if (event.key === KEYS.ARROW_RIGHT) { } else if (event.key === KEYS.ARROW_RIGHT) {
element.x += step; update.x = el.x + step;
} else if (event.key === KEYS.ARROW_UP) { } else if (event.key === KEYS.ARROW_UP) {
element.y -= step; update.y = el.y - step;
} else if (event.key === KEYS.ARROW_DOWN) { } else if (event.key === KEYS.ARROW_DOWN) {
element.y += step; update.y = el.y + step;
} }
return element; return newElementWith(el, update);
} }
return el; return el;
}); });
@ -803,9 +803,9 @@ export class App extends React.Component<any, AppState> {
textY = centerElementYInViewport; textY = centerElementYInViewport;
// x and y will change after calling newTextElement function // x and y will change after calling newTextElement function
mutateElement(element, element => { mutateElement(element, {
element.x = centerElementX; x: centerElementX,
element.y = centerElementY; y: centerElementY,
}); });
} else if (!event.altKey) { } else if (!event.altKey) {
const snappedToCenterPosition = this.getTextWysiwygSnappedToCenterPosition( const snappedToCenterPosition = this.getTextWysiwygSnappedToCenterPosition(
@ -814,9 +814,9 @@ export class App extends React.Component<any, AppState> {
); );
if (snappedToCenterPosition) { if (snappedToCenterPosition) {
mutateElement(element, element => { mutateElement(element, {
element.x = snappedToCenterPosition.elementCenterX; x: snappedToCenterPosition.elementCenterX,
element.y = snappedToCenterPosition.elementCenterY; y: snappedToCenterPosition.elementCenterY,
}); });
textX = snappedToCenterPosition.wysiwygX; textX = snappedToCenterPosition.wysiwygX;
textY = snappedToCenterPosition.wysiwygY; textY = snappedToCenterPosition.wysiwygY;
@ -1369,17 +1369,18 @@ export class App extends React.Component<any, AppState> {
const dx = element.x + width + p1[0]; const dx = element.x + width + p1[0];
const dy = element.y + height + p1[1]; const dy = element.y + height + p1[1];
mutateElement(element, element => { mutateElement(element, {
element.x = dx; x: dx,
element.y = dy; y: dy,
}); });
p1[0] = absPx - element.x; p1[0] = absPx - element.x;
p1[1] = absPy - element.y; p1[1] = absPy - element.y;
} else { } else {
mutateElement(element, element => { mutateElement(element, {
element.x += deltaX; x: element.x + deltaX,
element.y += deltaY; y: element.y + deltaY,
}); });
p1[0] -= deltaX; p1[0] -= deltaX;
p1[1] -= deltaY; p1[1] -= deltaY;
} }
@ -1489,16 +1490,15 @@ export class App extends React.Component<any, AppState> {
event.shiftKey, event.shiftKey,
); );
} else { } else {
mutateElement(element, element => { mutateElement(element, {
element.width -= deltaX; x: element.x + deltaX,
element.x += deltaX; y: event.shiftKey
if (event.shiftKey) { ? element.y + element.height - element.width
element.y += element.height - element.width; : element.y + deltaY,
element.height = element.width; width: element.width - deltaX,
} else { height: event.shiftKey
element.height -= deltaY; ? element.width
element.y += deltaY; : element.height - deltaY,
}
}); });
} }
break; break;
@ -1522,15 +1522,13 @@ export class App extends React.Component<any, AppState> {
event.shiftKey, event.shiftKey,
); );
} else { } else {
mutateElement(element, element => { const nextWidth = element.width + deltaX;
element.width += deltaX; mutateElement(element, {
if (event.shiftKey) { y: event.shiftKey
element.y += element.height - element.width; ? element.y + element.height - nextWidth
element.height = element.width; : element.y + deltaY,
} else { width: nextWidth,
element.height -= deltaY; height: event.shiftKey ? nextWidth : element.height - deltaY,
element.y += deltaY;
}
}); });
} }
break; break;
@ -1554,14 +1552,12 @@ export class App extends React.Component<any, AppState> {
event.shiftKey, event.shiftKey,
); );
} else { } else {
mutateElement(element, element => { mutateElement(element, {
element.width -= deltaX; x: element.x + deltaX,
element.x += deltaX; width: element.width - deltaX,
if (event.shiftKey) { height: event.shiftKey
element.height = element.width; ? element.width
} else { : element.height + deltaY,
element.height += deltaY;
}
}); });
} }
break; break;
@ -1585,26 +1581,17 @@ export class App extends React.Component<any, AppState> {
event.shiftKey, event.shiftKey,
); );
} else { } else {
mutateElement(element, element => { mutateElement(element, {
if (event.shiftKey) { width: element.width + deltaX,
element.width += deltaX; height: event.shiftKey
element.height = element.width; ? element.width
} else { : element.height + deltaY,
element.width += deltaX;
element.height += deltaY;
}
}); });
} }
break; break;
case "n": { case "n": {
mutateElement(element, element => {
element.height -= deltaY;
element.y += deltaY;
});
if (element.points.length > 0) { if (element.points.length > 0) {
const len = element.points.length; const len = element.points.length;
const points = [...element.points].sort((a, b) => a[1] - b[1]); const points = [...element.points].sort((a, b) => a[1] - b[1]);
for (let i = 1; i < points.length; ++i) { for (let i = 1; i < points.length; ++i) {
@ -1612,14 +1599,14 @@ export class App extends React.Component<any, AppState> {
pnt[1] -= deltaY / (len - i); pnt[1] -= deltaY / (len - i);
} }
} }
mutateElement(element, {
height: element.height - deltaY,
y: element.y + deltaY,
});
break; break;
} }
case "w": { case "w": {
mutateElement(element, element => {
element.width -= deltaX;
element.x += deltaX;
});
if (element.points.length > 0) { if (element.points.length > 0) {
const len = element.points.length; const len = element.points.length;
const points = [...element.points].sort((a, b) => a[0] - b[0]); const points = [...element.points].sort((a, b) => a[0] - b[0]);
@ -1629,39 +1616,44 @@ export class App extends React.Component<any, AppState> {
pnt[0] -= deltaX / (len - i); pnt[0] -= deltaX / (len - i);
} }
} }
mutateElement(element, {
width: element.width - deltaX,
x: element.x + deltaX,
});
break; break;
} }
case "s": { case "s": {
mutateElement(element, element => {
element.height += deltaY;
if (element.points.length > 0) { if (element.points.length > 0) {
const len = element.points.length; const len = element.points.length;
const points = [...element.points].sort( const points = [...element.points].sort((a, b) => a[1] - b[1]);
(a, b) => a[1] - b[1],
);
for (let i = 1; i < points.length; ++i) { for (let i = 1; i < points.length; ++i) {
const pnt = points[i]; const pnt = points[i];
pnt[1] += deltaY / (len - i); pnt[1] += deltaY / (len - i);
} }
} }
mutateElement(element, {
height: element.height + deltaY,
points: element.points, // no-op, but signifies that we mutated points in-place above
}); });
break; break;
} }
case "e": { case "e": {
mutateElement(element, element => {
element.width += deltaX;
if (element.points.length > 0) { if (element.points.length > 0) {
const len = element.points.length; const len = element.points.length;
const points = [...element.points].sort( const points = [...element.points].sort((a, b) => a[0] - b[0]);
(a, b) => a[0] - b[0],
);
for (let i = 1; i < points.length; ++i) { for (let i = 1; i < points.length; ++i) {
const pnt = points[i]; const pnt = points[i];
pnt[0] += deltaX / (len - i); pnt[0] += deltaX / (len - i);
} }
} }
mutateElement(element, {
width: element.width + deltaX,
points: element.points, // no-op, but signifies that we mutated points in-place above
}); });
break; break;
} }
@ -1676,9 +1668,9 @@ export class App extends React.Component<any, AppState> {
element, element,
resizeHandle, resizeHandle,
}); });
mutateElement(el, el => { mutateElement(el, {
el.x = element.x; x: element.x,
el.y = element.y; y: element.y,
}); });
invalidateShapeForElement(el); invalidateShapeForElement(el);
@ -1702,9 +1694,9 @@ export class App extends React.Component<any, AppState> {
); );
selectedElements.forEach(element => { selectedElements.forEach(element => {
mutateElement(element, element => { mutateElement(element, {
element.x += x - lastX; x: element.x + x - lastX,
element.y += y - lastY; y: element.y + y - lastY,
}); });
}); });
lastX = x; lastX = x;
@ -1767,12 +1759,11 @@ export class App extends React.Component<any, AppState> {
} }
} }
mutateElement(draggingElement, draggingElement => { mutateElement(draggingElement, {
draggingElement.x = x < originX ? originX - width : originX; x: x < originX ? originX - width : originX,
draggingElement.y = y < originY ? originY - height : originY; y: y < originY ? originY - height : originY,
width: width,
draggingElement.width = width; height: height,
draggingElement.height = height;
}); });
} }

View File

@ -52,6 +52,7 @@ export function restore(
return { return {
...element, ...element,
version: element.id ? element.version + 1 : element.version || 0,
id: element.id || nanoid(), id: element.id || nanoid(),
fillStyle: element.fillStyle || "hachure", fillStyle: element.fillStyle || "hachure",
strokeWidth: element.strokeWidth || 1, strokeWidth: element.strokeWidth || 1,

View File

@ -1,26 +1,42 @@
import { import { ExcalidrawElement, ExcalidrawTextElement } from "./types";
MutableExcalidrawElement,
MutableExcalidrawTextElement, type ElementUpdate<TElement extends ExcalidrawElement> = Omit<
} from "./types"; Partial<TElement>,
"id" | "seed"
>;
// This function tracks updates of text elements for the purposes for collaboration. // This function tracks updates of text elements for the purposes for collaboration.
// The version is used to compare updates when more than one user is working in // The version is used to compare updates when more than one user is working in
// the same drawing. // the same drawing.
export function mutateElement( export function mutateElement(
element: MutableExcalidrawElement, element: ExcalidrawElement,
callback: (mutatableElement: MutableExcalidrawElement) => void, updates: ElementUpdate<ExcalidrawElement>,
): void { ) {
element.version++; Object.assign(element, updates);
callback(element); (element as any).version++;
}
export function newElementWith(
element: ExcalidrawElement,
updates: ElementUpdate<ExcalidrawElement>,
): ExcalidrawElement {
return { ...element, ...updates, version: element.version + 1 };
} }
// This function tracks updates of text elements for the purposes for collaboration. // This function tracks updates of text elements for the purposes for collaboration.
// The version is used to compare updates when more than one user is working in // The version is used to compare updates when more than one user is working in
// the same document. // the same document.
export function mutateTextElement( export function mutateTextElement(
element: MutableExcalidrawTextElement, element: ExcalidrawTextElement,
callback: (mutatableElement: MutableExcalidrawTextElement) => void, updates: ElementUpdate<ExcalidrawTextElement>,
): void { ): void {
element.version++; Object.assign(element, updates);
callback(element); (element as any).version++;
}
export function newTextElementWith(
element: ExcalidrawTextElement,
updates: ElementUpdate<ExcalidrawTextElement>,
): ExcalidrawTextElement {
return { ...element, ...updates, version: element.version + 1 };
} }

View File

@ -1,4 +1,4 @@
import { ExcalidrawElement, MutableExcalidrawElement } from "./types"; import { ExcalidrawElement } from "./types";
import { invalidateShapeForElement } from "../renderer/renderElement"; import { invalidateShapeForElement } from "../renderer/renderElement";
import { mutateElement } from "./mutateElement"; import { mutateElement } from "./mutateElement";
@ -36,7 +36,7 @@ export function getPerfectElementSize(
} }
export function resizePerfectLineForNWHandler( export function resizePerfectLineForNWHandler(
element: MutableExcalidrawElement, element: ExcalidrawElement,
x: number, x: number,
y: number, y: number,
) { ) {
@ -45,21 +45,28 @@ export function resizePerfectLineForNWHandler(
const distanceToAnchorX = x - anchorX; const distanceToAnchorX = x - anchorX;
const distanceToAnchorY = y - anchorY; const distanceToAnchorY = y - anchorY;
if (Math.abs(distanceToAnchorX) < Math.abs(distanceToAnchorY) / 2) { if (Math.abs(distanceToAnchorX) < Math.abs(distanceToAnchorY) / 2) {
element.x = anchorX; mutateElement(element, {
element.width = 0; x: anchorX,
element.y = y; width: 0,
element.height = -distanceToAnchorY; y,
height: -distanceToAnchorY,
});
} else if (Math.abs(distanceToAnchorY) < Math.abs(element.width) / 2) { } else if (Math.abs(distanceToAnchorY) < Math.abs(element.width) / 2) {
element.y = anchorY; mutateElement(element, {
element.height = 0; y: anchorY,
height: 0,
});
} else { } else {
element.x = x; const nextHeight =
element.width = -distanceToAnchorX;
element.height =
Math.sign(distanceToAnchorY) * Math.sign(distanceToAnchorY) *
Math.sign(distanceToAnchorX) * Math.sign(distanceToAnchorX) *
element.width; element.width;
element.y = anchorY - element.height; mutateElement(element, {
x,
y: anchorY - nextHeight,
width: -distanceToAnchorX,
height: nextHeight,
});
} }
} }
@ -79,16 +86,18 @@ export function normalizeDimensions(
} }
if (element.width < 0) { if (element.width < 0) {
mutateElement(element, element => { const nextWidth = Math.abs(element.width);
element.width = Math.abs(element.width); mutateElement(element, {
element.x -= element.width; width: nextWidth,
x: element.x - nextWidth,
}); });
} }
if (element.height < 0) { if (element.height < 0) {
mutateElement(element, element => { const nextHeight = Math.abs(element.height);
element.height = Math.abs(element.height); mutateElement(element, {
element.y -= element.height; height: nextHeight,
y: element.y - nextHeight,
}); });
} }

View File

@ -1,11 +1,12 @@
import { measureText } from "../utils"; import { measureText } from "../utils";
import { MutableExcalidrawTextElement } from "./types"; import { ExcalidrawTextElement } from "./types";
import { mutateTextElement } from "./mutateElement";
export const redrawTextBoundingBox = ( export const redrawTextBoundingBox = (element: ExcalidrawTextElement) => {
element: MutableExcalidrawTextElement,
) => {
const metrics = measureText(element.text, element.font); const metrics = measureText(element.text, element.font);
element.width = metrics.width; mutateTextElement(element, {
element.height = metrics.height; width: metrics.width,
element.baseline = metrics.baseline; height: metrics.height,
baseline: metrics.baseline,
});
}; };

View File

@ -6,17 +6,15 @@ import { newElement } from "./newElement";
* between peers and contain no state local to the peer. * between peers and contain no state local to the peer.
*/ */
export type ExcalidrawElement = Readonly<ReturnType<typeof newElement>>; export type ExcalidrawElement = Readonly<ReturnType<typeof newElement>>;
export type MutableExcalidrawElement = ReturnType<typeof newElement>;
export type MutableExcalidrawTextElement = MutableExcalidrawElement & { export type ExcalidrawTextElement = ExcalidrawElement &
Readonly<{
type: "text"; type: "text";
font: string; font: string;
text: string; text: string;
// for backward compatibility // for backward compatibility
actualBoundingBoxAscent?: number; actualBoundingBoxAscent?: number;
baseline: number; baseline: number;
}; }>;
export type ExcalidrawTextElement = Readonly<MutableExcalidrawTextElement>;
export type PointerType = "mouse" | "pen" | "touch"; export type PointerType = "mouse" | "pen" | "touch";

View File

@ -1,6 +1,7 @@
import { AppState } from "./types"; import { AppState } from "./types";
import { ExcalidrawElement } from "./element/types"; import { ExcalidrawElement } from "./element/types";
import { clearAppStatePropertiesForHistory } from "./appState"; import { clearAppStatePropertiesForHistory } from "./appState";
import { newElementWith } from "./element/mutateElement";
type Result = { type Result = {
appState: AppState; appState: AppState;
@ -18,13 +19,14 @@ export class SceneHistory {
) { ) {
return JSON.stringify({ return JSON.stringify({
appState: clearAppStatePropertiesForHistory(appState), appState: clearAppStatePropertiesForHistory(appState),
elements: elements.map(element => ({ elements: elements.map(element =>
...element, newElementWith(element, {
points: points:
appState.multiElement && appState.multiElement.id === element.id appState.multiElement && appState.multiElement.id === element.id
? element.points.slice(0, -1) ? element.points.slice(0, -1)
: element.points, : element.points,
})), }),
),
}); });
} }