diff --git a/src/actions/actionCanvas.tsx b/src/actions/actionCanvas.tsx index bc268544b..04e58b5db 100644 --- a/src/actions/actionCanvas.tsx +++ b/src/actions/actionCanvas.tsx @@ -422,7 +422,7 @@ export const actionToggleHandTool = register({ type: "hand", lastActiveToolBeforeEraser: appState.activeTool, }); - setCursor(app.canvas, CURSOR_TYPE.GRAB); + setCursor(app.interactiveCanvas, CURSOR_TYPE.GRAB); } return { diff --git a/src/actions/actionDuplicateSelection.tsx b/src/actions/actionDuplicateSelection.tsx index 81dea88f1..60ebb54e0 100644 --- a/src/actions/actionDuplicateSelection.tsx +++ b/src/actions/actionDuplicateSelection.tsx @@ -276,6 +276,6 @@ const duplicateElements = ( getNonDeletedElements(finalElements), appState, null, - ), + ) as AppState, }; }; diff --git a/src/actions/actionFinalize.tsx b/src/actions/actionFinalize.tsx index 99c8d8cf5..493147761 100644 --- a/src/actions/actionFinalize.tsx +++ b/src/actions/actionFinalize.tsx @@ -19,7 +19,12 @@ import { AppState } from "../types"; export const actionFinalize = register({ name: "finalize", trackEvent: false, - perform: (elements, appState, _, { canvas, focusContainer, scene }) => { + perform: ( + elements, + appState, + _, + { interactiveCanvas, focusContainer, scene }, + ) => { if (appState.editingLinearElement) { const { elementId, startBindingElement, endBindingElement } = appState.editingLinearElement; @@ -132,7 +137,7 @@ export const actionFinalize = register({ appState.activeTool.type !== "freedraw") || !multiPointElement ) { - resetCursor(canvas); + resetCursor(interactiveCanvas); } let activeTool: AppState["activeTool"]; diff --git a/src/actions/actionFrame.ts b/src/actions/actionFrame.ts index 7cf6ea4e4..339545f87 100644 --- a/src/actions/actionFrame.ts +++ b/src/actions/actionFrame.ts @@ -108,7 +108,7 @@ export const actionSetFrameAsActiveTool = register({ type: "frame", }); - setCursorForShape(app.canvas, { + setCursorForShape(app.interactiveCanvas, { ...appState, activeTool: nextActiveTool, }); diff --git a/src/actions/actionGroup.tsx b/src/actions/actionGroup.tsx index 82293d7d1..c4b5aeb55 100644 --- a/src/actions/actionGroup.tsx +++ b/src/actions/actionGroup.tsx @@ -153,7 +153,7 @@ export const actionGroup = register({ newGroupId, { ...appState, selectedGroupIds: {} }, getNonDeletedElements(nextElements), - ), + ) as AppState, elements: nextElements, commitToHistory: true, }; @@ -216,7 +216,7 @@ export const actionUngroup = register({ getNonDeletedElements(nextElements), appState, null, - ); + ) as AppState; frames.forEach((frame) => { if (frame) { diff --git a/src/actions/actionSelectAll.ts b/src/actions/actionSelectAll.ts index eeadf2dc9..33e506fda 100644 --- a/src/actions/actionSelectAll.ts +++ b/src/actions/actionSelectAll.ts @@ -6,6 +6,7 @@ import { ExcalidrawElement } from "../element/types"; import { isLinearElement } from "../element/typeChecks"; import { LinearElementEditor } from "../element/linearElementEditor"; import { excludeElementsInFramesFromSelection } from "../scene/selection"; +import { AppState } from "../types"; export const actionSelectAll = register({ name: "selectAll", @@ -43,7 +44,7 @@ export const actionSelectAll = register({ getNonDeletedElements(elements), appState, app, - ), + ) as AppState, commitToHistory: true, }; }, diff --git a/src/components/Actions.tsx b/src/components/Actions.tsx index 594951ae1..e47e317bf 100644 --- a/src/components/Actions.tsx +++ b/src/components/Actions.tsx @@ -213,13 +213,13 @@ export const SelectedShapeActions = ({ }; export const ShapesSwitcher = ({ - canvas, + interactiveCanvas, activeTool, setAppState, onImageAction, appState, }: { - canvas: HTMLCanvasElement | null; + interactiveCanvas: HTMLCanvasElement | null; activeTool: UIAppState["activeTool"]; setAppState: React.Component["setState"]; onImageAction: (data: { pointerType: PointerType | null }) => void; @@ -269,7 +269,7 @@ export const ShapesSwitcher = ({ multiElement: null, selectedElementIds: {}, }); - setCursorForShape(canvas, { + setCursorForShape(interactiveCanvas, { ...appState, activeTool: nextActiveTool, }); diff --git a/src/components/App.test.tsx b/src/components/App.test.tsx index baf25ab9b..ff666c75b 100644 --- a/src/components/App.test.tsx +++ b/src/components/App.test.tsx @@ -5,14 +5,14 @@ import { render, queryByTestId } from "../tests/test-utils"; import ExcalidrawApp from "../excalidraw-app"; -const renderScene = jest.spyOn(Renderer, "renderScene"); +const renderStaticScene = jest.spyOn(Renderer, "renderStaticScene"); describe("Test ", () => { beforeEach(async () => { // Unmount ReactDOM from root ReactDOM.unmountComponentAtNode(document.getElementById("root")!); localStorage.clear(); - renderScene.mockClear(); + renderStaticScene.mockClear(); reseed(7); }); diff --git a/src/components/App.tsx b/src/components/App.tsx index 7f2c98e50..977783c00 100644 --- a/src/components/App.tsx +++ b/src/components/App.tsx @@ -173,6 +173,7 @@ import { getSelectedGroupIds, isElementInGroup, isSelectedViaGroup, + selectGroups, selectGroupsForSelectedElements, } from "../groups"; import History from "../history"; @@ -186,7 +187,7 @@ import { KEYS, } from "../keys"; import { distance2d, getGridPoint, isPathALoop } from "../math"; -import { isVisibleElement, renderScene } from "../renderer/renderScene"; +import { isVisibleElement } from "../renderer/renderScene"; import { invalidateShapeForElement } from "../renderer/renderElement"; import { calculateScrollCenter, @@ -199,7 +200,7 @@ import { isSomeElementSelected, } from "../scene"; import Scene from "../scene/Scene"; -import { RenderConfig, ScrollBars } from "../scene/types"; +import { RenderInteractiveSceneCallback, ScrollBars } from "../scene/types"; import { getStateForZoom } from "../scene/zoom"; import { findShapeByKey, SHAPES } from "../shapes"; import { @@ -331,6 +332,11 @@ import { actionWrapTextInContainer } from "../actions/actionBoundText"; import BraveMeasureTextError from "./BraveMeasureTextError"; import { activeEyeDropperAtom } from "./EyeDropper"; import { isSidebarDockedAtom } from "./Sidebar/Sidebar"; +import { StaticCanvas, InteractiveCanvas, CanvasesWrapper } from "./canvases"; + +// remove this hack when we can sync render & resizeObserver (state update) +// to rAF. See #5439 +window.EXCALIDRAW_THROTTLE_NEXT_RENDER = true; const AppContext = React.createContext(null!); const AppPropsContext = React.createContext(null!); @@ -400,10 +406,6 @@ let currentScrollBars: ScrollBars = { horizontal: null, vertical: null }; let touchTimeout = 0; let invalidateContextMenu = false; -// remove this hack when we can sync render & resizeObserver (state update) -// to rAF. See #5439 -let THROTTLE_NEXT_RENDER = true; - let IS_PLAIN_PASTE = false; let IS_PLAIN_PASTE_TIMER = 0; let PLAIN_PASTE_TOAST_SHOWN = false; @@ -418,6 +420,7 @@ const gesture: Gesture = { class App extends React.Component { canvas: AppClassProperties["canvas"] = null; + interactiveCanvas: AppClassProperties["canvas"] = null; rc: RoughCanvas | null = null; unmounted: boolean = false; actionManager: ActionManager; @@ -538,65 +541,6 @@ class App extends React.Component { this.actionManager.registerAction(createRedoAction(this.history)); } - private renderCanvas() { - const canvasScale = window.devicePixelRatio; - const { - width: canvasDOMWidth, - height: canvasDOMHeight, - viewModeEnabled, - } = this.state; - const canvasWidth = canvasDOMWidth * canvasScale; - const canvasHeight = canvasDOMHeight * canvasScale; - if (viewModeEnabled) { - return ( - ) => - this.handleCanvasContextMenu(event) - } - onPointerMove={this.handleCanvasPointerMove} - onPointerUp={this.handleCanvasPointerUp} - onPointerCancel={this.removePointer} - onTouchMove={this.handleTouchMove} - onPointerDown={this.handleCanvasPointerDown} - > - {t("labels.drawingCanvas")} - - ); - } - return ( - ) => - this.handleCanvasContextMenu(event) - } - onPointerDown={this.handleCanvasPointerDown} - onDoubleClick={this.handleCanvasDoubleClick} - onPointerMove={this.handleCanvasPointerMove} - onPointerUp={this.handleCanvasPointerUp} - onPointerCancel={this.removePointer} - onTouchMove={this.handleTouchMove} - > - {t("labels.drawingCanvas")} - - ); - } - private getFrameNameDOMId = (frameElement: ExcalidrawElement) => { return `${this.id}-frame-name-${frameElement.id}`; }; @@ -782,9 +726,11 @@ class App extends React.Component { }} onPointerDown={(event) => this.handleCanvasPointerDown(event)} onWheel={(event) => this.handleWheel(event)} - onContextMenu={(event: React.PointerEvent) => { - this.handleCanvasContextMenu(event); - }} + onContextMenu={ + this.handleCanvasContextMenu as ( + event: React.PointerEvent, + ) => void + } onDoubleClick={() => { this.setState({ editingFrame: f.id, @@ -830,6 +776,7 @@ class App extends React.Component { > { actionManager={this.actionManager} /> )} -
{this.renderCanvas()}
+ + {(elements, versionNonce) => ( + <> + + , + ) => void + } + onPointerMove={this.handleCanvasPointerMove} + onPointerUp={this.handleCanvasPointerUp} + onPointerCancel={this.removePointer} + onTouchMove={this.handleTouchMove} + onPointerDown={this.handleCanvasPointerDown} + onDoubleClick={ + this.state.viewModeEnabled + ? undefined + : this.handleCanvasDoubleClick + } + /> + + )} + {this.renderFrameNames()} @@ -1191,17 +1192,13 @@ class App extends React.Component { if (initialData?.scrollToContent) { scene.appState = { ...scene.appState, - ...calculateScrollCenter( - scene.elements, - { - ...scene.appState, - width: this.state.width, - height: this.state.height, - offsetTop: this.state.offsetTop, - offsetLeft: this.state.offsetLeft, - }, - null, - ), + ...calculateScrollCenter(scene.elements, { + ...scene.appState, + width: this.state.width, + height: this.state.height, + offsetTop: this.state.offsetTop, + offsetLeft: this.state.offsetLeft, + }), }; } // FontFaceSet loadingdone event we listen on may not always fire @@ -1286,7 +1283,7 @@ class App extends React.Component { if ("ResizeObserver" in window && this.excalidrawContainerRef?.current) { this.resizeObserver = new ResizeObserver(() => { - THROTTLE_NEXT_RENDER = false; + window.EXCALIDRAW_THROTTLE_NEXT_RENDER = false; // recompute device dimensions state // --------------------------------------------------------------------- this.refreshDeviceState(this.excalidrawContainerRef.current!); @@ -1351,6 +1348,7 @@ class App extends React.Component { this.library.destroy(); clearTimeout(touchTimeout); isSomeElementSelected.clearCache(); + selectGroups.clearCache(); touchTimeout = 0; } @@ -1519,7 +1517,7 @@ class App extends React.Component { this.state.activeTool.type === "eraser" && prevState.theme !== this.state.theme ) { - setEraserCursor(this.canvas, this.state.theme); + setEraserCursor(this.interactiveCanvas, this.state.theme); } // Hide hyperlink popup if shown when element type is not selection if ( @@ -1615,7 +1613,6 @@ class App extends React.Component { ), ); } - this.renderScene(); this.history.record(this.state, this.scene.getElementsIncludingDeleted()); // Do not notify consumers if we're still loading the scene. Among other @@ -1631,114 +1628,24 @@ class App extends React.Component { } } - private renderScene = () => { - const cursorButton: { - [id: string]: string | undefined; - } = {}; - const pointerViewportCoords: RenderConfig["remotePointerViewportCoords"] = - {}; - const remoteSelectedElementIds: RenderConfig["remoteSelectedElementIds"] = - {}; - const pointerUsernames: { [id: string]: string } = {}; - const pointerUserStates: { [id: string]: string } = {}; - this.state.collaborators.forEach((user, socketId) => { - if (user.selectedElementIds) { - for (const id of Object.keys(user.selectedElementIds)) { - if (!(id in remoteSelectedElementIds)) { - remoteSelectedElementIds[id] = []; - } - remoteSelectedElementIds[id].push(socketId); - } - } - if (!user.pointer) { - return; - } - if (user.username) { - pointerUsernames[socketId] = user.username; - } - if (user.userState) { - pointerUserStates[socketId] = user.userState; - } - pointerViewportCoords[socketId] = sceneCoordsToViewportCoords( - { - sceneX: user.pointer.x, - sceneY: user.pointer.y, - }, - this.state, - ); - cursorButton[socketId] = user.button; - }); - - const renderingElements = this.scene - .getNonDeletedElements() - .filter((element) => { - if (isImageElement(element)) { - if ( - // not placed on canvas yet (but in elements array) - this.state.pendingImageElementId === element.id - ) { - return false; - } - } - // don't render text element that's being currently edited (it's - // rendered on remote only) - return ( - !this.state.editingElement || - this.state.editingElement.type !== "text" || - element.id !== this.state.editingElement.id - ); - }); - - const selectionColor = getComputedStyle( - document.querySelector(".excalidraw")!, - ).getPropertyValue("--color-selection"); - - renderScene( - { - elements: renderingElements, - appState: this.state, - scale: window.devicePixelRatio, - rc: this.rc!, - canvas: this.canvas!, - renderConfig: { - selectionColor, - scrollX: this.state.scrollX, - scrollY: this.state.scrollY, - viewBackgroundColor: this.state.viewBackgroundColor, - zoom: this.state.zoom, - remotePointerViewportCoords: pointerViewportCoords, - remotePointerButton: cursorButton, - remoteSelectedElementIds, - remotePointerUsernames: pointerUsernames, - remotePointerUserStates: pointerUserStates, - shouldCacheIgnoreZoom: this.state.shouldCacheIgnoreZoom, - theme: this.state.theme, - imageCache: this.imageCache, - isExporting: false, - renderScrollbars: false, - }, - callback: ({ atLeastOneVisibleElement, scrollBars }) => { - if (scrollBars) { - currentScrollBars = scrollBars; - } - const scrolledOutside = - // hide when editing text - isTextElement(this.state.editingElement) - ? false - : !atLeastOneVisibleElement && renderingElements.length > 0; - if (this.state.scrolledOutside !== scrolledOutside) { - this.setState({ scrolledOutside }); - } - - this.scheduleImageRefresh(); - }, - }, - THROTTLE_NEXT_RENDER && window.EXCALIDRAW_THROTTLE_RENDER === true, - ); - - if (!THROTTLE_NEXT_RENDER) { - THROTTLE_NEXT_RENDER = true; + private renderInteractiveSceneCallback = ({ + atLeastOneVisibleElement, + scrollBars, + elements, + }: RenderInteractiveSceneCallback) => { + if (scrollBars) { + currentScrollBars = scrollBars; } + const scrolledOutside = + // hide when editing text + isTextElement(this.state.editingElement) + ? false + : !atLeastOneVisibleElement && elements.length > 0; + if (this.state.scrolledOutside !== scrolledOutside) { + this.setState({ scrolledOutside }); + } + + this.scheduleImageRefresh(); }; private onScroll = debounce(() => { @@ -2301,11 +2208,7 @@ class App extends React.Component { scrollY = appState.scrollY; } else { // compute only the viewport location, without any zoom adjustment - const scroll = calculateScrollCenter( - targetElements, - this.state, - this.canvas, - ); + const scroll = calculateScrollCenter(targetElements, this.state); scrollX = scroll.scrollX; scrollY = scroll.scrollY; } @@ -2694,7 +2597,7 @@ class App extends React.Component { } if (event.key === KEYS.SPACE && gesture.pointers.size === 0) { isHoldingSpace = true; - setCursor(this.canvas, CURSOR_TYPE.GRAB); + setCursor(this.interactiveCanvas, CURSOR_TYPE.GRAB); event.preventDefault(); } @@ -2758,11 +2661,11 @@ class App extends React.Component { private onKeyUp = withBatchedUpdates((event: KeyboardEvent) => { if (event.key === KEYS.SPACE) { if (this.state.viewModeEnabled) { - setCursor(this.canvas, CURSOR_TYPE.GRAB); + setCursor(this.interactiveCanvas, CURSOR_TYPE.GRAB); } else if (this.state.activeTool.type === "selection") { - resetCursor(this.canvas); + resetCursor(this.interactiveCanvas); } else { - setCursorForShape(this.canvas, this.state); + setCursorForShape(this.interactiveCanvas, this.state); this.setState({ selectedElementIds: makeNextSelectedElementIds({}, this.state), selectedGroupIds: {}, @@ -2790,9 +2693,9 @@ class App extends React.Component { ) => { const nextActiveTool = updateActiveTool(this.state, tool); if (nextActiveTool.type === "hand") { - setCursor(this.canvas, CURSOR_TYPE.GRAB); + setCursor(this.interactiveCanvas, CURSOR_TYPE.GRAB); } else if (!isHoldingSpace) { - setCursorForShape(this.canvas, this.state); + setCursorForShape(this.interactiveCanvas, this.state); } if (isToolIcon(document.activeElement)) { this.focusContainer(); @@ -2816,11 +2719,11 @@ class App extends React.Component { }; private setCursor = (cursor: string) => { - setCursor(this.canvas, cursor); + setCursor(this.interactiveCanvas, cursor); }; private resetCursor = () => { - resetCursor(this.canvas); + resetCursor(this.interactiveCanvas); }; /** * returns whether user is making a gesture with >= 2 fingers (points) @@ -2979,7 +2882,7 @@ class App extends React.Component { editingElement: null, }); if (this.state.activeTool.locked) { - setCursorForShape(this.canvas, this.state); + setCursorForShape(this.interactiveCanvas, this.state); } this.focusContainer(); @@ -3279,7 +3182,7 @@ class App extends React.Component { } } - resetCursor(this.canvas); + resetCursor(this.interactiveCanvas); let { x: sceneX, y: sceneY } = viewportCoordsToSceneCoords( event, @@ -3313,7 +3216,7 @@ class App extends React.Component { } } - resetCursor(this.canvas); + resetCursor(this.interactiveCanvas); if (!event[KEYS.CTRL_OR_CMD] && !this.state.viewModeEnabled) { const container = getTextBindableContainerAtPosition( this.scene.getNonDeletedElements(), @@ -3528,9 +3431,9 @@ class App extends React.Component { const isOverScrollBar = isPointerOverScrollBars.isOverEither; if (!this.state.draggingElement && !this.state.multiElement) { if (isOverScrollBar) { - resetCursor(this.canvas); + resetCursor(this.interactiveCanvas); } else { - setCursorForShape(this.canvas, this.state); + setCursorForShape(this.interactiveCanvas, this.state); } } @@ -3552,13 +3455,8 @@ class App extends React.Component { editingLinearElement && editingLinearElement !== this.state.editingLinearElement ) { - // Since we are reading from previous state which is not possible with - // automatic batching in React 18 hence using flush sync to synchronously - // update the state. Check https://github.com/excalidraw/excalidraw/pull/5508 for more details. - flushSync(() => { - this.setState({ - editingLinearElement, - }); + this.setState({ + editingLinearElement, }); } if (editingLinearElement?.lastUncommittedPoint != null) { @@ -3593,7 +3491,7 @@ class App extends React.Component { const { points, lastCommittedPoint } = multiElement; const lastPoint = points[points.length - 1]; - setCursorForShape(this.canvas, this.state); + setCursorForShape(this.interactiveCanvas, this.state); if (lastPoint === lastCommittedPoint) { // if we haven't yet created a temp point and we're beyond commit-zone @@ -3610,7 +3508,7 @@ class App extends React.Component { points: [...points, [scenePointerX - rx, scenePointerY - ry]], }); } else { - setCursor(this.canvas, CURSOR_TYPE.POINTER); + setCursor(this.interactiveCanvas, CURSOR_TYPE.POINTER); // in this branch, we're inside the commit zone, and no uncommitted // point exists. Thus do nothing (don't add/remove points). } @@ -3624,7 +3522,7 @@ class App extends React.Component { lastCommittedPoint[1], ) < LINE_CONFIRM_THRESHOLD ) { - setCursor(this.canvas, CURSOR_TYPE.POINTER); + setCursor(this.interactiveCanvas, CURSOR_TYPE.POINTER); mutateElement(multiElement, { points: points.slice(0, -1), }); @@ -3654,7 +3552,7 @@ class App extends React.Component { } if (isPathALoop(points, this.state.zoom.value)) { - setCursor(this.canvas, CURSOR_TYPE.POINTER); + setCursor(this.interactiveCanvas, CURSOR_TYPE.POINTER); } // update last uncommitted point mutateElement(multiElement, { @@ -3702,7 +3600,7 @@ class App extends React.Component { elementWithTransformHandleType.transformHandleType ) { setCursor( - this.canvas, + this.interactiveCanvas, getCursorForResizingElement(elementWithTransformHandleType), ); return; @@ -3717,7 +3615,7 @@ class App extends React.Component { ); if (transformHandleType) { setCursor( - this.canvas, + this.interactiveCanvas, getCursorForResizingElement({ transformHandleType, }), @@ -3741,7 +3639,7 @@ class App extends React.Component { this.hitLinkElement && !this.state.selectedElementIds[this.hitLinkElement.id] ) { - setCursor(this.canvas, CURSOR_TYPE.POINTER); + setCursor(this.interactiveCanvas, CURSOR_TYPE.POINTER); showHyperlinkTooltip(this.hitLinkElement, this.state); } else { hideHyperlinkToolip(); @@ -3755,13 +3653,13 @@ class App extends React.Component { this.setState({ showHyperlinkPopup: "info" }); } else if (this.state.activeTool.type === "text") { setCursor( - this.canvas, + this.interactiveCanvas, isTextElement(hitElement) ? CURSOR_TYPE.TEXT : CURSOR_TYPE.CROSSHAIR, ); } else if (this.state.viewModeEnabled) { - setCursor(this.canvas, CURSOR_TYPE.GRAB); + setCursor(this.interactiveCanvas, CURSOR_TYPE.GRAB); } else if (isOverScrollBar) { - setCursor(this.canvas, CURSOR_TYPE.AUTO); + setCursor(this.interactiveCanvas, CURSOR_TYPE.AUTO); } else if (this.state.selectedLinearElement) { this.handleHoverSelectedLinearElement( this.state.selectedLinearElement, @@ -3780,10 +3678,10 @@ class App extends React.Component { )) && !hitElement?.locked ) { - setCursor(this.canvas, CURSOR_TYPE.MOVE); + setCursor(this.interactiveCanvas, CURSOR_TYPE.MOVE); } } else { - setCursor(this.canvas, CURSOR_TYPE.AUTO); + setCursor(this.interactiveCanvas, CURSOR_TYPE.AUTO); } } }; @@ -3921,9 +3819,9 @@ class App extends React.Component { ); if (hoverPointIndex >= 0 || segmentMidPointHoveredCoords) { - setCursor(this.canvas, CURSOR_TYPE.POINTER); + setCursor(this.interactiveCanvas, CURSOR_TYPE.POINTER); } else { - setCursor(this.canvas, CURSOR_TYPE.MOVE); + setCursor(this.interactiveCanvas, CURSOR_TYPE.MOVE); } } else if ( shouldShowBoundingBox([element], this.state) && @@ -3935,7 +3833,7 @@ class App extends React.Component { scenePointerY, ) ) { - setCursor(this.canvas, CURSOR_TYPE.MOVE); + setCursor(this.interactiveCanvas, CURSOR_TYPE.MOVE); } else if ( boundTextElement && hitTest( @@ -3946,7 +3844,7 @@ class App extends React.Component { scenePointerY, ) ) { - setCursor(this.canvas, CURSOR_TYPE.MOVE); + setCursor(this.interactiveCanvas, CURSOR_TYPE.MOVE); } if ( @@ -3974,7 +3872,7 @@ class App extends React.Component { }); } } else { - setCursor(this.canvas, CURSOR_TYPE.AUTO); + setCursor(this.interactiveCanvas, CURSOR_TYPE.AUTO); } } private handleCanvasPointerDown = ( @@ -4128,7 +4026,7 @@ class App extends React.Component { ); } else if (this.state.activeTool.type === "image") { // reset image preview on pointerdown - setCursor(this.canvas, CURSOR_TYPE.CROSSHAIR); + setCursor(this.interactiveCanvas, CURSOR_TYPE.CROSSHAIR); // retrieve the latest element as the state may be stale const pendingImageElement = @@ -4158,7 +4056,7 @@ class App extends React.Component { pointerDownState, ); } else if (this.state.activeTool.type === "custom") { - setCursor(this.canvas, CURSOR_TYPE.AUTO); + setCursor(this.interactiveCanvas, CURSOR_TYPE.AUTO); } else if (this.state.activeTool.type === "frame") { this.createFrameElementOnPointerDown(pointerDownState); } else if ( @@ -4289,7 +4187,7 @@ class App extends React.Component { let nextPastePrevented = false; const isLinux = /Linux/.test(window.navigator.platform); - setCursor(this.canvas, CURSOR_TYPE.GRABBING); + setCursor(this.interactiveCanvas, CURSOR_TYPE.GRABBING); let { clientX: lastX, clientY: lastY } = event; const onPointerMove = withBatchedUpdatesThrottled((event: PointerEvent) => { const deltaX = lastX - event.clientX; @@ -4342,9 +4240,9 @@ class App extends React.Component { isPanning = false; if (!isHoldingSpace) { if (this.state.viewModeEnabled) { - setCursor(this.canvas, CURSOR_TYPE.GRAB); + setCursor(this.interactiveCanvas, CURSOR_TYPE.GRAB); } else { - setCursorForShape(this.canvas, this.state); + setCursorForShape(this.interactiveCanvas, this.state); } } this.setState({ @@ -4467,7 +4365,7 @@ class App extends React.Component { const onPointerUp = withBatchedUpdates(() => { isDraggingScrollBar = false; - setCursorForShape(this.canvas, this.state); + setCursorForShape(this.interactiveCanvas, this.state); lastPointerUp = null; this.setState({ cursorButton: "up", @@ -4822,7 +4720,7 @@ class App extends React.Component { container, }); - resetCursor(this.canvas); + resetCursor(this.interactiveCanvas); if (!this.state.activeTool.locked) { this.setState({ activeTool: updateActiveTool(this.state, { type: "selection" }), @@ -4984,7 +4882,7 @@ class App extends React.Component { mutateElement(multiElement, { lastCommittedPoint: multiElement.points[multiElement.points.length - 1], }); - setCursor(this.canvas, CURSOR_TYPE.POINTER); + setCursor(this.interactiveCanvas, CURSOR_TYPE.POINTER); } else { const [gridX, gridY] = getGridPoint( pointerDownState.origin.x, @@ -5876,7 +5774,7 @@ class App extends React.Component { } this.setState({ suggestedBindings: [], startBoundElement: null }); if (!activeTool.locked) { - resetCursor(this.canvas); + resetCursor(this.interactiveCanvas); this.setState((prevState) => ({ draggingElement: null, activeTool: updateActiveTool(this.state, { @@ -6371,7 +6269,7 @@ class App extends React.Component { } if (!activeTool.locked && activeTool.type !== "freedraw") { - resetCursor(this.canvas); + resetCursor(this.interactiveCanvas); this.setState({ draggingElement: null, suggestedBindings: [], @@ -6463,7 +6361,7 @@ class App extends React.Component { } const mimeType = imageFile.type; - setCursor(this.canvas, "wait"); + setCursor(this.interactiveCanvas, "wait"); if (mimeType === MIME_TYPES.svg) { try { @@ -6563,7 +6461,7 @@ class App extends React.Component { reject(new Error(t("errors.imageInsertError"))); } finally { if (!showCursorImagePreview) { - resetCursor(this.canvas); + resetCursor(this.interactiveCanvas); } } }, @@ -6632,7 +6530,7 @@ class App extends React.Component { } if (this.state.pendingImageElementId) { - setCursor(this.canvas, `url(${previewDataURL}) 4 4, auto`); + setCursor(this.interactiveCanvas, `url(${previewDataURL}) 4 4, auto`); } }; @@ -6912,21 +6810,40 @@ class App extends React.Component { }); } + private handleInteractiveCanvasRef = (canvas: HTMLCanvasElement) => { + // canvas is null when unmounting + if (canvas !== null) { + this.interactiveCanvas = canvas; + + this.interactiveCanvas.addEventListener(EVENT.WHEEL, this.handleWheel, { + passive: false, + }); + this.interactiveCanvas.addEventListener( + EVENT.TOUCH_START, + this.onTapStart, + ); + this.interactiveCanvas.addEventListener(EVENT.TOUCH_END, this.onTapEnd); + } else { + this.interactiveCanvas?.removeEventListener( + EVENT.WHEEL, + this.handleWheel, + ); + this.interactiveCanvas?.removeEventListener( + EVENT.TOUCH_START, + this.onTapStart, + ); + this.interactiveCanvas?.removeEventListener( + EVENT.TOUCH_END, + this.onTapEnd, + ); + } + }; + private handleCanvasRef = (canvas: HTMLCanvasElement) => { // canvas is null when unmounting if (canvas !== null) { this.canvas = canvas; this.rc = rough.canvas(this.canvas); - - this.canvas.addEventListener(EVENT.WHEEL, this.handleWheel, { - passive: false, - }); - this.canvas.addEventListener(EVENT.TOUCH_START, this.onTapStart); - this.canvas.addEventListener(EVENT.TOUCH_END, this.onTapEnd); - } else { - this.canvas?.removeEventListener(EVENT.WHEEL, this.handleWheel); - this.canvas?.removeEventListener(EVENT.TOUCH_START, this.onTapStart); - this.canvas?.removeEventListener(EVENT.TOUCH_END, this.onTapEnd); } }; @@ -7108,7 +7025,7 @@ class App extends React.Component { ) : this.state), showHyperlinkPopup: false, - }, + } as AppState, () => { this.setState({ contextMenu: { top, left, items: this.getContextMenuItems(type) }, diff --git a/src/components/LayerUI.tsx b/src/components/LayerUI.tsx index 100259a81..4195f0810 100644 --- a/src/components/LayerUI.tsx +++ b/src/components/LayerUI.tsx @@ -58,6 +58,7 @@ interface LayerUIProps { appState: UIAppState; files: BinaryFiles; canvas: HTMLCanvasElement | null; + interactiveCanvas: HTMLCanvasElement | null; setAppState: React.Component["setState"]; elements: readonly NonDeletedExcalidrawElement[]; onLockToggle: () => void; @@ -117,6 +118,7 @@ const LayerUI = ({ setAppState, elements, canvas, + interactiveCanvas, onLockToggle, onHandToolToggle, onPenModeToggle, @@ -272,7 +274,7 @@ const LayerUI = ({ { @@ -413,7 +415,7 @@ const LayerUI = ({ onLockToggle={onLockToggle} onHandToolToggle={onHandToolToggle} onPenModeToggle={onPenModeToggle} - canvas={canvas} + interactiveCanvas={interactiveCanvas} onImageAction={onImageAction} renderTopRightUI={renderTopRightUI} renderCustomStats={renderCustomStats} @@ -464,7 +466,7 @@ const LayerUI = ({ className="scroll-back-to-content" onClick={() => { setAppState((appState) => ({ - ...calculateScrollCenter(elements, appState, canvas), + ...calculateScrollCenter(elements, appState), })); }} > @@ -507,8 +509,18 @@ const areEqual = (prevProps: LayerUIProps, nextProps: LayerUIProps) => { return false; } - const { canvas: _prevCanvas, appState: prevAppState, ...prev } = prevProps; - const { canvas: _nextCanvas, appState: nextAppState, ...next } = nextProps; + const { + canvas: _pC, + interactiveCanvas: _pIC, + appState: prevAppState, + ...prev + } = prevProps; + const { + canvas: _nC, + interactiveCanvas: _nIC, + appState: nextAppState, + ...next + } = nextProps; return ( isShallowEqual( diff --git a/src/components/MobileMenu.tsx b/src/components/MobileMenu.tsx index 30f431cbf..f08b99969 100644 --- a/src/components/MobileMenu.tsx +++ b/src/components/MobileMenu.tsx @@ -36,7 +36,7 @@ type MobileMenuProps = { onLockToggle: () => void; onHandToolToggle: () => void; onPenModeToggle: () => void; - canvas: HTMLCanvasElement | null; + interactiveCanvas: HTMLCanvasElement | null; onImageAction: (data: { insertOnCanvasDirectly: boolean }) => void; renderTopRightUI?: ( @@ -58,7 +58,7 @@ export const MobileMenu = ({ onLockToggle, onHandToolToggle, onPenModeToggle, - canvas, + interactiveCanvas, onImageAction, renderTopRightUI, renderCustomStats, @@ -85,7 +85,7 @@ export const MobileMenu = ({ { @@ -202,7 +202,7 @@ export const MobileMenu = ({ className="scroll-back-to-content" onClick={() => { setAppState((appState) => ({ - ...calculateScrollCenter(elements, appState, canvas), + ...calculateScrollCenter(elements, appState), })); }} > diff --git a/src/components/canvases/CanvasesWrapper.tsx b/src/components/canvases/CanvasesWrapper.tsx new file mode 100644 index 000000000..215873d98 --- /dev/null +++ b/src/components/canvases/CanvasesWrapper.tsx @@ -0,0 +1,25 @@ +import { ReactNode } from "react"; +import { useMutatedElements } from "../../hooks/useMutatedElements"; +import { AppState } from "../../types"; +import { NonDeletedExcalidrawElement } from "../../element/types"; +import Scene from "../../scene/Scene"; + +type CanvasesWrapperProps = { + appState: AppState; + scene: Scene; + children: ( + elements: readonly NonDeletedExcalidrawElement[], + versionNonce: number | undefined, + ) => ReactNode; +}; + +const CanvasesWrapper = (props: CanvasesWrapperProps) => { + const [elements, versionNonce] = useMutatedElements({ + appState: props.appState, + scene: props.scene, + }); + + return
{props.children(elements, versionNonce)}
; +}; + +export default CanvasesWrapper; diff --git a/src/components/canvases/InteractiveCanvas.tsx b/src/components/canvases/InteractiveCanvas.tsx new file mode 100644 index 000000000..774e56f25 --- /dev/null +++ b/src/components/canvases/InteractiveCanvas.tsx @@ -0,0 +1,187 @@ +import React, { useEffect, useRef } from "react"; +import { renderInteractiveScene } from "../../renderer/renderScene"; +import { isShallowEqual, sceneCoordsToViewportCoords } from "../../utils"; +import { CURSOR_TYPE } from "../../constants"; +import { t } from "../../i18n"; +import type { DOMAttributes } from "react"; +import type { AppState, InteractiveCanvasAppState } from "../../types"; +import type { + InteractiveCanvasRenderConfig, + RenderInteractiveSceneCallback, +} from "../../scene/types"; +import type { NonDeletedExcalidrawElement } from "../../element/types"; + +type InteractiveCanvasProps = { + canvas: HTMLCanvasElement | null; + elements: readonly NonDeletedExcalidrawElement[]; + versionNonce: number | undefined; + selectionNonce: number | undefined; + appState: InteractiveCanvasAppState; + renderInteractiveSceneCallback: ( + data: RenderInteractiveSceneCallback, + ) => void; + handleCanvasRef: (canvas: HTMLCanvasElement) => void; + onContextMenu: DOMAttributes["onContextMenu"]; + onPointerMove: DOMAttributes["onPointerMove"]; + onPointerUp: DOMAttributes["onPointerUp"]; + onPointerCancel: DOMAttributes["onPointerCancel"]; + onTouchMove: DOMAttributes["onTouchMove"]; + onPointerDown: DOMAttributes["onPointerDown"]; + onDoubleClick: DOMAttributes["onDoubleClick"]; +}; + +const InteractiveCanvas = (props: InteractiveCanvasProps) => { + const isComponentMounted = useRef(false); + + useEffect(() => { + if (!isComponentMounted.current) { + isComponentMounted.current = true; + return; + } + + const cursorButton: { + [id: string]: string | undefined; + } = {}; + const pointerViewportCoords: InteractiveCanvasRenderConfig["remotePointerViewportCoords"] = + {}; + const remoteSelectedElementIds: InteractiveCanvasRenderConfig["remoteSelectedElementIds"] = + {}; + const pointerUsernames: { [id: string]: string } = {}; + const pointerUserStates: { [id: string]: string } = {}; + + props.appState.collaborators.forEach((user, socketId) => { + if (user.selectedElementIds) { + for (const id of Object.keys(user.selectedElementIds)) { + if (!(id in remoteSelectedElementIds)) { + remoteSelectedElementIds[id] = []; + } + remoteSelectedElementIds[id].push(socketId); + } + } + if (!user.pointer) { + return; + } + if (user.username) { + pointerUsernames[socketId] = user.username; + } + if (user.userState) { + pointerUserStates[socketId] = user.userState; + } + pointerViewportCoords[socketId] = sceneCoordsToViewportCoords( + { + sceneX: user.pointer.x, + sceneY: user.pointer.y, + }, + props.appState, + ); + cursorButton[socketId] = user.button; + }); + + const selectionColor = getComputedStyle( + document.querySelector(".excalidraw")!, + ).getPropertyValue("--color-selection"); + + renderInteractiveScene( + { + scale: window.devicePixelRatio, + elements: props.elements, + canvas: props.canvas, + appState: props.appState, + renderConfig: { + remotePointerViewportCoords: pointerViewportCoords, + remotePointerButton: cursorButton, + remoteSelectedElementIds, + remotePointerUsernames: pointerUsernames, + remotePointerUserStates: pointerUserStates, + selectionColor, + renderScrollbars: false, + }, + callback: props.renderInteractiveSceneCallback, + }, + window.EXCALIDRAW_THROTTLE_NEXT_RENDER && + window.EXCALIDRAW_THROTTLE_RENDER === true, + ); + + if (!window.EXCALIDRAW_THROTTLE_NEXT_RENDER) { + window.EXCALIDRAW_THROTTLE_NEXT_RENDER = true; + } + }); + + return ( + + {t("labels.drawingCanvas")} + + ); +}; + +const stripIrrelevantAppStateProps = ( + appState: AppState, +): Omit => ({ + zoom: appState.zoom, + scrollX: appState.scrollX, + scrollY: appState.scrollY, + width: appState.width, + height: appState.height, + viewModeEnabled: appState.viewModeEnabled, + editingGroupId: appState.editingGroupId, + editingLinearElement: appState.editingLinearElement, + selectedElementIds: appState.selectedElementIds, + frameToHighlight: appState.frameToHighlight, + offsetLeft: appState.offsetLeft, + offsetTop: appState.offsetTop, + theme: appState.theme, + pendingImageElementId: appState.pendingImageElementId, + selectionElement: appState.selectionElement, + selectedGroupIds: appState.selectedGroupIds, + selectedLinearElement: appState.selectedLinearElement, + multiElement: appState.multiElement, + isBindingEnabled: appState.isBindingEnabled, + suggestedBindings: appState.suggestedBindings, + isRotating: appState.isRotating, + elementsToHighlight: appState.elementsToHighlight, + openSidebar: appState.openSidebar, + showHyperlinkPopup: appState.showHyperlinkPopup, + collaborators: appState.collaborators, // Necessary for collab. sessions +}); + +const areEqual = ( + prevProps: InteractiveCanvasProps, + nextProps: InteractiveCanvasProps, +) => { + // This could be further optimised if needed, as we don't have to render interactive canvas on each mutation + if ( + prevProps.selectionNonce !== nextProps.selectionNonce || + prevProps.versionNonce !== nextProps.versionNonce + ) { + return false; + } + + // Comparing the interactive appState for changes in case of some edge cases + return isShallowEqual( + // asserting AppState because we're being passed the whole AppState + // but resolve to only the InteractiveCanvas-relevant props + stripIrrelevantAppStateProps(prevProps.appState as AppState), + stripIrrelevantAppStateProps(nextProps.appState as AppState), + ); +}; + +export default React.memo(InteractiveCanvas, areEqual); diff --git a/src/components/canvases/StaticCanvas.tsx b/src/components/canvases/StaticCanvas.tsx new file mode 100644 index 000000000..ebe3dc42c --- /dev/null +++ b/src/components/canvases/StaticCanvas.tsx @@ -0,0 +1,104 @@ +import React, { useEffect, useRef } from "react"; +import { RoughCanvas } from "roughjs/bin/canvas"; +import { renderStaticScene } from "../../renderer/renderScene"; +import { isShallowEqual } from "../../utils"; +import type { AppState, StaticCanvasAppState } from "../../types"; +import type { StaticCanvasRenderConfig } from "../../scene/types"; +import type { NonDeletedExcalidrawElement } from "../../element/types"; + +type StaticCanvasProps = { + canvas: HTMLCanvasElement | null; + rc: RoughCanvas | null; + elements: readonly NonDeletedExcalidrawElement[]; + versionNonce: number | undefined; + selectionNonce: number | undefined; + appState: StaticCanvasAppState; + renderConfig: StaticCanvasRenderConfig; + handleCanvasRef: (canvas: HTMLCanvasElement) => void; +}; + +const StaticCanvas = (props: StaticCanvasProps) => { + const isComponentMounted = useRef(false); + + useEffect(() => { + if (!isComponentMounted.current) { + isComponentMounted.current = true; + return; + } + renderStaticScene( + { + scale: window.devicePixelRatio, + elements: props.elements, + canvas: props.canvas, + rc: props.rc!, + appState: props.appState, + renderConfig: props.renderConfig, + }, + window.EXCALIDRAW_THROTTLE_NEXT_RENDER && + window.EXCALIDRAW_THROTTLE_RENDER === true, + ); + + if (!window.EXCALIDRAW_THROTTLE_NEXT_RENDER) { + window.EXCALIDRAW_THROTTLE_NEXT_RENDER = true; + } + }); + + return ( + + ); +}; + +const stripIrrelevantAppStateProps = ( + appState: AppState, +): Omit< + StaticCanvasAppState, + | "editingElement" + | "selectedElementIds" + | "editingGroupId" + | "frameToHighlight" +> => ({ + zoom: appState.zoom, + scrollX: appState.scrollX, + scrollY: appState.scrollY, + width: appState.width, + height: appState.height, + viewModeEnabled: appState.viewModeEnabled, + offsetLeft: appState.offsetLeft, + offsetTop: appState.offsetTop, + theme: appState.theme, + pendingImageElementId: appState.pendingImageElementId, + shouldCacheIgnoreZoom: appState.shouldCacheIgnoreZoom, + viewBackgroundColor: appState.viewBackgroundColor, + exportScale: appState.exportScale, + selectedElementsAreBeingDragged: appState.selectedElementsAreBeingDragged, + gridSize: appState.gridSize, + frameRendering: appState.frameRendering, +}); + +const areEqual = ( + prevProps: StaticCanvasProps, + nextProps: StaticCanvasProps, +) => { + if (prevProps.versionNonce !== nextProps.versionNonce) { + return false; + } + + return isShallowEqual( + // asserting AppState because we're being passed the whole AppState + // but resolve to only the InteractiveCanvas-relevant props + stripIrrelevantAppStateProps(prevProps.appState as AppState), + stripIrrelevantAppStateProps(nextProps.appState as AppState), + ); +}; + +export default React.memo(StaticCanvas, areEqual); diff --git a/src/components/canvases/index.tsx b/src/components/canvases/index.tsx new file mode 100644 index 000000000..a5fcb4e55 --- /dev/null +++ b/src/components/canvases/index.tsx @@ -0,0 +1,5 @@ +import CanvasesWrapper from "./CanvasesWrapper"; +import InteractiveCanvas from "./InteractiveCanvas"; +import StaticCanvas from "./StaticCanvas"; + +export { CanvasesWrapper, InteractiveCanvas, StaticCanvas }; diff --git a/src/css/styles.scss b/src/css/styles.scss index 826540d02..4001f4be7 100644 --- a/src/css/styles.scss +++ b/src/css/styles.scss @@ -3,8 +3,9 @@ :root { --zIndex-canvas: 1; - --zIndex-wysiwyg: 2; - --zIndex-layerUI: 3; + --zIndex-interactiveCanvas: 2; + --zIndex-wysiwyg: 3; + --zIndex-layerUI: 4; --zIndex-modal: 1000; --zIndex-popup: 1001; @@ -69,6 +70,10 @@ z-index: var(--zIndex-canvas); + &.interactive { + z-index: var(--zIndex-interactiveCanvas); + } + // Remove the main canvas from document flow to avoid resizeObserver // feedback loop (see https://github.com/excalidraw/excalidraw/pull/3379) } diff --git a/src/data/blob.ts b/src/data/blob.ts index c0aa66ee7..922400303 100644 --- a/src/data/blob.ts +++ b/src/data/blob.ts @@ -144,11 +144,7 @@ export const loadSceneOrLibraryFromBlob = async ( fileHandle: fileHandle || blob.handle || null, ...cleanAppStateForExport(data.appState || {}), ...(localAppState - ? calculateScrollCenter( - data.elements || [], - localAppState, - null, - ) + ? calculateScrollCenter(data.elements || [], localAppState) : {}), }, files: data.files, diff --git a/src/element/Hyperlink.tsx b/src/element/Hyperlink.tsx index cf741ce08..decd9c0e3 100644 --- a/src/element/Hyperlink.tsx +++ b/src/element/Hyperlink.tsx @@ -293,7 +293,7 @@ export const getContextMenuLabel = ( export const getLinkHandleFromCoords = ( [x1, y1, x2, y2]: Bounds, angle: number, - appState: UIAppState, + appState: Pick, ): [x: number, y: number, width: number, height: number] => { const size = DEFAULT_LINK_SIZE; const linkWidth = size / appState.zoom.value; diff --git a/src/element/binding.ts b/src/element/binding.ts index b175f14e7..7832906fc 100644 --- a/src/element/binding.ts +++ b/src/element/binding.ts @@ -474,6 +474,7 @@ const maybeCalculateNewGapWhenScaling = ( return { elementId, gap: newGap, focus }; }; +// TODO: this is a bottleneck, optimise export const getEligibleElementsForBinding = ( elements: NonDeleted[], ): SuggestedBinding[] => { diff --git a/src/element/linearElementEditor.ts b/src/element/linearElementEditor.ts index b10493e0d..94bde1f2a 100644 --- a/src/element/linearElementEditor.ts +++ b/src/element/linearElementEditor.ts @@ -25,7 +25,12 @@ import { getElementPointsCoords, getMinMaxXYFromCurvePathOps, } from "./bounds"; -import { Point, AppState, PointerCoords } from "../types"; +import { + Point, + AppState, + PointerCoords, + InteractiveCanvasAppState, +} from "../types"; import { mutateElement } from "./mutateElement"; import History from "../history"; @@ -398,7 +403,7 @@ export class LinearElementEditor { static getEditorMidPoints = ( element: NonDeleted, - appState: AppState, + appState: InteractiveCanvasAppState, ): typeof editorMidPointsCache["points"] => { const boundText = getBoundTextElement(element); @@ -422,7 +427,7 @@ export class LinearElementEditor { static updateEditorMidPointsCache = ( element: NonDeleted, - appState: AppState, + appState: InteractiveCanvasAppState, ) => { const points = LinearElementEditor.getPointsGlobalCoordinates(element); diff --git a/src/element/textWysiwyg.test.tsx b/src/element/textWysiwyg.test.tsx index 7fb153987..043c7e206 100644 --- a/src/element/textWysiwyg.test.tsx +++ b/src/element/textWysiwyg.test.tsx @@ -759,7 +759,7 @@ describe("textWysiwyg", () => { expect(h.elements[1].type).toBe("text"); API.setSelectedElements([h.elements[0], h.elements[1]]); - fireEvent.contextMenu(GlobalTestState.canvas, { + fireEvent.contextMenu(GlobalTestState.interactiveCanvas, { button: 2, clientX: 20, clientY: 30, @@ -903,7 +903,7 @@ describe("textWysiwyg", () => { mouse.clickAt(10, 20); mouse.down(); mouse.up(); - fireEvent.contextMenu(GlobalTestState.canvas, { + fireEvent.contextMenu(GlobalTestState.interactiveCanvas, { button: 2, clientX: 20, clientY: 30, @@ -1154,7 +1154,7 @@ describe("textWysiwyg", () => { h.elements = [container, text]; API.setSelectedElements([container, text]); - fireEvent.contextMenu(GlobalTestState.canvas, { + fireEvent.contextMenu(GlobalTestState.interactiveCanvas, { button: 2, clientX: 20, clientY: 30, @@ -1168,7 +1168,7 @@ describe("textWysiwyg", () => { expect((h.elements[1] as ExcalidrawTextElementWithContainer).text).toBe( "Online \nwhitebo\nard \ncollabo\nration \nmade \neasy", ); - fireEvent.contextMenu(GlobalTestState.canvas, { + fireEvent.contextMenu(GlobalTestState.interactiveCanvas, { button: 2, clientX: 20, clientY: 30, @@ -1406,7 +1406,7 @@ describe("textWysiwyg", () => { API.setSelectedElements([textElement]); - fireEvent.contextMenu(GlobalTestState.canvas, { + fireEvent.contextMenu(GlobalTestState.interactiveCanvas, { button: 2, clientX: 20, clientY: 30, diff --git a/src/element/transformHandles.ts b/src/element/transformHandles.ts index cf8e5b6a2..7785365c5 100644 --- a/src/element/transformHandles.ts +++ b/src/element/transformHandles.ts @@ -6,7 +6,7 @@ import { import { getElementAbsoluteCoords } from "./bounds"; import { rotate } from "../math"; -import { AppState, Zoom } from "../types"; +import { InteractiveCanvasAppState, Zoom } from "../types"; import { isTextElement } from "."; import { isFrameElement, isLinearElement } from "./typeChecks"; import { DEFAULT_SPACING } from "../renderer/renderScene"; @@ -277,7 +277,7 @@ export const getTransformHandles = ( export const shouldShowBoundingBox = ( elements: NonDeletedExcalidrawElement[], - appState: AppState, + appState: InteractiveCanvasAppState, ) => { if (appState.editingLinearElement) { return false; diff --git a/src/frame.ts b/src/frame.ts index 8a39a41b3..543f237d7 100644 --- a/src/frame.ts +++ b/src/frame.ts @@ -16,7 +16,7 @@ import { } from "./element/textElement"; import { arrayToMap, findIndex } from "./utils"; import { mutateElement } from "./element/mutateElement"; -import { AppClassProperties, AppState } from "./types"; +import { AppClassProperties, AppState, StaticCanvasAppState } from "./types"; import { getElementsWithinSelection, getSelectedElements } from "./scene"; import { isFrameElement } from "./element"; import { moveOneRight } from "./zindex"; @@ -469,9 +469,13 @@ export const addElementsToFrame = ( } let nextElements = allElements.slice(); + // Optimisation since findIndex on "newElements" is slow + const nextElementsIndex = nextElements.reduce((acc, element, index) => { + acc[element.id] = index; + return acc; + }, {} as Record); const frameBoundary = findIndex(nextElements, (e) => e.frameId === frame.id); - for (const element of omitGroupsContainingFrames( allElements, _elementsToAdd, @@ -485,8 +489,8 @@ export const addElementsToFrame = ( false, ); - const frameIndex = findIndex(nextElements, (e) => e.id === frame.id); - const elementIndex = findIndex(nextElements, (e) => e.id === element.id); + const frameIndex = nextElementsIndex[frame.id]; + const elementIndex = nextElementsIndex[element.id]; if (elementIndex < frameBoundary) { nextElements = [ @@ -648,7 +652,7 @@ export const omitGroupsContainingFrames = ( */ export const getTargetFrame = ( element: ExcalidrawElement, - appState: AppState, + appState: StaticCanvasAppState, ) => { const _element = isTextElement(element) ? getContainerElement(element) || element @@ -660,11 +664,12 @@ export const getTargetFrame = ( : getContainingFrame(_element); }; +// TODO: this a huge bottleneck for large scenes, optimise // given an element, return if the element is in some frame export const isElementInFrame = ( element: ExcalidrawElement, allElements: ExcalidrawElementsIncludingDeleted, - appState: AppState, + appState: StaticCanvasAppState, ) => { const frame = getTargetFrame(element, appState); const _element = isTextElement(element) diff --git a/src/global.d.ts b/src/global.d.ts index 3a666e11a..fc7f11fb8 100644 --- a/src/global.d.ts +++ b/src/global.d.ts @@ -17,6 +17,7 @@ interface Window { EXCALIDRAW_ASSET_PATH: string | undefined; EXCALIDRAW_EXPORT_SOURCE: string; EXCALIDRAW_THROTTLE_RENDER: boolean | undefined; + EXCALIDRAW_THROTTLE_NEXT_RENDER: boolean; gtag: Function; sa_event: Function; fathom: { trackEvent: Function }; diff --git a/src/groups.ts b/src/groups.ts index c7aa60fc2..8d6102b3c 100644 --- a/src/groups.ts +++ b/src/groups.ts @@ -4,21 +4,28 @@ import { NonDeleted, NonDeletedExcalidrawElement, } from "./element/types"; -import { AppClassProperties, AppState } from "./types"; +import { + AppClassProperties, + AppState, + InteractiveCanvasAppState, +} from "./types"; import { getSelectedElements } from "./scene"; import { getBoundTextElement } from "./element/textElement"; import { makeNextSelectedElementIds } from "./scene/selection"; export const selectGroup = ( groupId: GroupId, - appState: AppState, + appState: InteractiveCanvasAppState, elements: readonly NonDeleted[], -): AppState => { - const elementsInGroup = elements.filter((element) => - element.groupIds.includes(groupId), - ); +): InteractiveCanvasAppState => { + const elementsInGroup = elements.reduce((acc, element) => { + if (element.groupIds.includes(groupId)) { + acc[element.id] = true; + } + return acc; + }, {} as Record); - if (elementsInGroup.length < 2) { + if (Object.keys(elementsInGroup).length < 2) { if ( appState.selectedGroupIds[groupId] || appState.editingGroupId === groupId @@ -37,31 +44,120 @@ export const selectGroup = ( selectedGroupIds: { ...appState.selectedGroupIds, [groupId]: true }, selectedElementIds: { ...appState.selectedElementIds, - ...Object.fromEntries( - elementsInGroup.map((element) => [element.id, true]), - ), - }, + ...elementsInGroup, + } as AppState["selectedElementIds"], }; }; +export const selectGroups = (function () { + let lastSelectedElements: readonly NonDeleted[] | null = + null; + let lastElements: readonly NonDeleted[] | null = null; + let lastAppState: InteractiveCanvasAppState | null = null; + + const ret = ( + selectedElements: readonly NonDeleted[], + elements: readonly NonDeleted[], + appState: InteractiveCanvasAppState, + ): InteractiveCanvasAppState => { + if ( + lastAppState !== undefined && + elements === lastElements && + selectedElements === lastSelectedElements && + appState.editingGroupId === lastAppState?.editingGroupId && + appState.selectedGroupIds === lastAppState?.selectedGroupIds + ) { + return lastAppState; + } + + const selectedGroupIds: Record = {}; + // Gather all the groups withing selected elements + for (const selectedElement of selectedElements) { + let groupIds = selectedElement.groupIds; + if (appState.editingGroupId) { + // handle the case where a group is nested within a group + const indexOfEditingGroup = groupIds.indexOf(appState.editingGroupId); + if (indexOfEditingGroup > -1) { + groupIds = groupIds.slice(0, indexOfEditingGroup); + } + } + if (groupIds.length > 0) { + const lastSelectedGroup = groupIds[groupIds.length - 1]; + selectedGroupIds[lastSelectedGroup] = true; + } + } + + // Gather all the elements within selected groups + const groupElementsIndex: Record = {}; + const selectedElementIdsInGroups = elements.reduce((acc, element) => { + const groupId = element.groupIds.find((id) => selectedGroupIds[id]); + + if (groupId) { + acc[element.id] = true; + + // Populate the index + if (!Array.isArray(groupElementsIndex[groupId])) { + groupElementsIndex[groupId] = [element.id]; + } else { + groupElementsIndex[groupId].push(element.id); + } + } + return acc; + }, {} as Record); + + for (const groupId of Object.keys(groupElementsIndex)) { + // If there is one element in the group, and the group is selected or it's being edited, it's not a group + if (groupElementsIndex[groupId].length < 2) { + if (selectedGroupIds[groupId]) { + selectedGroupIds[groupId] = false; + } + } + } + + lastElements = elements; + lastSelectedElements = selectedElements; + + lastAppState = { + ...appState, + selectedGroupIds, + selectedElementIds: { + ...appState.selectedElementIds, + ...selectedElementIdsInGroups, + } as AppState["selectedElementIds"], + }; + + return lastAppState; + }; + + ret.clearCache = () => { + lastElements = null; + lastSelectedElements = null; + lastAppState = null; + }; + + return ret; +})(); + /** * If the element's group is selected, don't render an individual * selection border around it. */ export const isSelectedViaGroup = ( - appState: AppState, + appState: InteractiveCanvasAppState, element: ExcalidrawElement, ) => getSelectedGroupForElement(appState, element) != null; export const getSelectedGroupForElement = ( - appState: AppState, + appState: InteractiveCanvasAppState, element: ExcalidrawElement, ) => element.groupIds .filter((groupId) => groupId !== appState.editingGroupId) .find((groupId) => appState.selectedGroupIds[groupId]); -export const getSelectedGroupIds = (appState: AppState): GroupId[] => +export const getSelectedGroupIds = ( + appState: InteractiveCanvasAppState, +): GroupId[] => Object.entries(appState.selectedGroupIds) .filter(([groupId, isSelected]) => isSelected) .map(([groupId, isSelected]) => groupId); @@ -71,16 +167,19 @@ export const getSelectedGroupIds = (appState: AppState): GroupId[] => * you're currently editing that group. */ export const selectGroupsForSelectedElements = ( - appState: AppState, + appState: InteractiveCanvasAppState, elements: readonly NonDeletedExcalidrawElement[], - prevAppState: AppState, + prevAppState: InteractiveCanvasAppState, /** * supply null in cases where you don't have access to App instance and * you don't care about optimizing selectElements retrieval */ app: AppClassProperties | null, -): AppState => { - let nextAppState: AppState = { ...appState, selectedGroupIds: {} }; +): InteractiveCanvasAppState => { + let nextAppState: InteractiveCanvasAppState = { + ...appState, + selectedGroupIds: {}, + }; const selectedElements = app ? app.scene.getSelectedElements({ @@ -101,25 +200,7 @@ export const selectGroupsForSelectedElements = ( }; } - for (const selectedElement of selectedElements) { - let groupIds = selectedElement.groupIds; - if (appState.editingGroupId) { - // handle the case where a group is nested within a group - const indexOfEditingGroup = groupIds.indexOf(appState.editingGroupId); - if (indexOfEditingGroup > -1) { - groupIds = groupIds.slice(0, indexOfEditingGroup); - } - } - if (groupIds.length > 0) { - const groupId = groupIds[groupIds.length - 1]; - nextAppState = selectGroup(groupId, nextAppState, elements); - } - } - - nextAppState.selectedElementIds = makeNextSelectedElementIds( - nextAppState.selectedElementIds, - prevAppState, - ); + nextAppState = selectGroups(selectedElements, elements, appState); return nextAppState; }; @@ -128,9 +209,12 @@ export const selectGroupsForSelectedElements = ( // or used to update the elements export const selectGroupsFromGivenElements = ( elements: readonly NonDeleted[], - appState: AppState, + appState: InteractiveCanvasAppState, ) => { - let nextAppState: AppState = { ...appState, selectedGroupIds: {} }; + let nextAppState: InteractiveCanvasAppState = { + ...appState, + selectedGroupIds: {}, + }; for (const element of elements) { let groupIds = element.groupIds; @@ -146,6 +230,8 @@ export const selectGroupsFromGivenElements = ( } } + // nextAppState = selectGroups(elements, elements, appState); + return nextAppState.selectedGroupIds; }; diff --git a/src/hooks/useMutatedElements.ts b/src/hooks/useMutatedElements.ts new file mode 100644 index 000000000..cf03d74ba --- /dev/null +++ b/src/hooks/useMutatedElements.ts @@ -0,0 +1,42 @@ +import Scene from "../scene/Scene"; +import { useMemo } from "react"; +import { InteractiveCanvasAppState, StaticCanvasAppState } from "../types"; +import { isImageElement } from "../element/typeChecks"; +import { NonDeletedExcalidrawElement } from "../element/types"; + +export const useMutatedElements = ({ + appState, + scene, +}: { + appState: InteractiveCanvasAppState | StaticCanvasAppState; + scene: Scene; +}): [readonly NonDeletedExcalidrawElement[], number | undefined] => { + const versionNonce = scene.getVersionNonce(); + const nonDeletedElements = scene.getNonDeletedElements(); + + const elements = useMemo(() => { + return nonDeletedElements.filter((element) => { + if (isImageElement(element)) { + if ( + // not placed on canvas yet (but in elements array) + appState.pendingImageElementId === element.id + ) { + return false; + } + } + // don't render text element that's being currently edited (it's + // rendered on remote only) + return ( + !appState.editingElement || + appState.editingElement.type !== "text" || + element.id !== appState.editingElement.id + ); + }); + }, [ + nonDeletedElements, + appState.editingElement, + appState.pendingImageElementId, + ]); + + return [elements, versionNonce]; +}; diff --git a/src/renderer/renderElement.ts b/src/renderer/renderElement.ts index ff47d4359..d3df71314 100644 --- a/src/renderer/renderElement.ts +++ b/src/renderer/renderElement.ts @@ -26,11 +26,17 @@ import { Drawable, Options } from "roughjs/bin/core"; import { RoughSVG } from "roughjs/bin/svg"; import { RoughGenerator } from "roughjs/bin/generator"; -import { RenderConfig } from "../scene/types"; +import { StaticCanvasRenderConfig } from "../scene/types"; import { distance, getFontString, getFontFamilyString, isRTL } from "../utils"; import { getCornerRadius, isPathALoop, isRightAngle } from "../math"; import rough from "roughjs/bin/rough"; -import { AppState, BinaryFiles, Zoom } from "../types"; +import { + AppState, + StaticCanvasAppState, + BinaryFiles, + Zoom, + InteractiveCanvasAppState, +} from "../types"; import { getDefaultAppState } from "../appState"; import { BOUND_TEXT_PADDING, @@ -62,17 +68,18 @@ const defaultAppState = getDefaultAppState(); const isPendingImageElement = ( element: ExcalidrawElement, - renderConfig: RenderConfig, + renderConfig: StaticCanvasRenderConfig, ) => isInitializedImageElement(element) && !renderConfig.imageCache.has(element.fileId); const shouldResetImageFilter = ( element: ExcalidrawElement, - renderConfig: RenderConfig, + renderConfig: StaticCanvasRenderConfig, + appState: StaticCanvasAppState, ) => { return ( - renderConfig.theme === "dark" && + appState.theme === "dark" && isInitializedImageElement(element) && !isPendingImageElement(element, renderConfig) && renderConfig.imageCache.get(element.fileId)?.mimeType !== MIME_TYPES.svg @@ -89,9 +96,9 @@ const getCanvasPadding = (element: ExcalidrawElement) => export interface ExcalidrawElementWithCanvas { element: ExcalidrawElement | ExcalidrawTextElement; canvas: HTMLCanvasElement; - theme: RenderConfig["theme"]; + theme: AppState["theme"]; scale: number; - zoomValue: RenderConfig["zoom"]["value"]; + zoomValue: AppState["zoom"]["value"]; canvasOffsetX: number; canvasOffsetY: number; boundTextElementVersion: number | null; @@ -155,7 +162,8 @@ const cappedElementCanvasSize = ( const generateElementCanvas = ( element: NonDeletedExcalidrawElement, zoom: Zoom, - renderConfig: RenderConfig, + renderConfig: StaticCanvasRenderConfig, + appState: StaticCanvasAppState, ): ExcalidrawElementWithCanvas => { const canvas = document.createElement("canvas"); const context = canvas.getContext("2d")!; @@ -195,17 +203,17 @@ const generateElementCanvas = ( const rc = rough.canvas(canvas); // in dark theme, revert the image color filter - if (shouldResetImageFilter(element, renderConfig)) { + if (shouldResetImageFilter(element, renderConfig, appState)) { context.filter = IMAGE_INVERT_FILTER; } - drawElementOnCanvas(element, rc, context, renderConfig); + drawElementOnCanvas(element, rc, context, renderConfig, appState); context.restore(); return { element, canvas, - theme: renderConfig.theme, + theme: appState.theme, scale, zoomValue: zoom.value, canvasOffsetX, @@ -256,7 +264,8 @@ const drawElementOnCanvas = ( element: NonDeletedExcalidrawElement, rc: RoughCanvas, context: CanvasRenderingContext2D, - renderConfig: RenderConfig, + renderConfig: StaticCanvasRenderConfig, + appState: StaticCanvasAppState, ) => { context.globalAlpha = ((getContainingFrame(element)?.opacity ?? 100) * element.opacity) / 10000; @@ -310,7 +319,7 @@ const drawElementOnCanvas = ( element.height, ); } else { - drawImagePlaceholder(element, context, renderConfig.zoom.value); + drawImagePlaceholder(element, context, appState.zoom.value); } break; } @@ -715,21 +724,22 @@ const generateElementShape = ( const generateElementWithCanvas = ( element: NonDeletedExcalidrawElement, - renderConfig: RenderConfig, + renderConfig: StaticCanvasRenderConfig, + appState: StaticCanvasAppState, ) => { - const zoom: Zoom = renderConfig ? renderConfig.zoom : defaultAppState.zoom; + const zoom: Zoom = renderConfig ? appState.zoom : defaultAppState.zoom; const prevElementWithCanvas = elementWithCanvasCache.get(element); const shouldRegenerateBecauseZoom = prevElementWithCanvas && prevElementWithCanvas.zoomValue !== zoom.value && - !renderConfig?.shouldCacheIgnoreZoom; + !appState?.shouldCacheIgnoreZoom; const boundTextElementVersion = getBoundTextElement(element)?.version || null; const containingFrameOpacity = getContainingFrame(element)?.opacity || 100; if ( !prevElementWithCanvas || shouldRegenerateBecauseZoom || - prevElementWithCanvas.theme !== renderConfig.theme || + prevElementWithCanvas.theme !== appState.theme || prevElementWithCanvas.boundTextElementVersion !== boundTextElementVersion || prevElementWithCanvas.containingFrameOpacity !== containingFrameOpacity ) { @@ -737,6 +747,7 @@ const generateElementWithCanvas = ( element, zoom, renderConfig, + appState, ); elementWithCanvasCache.set(element, elementWithCanvas); @@ -748,9 +759,9 @@ const generateElementWithCanvas = ( const drawElementFromCanvas = ( elementWithCanvas: ExcalidrawElementWithCanvas, - rc: RoughCanvas, context: CanvasRenderingContext2D, - renderConfig: RenderConfig, + renderConfig: StaticCanvasRenderConfig, + appState: StaticCanvasAppState, ) => { const element = elementWithCanvas.element; const padding = getCanvasPadding(element); @@ -765,8 +776,8 @@ const drawElementFromCanvas = ( y2 = Math.ceil(y2); } - const cx = ((x1 + x2) / 2 + renderConfig.scrollX) * window.devicePixelRatio; - const cy = ((y1 + y2) / 2 + renderConfig.scrollY) * window.devicePixelRatio; + const cx = ((x1 + x2) / 2 + appState.scrollX) * window.devicePixelRatio; + const cy = ((y1 + y2) / 2 + appState.scrollY) * window.devicePixelRatio; context.save(); context.scale(1 / window.devicePixelRatio, 1 / window.devicePixelRatio); @@ -864,9 +875,9 @@ const drawElementFromCanvas = ( context.drawImage( elementWithCanvas.canvas!, - (x1 + renderConfig.scrollX) * window.devicePixelRatio - + (x1 + appState.scrollX) * window.devicePixelRatio - (padding * elementWithCanvas.scale) / elementWithCanvas.scale, - (y1 + renderConfig.scrollY) * window.devicePixelRatio - + (y1 + appState.scrollY) * window.devicePixelRatio - (padding * elementWithCanvas.scale) / elementWithCanvas.scale, elementWithCanvas.canvas!.width / elementWithCanvas.scale, elementWithCanvas.canvas!.height / elementWithCanvas.scale, @@ -884,8 +895,8 @@ const drawElementFromCanvas = ( context.strokeStyle = "#c92a2a"; context.lineWidth = 3; context.strokeRect( - (coords.x + renderConfig.scrollX) * window.devicePixelRatio, - (coords.y + renderConfig.scrollY) * window.devicePixelRatio, + (coords.x + appState.scrollX) * window.devicePixelRatio, + (coords.y + appState.scrollY) * window.devicePixelRatio, getBoundTextMaxWidth(element) * window.devicePixelRatio, getBoundTextMaxHeight(element, textElement) * window.devicePixelRatio, ); @@ -896,40 +907,38 @@ const drawElementFromCanvas = ( // Clear the nested element we appended to the DOM }; +export const renderSelectionElement = ( + element: NonDeletedExcalidrawElement, + context: CanvasRenderingContext2D, + appState: InteractiveCanvasAppState, +) => { + context.save(); + context.translate(element.x + appState.scrollX, element.y + appState.scrollY); + context.fillStyle = "rgba(0, 0, 200, 0.04)"; + + // render from 0.5px offset to get 1px wide line + // https://stackoverflow.com/questions/7530593/html5-canvas-and-line-width/7531540#7531540 + // TODO can be be improved by offseting to the negative when user selects + // from right to left + const offset = 0.5 / appState.zoom.value; + + context.fillRect(offset, offset, element.width, element.height); + context.lineWidth = 1 / appState.zoom.value; + context.strokeStyle = " rgb(105, 101, 219)"; + context.strokeRect(offset, offset, element.width, element.height); + + context.restore(); +}; + export const renderElement = ( element: NonDeletedExcalidrawElement, rc: RoughCanvas, context: CanvasRenderingContext2D, - renderConfig: RenderConfig, - appState: AppState, + renderConfig: StaticCanvasRenderConfig, + appState: StaticCanvasAppState, ) => { const generator = rc.generator; switch (element.type) { - case "selection": { - // do not render selection when exporting - if (!renderConfig.isExporting) { - context.save(); - context.translate( - element.x + renderConfig.scrollX, - element.y + renderConfig.scrollY, - ); - context.fillStyle = "rgba(0, 0, 200, 0.04)"; - - // render from 0.5px offset to get 1px wide line - // https://stackoverflow.com/questions/7530593/html5-canvas-and-line-width/7531540#7531540 - // TODO can be be improved by offseting to the negative when user selects - // from right to left - const offset = 0.5 / renderConfig.zoom.value; - - context.fillRect(offset, offset, element.width, element.height); - context.lineWidth = 1 / renderConfig.zoom.value; - context.strokeStyle = " rgb(105, 101, 219)"; - context.strokeRect(offset, offset, element.width, element.height); - - context.restore(); - } - break; - } case "frame": { if ( !renderConfig.isExporting && @@ -938,12 +947,12 @@ export const renderElement = ( ) { context.save(); context.translate( - element.x + renderConfig.scrollX, - element.y + renderConfig.scrollY, + element.x + appState.scrollX, + element.y + appState.scrollY, ); context.fillStyle = "rgba(0, 0, 200, 0.04)"; - context.lineWidth = 2 / renderConfig.zoom.value; + context.lineWidth = 2 / appState.zoom.value; context.strokeStyle = FRAME_STYLE.strokeColor; if (FRAME_STYLE.radius && context.roundRect) { @@ -953,7 +962,7 @@ export const renderElement = ( 0, element.width, element.height, - FRAME_STYLE.radius / renderConfig.zoom.value, + FRAME_STYLE.radius / appState.zoom.value, ); context.stroke(); context.closePath(); @@ -970,22 +979,28 @@ export const renderElement = ( if (renderConfig.isExporting) { const [x1, y1, x2, y2] = getElementAbsoluteCoords(element); - const cx = (x1 + x2) / 2 + renderConfig.scrollX; - const cy = (y1 + y2) / 2 + renderConfig.scrollY; + const cx = (x1 + x2) / 2 + appState.scrollX; + const cy = (y1 + y2) / 2 + appState.scrollY; const shiftX = (x2 - x1) / 2 - (element.x - x1); const shiftY = (y2 - y1) / 2 - (element.y - y1); context.save(); context.translate(cx, cy); context.rotate(element.angle); context.translate(-shiftX, -shiftY); - drawElementOnCanvas(element, rc, context, renderConfig); + drawElementOnCanvas(element, rc, context, renderConfig, appState); context.restore(); } else { const elementWithCanvas = generateElementWithCanvas( element, renderConfig, + appState, + ); + drawElementFromCanvas( + elementWithCanvas, + context, + renderConfig, + appState, ); - drawElementFromCanvas(elementWithCanvas, rc, context, renderConfig); } break; @@ -1000,8 +1015,8 @@ export const renderElement = ( generateElementShape(element, generator); if (renderConfig.isExporting) { const [x1, y1, x2, y2] = getElementAbsoluteCoords(element); - const cx = (x1 + x2) / 2 + renderConfig.scrollX; - const cy = (y1 + y2) / 2 + renderConfig.scrollY; + const cx = (x1 + x2) / 2 + appState.scrollX; + const cy = (y1 + y2) / 2 + appState.scrollY; let shiftX = (x2 - x1) / 2 - (element.x - x1); let shiftY = (y2 - y1) / 2 - (element.y - y1); if (isTextElement(element)) { @@ -1019,7 +1034,7 @@ export const renderElement = ( context.save(); context.translate(cx, cy); - if (shouldResetImageFilter(element, renderConfig)) { + if (shouldResetImageFilter(element, renderConfig, appState)) { context.filter = "none"; } const boundTextElement = getBoundTextElement(element); @@ -1053,7 +1068,13 @@ export const renderElement = ( tempCanvasContext.translate(-shiftX, -shiftY); - drawElementOnCanvas(element, tempRc, tempCanvasContext, renderConfig); + drawElementOnCanvas( + element, + tempRc, + tempCanvasContext, + renderConfig, + appState, + ); tempCanvasContext.translate(shiftX, shiftY); @@ -1090,7 +1111,7 @@ export const renderElement = ( } context.translate(-shiftX, -shiftY); - drawElementOnCanvas(element, rc, context, renderConfig); + drawElementOnCanvas(element, rc, context, renderConfig, appState); } context.restore(); @@ -1100,6 +1121,7 @@ export const renderElement = ( const elementWithCanvas = generateElementWithCanvas( element, renderConfig, + appState, ); const currentImageSmoothingStatus = context.imageSmoothingEnabled; @@ -1107,7 +1129,7 @@ export const renderElement = ( if ( // do not disable smoothing during zoom as blurry shapes look better // on low resolution (while still zooming in) than sharp ones - !renderConfig?.shouldCacheIgnoreZoom && + !appState?.shouldCacheIgnoreZoom && // angle is 0 -> always disable smoothing (!element.angle || // or check if angle is a right angle in which case we can still @@ -1124,7 +1146,12 @@ export const renderElement = ( context.imageSmoothingEnabled = false; } - drawElementFromCanvas(elementWithCanvas, rc, context, renderConfig); + drawElementFromCanvas( + elementWithCanvas, + context, + renderConfig, + appState, + ); // reset context.imageSmoothingEnabled = currentImageSmoothingStatus; diff --git a/src/renderer/renderScene.ts b/src/renderer/renderScene.ts index 1344329e5..cb4cb58a4 100644 --- a/src/renderer/renderScene.ts +++ b/src/renderer/renderScene.ts @@ -1,8 +1,14 @@ -import { RoughCanvas } from "roughjs/bin/canvas"; import { RoughSVG } from "roughjs/bin/svg"; import oc from "open-color"; -import { AppState, BinaryFiles, Point, Zoom } from "../types"; +import { + InteractiveCanvasAppState, + StaticCanvasAppState, + BinaryFiles, + Point, + Zoom, + CommonCanvasAppState, +} from "../types"; import { ExcalidrawElement, NonDeletedExcalidrawElement, @@ -22,7 +28,12 @@ import { } from "../element"; import { roundRect } from "./roundRect"; -import { RenderConfig } from "../scene/types"; +import { + InteractiveCanvasRenderConfig, + InteractiveSceneRenderConfig, + StaticCanvasRenderConfig, + StaticSceneRenderConfig, +} from "../scene/types"; import { getScrollBars, SCROLLBAR_COLOR, @@ -30,7 +41,11 @@ import { } from "../scene/scrollbars"; import { getSelectedElements } from "../scene/selection"; -import { renderElement, renderElementToSvg } from "./renderElement"; +import { + renderElement, + renderElementToSvg, + renderSelectionElement, +} from "./renderElement"; import { getClientColor } from "../clients"; import { LinearElementEditor } from "../element/linearElementEditor"; import { @@ -40,11 +55,7 @@ import { selectGroupsFromGivenElements, } from "../groups"; import { maxBindingGap } from "../element/collision"; -import { - SuggestedBinding, - SuggestedPointBinding, - isBindingEnabled, -} from "../element/binding"; +import { SuggestedBinding, SuggestedPointBinding } from "../element/binding"; import { OMIT_SIDES_FOR_FRAME, shouldShowBoundingBox, @@ -176,7 +187,7 @@ const strokeGrid = ( const renderSingleLinearPoint = ( context: CanvasRenderingContext2D, - renderConfig: RenderConfig, + appState: InteractiveCanvasAppState, point: Point, radius: number, isSelected: boolean, @@ -195,23 +206,22 @@ const renderSingleLinearPoint = ( context, point[0], point[1], - radius / renderConfig.zoom.value, + radius / appState.zoom.value, !isPhantomPoint, ); }; const renderLinearPointHandles = ( context: CanvasRenderingContext2D, - appState: AppState, - renderConfig: RenderConfig, + appState: InteractiveCanvasAppState, element: NonDeleted, ) => { if (!appState.selectedLinearElement) { return; } context.save(); - context.translate(renderConfig.scrollX, renderConfig.scrollY); - context.lineWidth = 1 / renderConfig.zoom.value; + context.translate(appState.scrollX, appState.scrollY); + context.lineWidth = 1 / appState.zoom.value; const points = LinearElementEditor.getPointsGlobalCoordinates(element); const { POINT_HANDLE_SIZE } = LinearElementEditor; @@ -222,7 +232,7 @@ const renderLinearPointHandles = ( const isSelected = !!appState.editingLinearElement?.selectedPointsIndices?.includes(idx); - renderSingleLinearPoint(context, renderConfig, point, radius, isSelected); + renderSingleLinearPoint(context, appState, point, radius, isSelected); }); //Rendering segment mid points @@ -246,17 +256,17 @@ const renderLinearPointHandles = ( if (appState.editingLinearElement) { renderSingleLinearPoint( context, - renderConfig, + appState, segmentMidPoint, radius, false, ); - highlightPoint(segmentMidPoint, context, renderConfig); + highlightPoint(segmentMidPoint, context, appState); } else { - highlightPoint(segmentMidPoint, context, renderConfig); + highlightPoint(segmentMidPoint, context, appState); renderSingleLinearPoint( context, - renderConfig, + appState, segmentMidPoint, radius, false, @@ -265,7 +275,7 @@ const renderLinearPointHandles = ( } else if (appState.editingLinearElement || points.length === 2) { renderSingleLinearPoint( context, - renderConfig, + appState, segmentMidPoint, POINT_HANDLE_SIZE / 2, false, @@ -280,7 +290,7 @@ const renderLinearPointHandles = ( const highlightPoint = ( point: Point, context: CanvasRenderingContext2D, - renderConfig: RenderConfig, + appState: InteractiveCanvasAppState, ) => { context.fillStyle = "rgba(105, 101, 219, 0.4)"; @@ -288,14 +298,13 @@ const highlightPoint = ( context, point[0], point[1], - LinearElementEditor.POINT_HANDLE_SIZE / renderConfig.zoom.value, + LinearElementEditor.POINT_HANDLE_SIZE / appState.zoom.value, false, ); }; const renderLinearElementPointHighlight = ( context: CanvasRenderingContext2D, - appState: AppState, - renderConfig: RenderConfig, + appState: InteractiveCanvasAppState, ) => { const { elementId, hoverPointIndex } = appState.selectedLinearElement!; if ( @@ -314,21 +323,19 @@ const renderLinearElementPointHighlight = ( hoverPointIndex, ); context.save(); - context.translate(renderConfig.scrollX, renderConfig.scrollY); + context.translate(appState.scrollX, appState.scrollY); - highlightPoint(point, context, renderConfig); + highlightPoint(point, context, appState); context.restore(); }; const frameClip = ( frame: ExcalidrawFrameElement, context: CanvasRenderingContext2D, - renderConfig: RenderConfig, + renderConfig: StaticCanvasRenderConfig, + appState: StaticCanvasAppState, ) => { - context.translate( - frame.x + renderConfig.scrollX, - frame.y + renderConfig.scrollY, - ); + context.translate(frame.x + appState.scrollX, frame.y + appState.scrollY); context.beginPath(); if (context.roundRect && !renderConfig.isExporting) { context.roundRect( @@ -336,618 +343,686 @@ const frameClip = ( 0, frame.width, frame.height, - FRAME_STYLE.radius / renderConfig.zoom.value, + FRAME_STYLE.radius / appState.zoom.value, ); } else { context.rect(0, 0, frame.width, frame.height); } context.clip(); context.translate( - -(frame.x + renderConfig.scrollX), - -(frame.y + renderConfig.scrollY), + -(frame.x + appState.scrollX), + -(frame.y + appState.scrollY), ); }; -export const _renderScene = ({ +const getNormalizedCanvasDimensions = ( + canvas: HTMLCanvasElement, + scale: number, +): [number, number] => { + // When doing calculations based on canvas width we should used normalized one + return [canvas.width / scale, canvas.height / scale]; +}; + +const bootstrapCanvas = ({ + canvas, + scale, + zoom, + normalizedWidth, + normalizedHeight, + theme, + isExporting, + viewBackgroundColor, +}: { + canvas: HTMLCanvasElement; + scale: number; + zoom: CommonCanvasAppState["zoom"]; + normalizedWidth: number; + normalizedHeight: number; + theme?: CommonCanvasAppState["theme"]; + isExporting?: StaticCanvasRenderConfig["isExporting"]; + viewBackgroundColor?: StaticCanvasAppState["viewBackgroundColor"]; +}): CanvasRenderingContext2D => { + const context = canvas.getContext("2d")!; + + context.setTransform(1, 0, 0, 1, 0, 0); + context.save(); + context.scale(scale, scale); + + if (isExporting && theme === "dark") { + context.filter = THEME_FILTER; + } + + // Paint background + if (typeof viewBackgroundColor === "string") { + const hasTransparence = + viewBackgroundColor === "transparent" || + viewBackgroundColor.length === 5 || // #RGBA + viewBackgroundColor.length === 9 || // #RRGGBBA + /(hsla|rgba)\(/.test(viewBackgroundColor); + if (hasTransparence) { + context.clearRect(0, 0, normalizedWidth, normalizedHeight); + } + context.save(); + context.fillStyle = viewBackgroundColor; + context.fillRect(0, 0, normalizedWidth, normalizedHeight); + context.restore(); + } else { + context.clearRect(0, 0, normalizedWidth, normalizedHeight); + } + + // Apply zoom + context.save(); + context.scale(zoom.value, zoom.value); + + return context; +}; + +const _renderInteractiveScene = ({ + elements, + appState, + scale, + canvas, + renderConfig, +}: InteractiveSceneRenderConfig) => { + if (canvas === null) { + return { atLeastOneVisibleElement: false, elements }; + } + + const [normalizedWidth, normalizedHeight] = getNormalizedCanvasDimensions( + canvas, + scale, + ); + + const context = bootstrapCanvas({ + canvas, + scale, + normalizedWidth, + normalizedHeight, + zoom: appState.zoom, + }); + + let editingLinearElement: NonDeleted | undefined = + undefined; + + // FIXME I: memo? + const visibleElements = elements.filter((element) => + isVisibleElement(element, normalizedWidth, normalizedHeight, { + zoom: appState.zoom, + offsetLeft: appState.offsetLeft, + offsetTop: appState.offsetTop, + scrollX: appState.scrollX, + scrollY: appState.scrollY, + }), + ); + + visibleElements.forEach((element) => { + // Getting the element using LinearElementEditor during collab mismatches version - being one head of visible elements due to + // ShapeCache returns empty hence making sure that we get the + // correct element from visible elements + if (appState.editingLinearElement?.elementId === element.id) { + if (element) { + editingLinearElement = element as NonDeleted; + } + } + }); + + if (editingLinearElement) { + renderLinearPointHandles(context, appState, editingLinearElement); + } + + // Paint selection element + if (appState.selectionElement) { + try { + renderSelectionElement(appState.selectionElement, context, appState); + } catch (error: any) { + console.error(error); + } + } + + if (appState.isBindingEnabled) { + appState.suggestedBindings + .filter((binding) => binding != null) + .forEach((suggestedBinding) => { + renderBindingHighlight(context, appState, suggestedBinding!); + }); + } + + if (appState.frameToHighlight) { + renderFrameHighlight(context, appState, appState.frameToHighlight); + } + + if (appState.elementsToHighlight) { + renderElementsBoxHighlight(context, appState, appState.elementsToHighlight); + } + + const locallySelectedElements = getSelectedElements(elements, appState); + const isFrameSelected = locallySelectedElements.some((element) => + isFrameElement(element), + ); + + // Getting the element using LinearElementEditor during collab mismatches version - being one head of visible elements due to + // ShapeCache returns empty hence making sure that we get the + // correct element from visible elements + if ( + locallySelectedElements.length === 1 && + appState.editingLinearElement?.elementId === locallySelectedElements[0].id + ) { + renderLinearPointHandles( + context, + appState, + locallySelectedElements[0] as NonDeleted, + ); + } + + if ( + appState.selectedLinearElement && + appState.selectedLinearElement.hoverPointIndex >= 0 + ) { + renderLinearElementPointHighlight(context, appState); + } + // Paint selected elements + if (!appState.multiElement && !appState.editingLinearElement) { + const showBoundingBox = shouldShowBoundingBox( + locallySelectedElements, + appState, + ); + + const isSingleLinearElementSelected = + locallySelectedElements.length === 1 && + isLinearElement(locallySelectedElements[0]); + // render selected linear element points + if ( + isSingleLinearElementSelected && + appState.selectedLinearElement?.elementId === + locallySelectedElements[0].id && + !locallySelectedElements[0].locked + ) { + renderLinearPointHandles( + context, + appState, + locallySelectedElements[0] as ExcalidrawLinearElement, + ); + } + const selectionColor = renderConfig.selectionColor || oc.black; + + if (showBoundingBox) { + // Optimisation for finding quickly relevant element ids + const locallySelectedIds = locallySelectedElements.reduce( + (acc, element) => { + acc[element.id] = true; + return acc; + }, + {} as Record, + ); + + const selections = elements.reduce((acc, element) => { + const selectionColors = []; + // local user + if ( + locallySelectedIds[element.id] && + !isSelectedViaGroup(appState, element) + ) { + selectionColors.push(selectionColor); + } + // remote users + if (renderConfig.remoteSelectedElementIds[element.id]) { + selectionColors.push( + ...renderConfig.remoteSelectedElementIds[element.id].map( + (socketId: string) => { + const background = getClientColor(socketId); + return background; + }, + ), + ); + } + + if (selectionColors.length) { + const [elementX1, elementY1, elementX2, elementY2, cx, cy] = + getElementAbsoluteCoords(element, true); + acc.push({ + angle: element.angle, + elementX1, + elementY1, + elementX2, + elementY2, + selectionColors, + dashed: !!renderConfig.remoteSelectedElementIds[element.id], + cx, + cy, + }); + } + return acc; + }, [] as { angle: number; elementX1: number; elementY1: number; elementX2: number; elementY2: number; selectionColors: string[]; dashed?: boolean; cx: number; cy: number }[]); + + const addSelectionForGroupId = (groupId: GroupId) => { + const groupElements = getElementsInGroup(elements, groupId); + const [elementX1, elementY1, elementX2, elementY2] = + getCommonBounds(groupElements); + selections.push({ + angle: 0, + elementX1, + elementX2, + elementY1, + elementY2, + selectionColors: [oc.black], + dashed: true, + cx: elementX1 + (elementX2 - elementX1) / 2, + cy: elementY1 + (elementY2 - elementY1) / 2, + }); + }; + + for (const groupId of getSelectedGroupIds(appState)) { + // TODO: support multiplayer selected group IDs + addSelectionForGroupId(groupId); + } + + if (appState.editingGroupId) { + addSelectionForGroupId(appState.editingGroupId); + } + + selections.forEach((selection) => + renderSelectionBorder(context, appState, selection), + ); + } + // Paint resize transformHandles + context.save(); + context.translate(appState.scrollX, appState.scrollY); + + if (locallySelectedElements.length === 1) { + context.fillStyle = oc.white; + const transformHandles = getTransformHandles( + locallySelectedElements[0], + appState.zoom, + "mouse", // when we render we don't know which pointer type so use mouse + ); + if (!appState.viewModeEnabled && showBoundingBox) { + renderTransformHandles( + context, + renderConfig, + appState, + transformHandles, + locallySelectedElements[0].angle, + ); + } + } else if (locallySelectedElements.length > 1 && !appState.isRotating) { + const dashedLinePadding = (DEFAULT_SPACING * 2) / appState.zoom.value; + context.fillStyle = oc.white; + const [x1, y1, x2, y2] = getCommonBounds(locallySelectedElements); + const initialLineDash = context.getLineDash(); + context.setLineDash([2 / appState.zoom.value]); + const lineWidth = context.lineWidth; + context.lineWidth = 1 / appState.zoom.value; + context.strokeStyle = selectionColor; + strokeRectWithRotation( + context, + x1 - dashedLinePadding, + y1 - dashedLinePadding, + x2 - x1 + dashedLinePadding * 2, + y2 - y1 + dashedLinePadding * 2, + (x1 + x2) / 2, + (y1 + y2) / 2, + 0, + ); + context.lineWidth = lineWidth; + context.setLineDash(initialLineDash); + const transformHandles = getTransformHandlesFromCoords( + [x1, y1, x2, y2, (x1 + x2) / 2, (y1 + y2) / 2], + 0, + appState.zoom, + "mouse", + isFrameSelected + ? OMIT_SIDES_FOR_FRAME + : OMIT_SIDES_FOR_MULTIPLE_ELEMENTS, + ); + if (locallySelectedElements.some((element) => !element.locked)) { + renderTransformHandles( + context, + renderConfig, + appState, + transformHandles, + 0, + ); + } + } + context.restore(); + } + + // Reset zoom + context.restore(); + + // Paint remote pointers + for (const clientId in renderConfig.remotePointerViewportCoords) { + let { x, y } = renderConfig.remotePointerViewportCoords[clientId]; + + x -= appState.offsetLeft; + y -= appState.offsetTop; + + const width = 11; + const height = 14; + + const isOutOfBounds = + x < 0 || + x > normalizedWidth - width || + y < 0 || + y > normalizedHeight - height; + + x = Math.max(x, 0); + x = Math.min(x, normalizedWidth - width); + y = Math.max(y, 0); + y = Math.min(y, normalizedHeight - height); + + const background = getClientColor(clientId); + + context.save(); + context.strokeStyle = background; + context.fillStyle = background; + + const userState = renderConfig.remotePointerUserStates[clientId]; + const isInactive = + isOutOfBounds || + userState === UserIdleState.IDLE || + userState === UserIdleState.AWAY; + + if (isInactive) { + context.globalAlpha = 0.3; + } + + if ( + renderConfig.remotePointerButton && + renderConfig.remotePointerButton[clientId] === "down" + ) { + context.beginPath(); + context.arc(x, y, 15, 0, 2 * Math.PI, false); + context.lineWidth = 3; + context.strokeStyle = "#ffffff88"; + context.stroke(); + context.closePath(); + + context.beginPath(); + context.arc(x, y, 15, 0, 2 * Math.PI, false); + context.lineWidth = 1; + context.strokeStyle = background; + context.stroke(); + context.closePath(); + } + + // Background (white outline) for arrow + context.fillStyle = oc.white; + context.strokeStyle = oc.white; + context.lineWidth = 6; + context.lineJoin = "round"; + context.beginPath(); + context.moveTo(x, y); + context.lineTo(x + 0, y + 14); + context.lineTo(x + 4, y + 9); + context.lineTo(x + 11, y + 8); + context.closePath(); + context.stroke(); + context.fill(); + + // Arrow + context.fillStyle = background; + context.strokeStyle = background; + context.lineWidth = 2; + context.lineJoin = "round"; + context.beginPath(); + if (isInactive) { + context.moveTo(x - 1, y - 1); + context.lineTo(x - 1, y + 15); + context.lineTo(x + 5, y + 10); + context.lineTo(x + 12, y + 9); + context.closePath(); + context.fill(); + } else { + context.moveTo(x, y); + context.lineTo(x + 0, y + 14); + context.lineTo(x + 4, y + 9); + context.lineTo(x + 11, y + 8); + context.closePath(); + context.fill(); + context.stroke(); + } + + const username = renderConfig.remotePointerUsernames[clientId] || ""; + + if (!isOutOfBounds && username) { + context.font = "600 12px sans-serif"; // font has to be set before context.measureText() + + const offsetX = x + width / 2; + const offsetY = y + height + 2; + const paddingHorizontal = 5; + const paddingVertical = 3; + const measure = context.measureText(username); + const measureHeight = + measure.actualBoundingBoxDescent + measure.actualBoundingBoxAscent; + const finalHeight = Math.max(measureHeight, 12); + + const boxX = offsetX - 1; + const boxY = offsetY - 1; + const boxWidth = measure.width + 2 + paddingHorizontal * 2 + 2; + const boxHeight = finalHeight + 2 + paddingVertical * 2 + 2; + if (context.roundRect) { + context.beginPath(); + context.roundRect(boxX, boxY, boxWidth, boxHeight, 8); + context.fillStyle = background; + context.fill(); + context.strokeStyle = oc.white; + context.stroke(); + } else { + roundRect(context, boxX, boxY, boxWidth, boxHeight, 8, oc.white); + } + context.fillStyle = oc.black; + + context.fillText( + username, + offsetX + paddingHorizontal + 1, + offsetY + + paddingVertical + + measure.actualBoundingBoxAscent + + Math.floor((finalHeight - measureHeight) / 2) + + 2, + ); + } + + context.restore(); + context.closePath(); + } + + // Paint scrollbars + let scrollBars; + if (renderConfig.renderScrollbars) { + scrollBars = getScrollBars( + elements, + normalizedWidth, + normalizedHeight, + appState, + ); + + context.save(); + context.fillStyle = SCROLLBAR_COLOR; + context.strokeStyle = "rgba(255,255,255,0.8)"; + [scrollBars.horizontal, scrollBars.vertical].forEach((scrollBar) => { + if (scrollBar) { + roundRect( + context, + scrollBar.x, + scrollBar.y, + scrollBar.width, + scrollBar.height, + SCROLLBAR_WIDTH / 2, + ); + } + }); + context.restore(); + } + + context.restore(); + context.restore(); + + return { + scrollBars, + atLeastOneVisibleElement: visibleElements.length > 0, + elements, + }; +}; + +const _renderStaticScene = ({ elements, appState, scale, rc, canvas, renderConfig, -}: { - elements: readonly NonDeletedExcalidrawElement[]; - appState: AppState; - scale: number; - rc: RoughCanvas; - canvas: HTMLCanvasElement; - renderConfig: RenderConfig; -}) => - // extra options passed to the renderer - { - if (canvas === null) { - return { atLeastOneVisibleElement: false }; - } - const { - renderScrollbars = false, - renderSelection = true, - renderGrid = true, - isExporting, - } = renderConfig; - - const selectionColor = renderConfig.selectionColor || oc.black; - - const context = canvas.getContext("2d")!; - - context.setTransform(1, 0, 0, 1, 0, 0); - context.save(); - context.scale(scale, scale); - // When doing calculations based on canvas width we should used normalized one - const normalizedCanvasWidth = canvas.width / scale; - const normalizedCanvasHeight = canvas.height / scale; - - if (isExporting && renderConfig.theme === "dark") { - context.filter = THEME_FILTER; - } - - // Paint background - if (typeof renderConfig.viewBackgroundColor === "string") { - const hasTransparence = - renderConfig.viewBackgroundColor === "transparent" || - renderConfig.viewBackgroundColor.length === 5 || // #RGBA - renderConfig.viewBackgroundColor.length === 9 || // #RRGGBBA - /(hsla|rgba)\(/.test(renderConfig.viewBackgroundColor); - if (hasTransparence) { - context.clearRect(0, 0, normalizedCanvasWidth, normalizedCanvasHeight); - } - context.save(); - context.fillStyle = renderConfig.viewBackgroundColor; - context.fillRect(0, 0, normalizedCanvasWidth, normalizedCanvasHeight); - context.restore(); - } else { - context.clearRect(0, 0, normalizedCanvasWidth, normalizedCanvasHeight); - } - - // Apply zoom - context.save(); - context.scale(renderConfig.zoom.value, renderConfig.zoom.value); - - // Grid - if (renderGrid && appState.gridSize) { - strokeGrid( - context, - appState.gridSize, - -Math.ceil(renderConfig.zoom.value / appState.gridSize) * - appState.gridSize + - (renderConfig.scrollX % appState.gridSize), - -Math.ceil(renderConfig.zoom.value / appState.gridSize) * - appState.gridSize + - (renderConfig.scrollY % appState.gridSize), - normalizedCanvasWidth / renderConfig.zoom.value, - normalizedCanvasHeight / renderConfig.zoom.value, - ); - } - - // Paint visible elements - const visibleElements = elements.filter((element) => - isVisibleElement(element, normalizedCanvasWidth, normalizedCanvasHeight, { - zoom: renderConfig.zoom, - offsetLeft: appState.offsetLeft, - offsetTop: appState.offsetTop, - scrollX: renderConfig.scrollX, - scrollY: renderConfig.scrollY, - }), - ); - - const groupsToBeAddedToFrame = new Set(); - - visibleElements.forEach((element) => { - if ( - element.groupIds.length > 0 && - appState.frameToHighlight && - appState.selectedElementIds[element.id] && - (elementOverlapsWithFrame(element, appState.frameToHighlight) || - element.groupIds.find((groupId) => - groupsToBeAddedToFrame.has(groupId), - )) - ) { - element.groupIds.forEach((groupId) => - groupsToBeAddedToFrame.add(groupId), - ); - } - }); - - let editingLinearElement: NonDeleted | undefined = - undefined; - - visibleElements.forEach((element) => { - try { - // - when exporting the whole canvas, we DO NOT apply clipping - // - when we are exporting a particular frame, apply clipping - // if the containing frame is not selected, apply clipping - const frameId = element.frameId || appState.frameToHighlight?.id; - - if ( - frameId && - ((renderConfig.isExporting && isOnlyExportingSingleFrame(elements)) || - (!renderConfig.isExporting && - appState.frameRendering.enabled && - appState.frameRendering.clip)) - ) { - context.save(); - - const frame = getTargetFrame(element, appState); - - if (frame && isElementInFrame(element, elements, appState)) { - frameClip(frame, context, renderConfig); - } - renderElement(element, rc, context, renderConfig, appState); - context.restore(); - } else { - renderElement(element, rc, context, renderConfig, appState); - } - // Getting the element using LinearElementEditor during collab mismatches version - being one head of visible elements due to - // ShapeCache returns empty hence making sure that we get the - // correct element from visible elements - if (appState.editingLinearElement?.elementId === element.id) { - if (element) { - editingLinearElement = - element as NonDeleted; - } - } - if (!isExporting) { - renderLinkIcon(element, context, appState); - } - } catch (error: any) { - console.error(error); - } - }); - - if (editingLinearElement) { - renderLinearPointHandles( - context, - appState, - renderConfig, - editingLinearElement, - ); - } - - // Paint selection element - if (appState.selectionElement) { - try { - renderElement( - appState.selectionElement, - rc, - context, - renderConfig, - appState, - ); - } catch (error: any) { - console.error(error); - } - } - - if (isBindingEnabled(appState)) { - appState.suggestedBindings - .filter((binding) => binding != null) - .forEach((suggestedBinding) => { - renderBindingHighlight(context, renderConfig, suggestedBinding!); - }); - } - - if (appState.frameToHighlight) { - renderFrameHighlight(context, renderConfig, appState.frameToHighlight); - } - - if (appState.elementsToHighlight) { - renderElementsBoxHighlight( - context, - renderConfig, - appState.elementsToHighlight, - appState, - ); - } - - const locallySelectedElements = getSelectedElements(elements, appState); - const isFrameSelected = locallySelectedElements.some((element) => - isFrameElement(element), - ); - - // Getting the element using LinearElementEditor during collab mismatches version - being one head of visible elements due to - // ShapeCache returns empty hence making sure that we get the - // correct element from visible elements - if ( - locallySelectedElements.length === 1 && - appState.editingLinearElement?.elementId === locallySelectedElements[0].id - ) { - renderLinearPointHandles( - context, - appState, - renderConfig, - locallySelectedElements[0] as NonDeleted, - ); - } - - if ( - appState.selectedLinearElement && - appState.selectedLinearElement.hoverPointIndex >= 0 - ) { - renderLinearElementPointHighlight(context, appState, renderConfig); - } - // Paint selected elements - if ( - renderSelection && - !appState.multiElement && - !appState.editingLinearElement - ) { - const showBoundingBox = shouldShowBoundingBox( - locallySelectedElements, - appState, - ); - - const locallySelectedIds = locallySelectedElements.map( - (element) => element.id, - ); - const isSingleLinearElementSelected = - locallySelectedElements.length === 1 && - isLinearElement(locallySelectedElements[0]); - // render selected linear element points - if ( - isSingleLinearElementSelected && - appState.selectedLinearElement?.elementId === - locallySelectedElements[0].id && - !locallySelectedElements[0].locked - ) { - renderLinearPointHandles( - context, - appState, - renderConfig, - locallySelectedElements[0] as ExcalidrawLinearElement, - ); - } - if (showBoundingBox) { - const selections = elements.reduce((acc, element) => { - const selectionColors = []; - // local user - if ( - locallySelectedIds.includes(element.id) && - !isSelectedViaGroup(appState, element) - ) { - selectionColors.push(selectionColor); - } - // remote users - if (renderConfig.remoteSelectedElementIds[element.id]) { - selectionColors.push( - ...renderConfig.remoteSelectedElementIds[element.id].map( - (socketId) => { - const background = getClientColor(socketId); - return background; - }, - ), - ); - } - - if (selectionColors.length) { - const [elementX1, elementY1, elementX2, elementY2, cx, cy] = - getElementAbsoluteCoords(element, true); - acc.push({ - angle: element.angle, - elementX1, - elementY1, - elementX2, - elementY2, - selectionColors, - dashed: !!renderConfig.remoteSelectedElementIds[element.id], - cx, - cy, - }); - } - return acc; - }, [] as { angle: number; elementX1: number; elementY1: number; elementX2: number; elementY2: number; selectionColors: string[]; dashed?: boolean; cx: number; cy: number }[]); - - const addSelectionForGroupId = (groupId: GroupId) => { - const groupElements = getElementsInGroup(elements, groupId); - const [elementX1, elementY1, elementX2, elementY2] = - getCommonBounds(groupElements); - selections.push({ - angle: 0, - elementX1, - elementX2, - elementY1, - elementY2, - selectionColors: [oc.black], - dashed: true, - cx: elementX1 + (elementX2 - elementX1) / 2, - cy: elementY1 + (elementY2 - elementY1) / 2, - }); - }; - - for (const groupId of getSelectedGroupIds(appState)) { - // TODO: support multiplayer selected group IDs - addSelectionForGroupId(groupId); - } - - if (appState.editingGroupId) { - addSelectionForGroupId(appState.editingGroupId); - } - - selections.forEach((selection) => - renderSelectionBorder(context, renderConfig, selection), - ); - } - // Paint resize transformHandles - context.save(); - context.translate(renderConfig.scrollX, renderConfig.scrollY); - - if (locallySelectedElements.length === 1) { - context.fillStyle = oc.white; - const transformHandles = getTransformHandles( - locallySelectedElements[0], - renderConfig.zoom, - "mouse", // when we render we don't know which pointer type so use mouse - ); - if (!appState.viewModeEnabled && showBoundingBox) { - renderTransformHandles( - context, - renderConfig, - transformHandles, - locallySelectedElements[0].angle, - ); - } - } else if (locallySelectedElements.length > 1 && !appState.isRotating) { - const dashedLinePadding = - (DEFAULT_SPACING * 2) / renderConfig.zoom.value; - context.fillStyle = oc.white; - const [x1, y1, x2, y2] = getCommonBounds(locallySelectedElements); - const initialLineDash = context.getLineDash(); - context.setLineDash([2 / renderConfig.zoom.value]); - const lineWidth = context.lineWidth; - context.lineWidth = 1 / renderConfig.zoom.value; - context.strokeStyle = selectionColor; - strokeRectWithRotation( - context, - x1 - dashedLinePadding, - y1 - dashedLinePadding, - x2 - x1 + dashedLinePadding * 2, - y2 - y1 + dashedLinePadding * 2, - (x1 + x2) / 2, - (y1 + y2) / 2, - 0, - ); - context.lineWidth = lineWidth; - context.setLineDash(initialLineDash); - const transformHandles = getTransformHandlesFromCoords( - [x1, y1, x2, y2, (x1 + x2) / 2, (y1 + y2) / 2], - 0, - renderConfig.zoom, - "mouse", - isFrameSelected - ? OMIT_SIDES_FOR_FRAME - : OMIT_SIDES_FOR_MULTIPLE_ELEMENTS, - ); - if (locallySelectedElements.some((element) => !element.locked)) { - renderTransformHandles(context, renderConfig, transformHandles, 0); - } - } - context.restore(); - } - - // Reset zoom - context.restore(); - - // Paint remote pointers - for (const clientId in renderConfig.remotePointerViewportCoords) { - let { x, y } = renderConfig.remotePointerViewportCoords[clientId]; - - x -= appState.offsetLeft; - y -= appState.offsetTop; - - const width = 11; - const height = 14; - - const isOutOfBounds = - x < 0 || - x > normalizedCanvasWidth - width || - y < 0 || - y > normalizedCanvasHeight - height; - - x = Math.max(x, 0); - x = Math.min(x, normalizedCanvasWidth - width); - y = Math.max(y, 0); - y = Math.min(y, normalizedCanvasHeight - height); - - const background = getClientColor(clientId); - - context.save(); - context.strokeStyle = background; - context.fillStyle = background; - - const userState = renderConfig.remotePointerUserStates[clientId]; - const isInactive = - isOutOfBounds || - userState === UserIdleState.IDLE || - userState === UserIdleState.AWAY; - - if (isInactive) { - context.globalAlpha = 0.3; - } - - if ( - renderConfig.remotePointerButton && - renderConfig.remotePointerButton[clientId] === "down" - ) { - context.beginPath(); - context.arc(x, y, 15, 0, 2 * Math.PI, false); - context.lineWidth = 3; - context.strokeStyle = "#ffffff88"; - context.stroke(); - context.closePath(); - - context.beginPath(); - context.arc(x, y, 15, 0, 2 * Math.PI, false); - context.lineWidth = 1; - context.strokeStyle = background; - context.stroke(); - context.closePath(); - } - - // Background (white outline) for arrow - context.fillStyle = oc.white; - context.strokeStyle = oc.white; - context.lineWidth = 6; - context.lineJoin = "round"; - context.beginPath(); - context.moveTo(x, y); - context.lineTo(x + 0, y + 14); - context.lineTo(x + 4, y + 9); - context.lineTo(x + 11, y + 8); - context.closePath(); - context.stroke(); - context.fill(); - - // Arrow - context.fillStyle = background; - context.strokeStyle = background; - context.lineWidth = 2; - context.lineJoin = "round"; - context.beginPath(); - if (isInactive) { - context.moveTo(x - 1, y - 1); - context.lineTo(x - 1, y + 15); - context.lineTo(x + 5, y + 10); - context.lineTo(x + 12, y + 9); - context.closePath(); - context.fill(); - } else { - context.moveTo(x, y); - context.lineTo(x + 0, y + 14); - context.lineTo(x + 4, y + 9); - context.lineTo(x + 11, y + 8); - context.closePath(); - context.fill(); - context.stroke(); - } - - const username = renderConfig.remotePointerUsernames[clientId] || ""; - - if (!isOutOfBounds && username) { - context.font = "600 12px sans-serif"; // font has to be set before context.measureText() - - const offsetX = x + width / 2; - const offsetY = y + height + 2; - const paddingHorizontal = 5; - const paddingVertical = 3; - const measure = context.measureText(username); - const measureHeight = - measure.actualBoundingBoxDescent + measure.actualBoundingBoxAscent; - const finalHeight = Math.max(measureHeight, 12); - - const boxX = offsetX - 1; - const boxY = offsetY - 1; - const boxWidth = measure.width + 2 + paddingHorizontal * 2 + 2; - const boxHeight = finalHeight + 2 + paddingVertical * 2 + 2; - if (context.roundRect) { - context.beginPath(); - context.roundRect(boxX, boxY, boxWidth, boxHeight, 8); - context.fillStyle = background; - context.fill(); - context.strokeStyle = oc.white; - context.stroke(); - } else { - roundRect(context, boxX, boxY, boxWidth, boxHeight, 8, oc.white); - } - context.fillStyle = oc.black; - - context.fillText( - username, - offsetX + paddingHorizontal + 1, - offsetY + - paddingVertical + - measure.actualBoundingBoxAscent + - Math.floor((finalHeight - measureHeight) / 2) + - 2, - ); - } - - context.restore(); - context.closePath(); - } - - // Paint scrollbars - let scrollBars; - if (renderScrollbars) { - scrollBars = getScrollBars( - elements, - normalizedCanvasWidth, - normalizedCanvasHeight, - renderConfig, - ); - - context.save(); - context.fillStyle = SCROLLBAR_COLOR; - context.strokeStyle = "rgba(255,255,255,0.8)"; - [scrollBars.horizontal, scrollBars.vertical].forEach((scrollBar) => { - if (scrollBar) { - roundRect( - context, - scrollBar.x, - scrollBar.y, - scrollBar.width, - scrollBar.height, - SCROLLBAR_WIDTH / 2, - ); - } - }); - context.restore(); - } - - context.restore(); - return { atLeastOneVisibleElement: visibleElements.length > 0, scrollBars }; - }; - -const renderSceneThrottled = throttleRAF( - (config: { - elements: readonly NonDeletedExcalidrawElement[]; - appState: AppState; - scale: number; - rc: RoughCanvas; - canvas: HTMLCanvasElement; - renderConfig: RenderConfig; - callback?: (data: ReturnType) => void; - }) => { - const ret = _renderScene(config); - config.callback?.(ret); - }, - { trailing: true }, -); - -/** renderScene throttled to animation framerate */ -export const renderScene = ( - config: { - elements: readonly NonDeletedExcalidrawElement[]; - appState: AppState; - scale: number; - rc: RoughCanvas; - canvas: HTMLCanvasElement; - renderConfig: RenderConfig; - callback?: (data: ReturnType) => void; - }, - /** Whether to throttle rendering. Defaults to false. - * When throttling, no value is returned. Use the callback instead. */ - throttle?: T, -): T extends true ? void : ReturnType => { - if (throttle) { - renderSceneThrottled(config); - return undefined as T extends true ? void : ReturnType; +}: StaticSceneRenderConfig) => { + if (canvas === null) { + return; } - const ret = _renderScene(config); - config.callback?.(ret); - return ret as T extends true ? void : ReturnType; + + const { renderGrid = true, isExporting } = renderConfig; + + const [normalizedWidth, normalizedHeight] = getNormalizedCanvasDimensions( + canvas, + scale, + ); + + const context = bootstrapCanvas({ + canvas, + scale, + normalizedWidth, + normalizedHeight, + zoom: appState.zoom, + theme: appState.theme, + isExporting, + viewBackgroundColor: appState.viewBackgroundColor, + }); + + // Grid + if (renderGrid && appState.gridSize) { + strokeGrid( + context, + appState.gridSize, + -Math.ceil(appState.zoom.value / appState.gridSize) * appState.gridSize + + (appState.scrollX % appState.gridSize), + -Math.ceil(appState.zoom.value / appState.gridSize) * appState.gridSize + + (appState.scrollY % appState.gridSize), + normalizedWidth / appState.zoom.value, + normalizedHeight / appState.zoom.value, + ); + } + + // Paint visible elements + const visibleElements = elements.filter((element) => + isVisibleElement(element, normalizedWidth, normalizedHeight, { + zoom: appState.zoom, + offsetLeft: appState.offsetLeft, + offsetTop: appState.offsetTop, + scrollX: appState.scrollX, + scrollY: appState.scrollY, + }), + ); + + const groupsToBeAddedToFrame = new Set(); + + visibleElements.forEach((element) => { + if ( + element.groupIds.length > 0 && + appState.frameToHighlight && + appState.selectedElementIds[element.id] && + (elementOverlapsWithFrame(element, appState.frameToHighlight) || + element.groupIds.find((groupId) => groupsToBeAddedToFrame.has(groupId))) + ) { + element.groupIds.forEach((groupId) => + groupsToBeAddedToFrame.add(groupId), + ); + } + }); + + visibleElements.forEach((element) => { + try { + // - when exporting the whole canvas, we DO NOT apply clipping + // - when we are exporting a particular frame, apply clipping + // if the containing frame is not selected, apply clipping + const frameId = element.frameId || appState.frameToHighlight?.id; + + if ( + frameId && + ((renderConfig.isExporting && isOnlyExportingSingleFrame(elements)) || + (!renderConfig.isExporting && + appState.frameRendering.enabled && + appState.frameRendering.clip)) + ) { + context.save(); + + const frame = getTargetFrame(element, appState); + + if (frame && isElementInFrame(element, elements, appState)) { + frameClip(frame, context, renderConfig, appState); + } + renderElement(element, rc, context, renderConfig, appState); + context.restore(); + } else { + renderElement(element, rc, context, renderConfig, appState); + } + if (!isExporting) { + renderLinkIcon(element, context, appState); + } + } catch (error: any) { + console.error(error); + } + }); + + context.restore(); +}; + +export const renderInteractiveScene = < + U extends typeof _renderInteractiveScene, + T extends boolean = false, +>( + renderConfig: InteractiveSceneRenderConfig, + throttle?: T, +): T extends true ? void : ReturnType => { + if (throttle) { + /** throttled to animation framerate */ + const renderFuncThrottled = throttleRAF( + (config) => { + const ret = _renderInteractiveScene(config); + config.callback?.(ret); + }, + { trailing: true }, + ); + + renderFuncThrottled(renderConfig); + return undefined as T extends true ? void : ReturnType; + } + const ret = _renderInteractiveScene(renderConfig); + renderConfig.callback(ret); + return ret as T extends true ? void : ReturnType; +}; + +export const renderStaticScene = ( + renderConfig: StaticSceneRenderConfig, + throttle?: boolean, +) => { + if (throttle) { + /** throttled to animation framerate */ + const renderFuncThrottled = throttleRAF( + (config) => { + _renderStaticScene(config); + }, + { trailing: true }, + ); + + renderFuncThrottled(renderConfig); + return; + } + + _renderStaticScene(renderConfig); }; const renderTransformHandles = ( context: CanvasRenderingContext2D, - renderConfig: RenderConfig, + renderConfig: InteractiveCanvasRenderConfig, + appState: InteractiveCanvasAppState, transformHandles: TransformHandles, angle: number, ): void => { @@ -957,7 +1032,7 @@ const renderTransformHandles = ( const [x, y, width, height] = transformHandle; context.save(); - context.lineWidth = 1 / renderConfig.zoom.value; + context.lineWidth = 1 / appState.zoom.value; if (renderConfig.selectionColor) { context.strokeStyle = renderConfig.selectionColor; } @@ -966,7 +1041,7 @@ const renderTransformHandles = ( // prefer round corners if roundRect API is available } else if (context.roundRect) { context.beginPath(); - context.roundRect(x, y, width, height, 2 / renderConfig.zoom.value); + context.roundRect(x, y, width, height, 2 / appState.zoom.value); context.fill(); context.stroke(); } else { @@ -989,7 +1064,7 @@ const renderTransformHandles = ( const renderSelectionBorder = ( context: CanvasRenderingContext2D, - renderConfig: RenderConfig, + appState: InteractiveCanvasAppState, elementProperties: { angle: number; elementX1: number; @@ -1017,13 +1092,13 @@ const renderSelectionBorder = ( const elementWidth = elementX2 - elementX1; const elementHeight = elementY2 - elementY1; - const linePadding = padding / renderConfig.zoom.value; - const lineWidth = 8 / renderConfig.zoom.value; - const spaceWidth = 4 / renderConfig.zoom.value; + const linePadding = padding / appState.zoom.value; + const lineWidth = 8 / appState.zoom.value; + const spaceWidth = 4 / appState.zoom.value; context.save(); - context.translate(renderConfig.scrollX, renderConfig.scrollY); - context.lineWidth = 1 / renderConfig.zoom.value; + context.translate(appState.scrollX, appState.scrollY); + context.lineWidth = 1 / appState.zoom.value; const count = selectionColors.length; for (let index = 0; index < count; ++index) { @@ -1051,7 +1126,7 @@ const renderSelectionBorder = ( const renderBindingHighlight = ( context: CanvasRenderingContext2D, - renderConfig: RenderConfig, + appState: InteractiveCanvasAppState, suggestedBinding: SuggestedBinding, ) => { const renderHighlight = Array.isArray(suggestedBinding) @@ -1059,7 +1134,7 @@ const renderBindingHighlight = ( : renderBindingHighlightForBindableElement; context.save(); - context.translate(renderConfig.scrollX, renderConfig.scrollY); + context.translate(appState.scrollX, appState.scrollY); renderHighlight(context, suggestedBinding as any); context.restore(); @@ -1124,7 +1199,7 @@ const renderBindingHighlightForBindableElement = ( const renderFrameHighlight = ( context: CanvasRenderingContext2D, - renderConfig: RenderConfig, + appState: InteractiveCanvasAppState, frame: NonDeleted, ) => { const [x1, y1, x2, y2] = getElementAbsoluteCoords(frame); @@ -1132,10 +1207,10 @@ const renderFrameHighlight = ( const height = y2 - y1; context.strokeStyle = "rgb(0,118,255)"; - context.lineWidth = (FRAME_STYLE.strokeWidth * 2) / renderConfig.zoom.value; + context.lineWidth = (FRAME_STYLE.strokeWidth * 2) / appState.zoom.value; context.save(); - context.translate(renderConfig.scrollX, renderConfig.scrollY); + context.translate(appState.scrollX, appState.scrollY); strokeRectWithRotation( context, x1, @@ -1146,16 +1221,15 @@ const renderFrameHighlight = ( y1 + height / 2, frame.angle, false, - FRAME_STYLE.radius / renderConfig.zoom.value, + FRAME_STYLE.radius / appState.zoom.value, ); context.restore(); }; const renderElementsBoxHighlight = ( context: CanvasRenderingContext2D, - renderConfig: RenderConfig, + appState: InteractiveCanvasAppState, elements: NonDeleted[], - appState: AppState, ) => { const individualElements = elements.filter( (element) => element.groupIds.length === 0, @@ -1194,7 +1268,7 @@ const renderElementsBoxHighlight = ( individualElements.map((element) => getSelectionFromElements([element])), ) .forEach((selection) => - renderSelectionBorder(context, renderConfig, selection), + renderSelectionBorder(context, appState, selection), ); }; @@ -1228,7 +1302,7 @@ let linkCanvasCache: any; const renderLinkIcon = ( element: NonDeletedExcalidrawElement, context: CanvasRenderingContext2D, - appState: AppState, + appState: StaticCanvasAppState, ) => { if (element.link && !appState.selectedElementIds[element.id]) { const [x1, y1, x2, y2] = getElementAbsoluteCoords(element); diff --git a/src/scene/Scene.ts b/src/scene/Scene.ts index 5b5b7970b..e64da78fc 100644 --- a/src/scene/Scene.ts +++ b/src/scene/Scene.ts @@ -14,6 +14,7 @@ import { isFrameElement } from "../element/typeChecks"; import { getSelectedElements } from "./selection"; import { AppState } from "../types"; import { Assert, SameType } from "../utility-types"; +import { randomInteger } from "../random"; type ElementIdKey = InstanceType["elementId"]; type ElementKey = ExcalidrawElement | ElementIdKey; @@ -105,6 +106,7 @@ class Scene { elements: null, cache: new Map(), }; + private versionNonce: number | undefined; getElementsIncludingDeleted() { return this.elements; @@ -172,6 +174,10 @@ class Scene { return (this.elementsMap.get(id) as T | undefined) || null; } + getVersionNonce() { + return this.versionNonce; + } + getNonDeletedElement( id: ExcalidrawElement["id"], ): NonDeleted | null { @@ -230,6 +236,8 @@ class Scene { } informMutation() { + this.versionNonce = randomInteger(); + for (const callback of Array.from(this.callbacks)) { callback(); } diff --git a/src/scene/export.ts b/src/scene/export.ts index 78fc9b466..985602289 100644 --- a/src/scene/export.ts +++ b/src/scene/export.ts @@ -1,7 +1,7 @@ import rough from "roughjs/bin/rough"; import { NonDeletedExcalidrawElement } from "../element/types"; import { getCommonBounds, getElementAbsoluteCoords } from "../element/bounds"; -import { renderScene, renderSceneToSvg } from "../renderer/renderScene"; +import { renderSceneToSvg, renderStaticScene } from "../renderer/renderScene"; import { distance, isOnlyExportingSingleFrame } from "../utils"; import { AppState, BinaryFiles } from "../types"; import { DEFAULT_EXPORT_PADDING, SVG_NS, THEME_FILTER } from "../constants"; @@ -54,26 +54,21 @@ export const exportToCanvas = async ( const onlyExportingSingleFrame = isOnlyExportingSingleFrame(elements); - renderScene({ + renderStaticScene({ elements, - appState, + appState: { + ...appState, + scrollX: -minX + (onlyExportingSingleFrame ? 0 : exportPadding), + scrollY: -minY + (onlyExportingSingleFrame ? 0 : exportPadding), + zoom: defaultAppState.zoom, + shouldCacheIgnoreZoom: false, + theme: appState.exportWithDarkMode ? "dark" : "light", + }, scale, rc: rough.canvas(canvas), canvas, renderConfig: { - viewBackgroundColor: exportBackground ? viewBackgroundColor : null, - scrollX: -minX + (onlyExportingSingleFrame ? 0 : exportPadding), - scrollY: -minY + (onlyExportingSingleFrame ? 0 : exportPadding), - zoom: defaultAppState.zoom, - remotePointerViewportCoords: {}, - remoteSelectedElementIds: {}, - shouldCacheIgnoreZoom: false, - remotePointerUsernames: {}, - remotePointerUserStates: {}, - theme: appState.exportWithDarkMode ? "dark" : "light", imageCache, - renderScrollbars: false, - renderSelection: false, renderGrid: false, isExporting: true, }, diff --git a/src/scene/scroll.ts b/src/scene/scroll.ts index 114d6db05..b5b8176e1 100644 --- a/src/scene/scroll.ts +++ b/src/scene/scroll.ts @@ -11,11 +11,7 @@ import { viewportCoordsToSceneCoords, } from "../utils"; -const isOutsideViewPort = ( - appState: AppState, - canvas: HTMLCanvasElement | null, - cords: Array, -) => { +const isOutsideViewPort = (appState: AppState, cords: Array) => { const [x1, y1, x2, y2] = cords; const { x: viewportX1, y: viewportY1 } = sceneCoordsToViewportCoords( { sceneX: x1, sceneY: y1 }, @@ -49,7 +45,6 @@ export const centerScrollOn = ({ export const calculateScrollCenter = ( elements: readonly ExcalidrawElement[], appState: AppState, - canvas: HTMLCanvasElement | null, ): { scrollX: number; scrollY: number } => { elements = getVisibleElements(elements); @@ -61,7 +56,7 @@ export const calculateScrollCenter = ( } let [x1, y1, x2, y2] = getCommonBounds(elements); - if (isOutsideViewPort(appState, canvas, [x1, y1, x2, y2])) { + if (isOutsideViewPort(appState, [x1, y1, x2, y2])) { [x1, y1, x2, y2] = getClosestElementBounds( elements, viewportCoordsToSceneCoords( diff --git a/src/scene/scrollbars.ts b/src/scene/scrollbars.ts index 76a04d606..1d93f688f 100644 --- a/src/scene/scrollbars.ts +++ b/src/scene/scrollbars.ts @@ -1,6 +1,6 @@ import { ExcalidrawElement } from "../element/types"; import { getCommonBounds } from "../element"; -import { Zoom } from "../types"; +import { InteractiveCanvasAppState } from "../types"; import { ScrollBars } from "./types"; import { getGlobalCSSVariable } from "../utils"; import { getLanguage } from "../i18n"; @@ -13,15 +13,7 @@ export const getScrollBars = ( elements: readonly ExcalidrawElement[], viewportWidth: number, viewportHeight: number, - { - scrollX, - scrollY, - zoom, - }: { - scrollX: number; - scrollY: number; - zoom: Zoom; - }, + appState: InteractiveCanvasAppState, ): ScrollBars => { if (elements.length === 0) { return { @@ -34,8 +26,8 @@ export const getScrollBars = ( getCommonBounds(elements); // Apply zoom - const viewportWidthWithZoom = viewportWidth / zoom.value; - const viewportHeightWithZoom = viewportHeight / zoom.value; + const viewportWidthWithZoom = viewportWidth / appState.zoom.value; + const viewportHeightWithZoom = viewportHeight / appState.zoom.value; const viewportWidthDiff = viewportWidth - viewportWidthWithZoom; const viewportHeightDiff = viewportHeight - viewportHeightWithZoom; @@ -50,8 +42,10 @@ export const getScrollBars = ( const isRTL = getLanguage().rtl; // The viewport is the rectangle currently visible for the user - const viewportMinX = -scrollX + viewportWidthDiff / 2 + safeArea.left; - const viewportMinY = -scrollY + viewportHeightDiff / 2 + safeArea.top; + const viewportMinX = + -appState.scrollX + viewportWidthDiff / 2 + safeArea.left; + const viewportMinY = + -appState.scrollY + viewportHeightDiff / 2 + safeArea.top; const viewportMaxX = viewportMinX + viewportWidthWithZoom - safeArea.right; const viewportMaxY = viewportMinY + viewportHeightWithZoom - safeArea.bottom; diff --git a/src/scene/selection.ts b/src/scene/selection.ts index bbb629d3c..e678894ab 100644 --- a/src/scene/selection.ts +++ b/src/scene/selection.ts @@ -3,7 +3,7 @@ import { NonDeletedExcalidrawElement, } from "../element/types"; import { getElementAbsoluteCoords, getElementBounds } from "../element"; -import { AppState } from "../types"; +import { AppState, InteractiveCanvasAppState } from "../types"; import { isBoundToContainer } from "../element/typeChecks"; import { elementOverlapsWithFrame, @@ -146,7 +146,7 @@ export const getCommonAttributeOfSelectedElements = ( export const getSelectedElements = ( elements: readonly NonDeletedExcalidrawElement[], - appState: Pick, + appState: Pick, opts?: { includeBoundTextElement?: boolean; includeElementsInFrames?: boolean; diff --git a/src/scene/types.ts b/src/scene/types.ts index a54b02b26..148773055 100644 --- a/src/scene/types.ts +++ b/src/scene/types.ts @@ -1,33 +1,60 @@ -import { ExcalidrawTextElement } from "../element/types"; -import { AppClassProperties, AppState } from "../types"; +import { RoughCanvas } from "roughjs/bin/canvas"; +import { + ExcalidrawTextElement, + NonDeletedExcalidrawElement, +} from "../element/types"; +import { + AppClassProperties, + InteractiveCanvasAppState, + StaticCanvasAppState, +} from "../types"; -export type RenderConfig = { - // AppState values - // --------------------------------------------------------------------------- - scrollX: AppState["scrollX"]; - scrollY: AppState["scrollY"]; - /** null indicates transparent bg */ - viewBackgroundColor: AppState["viewBackgroundColor"] | null; - zoom: AppState["zoom"]; - shouldCacheIgnoreZoom: AppState["shouldCacheIgnoreZoom"]; - theme: AppState["theme"]; - // collab-related state - // --------------------------------------------------------------------------- - remotePointerViewportCoords: { [id: string]: { x: number; y: number } }; - remotePointerButton?: { [id: string]: string | undefined }; - remoteSelectedElementIds: { [elementId: string]: string[] }; - remotePointerUsernames: { [id: string]: string }; - remotePointerUserStates: { [id: string]: string }; +export type StaticCanvasRenderConfig = { // extra options passed to the renderer // --------------------------------------------------------------------------- imageCache: AppClassProperties["imageCache"]; - renderScrollbars?: boolean; - renderSelection?: boolean; - renderGrid?: boolean; + renderGrid: boolean; /** when exporting the behavior is slightly different (e.g. we can't use - CSS filters), and we disable render optimizations for best output */ + CSS filters), and we disable render optimizations for best output */ isExporting: boolean; +}; + +export type InteractiveCanvasRenderConfig = { + // collab-related state + // --------------------------------------------------------------------------- + remoteSelectedElementIds: { [elementId: string]: string[] }; + remotePointerViewportCoords: { [id: string]: { x: number; y: number } }; + remotePointerUserStates: { [id: string]: string }; + remotePointerUsernames: { [id: string]: string }; + remotePointerButton?: { [id: string]: string | undefined }; selectionColor?: string; + // extra options passed to the renderer + // --------------------------------------------------------------------------- + renderScrollbars?: boolean; +}; + +export type RenderInteractiveSceneCallback = { + atLeastOneVisibleElement: boolean; + elements: readonly NonDeletedExcalidrawElement[]; + scrollBars?: ScrollBars; +}; + +export type StaticSceneRenderConfig = { + elements: readonly NonDeletedExcalidrawElement[]; + scale: number; + canvas: HTMLCanvasElement | null; + rc: RoughCanvas; + appState: StaticCanvasAppState; + renderConfig: StaticCanvasRenderConfig; +}; + +export type InteractiveSceneRenderConfig = { + elements: readonly NonDeletedExcalidrawElement[]; + canvas: HTMLCanvasElement | null; + scale: number; + appState: InteractiveCanvasAppState; + renderConfig: InteractiveCanvasRenderConfig; + callback: (data: RenderInteractiveSceneCallback) => void; }; export type SceneScroll = { diff --git a/src/tests/__snapshots__/contextmenu.test.tsx.snap b/src/tests/__snapshots__/contextmenu.test.tsx.snap index 0049c9199..c59272932 100644 --- a/src/tests/__snapshots__/contextmenu.test.tsx.snap +++ b/src/tests/__snapshots__/contextmenu.test.tsx.snap @@ -396,7 +396,7 @@ Object { "roundness": Object { "type": 3, }, - "seed": 337897, + "seed": 1278240551, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, @@ -430,7 +430,7 @@ Object { "roundness": Object { "type": 3, }, - "seed": 1278240551, + "seed": 449462985, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, @@ -466,7 +466,7 @@ Object { exports[`contextMenu element right-clicking on a group should select whole group: [end of test] number of elements 1`] = `2`; -exports[`contextMenu element right-clicking on a group should select whole group: [end of test] number of renders 1`] = `7`; +exports[`contextMenu element right-clicking on a group should select whole group: [end of test] number of renders 1`] = `3`; exports[`contextMenu element selecting 'Add to library' in context menu adds element to library: [end of test] appState 1`] = ` Object { @@ -585,14 +585,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 1278240551, + "seed": 449462985, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 449462985, + "versionNonce": 401146281, "width": 20, "x": -10, "y": 0, @@ -644,14 +644,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 1278240551, + "seed": 449462985, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 449462985, + "versionNonce": 401146281, "width": 20, "x": -10, "y": 0, @@ -664,7 +664,7 @@ Object { exports[`contextMenu element selecting 'Add to library' in context menu adds element to library: [end of test] number of elements 1`] = `1`; -exports[`contextMenu element selecting 'Add to library' in context menu adds element to library: [end of test] number of renders 1`] = `14`; +exports[`contextMenu element selecting 'Add to library' in context menu adds element to library: [end of test] number of renders 1`] = `4`; exports[`contextMenu element selecting 'Bring forward' in context menu brings element forward: [end of test] appState 1`] = ` Object { @@ -781,14 +781,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 453191, + "seed": 1150084233, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 401146281, + "versionNonce": 1014066025, "width": 20, "x": 20, "y": 30, @@ -813,14 +813,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 1278240551, + "seed": 449462985, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 3, - "versionNonce": 2019559783, + "versionNonce": 400692809, "width": 20, "x": -10, "y": 0, @@ -872,14 +872,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 1278240551, + "seed": 449462985, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 449462985, + "versionNonce": 401146281, "width": 20, "x": -10, "y": 0, @@ -915,14 +915,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 1278240551, + "seed": 449462985, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 449462985, + "versionNonce": 401146281, "width": 20, "x": -10, "y": 0, @@ -944,14 +944,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 453191, + "seed": 1150084233, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 401146281, + "versionNonce": 1014066025, "width": 20, "x": 20, "y": 30, @@ -987,14 +987,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 453191, + "seed": 1150084233, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 401146281, + "versionNonce": 1014066025, "width": 20, "x": 20, "y": 30, @@ -1016,14 +1016,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 1278240551, + "seed": 449462985, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 3, - "versionNonce": 2019559783, + "versionNonce": 400692809, "width": 20, "x": -10, "y": 0, @@ -1036,7 +1036,7 @@ Object { exports[`contextMenu element selecting 'Bring forward' in context menu brings element forward: [end of test] number of elements 1`] = `2`; -exports[`contextMenu element selecting 'Bring forward' in context menu brings element forward: [end of test] number of renders 1`] = `18`; +exports[`contextMenu element selecting 'Bring forward' in context menu brings element forward: [end of test] number of renders 1`] = `7`; exports[`contextMenu element selecting 'Bring to front' in context menu brings element to front: [end of test] appState 1`] = ` Object { @@ -1153,14 +1153,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 453191, + "seed": 1150084233, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 401146281, + "versionNonce": 1014066025, "width": 20, "x": 20, "y": 30, @@ -1185,14 +1185,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 1278240551, + "seed": 449462985, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 3, - "versionNonce": 2019559783, + "versionNonce": 400692809, "width": 20, "x": -10, "y": 0, @@ -1244,14 +1244,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 1278240551, + "seed": 449462985, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 449462985, + "versionNonce": 401146281, "width": 20, "x": -10, "y": 0, @@ -1287,14 +1287,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 1278240551, + "seed": 449462985, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 449462985, + "versionNonce": 401146281, "width": 20, "x": -10, "y": 0, @@ -1316,14 +1316,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 453191, + "seed": 1150084233, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 401146281, + "versionNonce": 1014066025, "width": 20, "x": 20, "y": 30, @@ -1359,14 +1359,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 453191, + "seed": 1150084233, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 401146281, + "versionNonce": 1014066025, "width": 20, "x": 20, "y": 30, @@ -1388,14 +1388,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 1278240551, + "seed": 449462985, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 3, - "versionNonce": 2019559783, + "versionNonce": 400692809, "width": 20, "x": -10, "y": 0, @@ -1408,7 +1408,7 @@ Object { exports[`contextMenu element selecting 'Bring to front' in context menu brings element to front: [end of test] number of elements 1`] = `2`; -exports[`contextMenu element selecting 'Bring to front' in context menu brings element to front: [end of test] number of renders 1`] = `18`; +exports[`contextMenu element selecting 'Bring to front' in context menu brings element to front: [end of test] number of renders 1`] = `7`; exports[`contextMenu element selecting 'Copy styles' in context menu copies styles: [end of test] appState 1`] = ` Object { @@ -1527,14 +1527,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 1278240551, + "seed": 453191, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 449462985, + "versionNonce": 2019559783, "width": 20, "x": -10, "y": 0, @@ -1586,14 +1586,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 1278240551, + "seed": 453191, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 449462985, + "versionNonce": 2019559783, "width": 20, "x": -10, "y": 0, @@ -1606,7 +1606,7 @@ Object { exports[`contextMenu element selecting 'Copy styles' in context menu copies styles: [end of test] number of elements 1`] = `1`; -exports[`contextMenu element selecting 'Copy styles' in context menu copies styles: [end of test] number of renders 1`] = `14`; +exports[`contextMenu element selecting 'Copy styles' in context menu copies styles: [end of test] number of renders 1`] = `4`; exports[`contextMenu element selecting 'Delete' in context menu deletes element: [end of test] appState 1`] = ` Object { @@ -1721,14 +1721,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 1278240551, + "seed": 449462985, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 3, - "versionNonce": 453191, + "versionNonce": 1150084233, "width": 20, "x": -10, "y": 0, @@ -1780,14 +1780,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 1278240551, + "seed": 449462985, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 449462985, + "versionNonce": 401146281, "width": 20, "x": -10, "y": 0, @@ -1821,14 +1821,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 1278240551, + "seed": 449462985, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 3, - "versionNonce": 453191, + "versionNonce": 1150084233, "width": 20, "x": -10, "y": 0, @@ -1841,7 +1841,7 @@ Object { exports[`contextMenu element selecting 'Delete' in context menu deletes element: [end of test] number of elements 1`] = `1`; -exports[`contextMenu element selecting 'Delete' in context menu deletes element: [end of test] number of renders 1`] = `14`; +exports[`contextMenu element selecting 'Delete' in context menu deletes element: [end of test] number of renders 1`] = `5`; exports[`contextMenu element selecting 'Duplicate' in context menu duplicates element: [end of test] appState 1`] = ` Object { @@ -1958,14 +1958,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 1278240551, + "seed": 449462985, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 449462985, + "versionNonce": 401146281, "width": 20, "x": -10, "y": 0, @@ -1990,14 +1990,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 453191, + "seed": 1150084233, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 449462985, + "versionNonce": 401146281, "width": 20, "x": 0, "y": 10, @@ -2049,14 +2049,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 1278240551, + "seed": 449462985, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 449462985, + "versionNonce": 401146281, "width": 20, "x": -10, "y": 0, @@ -2092,14 +2092,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 1278240551, + "seed": 449462985, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 449462985, + "versionNonce": 401146281, "width": 20, "x": -10, "y": 0, @@ -2121,14 +2121,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 453191, + "seed": 1150084233, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 449462985, + "versionNonce": 401146281, "width": 20, "x": 0, "y": 10, @@ -2141,7 +2141,7 @@ Object { exports[`contextMenu element selecting 'Duplicate' in context menu duplicates element: [end of test] number of elements 1`] = `2`; -exports[`contextMenu element selecting 'Duplicate' in context menu duplicates element: [end of test] number of renders 1`] = `14`; +exports[`contextMenu element selecting 'Duplicate' in context menu duplicates element: [end of test] number of renders 1`] = `5`; exports[`contextMenu element selecting 'Group selection' in context menu groups selected elements: [end of test] appState 1`] = ` Object { @@ -2265,14 +2265,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 1278240551, + "seed": 449462985, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 3, - "versionNonce": 1150084233, + "versionNonce": 1604849351, "width": 20, "x": -10, "y": 0, @@ -2299,14 +2299,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 453191, + "seed": 1150084233, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 3, - "versionNonce": 1116226695, + "versionNonce": 1505387817, "width": 20, "x": 20, "y": 30, @@ -2358,14 +2358,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 1278240551, + "seed": 449462985, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 449462985, + "versionNonce": 401146281, "width": 20, "x": -10, "y": 0, @@ -2401,14 +2401,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 1278240551, + "seed": 449462985, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 449462985, + "versionNonce": 401146281, "width": 20, "x": -10, "y": 0, @@ -2430,14 +2430,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 453191, + "seed": 1150084233, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 401146281, + "versionNonce": 1014066025, "width": 20, "x": 20, "y": 30, @@ -2478,14 +2478,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 1278240551, + "seed": 449462985, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 3, - "versionNonce": 1150084233, + "versionNonce": 1604849351, "width": 20, "x": -10, "y": 0, @@ -2509,14 +2509,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 453191, + "seed": 1150084233, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 3, - "versionNonce": 1116226695, + "versionNonce": 1505387817, "width": 20, "x": 20, "y": 30, @@ -2529,7 +2529,7 @@ Object { exports[`contextMenu element selecting 'Group selection' in context menu groups selected elements: [end of test] number of elements 1`] = `2`; -exports[`contextMenu element selecting 'Group selection' in context menu groups selected elements: [end of test] number of renders 1`] = `20`; +exports[`contextMenu element selecting 'Group selection' in context menu groups selected elements: [end of test] number of renders 1`] = `7`; exports[`contextMenu element selecting 'Paste styles' in context menu pastes styles: [end of test] appState 1`] = ` Object { @@ -2648,14 +2648,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 1278240551, + "seed": 449462985, "strokeColor": "#e03131", "strokeStyle": "dotted", "strokeWidth": 2, "type": "rectangle", "updated": 1, "version": 3, - "versionNonce": 23633383, + "versionNonce": 406373543, "width": 20, "x": -10, "y": 0, @@ -2680,14 +2680,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 400692809, + "seed": 1006504105, "strokeColor": "#e03131", "strokeStyle": "dotted", "strokeWidth": 2, "type": "rectangle", "updated": 1, "version": 9, - "versionNonce": 1505387817, + "versionNonce": 1898319239, "width": 20, "x": 20, "y": 30, @@ -2739,79 +2739,7 @@ Object { "roundness": Object { "type": 3, }, - "seed": 1278240551, - "strokeColor": "#1e1e1e", - "strokeStyle": "solid", - "strokeWidth": 1, - "type": "rectangle", - "updated": 1, - "version": 2, - "versionNonce": 449462985, - "width": 20, - "x": -10, - "y": 0, - }, - ], - }, - Object { - "appState": Object { - "editingGroupId": null, - "editingLinearElement": null, - "name": "Untitled-201933152653", - "selectedElementIds": Object { - "id1": true, - }, - "selectedGroupIds": Object {}, - "viewBackgroundColor": "#ffffff", - }, - "elements": Array [ - Object { - "angle": 0, - "backgroundColor": "transparent", - "boundElements": null, - "fillStyle": "hachure", - "frameId": null, - "groupIds": Array [], - "height": 20, - "id": "id0", - "isDeleted": false, - "link": null, - "locked": false, - "opacity": 100, - "roughness": 1, - "roundness": Object { - "type": 3, - }, - "seed": 1278240551, - "strokeColor": "#1e1e1e", - "strokeStyle": "solid", - "strokeWidth": 1, - "type": "rectangle", - "updated": 1, - "version": 2, - "versionNonce": 449462985, - "width": 20, - "x": -10, - "y": 0, - }, - Object { - "angle": 0, - "backgroundColor": "transparent", - "boundElements": null, - "fillStyle": "hachure", - "frameId": null, - "groupIds": Array [], - "height": 20, - "id": "id1", - "isDeleted": false, - "link": null, - "locked": false, - "opacity": 100, - "roughness": 1, - "roundness": Object { - "type": 3, - }, - "seed": 453191, + "seed": 449462985, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, @@ -2820,8 +2748,8 @@ Object { "version": 2, "versionNonce": 401146281, "width": 20, - "x": 20, - "y": 30, + "x": -10, + "y": 0, }, ], }, @@ -2854,14 +2782,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 1278240551, + "seed": 449462985, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 449462985, + "versionNonce": 401146281, "width": 20, "x": -10, "y": 0, @@ -2883,229 +2811,13 @@ Object { "roundness": Object { "type": 3, }, - "seed": 453191, - "strokeColor": "#e03131", - "strokeStyle": "solid", - "strokeWidth": 1, - "type": "rectangle", - "updated": 1, - "version": 3, - "versionNonce": 2019559783, - "width": 20, - "x": 20, - "y": 30, - }, - ], - }, - Object { - "appState": Object { - "editingGroupId": null, - "editingLinearElement": null, - "name": "Untitled-201933152653", - "selectedElementIds": Object { - "id1": true, - }, - "selectedGroupIds": Object {}, - "viewBackgroundColor": "#ffffff", - }, - "elements": Array [ - Object { - "angle": 0, - "backgroundColor": "transparent", - "boundElements": null, - "fillStyle": "hachure", - "frameId": null, - "groupIds": Array [], - "height": 20, - "id": "id0", - "isDeleted": false, - "link": null, - "locked": false, - "opacity": 100, - "roughness": 1, - "roundness": Object { - "type": 3, - }, - "seed": 1278240551, + "seed": 1150084233, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 449462985, - "width": 20, - "x": -10, - "y": 0, - }, - Object { - "angle": 0, - "backgroundColor": "#a5d8ff", - "boundElements": null, - "fillStyle": "hachure", - "frameId": null, - "groupIds": Array [], - "height": 20, - "id": "id1", - "isDeleted": false, - "link": null, - "locked": false, - "opacity": 100, - "roughness": 1, - "roundness": Object { - "type": 3, - }, - "seed": 453191, - "strokeColor": "#e03131", - "strokeStyle": "solid", - "strokeWidth": 1, - "type": "rectangle", - "updated": 1, - "version": 4, - "versionNonce": 1150084233, - "width": 20, - "x": 20, - "y": 30, - }, - ], - }, - Object { - "appState": Object { - "editingGroupId": null, - "editingLinearElement": null, - "name": "Untitled-201933152653", - "selectedElementIds": Object { - "id1": true, - }, - "selectedGroupIds": Object {}, - "viewBackgroundColor": "#ffffff", - }, - "elements": Array [ - Object { - "angle": 0, - "backgroundColor": "transparent", - "boundElements": null, - "fillStyle": "hachure", - "frameId": null, - "groupIds": Array [], - "height": 20, - "id": "id0", - "isDeleted": false, - "link": null, - "locked": false, - "opacity": 100, - "roughness": 1, - "roundness": Object { - "type": 3, - }, - "seed": 1278240551, - "strokeColor": "#1e1e1e", - "strokeStyle": "solid", - "strokeWidth": 1, - "type": "rectangle", - "updated": 1, - "version": 2, - "versionNonce": 449462985, - "width": 20, - "x": -10, - "y": 0, - }, - Object { - "angle": 0, - "backgroundColor": "#a5d8ff", - "boundElements": null, - "fillStyle": "cross-hatch", - "frameId": null, - "groupIds": Array [], - "height": 20, - "id": "id1", - "isDeleted": false, - "link": null, - "locked": false, - "opacity": 100, - "roughness": 1, - "roundness": Object { - "type": 3, - }, - "seed": 453191, - "strokeColor": "#e03131", - "strokeStyle": "solid", - "strokeWidth": 1, - "type": "rectangle", - "updated": 1, - "version": 5, - "versionNonce": 1116226695, - "width": 20, - "x": 20, - "y": 30, - }, - ], - }, - Object { - "appState": Object { - "editingGroupId": null, - "editingLinearElement": null, - "name": "Untitled-201933152653", - "selectedElementIds": Object { - "id1": true, - }, - "selectedGroupIds": Object {}, - "viewBackgroundColor": "#ffffff", - }, - "elements": Array [ - Object { - "angle": 0, - "backgroundColor": "transparent", - "boundElements": null, - "fillStyle": "hachure", - "frameId": null, - "groupIds": Array [], - "height": 20, - "id": "id0", - "isDeleted": false, - "link": null, - "locked": false, - "opacity": 100, - "roughness": 1, - "roundness": Object { - "type": 3, - }, - "seed": 1278240551, - "strokeColor": "#1e1e1e", - "strokeStyle": "solid", - "strokeWidth": 1, - "type": "rectangle", - "updated": 1, - "version": 2, - "versionNonce": 449462985, - "width": 20, - "x": -10, - "y": 0, - }, - Object { - "angle": 0, - "backgroundColor": "#a5d8ff", - "boundElements": null, - "fillStyle": "cross-hatch", - "frameId": null, - "groupIds": Array [], - "height": 20, - "id": "id1", - "isDeleted": false, - "link": null, - "locked": false, - "opacity": 100, - "roughness": 1, - "roundness": Object { - "type": 3, - }, - "seed": 453191, - "strokeColor": "#e03131", - "strokeStyle": "solid", - "strokeWidth": 2, - "type": "rectangle", - "updated": 1, - "version": 6, "versionNonce": 1014066025, "width": 20, "x": 20, @@ -3142,23 +2854,23 @@ Object { "roundness": Object { "type": 3, }, - "seed": 1278240551, + "seed": 449462985, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 449462985, + "versionNonce": 401146281, "width": 20, "x": -10, "y": 0, }, Object { "angle": 0, - "backgroundColor": "#a5d8ff", + "backgroundColor": "transparent", "boundElements": null, - "fillStyle": "cross-hatch", + "fillStyle": "hachure", "frameId": null, "groupIds": Array [], "height": 20, @@ -3171,14 +2883,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 453191, + "seed": 1150084233, "strokeColor": "#e03131", - "strokeStyle": "dotted", - "strokeWidth": 2, + "strokeStyle": "solid", + "strokeWidth": 1, "type": "rectangle", "updated": 1, - "version": 7, - "versionNonce": 238820263, + "version": 3, + "versionNonce": 400692809, "width": 20, "x": 20, "y": 30, @@ -3214,14 +2926,302 @@ Object { "roundness": Object { "type": 3, }, - "seed": 1278240551, + "seed": 449462985, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 449462985, + "versionNonce": 401146281, + "width": 20, + "x": -10, + "y": 0, + }, + Object { + "angle": 0, + "backgroundColor": "#a5d8ff", + "boundElements": null, + "fillStyle": "hachure", + "frameId": null, + "groupIds": Array [], + "height": 20, + "id": "id1", + "isDeleted": false, + "link": null, + "locked": false, + "opacity": 100, + "roughness": 1, + "roundness": Object { + "type": 3, + }, + "seed": 1150084233, + "strokeColor": "#e03131", + "strokeStyle": "solid", + "strokeWidth": 1, + "type": "rectangle", + "updated": 1, + "version": 4, + "versionNonce": 1505387817, + "width": 20, + "x": 20, + "y": 30, + }, + ], + }, + Object { + "appState": Object { + "editingGroupId": null, + "editingLinearElement": null, + "name": "Untitled-201933152653", + "selectedElementIds": Object { + "id1": true, + }, + "selectedGroupIds": Object {}, + "viewBackgroundColor": "#ffffff", + }, + "elements": Array [ + Object { + "angle": 0, + "backgroundColor": "transparent", + "boundElements": null, + "fillStyle": "hachure", + "frameId": null, + "groupIds": Array [], + "height": 20, + "id": "id0", + "isDeleted": false, + "link": null, + "locked": false, + "opacity": 100, + "roughness": 1, + "roundness": Object { + "type": 3, + }, + "seed": 449462985, + "strokeColor": "#1e1e1e", + "strokeStyle": "solid", + "strokeWidth": 1, + "type": "rectangle", + "updated": 1, + "version": 2, + "versionNonce": 401146281, + "width": 20, + "x": -10, + "y": 0, + }, + Object { + "angle": 0, + "backgroundColor": "#a5d8ff", + "boundElements": null, + "fillStyle": "cross-hatch", + "frameId": null, + "groupIds": Array [], + "height": 20, + "id": "id1", + "isDeleted": false, + "link": null, + "locked": false, + "opacity": 100, + "roughness": 1, + "roundness": Object { + "type": 3, + }, + "seed": 1150084233, + "strokeColor": "#e03131", + "strokeStyle": "solid", + "strokeWidth": 1, + "type": "rectangle", + "updated": 1, + "version": 5, + "versionNonce": 493213705, + "width": 20, + "x": 20, + "y": 30, + }, + ], + }, + Object { + "appState": Object { + "editingGroupId": null, + "editingLinearElement": null, + "name": "Untitled-201933152653", + "selectedElementIds": Object { + "id1": true, + }, + "selectedGroupIds": Object {}, + "viewBackgroundColor": "#ffffff", + }, + "elements": Array [ + Object { + "angle": 0, + "backgroundColor": "transparent", + "boundElements": null, + "fillStyle": "hachure", + "frameId": null, + "groupIds": Array [], + "height": 20, + "id": "id0", + "isDeleted": false, + "link": null, + "locked": false, + "opacity": 100, + "roughness": 1, + "roundness": Object { + "type": 3, + }, + "seed": 449462985, + "strokeColor": "#1e1e1e", + "strokeStyle": "solid", + "strokeWidth": 1, + "type": "rectangle", + "updated": 1, + "version": 2, + "versionNonce": 401146281, + "width": 20, + "x": -10, + "y": 0, + }, + Object { + "angle": 0, + "backgroundColor": "#a5d8ff", + "boundElements": null, + "fillStyle": "cross-hatch", + "frameId": null, + "groupIds": Array [], + "height": 20, + "id": "id1", + "isDeleted": false, + "link": null, + "locked": false, + "opacity": 100, + "roughness": 1, + "roundness": Object { + "type": 3, + }, + "seed": 1150084233, + "strokeColor": "#e03131", + "strokeStyle": "solid", + "strokeWidth": 2, + "type": "rectangle", + "updated": 1, + "version": 6, + "versionNonce": 81784553, + "width": 20, + "x": 20, + "y": 30, + }, + ], + }, + Object { + "appState": Object { + "editingGroupId": null, + "editingLinearElement": null, + "name": "Untitled-201933152653", + "selectedElementIds": Object { + "id1": true, + }, + "selectedGroupIds": Object {}, + "viewBackgroundColor": "#ffffff", + }, + "elements": Array [ + Object { + "angle": 0, + "backgroundColor": "transparent", + "boundElements": null, + "fillStyle": "hachure", + "frameId": null, + "groupIds": Array [], + "height": 20, + "id": "id0", + "isDeleted": false, + "link": null, + "locked": false, + "opacity": 100, + "roughness": 1, + "roundness": Object { + "type": 3, + }, + "seed": 449462985, + "strokeColor": "#1e1e1e", + "strokeStyle": "solid", + "strokeWidth": 1, + "type": "rectangle", + "updated": 1, + "version": 2, + "versionNonce": 401146281, + "width": 20, + "x": -10, + "y": 0, + }, + Object { + "angle": 0, + "backgroundColor": "#a5d8ff", + "boundElements": null, + "fillStyle": "cross-hatch", + "frameId": null, + "groupIds": Array [], + "height": 20, + "id": "id1", + "isDeleted": false, + "link": null, + "locked": false, + "opacity": 100, + "roughness": 1, + "roundness": Object { + "type": 3, + }, + "seed": 1150084233, + "strokeColor": "#e03131", + "strokeStyle": "dotted", + "strokeWidth": 2, + "type": "rectangle", + "updated": 1, + "version": 7, + "versionNonce": 1723083209, + "width": 20, + "x": 20, + "y": 30, + }, + ], + }, + Object { + "appState": Object { + "editingGroupId": null, + "editingLinearElement": null, + "name": "Untitled-201933152653", + "selectedElementIds": Object { + "id1": true, + }, + "selectedGroupIds": Object {}, + "viewBackgroundColor": "#ffffff", + }, + "elements": Array [ + Object { + "angle": 0, + "backgroundColor": "transparent", + "boundElements": null, + "fillStyle": "hachure", + "frameId": null, + "groupIds": Array [], + "height": 20, + "id": "id0", + "isDeleted": false, + "link": null, + "locked": false, + "opacity": 100, + "roughness": 1, + "roundness": Object { + "type": 3, + }, + "seed": 449462985, + "strokeColor": "#1e1e1e", + "strokeStyle": "solid", + "strokeWidth": 1, + "type": "rectangle", + "updated": 1, + "version": 2, + "versionNonce": 401146281, "width": 20, "x": -10, "y": 0, @@ -3243,14 +3243,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 400692809, + "seed": 1006504105, "strokeColor": "#e03131", "strokeStyle": "dotted", "strokeWidth": 2, "type": "rectangle", "updated": 1, "version": 8, - "versionNonce": 1604849351, + "versionNonce": 289600103, "width": 20, "x": 20, "y": 30, @@ -3286,14 +3286,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 1278240551, + "seed": 449462985, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 449462985, + "versionNonce": 401146281, "width": 20, "x": -10, "y": 0, @@ -3315,14 +3315,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 400692809, + "seed": 1006504105, "strokeColor": "#e03131", "strokeStyle": "dotted", "strokeWidth": 2, "type": "rectangle", "updated": 1, "version": 9, - "versionNonce": 1505387817, + "versionNonce": 1898319239, "width": 20, "x": 20, "y": 30, @@ -3358,14 +3358,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 1278240551, + "seed": 449462985, "strokeColor": "#e03131", "strokeStyle": "dotted", "strokeWidth": 2, "type": "rectangle", "updated": 1, "version": 3, - "versionNonce": 23633383, + "versionNonce": 406373543, "width": 20, "x": -10, "y": 0, @@ -3387,14 +3387,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 400692809, + "seed": 1006504105, "strokeColor": "#e03131", "strokeStyle": "dotted", "strokeWidth": 2, "type": "rectangle", "updated": 1, "version": 9, - "versionNonce": 1505387817, + "versionNonce": 1898319239, "width": 20, "x": 20, "y": 30, @@ -3407,7 +3407,7 @@ Object { exports[`contextMenu element selecting 'Paste styles' in context menu pastes styles: [end of test] number of elements 1`] = `2`; -exports[`contextMenu element selecting 'Paste styles' in context menu pastes styles: [end of test] number of renders 1`] = `32`; +exports[`contextMenu element selecting 'Paste styles' in context menu pastes styles: [end of test] number of renders 1`] = `14`; exports[`contextMenu element selecting 'Send backward' in context menu sends element backward: [end of test] appState 1`] = ` Object { @@ -3524,14 +3524,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 453191, + "seed": 1150084233, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 3, - "versionNonce": 2019559783, + "versionNonce": 400692809, "width": 20, "x": 20, "y": 30, @@ -3556,14 +3556,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 1278240551, + "seed": 449462985, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 449462985, + "versionNonce": 401146281, "width": 20, "x": -10, "y": 0, @@ -3615,79 +3615,7 @@ Object { "roundness": Object { "type": 3, }, - "seed": 1278240551, - "strokeColor": "#1e1e1e", - "strokeStyle": "solid", - "strokeWidth": 1, - "type": "rectangle", - "updated": 1, - "version": 2, - "versionNonce": 449462985, - "width": 20, - "x": -10, - "y": 0, - }, - ], - }, - Object { - "appState": Object { - "editingGroupId": null, - "editingLinearElement": null, - "name": "Untitled-201933152653", - "selectedElementIds": Object { - "id1": true, - }, - "selectedGroupIds": Object {}, - "viewBackgroundColor": "#ffffff", - }, - "elements": Array [ - Object { - "angle": 0, - "backgroundColor": "transparent", - "boundElements": null, - "fillStyle": "hachure", - "frameId": null, - "groupIds": Array [], - "height": 20, - "id": "id0", - "isDeleted": false, - "link": null, - "locked": false, - "opacity": 100, - "roughness": 1, - "roundness": Object { - "type": 3, - }, - "seed": 1278240551, - "strokeColor": "#1e1e1e", - "strokeStyle": "solid", - "strokeWidth": 1, - "type": "rectangle", - "updated": 1, - "version": 2, - "versionNonce": 449462985, - "width": 20, - "x": -10, - "y": 0, - }, - Object { - "angle": 0, - "backgroundColor": "transparent", - "boundElements": null, - "fillStyle": "hachure", - "frameId": null, - "groupIds": Array [], - "height": 20, - "id": "id1", - "isDeleted": false, - "link": null, - "locked": false, - "opacity": 100, - "roughness": 1, - "roundness": Object { - "type": 3, - }, - "seed": 453191, + "seed": 449462985, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, @@ -3696,6 +3624,78 @@ Object { "version": 2, "versionNonce": 401146281, "width": 20, + "x": -10, + "y": 0, + }, + ], + }, + Object { + "appState": Object { + "editingGroupId": null, + "editingLinearElement": null, + "name": "Untitled-201933152653", + "selectedElementIds": Object { + "id1": true, + }, + "selectedGroupIds": Object {}, + "viewBackgroundColor": "#ffffff", + }, + "elements": Array [ + Object { + "angle": 0, + "backgroundColor": "transparent", + "boundElements": null, + "fillStyle": "hachure", + "frameId": null, + "groupIds": Array [], + "height": 20, + "id": "id0", + "isDeleted": false, + "link": null, + "locked": false, + "opacity": 100, + "roughness": 1, + "roundness": Object { + "type": 3, + }, + "seed": 449462985, + "strokeColor": "#1e1e1e", + "strokeStyle": "solid", + "strokeWidth": 1, + "type": "rectangle", + "updated": 1, + "version": 2, + "versionNonce": 401146281, + "width": 20, + "x": -10, + "y": 0, + }, + Object { + "angle": 0, + "backgroundColor": "transparent", + "boundElements": null, + "fillStyle": "hachure", + "frameId": null, + "groupIds": Array [], + "height": 20, + "id": "id1", + "isDeleted": false, + "link": null, + "locked": false, + "opacity": 100, + "roughness": 1, + "roundness": Object { + "type": 3, + }, + "seed": 1150084233, + "strokeColor": "#1e1e1e", + "strokeStyle": "solid", + "strokeWidth": 1, + "type": "rectangle", + "updated": 1, + "version": 2, + "versionNonce": 1014066025, + "width": 20, "x": 20, "y": 30, }, @@ -3730,14 +3730,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 453191, + "seed": 1150084233, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 3, - "versionNonce": 2019559783, + "versionNonce": 400692809, "width": 20, "x": 20, "y": 30, @@ -3759,14 +3759,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 1278240551, + "seed": 449462985, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 449462985, + "versionNonce": 401146281, "width": 20, "x": -10, "y": 0, @@ -3779,7 +3779,7 @@ Object { exports[`contextMenu element selecting 'Send backward' in context menu sends element backward: [end of test] number of elements 1`] = `2`; -exports[`contextMenu element selecting 'Send backward' in context menu sends element backward: [end of test] number of renders 1`] = `18`; +exports[`contextMenu element selecting 'Send backward' in context menu sends element backward: [end of test] number of renders 1`] = `7`; exports[`contextMenu element selecting 'Send to back' in context menu sends element to back: [end of test] appState 1`] = ` Object { @@ -3896,14 +3896,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 453191, + "seed": 1150084233, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 3, - "versionNonce": 2019559783, + "versionNonce": 400692809, "width": 20, "x": 20, "y": 30, @@ -3928,14 +3928,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 1278240551, + "seed": 449462985, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 449462985, + "versionNonce": 401146281, "width": 20, "x": -10, "y": 0, @@ -3987,79 +3987,7 @@ Object { "roundness": Object { "type": 3, }, - "seed": 1278240551, - "strokeColor": "#1e1e1e", - "strokeStyle": "solid", - "strokeWidth": 1, - "type": "rectangle", - "updated": 1, - "version": 2, - "versionNonce": 449462985, - "width": 20, - "x": -10, - "y": 0, - }, - ], - }, - Object { - "appState": Object { - "editingGroupId": null, - "editingLinearElement": null, - "name": "Untitled-201933152653", - "selectedElementIds": Object { - "id1": true, - }, - "selectedGroupIds": Object {}, - "viewBackgroundColor": "#ffffff", - }, - "elements": Array [ - Object { - "angle": 0, - "backgroundColor": "transparent", - "boundElements": null, - "fillStyle": "hachure", - "frameId": null, - "groupIds": Array [], - "height": 20, - "id": "id0", - "isDeleted": false, - "link": null, - "locked": false, - "opacity": 100, - "roughness": 1, - "roundness": Object { - "type": 3, - }, - "seed": 1278240551, - "strokeColor": "#1e1e1e", - "strokeStyle": "solid", - "strokeWidth": 1, - "type": "rectangle", - "updated": 1, - "version": 2, - "versionNonce": 449462985, - "width": 20, - "x": -10, - "y": 0, - }, - Object { - "angle": 0, - "backgroundColor": "transparent", - "boundElements": null, - "fillStyle": "hachure", - "frameId": null, - "groupIds": Array [], - "height": 20, - "id": "id1", - "isDeleted": false, - "link": null, - "locked": false, - "opacity": 100, - "roughness": 1, - "roundness": Object { - "type": 3, - }, - "seed": 453191, + "seed": 449462985, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, @@ -4068,6 +3996,78 @@ Object { "version": 2, "versionNonce": 401146281, "width": 20, + "x": -10, + "y": 0, + }, + ], + }, + Object { + "appState": Object { + "editingGroupId": null, + "editingLinearElement": null, + "name": "Untitled-201933152653", + "selectedElementIds": Object { + "id1": true, + }, + "selectedGroupIds": Object {}, + "viewBackgroundColor": "#ffffff", + }, + "elements": Array [ + Object { + "angle": 0, + "backgroundColor": "transparent", + "boundElements": null, + "fillStyle": "hachure", + "frameId": null, + "groupIds": Array [], + "height": 20, + "id": "id0", + "isDeleted": false, + "link": null, + "locked": false, + "opacity": 100, + "roughness": 1, + "roundness": Object { + "type": 3, + }, + "seed": 449462985, + "strokeColor": "#1e1e1e", + "strokeStyle": "solid", + "strokeWidth": 1, + "type": "rectangle", + "updated": 1, + "version": 2, + "versionNonce": 401146281, + "width": 20, + "x": -10, + "y": 0, + }, + Object { + "angle": 0, + "backgroundColor": "transparent", + "boundElements": null, + "fillStyle": "hachure", + "frameId": null, + "groupIds": Array [], + "height": 20, + "id": "id1", + "isDeleted": false, + "link": null, + "locked": false, + "opacity": 100, + "roughness": 1, + "roundness": Object { + "type": 3, + }, + "seed": 1150084233, + "strokeColor": "#1e1e1e", + "strokeStyle": "solid", + "strokeWidth": 1, + "type": "rectangle", + "updated": 1, + "version": 2, + "versionNonce": 1014066025, + "width": 20, "x": 20, "y": 30, }, @@ -4102,14 +4102,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 453191, + "seed": 1150084233, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 3, - "versionNonce": 2019559783, + "versionNonce": 400692809, "width": 20, "x": 20, "y": 30, @@ -4131,14 +4131,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 1278240551, + "seed": 449462985, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 449462985, + "versionNonce": 401146281, "width": 20, "x": -10, "y": 0, @@ -4151,7 +4151,7 @@ Object { exports[`contextMenu element selecting 'Send to back' in context menu sends element to back: [end of test] number of elements 1`] = `2`; -exports[`contextMenu element selecting 'Send to back' in context menu sends element to back: [end of test] number of renders 1`] = `18`; +exports[`contextMenu element selecting 'Send to back' in context menu sends element to back: [end of test] number of renders 1`] = `7`; exports[`contextMenu element selecting 'Ungroup selection' in context menu ungroups selected group: [end of test] appState 1`] = ` Object { @@ -4271,14 +4271,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 1278240551, + "seed": 453191, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 4, - "versionNonce": 1014066025, + "versionNonce": 915032327, "width": 20, "x": -10, "y": 0, @@ -4303,14 +4303,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 453191, + "seed": 1116226695, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 4, - "versionNonce": 238820263, + "versionNonce": 81784553, "width": 20, "x": 20, "y": 30, @@ -4362,14 +4362,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 1278240551, + "seed": 453191, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 449462985, + "versionNonce": 2019559783, "width": 20, "x": -10, "y": 0, @@ -4405,14 +4405,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 1278240551, + "seed": 453191, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 449462985, + "versionNonce": 2019559783, "width": 20, "x": -10, "y": 0, @@ -4434,14 +4434,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 453191, + "seed": 1116226695, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 401146281, + "versionNonce": 238820263, "width": 20, "x": 20, "y": 30, @@ -4482,14 +4482,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 1278240551, + "seed": 453191, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 3, - "versionNonce": 1150084233, + "versionNonce": 1505387817, "width": 20, "x": -10, "y": 0, @@ -4513,14 +4513,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 453191, + "seed": 1116226695, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 3, - "versionNonce": 1116226695, + "versionNonce": 23633383, "width": 20, "x": 20, "y": 30, @@ -4557,14 +4557,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 1278240551, + "seed": 453191, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 4, - "versionNonce": 1014066025, + "versionNonce": 915032327, "width": 20, "x": -10, "y": 0, @@ -4586,14 +4586,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 453191, + "seed": 1116226695, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 4, - "versionNonce": 238820263, + "versionNonce": 81784553, "width": 20, "x": 20, "y": 30, @@ -4606,7 +4606,7 @@ Object { exports[`contextMenu element selecting 'Ungroup selection' in context menu ungroups selected group: [end of test] number of elements 1`] = `2`; -exports[`contextMenu element selecting 'Ungroup selection' in context menu ungroups selected group: [end of test] number of renders 1`] = `21`; +exports[`contextMenu element selecting 'Ungroup selection' in context menu ungroups selected group: [end of test] number of renders 1`] = `8`; exports[`contextMenu element shows 'Group selection' in context menu for multiple selected elements: [end of test] appState 1`] = ` Object { @@ -5002,14 +5002,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 1278240551, + "seed": 449462985, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 449462985, + "versionNonce": 401146281, "width": 10, "x": -10, "y": 0, @@ -5034,14 +5034,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 453191, + "seed": 1150084233, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 401146281, + "versionNonce": 1014066025, "width": 10, "x": 10, "y": 0, @@ -5093,14 +5093,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 1278240551, + "seed": 449462985, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 449462985, + "versionNonce": 401146281, "width": 10, "x": -10, "y": 0, @@ -5136,14 +5136,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 1278240551, + "seed": 449462985, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 449462985, + "versionNonce": 401146281, "width": 10, "x": -10, "y": 0, @@ -5165,14 +5165,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 453191, + "seed": 1150084233, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 401146281, + "versionNonce": 1014066025, "width": 10, "x": 10, "y": 0, @@ -5185,7 +5185,7 @@ Object { exports[`contextMenu element shows 'Group selection' in context menu for multiple selected elements: [end of test] number of elements 1`] = `2`; -exports[`contextMenu element shows 'Group selection' in context menu for multiple selected elements: [end of test] number of renders 1`] = `20`; +exports[`contextMenu element shows 'Group selection' in context menu for multiple selected elements: [end of test] number of renders 1`] = `6`; exports[`contextMenu element shows 'Ungroup selection' in context menu for group inside selected elements: [end of test] appState 1`] = ` Object { @@ -5585,14 +5585,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 1278240551, + "seed": 453191, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 3, - "versionNonce": 1116226695, + "versionNonce": 23633383, "width": 10, "x": -10, "y": 0, @@ -5619,14 +5619,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 453191, + "seed": 1116226695, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 3, - "versionNonce": 1014066025, + "versionNonce": 493213705, "width": 10, "x": 10, "y": 0, @@ -5678,14 +5678,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 1278240551, + "seed": 453191, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 449462985, + "versionNonce": 2019559783, "width": 10, "x": -10, "y": 0, @@ -5721,14 +5721,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 1278240551, + "seed": 453191, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 449462985, + "versionNonce": 2019559783, "width": 10, "x": -10, "y": 0, @@ -5750,14 +5750,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 453191, + "seed": 1116226695, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 401146281, + "versionNonce": 238820263, "width": 10, "x": 10, "y": 0, @@ -5798,14 +5798,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 1278240551, + "seed": 453191, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 3, - "versionNonce": 1116226695, + "versionNonce": 23633383, "width": 10, "x": -10, "y": 0, @@ -5829,14 +5829,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 453191, + "seed": 1116226695, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 3, - "versionNonce": 1014066025, + "versionNonce": 493213705, "width": 10, "x": 10, "y": 0, @@ -5849,7 +5849,7 @@ Object { exports[`contextMenu element shows 'Ungroup selection' in context menu for group inside selected elements: [end of test] number of elements 1`] = `2`; -exports[`contextMenu element shows 'Ungroup selection' in context menu for group inside selected elements: [end of test] number of renders 1`] = `21`; +exports[`contextMenu element shows 'Ungroup selection' in context menu for group inside selected elements: [end of test] number of renders 1`] = `7`; exports[`contextMenu element shows context menu for canvas: [end of test] appState 1`] = ` Object { @@ -6083,7 +6083,7 @@ Object { exports[`contextMenu element shows context menu for canvas: [end of test] number of elements 1`] = `0`; -exports[`contextMenu element shows context menu for canvas: [end of test] number of renders 1`] = `6`; +exports[`contextMenu element shows context menu for canvas: [end of test] number of renders 1`] = `2`; exports[`contextMenu element shows context menu for element: [end of test] appState 1`] = ` Object { @@ -6849,14 +6849,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 1278240551, + "seed": 449462985, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 449462985, + "versionNonce": 401146281, "width": 20, "x": -10, "y": 0, @@ -6881,7 +6881,7 @@ Object { "roundness": Object { "type": 3, }, - "seed": 337897, + "seed": 1278240551, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, @@ -6913,7 +6913,7 @@ Object { "roundness": Object { "type": 3, }, - "seed": 1278240551, + "seed": 449462985, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, @@ -6972,14 +6972,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 1278240551, + "seed": 449462985, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 449462985, + "versionNonce": 401146281, "width": 20, "x": -10, "y": 0, @@ -7014,6 +7014,6 @@ exports[`contextMenu element shows context menu for element: [end of test] numbe 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`] = `12`; +exports[`contextMenu element shows context menu for element: [end of test] number of renders 1`] = `4`; -exports[`contextMenu element shows context menu for element: [end of test] number of renders 2`] = `11`; +exports[`contextMenu element shows context menu for element: [end of test] number of renders 2`] = `3`; diff --git a/src/tests/__snapshots__/dragCreate.test.tsx.snap b/src/tests/__snapshots__/dragCreate.test.tsx.snap index b8d95d221..1a5cd1a3f 100644 --- a/src/tests/__snapshots__/dragCreate.test.tsx.snap +++ b/src/tests/__snapshots__/dragCreate.test.tsx.snap @@ -33,7 +33,7 @@ Object { "roundness": Object { "type": 2, }, - "seed": 337897, + "seed": 1278240551, "startArrowhead": null, "startBinding": null, "strokeColor": "#1e1e1e", @@ -42,7 +42,7 @@ Object { "type": "arrow", "updated": 1, "version": 3, - "versionNonce": 449462985, + "versionNonce": 401146281, "width": 30, "x": 30, "y": 20, @@ -69,14 +69,14 @@ Object { "roundness": Object { "type": 2, }, - "seed": 337897, + "seed": 1278240551, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "diamond", "updated": 1, "version": 2, - "versionNonce": 1278240551, + "versionNonce": 453191, "width": 30, "x": 30, "y": 20, @@ -103,14 +103,14 @@ Object { "roundness": Object { "type": 2, }, - "seed": 337897, + "seed": 1278240551, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "ellipse", "updated": 1, "version": 2, - "versionNonce": 1278240551, + "versionNonce": 453191, "width": 30, "x": 30, "y": 20, @@ -148,7 +148,7 @@ Object { "roundness": Object { "type": 2, }, - "seed": 337897, + "seed": 1278240551, "startArrowhead": null, "startBinding": null, "strokeColor": "#1e1e1e", @@ -157,7 +157,7 @@ Object { "type": "line", "updated": 1, "version": 3, - "versionNonce": 449462985, + "versionNonce": 401146281, "width": 30, "x": 30, "y": 20, @@ -184,14 +184,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 337897, + "seed": 1278240551, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 1278240551, + "versionNonce": 453191, "width": 30, "x": 30, "y": 20, diff --git a/src/tests/__snapshots__/move.test.tsx.snap b/src/tests/__snapshots__/move.test.tsx.snap index 5084d1508..7ec4b0690 100644 --- a/src/tests/__snapshots__/move.test.tsx.snap +++ b/src/tests/__snapshots__/move.test.tsx.snap @@ -18,14 +18,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 401146281, + "seed": 1014066025, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 4, - "versionNonce": 2019559783, + "versionNonce": 238820263, "width": 30, "x": 30, "y": 20, @@ -50,14 +50,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 337897, + "seed": 1278240551, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 4, - "versionNonce": 1150084233, + "versionNonce": 1604849351, "width": 30, "x": -10, "y": 60, @@ -82,14 +82,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 337897, + "seed": 1278240551, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 3, - "versionNonce": 453191, + "versionNonce": 1150084233, "width": 30, "x": 0, "y": 40, @@ -119,14 +119,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 337897, + "seed": 1278240551, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 3, - "versionNonce": 1014066025, + "versionNonce": 81784553, "width": 100, "x": 0, "y": 0, @@ -156,14 +156,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 449462985, + "seed": 2019559783, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 6, - "versionNonce": 1723083209, + "versionNonce": 927333447, "width": 300, "x": 201, "y": 2, @@ -205,7 +205,7 @@ Object { "roundness": Object { "type": 2, }, - "seed": 401146281, + "seed": 238820263, "startArrowhead": null, "startBinding": Object { "elementId": "id0", @@ -218,7 +218,7 @@ Object { "type": "line", "updated": 1, "version": 11, - "versionNonce": 1006504105, + "versionNonce": 1051383431, "width": 81, "x": 110, "y": 49.981789081137734, diff --git a/src/tests/__snapshots__/multiPointCreate.test.tsx.snap b/src/tests/__snapshots__/multiPointCreate.test.tsx.snap index 0a6ae0cc8..12f8b3984 100644 --- a/src/tests/__snapshots__/multiPointCreate.test.tsx.snap +++ b/src/tests/__snapshots__/multiPointCreate.test.tsx.snap @@ -38,7 +38,7 @@ Object { "roundness": Object { "type": 2, }, - "seed": 337897, + "seed": 1278240551, "startArrowhead": null, "startBinding": null, "strokeColor": "#1e1e1e", @@ -47,7 +47,7 @@ Object { "type": "arrow", "updated": 1, "version": 7, - "versionNonce": 1150084233, + "versionNonce": 1505387817, "width": 70, "x": 30, "y": 30, @@ -92,7 +92,7 @@ Object { "roundness": Object { "type": 2, }, - "seed": 337897, + "seed": 1278240551, "startArrowhead": null, "startBinding": null, "strokeColor": "#1e1e1e", @@ -101,7 +101,7 @@ Object { "type": "line", "updated": 1, "version": 7, - "versionNonce": 1150084233, + "versionNonce": 1505387817, "width": 70, "x": 30, "y": 30, diff --git a/src/tests/__snapshots__/regressionTests.test.tsx.snap b/src/tests/__snapshots__/regressionTests.test.tsx.snap index 03ced1c37..d33c460e5 100644 --- a/src/tests/__snapshots__/regressionTests.test.tsx.snap +++ b/src/tests/__snapshots__/regressionTests.test.tsx.snap @@ -149,14 +149,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 337897, + "seed": 1278240551, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 1278240551, + "versionNonce": 453191, "width": 10, "x": 0, "y": 0, @@ -192,14 +192,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 337897, + "seed": 1278240551, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 1278240551, + "versionNonce": 453191, "width": 10, "x": 0, "y": 0, @@ -221,14 +221,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 449462985, + "seed": 2019559783, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 453191, + "versionNonce": 1116226695, "width": 10, "x": 0, "y": 30, @@ -264,14 +264,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 337897, + "seed": 1278240551, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 1278240551, + "versionNonce": 453191, "width": 10, "x": 0, "y": 0, @@ -293,14 +293,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 449462985, + "seed": 2019559783, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 453191, + "versionNonce": 1116226695, "width": 10, "x": 0, "y": 30, @@ -322,14 +322,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 401146281, + "seed": 238820263, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 2019559783, + "versionNonce": 1604849351, "width": 10, "x": 0, "y": 60, @@ -368,14 +368,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 449462985, + "seed": 2019559783, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 453191, + "versionNonce": 1116226695, "width": 10, "x": 0, "y": 30, @@ -399,14 +399,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 337897, + "seed": 1278240551, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 3, - "versionNonce": 1014066025, + "versionNonce": 915032327, "width": 10, "x": 0, "y": 0, @@ -430,14 +430,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 401146281, + "seed": 238820263, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 3, - "versionNonce": 238820263, + "versionNonce": 81784553, "width": 10, "x": 0, "y": 60, @@ -450,7 +450,7 @@ Object { exports[`given element A and group of elements B and given both are selected when user clicks on B, on pointer up only elements from B should be selected: [end of test] number of elements 1`] = `0`; -exports[`given element A and group of elements B and given both are selected when user clicks on B, on pointer up only elements from B should be selected: [end of test] number of renders 1`] = `28`; +exports[`given element A and group of elements B and given both are selected when user clicks on B, on pointer up only elements from B should be selected: [end of test] number of renders 1`] = `10`; exports[`given element A and group of elements B and given both are selected when user shift-clicks on B, on pointer up only element A should be selected: [end of test] appState 1`] = ` Object { @@ -603,14 +603,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 337897, + "seed": 1278240551, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 1278240551, + "versionNonce": 453191, "width": 100, "x": 0, "y": 0, @@ -646,14 +646,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 337897, + "seed": 1278240551, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 1278240551, + "versionNonce": 453191, "width": 100, "x": 0, "y": 0, @@ -675,14 +675,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 449462985, + "seed": 2019559783, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 453191, + "versionNonce": 1116226695, "width": 100, "x": 110, "y": 110, @@ -718,14 +718,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 337897, + "seed": 1278240551, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 1278240551, + "versionNonce": 453191, "width": 100, "x": 0, "y": 0, @@ -747,14 +747,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 449462985, + "seed": 2019559783, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 453191, + "versionNonce": 1116226695, "width": 100, "x": 110, "y": 110, @@ -776,14 +776,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 401146281, + "seed": 238820263, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 2019559783, + "versionNonce": 1604849351, "width": 100, "x": 220, "y": 220, @@ -822,14 +822,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 449462985, + "seed": 2019559783, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 453191, + "versionNonce": 1116226695, "width": 100, "x": 110, "y": 110, @@ -853,14 +853,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 337897, + "seed": 1278240551, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 3, - "versionNonce": 1116226695, + "versionNonce": 493213705, "width": 100, "x": 0, "y": 0, @@ -884,14 +884,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 401146281, + "seed": 238820263, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 3, - "versionNonce": 1014066025, + "versionNonce": 915032327, "width": 100, "x": 220, "y": 220, @@ -904,7 +904,7 @@ Object { exports[`given element A and group of elements B and given both are selected when user shift-clicks on B, on pointer up only element A should be selected: [end of test] number of elements 1`] = `0`; -exports[`given element A and group of elements B and given both are selected when user shift-clicks on B, on pointer up only element A should be selected: [end of test] number of renders 1`] = `24`; +exports[`given element A and group of elements B and given both are selected when user shift-clicks on B, on pointer up only element A should be selected: [end of test] number of renders 1`] = `10`; exports[`regression tests Cmd/Ctrl-click exclusively select element under pointer: [end of test] appState 1`] = ` Object { @@ -1048,14 +1048,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 337897, + "seed": 1278240551, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 1278240551, + "versionNonce": 453191, "width": 10, "x": 0, "y": 0, @@ -1091,14 +1091,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 337897, + "seed": 1278240551, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 1278240551, + "versionNonce": 453191, "width": 10, "x": 0, "y": 0, @@ -1120,14 +1120,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 449462985, + "seed": 2019559783, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 453191, + "versionNonce": 1116226695, "width": 10, "x": 30, "y": 0, @@ -1168,14 +1168,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 337897, + "seed": 1278240551, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 3, - "versionNonce": 1150084233, + "versionNonce": 1604849351, "width": 10, "x": 0, "y": 0, @@ -1199,14 +1199,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 449462985, + "seed": 2019559783, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 3, - "versionNonce": 1116226695, + "versionNonce": 1505387817, "width": 10, "x": 30, "y": 0, @@ -1244,14 +1244,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 337897, + "seed": 1278240551, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 3, - "versionNonce": 1150084233, + "versionNonce": 1604849351, "width": 10, "x": 0, "y": 0, @@ -1275,14 +1275,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 449462985, + "seed": 2019559783, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 3, - "versionNonce": 1116226695, + "versionNonce": 1505387817, "width": 10, "x": 30, "y": 0, @@ -1320,14 +1320,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 337897, + "seed": 1278240551, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 3, - "versionNonce": 1150084233, + "versionNonce": 1604849351, "width": 10, "x": 0, "y": 0, @@ -1351,14 +1351,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 449462985, + "seed": 2019559783, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 3, - "versionNonce": 1116226695, + "versionNonce": 1505387817, "width": 10, "x": 30, "y": 0, @@ -1380,14 +1380,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 400692809, + "seed": 81784553, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 1604849351, + "versionNonce": 1723083209, "width": 10, "x": 60, "y": 0, @@ -1430,14 +1430,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 337897, + "seed": 1278240551, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 4, - "versionNonce": 493213705, + "versionNonce": 1315507081, "width": 10, "x": 0, "y": 0, @@ -1462,14 +1462,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 449462985, + "seed": 2019559783, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 4, - "versionNonce": 915032327, + "versionNonce": 1898319239, "width": 10, "x": 30, "y": 0, @@ -1493,14 +1493,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 400692809, + "seed": 81784553, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 3, - "versionNonce": 81784553, + "versionNonce": 640725609, "width": 10, "x": 60, "y": 0, @@ -1539,14 +1539,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 337897, + "seed": 1278240551, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 4, - "versionNonce": 493213705, + "versionNonce": 1315507081, "width": 10, "x": 0, "y": 0, @@ -1571,14 +1571,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 449462985, + "seed": 2019559783, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 4, - "versionNonce": 915032327, + "versionNonce": 1898319239, "width": 10, "x": 30, "y": 0, @@ -1602,14 +1602,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 400692809, + "seed": 81784553, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 3, - "versionNonce": 81784553, + "versionNonce": 640725609, "width": 10, "x": 60, "y": 0, @@ -1648,14 +1648,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 337897, + "seed": 1278240551, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 4, - "versionNonce": 493213705, + "versionNonce": 1315507081, "width": 10, "x": 0, "y": 0, @@ -1680,14 +1680,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 449462985, + "seed": 2019559783, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 4, - "versionNonce": 915032327, + "versionNonce": 1898319239, "width": 10, "x": 30, "y": 0, @@ -1711,14 +1711,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 400692809, + "seed": 81784553, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 3, - "versionNonce": 81784553, + "versionNonce": 640725609, "width": 10, "x": 60, "y": 0, @@ -1731,7 +1731,7 @@ Object { exports[`regression tests Cmd/Ctrl-click exclusively select element under pointer: [end of test] number of elements 1`] = `0`; -exports[`regression tests Cmd/Ctrl-click exclusively select element under pointer: [end of test] number of renders 1`] = `43`; +exports[`regression tests Cmd/Ctrl-click exclusively select element under pointer: [end of test] number of renders 1`] = `11`; exports[`regression tests Drags selected element when hitting only bounding box and keeps element selected: [end of test] appState 1`] = ` Object { @@ -1877,14 +1877,14 @@ Object { "roundness": Object { "type": 2, }, - "seed": 337897, + "seed": 1278240551, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "ellipse", "updated": 1, "version": 2, - "versionNonce": 1278240551, + "versionNonce": 453191, "width": 10, "x": 0, "y": 0, @@ -1920,14 +1920,14 @@ Object { "roundness": Object { "type": 2, }, - "seed": 337897, + "seed": 1278240551, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "ellipse", "updated": 1, "version": 3, - "versionNonce": 453191, + "versionNonce": 1150084233, "width": 10, "x": 25, "y": 25, @@ -1940,7 +1940,7 @@ Object { exports[`regression tests Drags selected element when hitting only bounding box and keeps element selected: [end of test] number of elements 1`] = `0`; -exports[`regression tests Drags selected element when hitting only bounding box and keeps element selected: [end of test] number of renders 1`] = `12`; +exports[`regression tests Drags selected element when hitting only bounding box and keeps element selected: [end of test] number of renders 1`] = `7`; exports[`regression tests adjusts z order when grouping: [end of test] appState 1`] = ` Object { @@ -2089,14 +2089,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 337897, + "seed": 1278240551, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 1278240551, + "versionNonce": 453191, "width": 10, "x": 10, "y": 10, @@ -2132,14 +2132,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 337897, + "seed": 1278240551, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 1278240551, + "versionNonce": 453191, "width": 10, "x": 10, "y": 10, @@ -2161,14 +2161,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 449462985, + "seed": 2019559783, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 453191, + "versionNonce": 1116226695, "width": 10, "x": 30, "y": 10, @@ -2204,14 +2204,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 337897, + "seed": 1278240551, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 1278240551, + "versionNonce": 453191, "width": 10, "x": 10, "y": 10, @@ -2233,14 +2233,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 449462985, + "seed": 2019559783, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 453191, + "versionNonce": 1116226695, "width": 10, "x": 30, "y": 10, @@ -2262,14 +2262,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 401146281, + "seed": 238820263, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 2019559783, + "versionNonce": 1604849351, "width": 10, "x": 50, "y": 10, @@ -2308,14 +2308,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 449462985, + "seed": 2019559783, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 453191, + "versionNonce": 1116226695, "width": 10, "x": 30, "y": 10, @@ -2339,14 +2339,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 337897, + "seed": 1278240551, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 3, - "versionNonce": 1014066025, + "versionNonce": 915032327, "width": 10, "x": 10, "y": 10, @@ -2370,14 +2370,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 401146281, + "seed": 238820263, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 3, - "versionNonce": 238820263, + "versionNonce": 81784553, "width": 10, "x": 50, "y": 10, @@ -2390,7 +2390,7 @@ Object { exports[`regression tests adjusts z order when grouping: [end of test] number of elements 1`] = `0`; -exports[`regression tests adjusts z order when grouping: [end of test] number of renders 1`] = `22`; +exports[`regression tests adjusts z order when grouping: [end of test] number of renders 1`] = `10`; exports[`regression tests alt-drag duplicates an element: [end of test] appState 1`] = ` Object { @@ -2536,14 +2536,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 337897, + "seed": 1278240551, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 1278240551, + "versionNonce": 453191, "width": 10, "x": 10, "y": 10, @@ -2579,14 +2579,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 401146281, + "seed": 1014066025, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 4, - "versionNonce": 2019559783, + "versionNonce": 238820263, "width": 10, "x": 10, "y": 10, @@ -2608,14 +2608,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 337897, + "seed": 1278240551, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 3, - "versionNonce": 453191, + "versionNonce": 1150084233, "width": 10, "x": 20, "y": 20, @@ -2628,7 +2628,7 @@ Object { exports[`regression tests alt-drag duplicates an element: [end of test] number of elements 1`] = `0`; -exports[`regression tests alt-drag duplicates an element: [end of test] number of renders 1`] = `12`; +exports[`regression tests alt-drag duplicates an element: [end of test] number of renders 1`] = `7`; exports[`regression tests arrow keys: [end of test] appState 1`] = ` Object { @@ -2772,14 +2772,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 337897, + "seed": 1278240551, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 1278240551, + "versionNonce": 453191, "width": 10, "x": 10, "y": 10, @@ -2792,7 +2792,7 @@ Object { exports[`regression tests arrow keys: [end of test] number of elements 1`] = `0`; -exports[`regression tests arrow keys: [end of test] number of renders 1`] = `21`; +exports[`regression tests arrow keys: [end of test] number of renders 1`] = `11`; exports[`regression tests can drag element that covers another element, while another elem is selected: [end of test] appState 1`] = ` Object { @@ -2938,14 +2938,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 337897, + "seed": 1278240551, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 1278240551, + "versionNonce": 453191, "width": 200, "x": 100, "y": 100, @@ -2981,14 +2981,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 337897, + "seed": 1278240551, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 1278240551, + "versionNonce": 453191, "width": 200, "x": 100, "y": 100, @@ -3010,14 +3010,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 449462985, + "seed": 2019559783, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 453191, + "versionNonce": 1116226695, "width": 200, "x": 100, "y": 100, @@ -3053,14 +3053,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 337897, + "seed": 1278240551, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 1278240551, + "versionNonce": 453191, "width": 200, "x": 100, "y": 100, @@ -3082,14 +3082,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 449462985, + "seed": 2019559783, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 453191, + "versionNonce": 1116226695, "width": 200, "x": 100, "y": 100, @@ -3111,14 +3111,14 @@ Object { "roundness": Object { "type": 2, }, - "seed": 401146281, + "seed": 238820263, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "ellipse", "updated": 1, "version": 2, - "versionNonce": 2019559783, + "versionNonce": 1604849351, "width": 350, "x": 300, "y": 300, @@ -3154,14 +3154,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 337897, + "seed": 1278240551, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 1278240551, + "versionNonce": 453191, "width": 200, "x": 100, "y": 100, @@ -3183,14 +3183,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 449462985, + "seed": 2019559783, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 3, - "versionNonce": 1116226695, + "versionNonce": 493213705, "width": 200, "x": 300, "y": 300, @@ -3212,14 +3212,14 @@ Object { "roundness": Object { "type": 2, }, - "seed": 401146281, + "seed": 238820263, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "ellipse", "updated": 1, "version": 2, - "versionNonce": 2019559783, + "versionNonce": 1604849351, "width": 350, "x": 300, "y": 300, @@ -3232,7 +3232,7 @@ Object { exports[`regression tests can drag element that covers another element, while another elem is selected: [end of test] number of elements 1`] = `0`; -exports[`regression tests can drag element that covers another element, while another elem is selected: [end of test] number of renders 1`] = `20`; +exports[`regression tests can drag element that covers another element, while another elem is selected: [end of test] number of renders 1`] = `11`; exports[`regression tests change the properties of a shape: [end of test] appState 1`] = ` Object { @@ -3376,14 +3376,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 337897, + "seed": 1278240551, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 1278240551, + "versionNonce": 453191, "width": 10, "x": 10, "y": 10, @@ -3419,14 +3419,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 337897, + "seed": 1278240551, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 3, - "versionNonce": 449462985, + "versionNonce": 2019559783, "width": 10, "x": 10, "y": 10, @@ -3462,14 +3462,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 337897, + "seed": 1278240551, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 4, - "versionNonce": 453191, + "versionNonce": 1116226695, "width": 10, "x": 10, "y": 10, @@ -3505,14 +3505,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 337897, + "seed": 1278240551, "strokeColor": "#1971c2", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 5, - "versionNonce": 401146281, + "versionNonce": 238820263, "width": 10, "x": 10, "y": 10, @@ -3525,7 +3525,7 @@ Object { exports[`regression tests change the properties of a shape: [end of test] number of elements 1`] = `0`; -exports[`regression tests change the properties of a shape: [end of test] number of renders 1`] = `14`; +exports[`regression tests change the properties of a shape: [end of test] number of renders 1`] = `8`; exports[`regression tests click on an element and drag it: [dragged] appState 1`] = ` Object { @@ -3644,14 +3644,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 337897, + "seed": 1278240551, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 3, - "versionNonce": 453191, + "versionNonce": 1150084233, "width": 10, "x": 20, "y": 20, @@ -3703,14 +3703,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 337897, + "seed": 1278240551, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 1278240551, + "versionNonce": 453191, "width": 10, "x": 10, "y": 10, @@ -3746,14 +3746,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 337897, + "seed": 1278240551, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 3, - "versionNonce": 453191, + "versionNonce": 1150084233, "width": 10, "x": 20, "y": 20, @@ -3766,7 +3766,7 @@ Object { exports[`regression tests click on an element and drag it: [dragged] number of elements 1`] = `1`; -exports[`regression tests click on an element and drag it: [dragged] number of renders 1`] = `12`; +exports[`regression tests click on an element and drag it: [dragged] number of renders 1`] = `7`; exports[`regression tests click on an element and drag it: [end of test] appState 1`] = ` Object { @@ -3912,14 +3912,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 337897, + "seed": 1278240551, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 1278240551, + "versionNonce": 453191, "width": 10, "x": 10, "y": 10, @@ -3955,14 +3955,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 337897, + "seed": 1278240551, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 3, - "versionNonce": 453191, + "versionNonce": 1150084233, "width": 10, "x": 20, "y": 20, @@ -3998,14 +3998,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 337897, + "seed": 1278240551, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 4, - "versionNonce": 2019559783, + "versionNonce": 400692809, "width": 10, "x": 10, "y": 10, @@ -4018,7 +4018,7 @@ Object { exports[`regression tests click on an element and drag it: [end of test] number of elements 1`] = `0`; -exports[`regression tests click on an element and drag it: [end of test] number of renders 1`] = `15`; +exports[`regression tests click on an element and drag it: [end of test] number of renders 1`] = `9`; exports[`regression tests click to select a shape: [end of test] appState 1`] = ` Object { @@ -4164,14 +4164,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 337897, + "seed": 1278240551, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 1278240551, + "versionNonce": 453191, "width": 10, "x": 10, "y": 10, @@ -4207,14 +4207,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 337897, + "seed": 1278240551, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 1278240551, + "versionNonce": 453191, "width": 10, "x": 10, "y": 10, @@ -4236,14 +4236,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 449462985, + "seed": 2019559783, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 453191, + "versionNonce": 1116226695, "width": 10, "x": 30, "y": 10, @@ -4256,7 +4256,7 @@ Object { exports[`regression tests click to select a shape: [end of test] number of elements 1`] = `0`; -exports[`regression tests click to select a shape: [end of test] number of renders 1`] = `15`; +exports[`regression tests click to select a shape: [end of test] number of renders 1`] = `7`; exports[`regression tests click-drag to select a group: [end of test] appState 1`] = ` Object { @@ -4403,14 +4403,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 337897, + "seed": 1278240551, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 1278240551, + "versionNonce": 453191, "width": 10, "x": 10, "y": 10, @@ -4446,14 +4446,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 337897, + "seed": 1278240551, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 1278240551, + "versionNonce": 453191, "width": 10, "x": 10, "y": 10, @@ -4475,14 +4475,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 449462985, + "seed": 2019559783, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 453191, + "versionNonce": 1116226695, "width": 10, "x": 30, "y": 10, @@ -4518,14 +4518,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 337897, + "seed": 1278240551, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 1278240551, + "versionNonce": 453191, "width": 10, "x": 10, "y": 10, @@ -4547,14 +4547,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 449462985, + "seed": 2019559783, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 453191, + "versionNonce": 1116226695, "width": 10, "x": 30, "y": 10, @@ -4576,14 +4576,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 401146281, + "seed": 238820263, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 2019559783, + "versionNonce": 1604849351, "width": 10, "x": 50, "y": 10, @@ -4596,7 +4596,478 @@ Object { exports[`regression tests click-drag to select a group: [end of test] number of elements 1`] = `0`; -exports[`regression tests click-drag to select a group: [end of test] number of renders 1`] = `21`; +exports[`regression tests click-drag to select a group: [end of test] number of renders 1`] = `9`; + +exports[`regression tests deleting last but one element in editing group should unselect the group: [end of test] appState 1`] = ` +Object { + "activeTool": Object { + "customType": null, + "lastActiveTool": null, + "locked": false, + "type": "selection", + }, + "collaborators": Map {}, + "contextMenu": null, + "currentChartType": "bar", + "currentItemBackgroundColor": "transparent", + "currentItemEndArrowhead": "arrow", + "currentItemFillStyle": "hachure", + "currentItemFontFamily": 1, + "currentItemFontSize": 20, + "currentItemOpacity": 100, + "currentItemRoughness": 1, + "currentItemRoundness": "round", + "currentItemStartArrowhead": null, + "currentItemStrokeColor": "#1e1e1e", + "currentItemStrokeStyle": "solid", + "currentItemStrokeWidth": 1, + "currentItemTextAlign": "left", + "cursorButton": "up", + "defaultSidebarDockedPreference": false, + "draggingElement": null, + "editingElement": null, + "editingFrame": null, + "editingGroupId": null, + "editingLinearElement": null, + "elementsToHighlight": null, + "errorMessage": null, + "exportBackground": true, + "exportEmbedScene": false, + "exportScale": 1, + "exportWithDarkMode": false, + "fileHandle": null, + "frameRendering": Object { + "clip": true, + "enabled": true, + "name": true, + "outline": true, + }, + "frameToHighlight": null, + "gridSize": null, + "height": 768, + "isBindingEnabled": true, + "isLoading": false, + "isResizing": false, + "isRotating": false, + "lastPointerDownWith": "mouse", + "multiElement": null, + "name": "Untitled-201933152653", + "offsetLeft": 0, + "offsetTop": 0, + "openDialog": null, + "openMenu": null, + "openPopup": null, + "openSidebar": null, + "pasteDialog": Object { + "data": null, + "shown": false, + }, + "penDetected": false, + "penMode": false, + "pendingImageElementId": null, + "previousSelectedElementIds": Object {}, + "resizingElement": null, + "scrollX": 0, + "scrollY": 0, + "scrolledOutside": false, + "selectedElementIds": Object { + "id1": true, + }, + "selectedElementsAreBeingDragged": false, + "selectedGroupIds": Object { + "id4": false, + }, + "selectedLinearElement": null, + "selectionElement": null, + "shouldCacheIgnoreZoom": false, + "showHyperlinkPopup": false, + "showStats": false, + "showWelcomeScreen": true, + "startBoundElement": null, + "suggestedBindings": Array [], + "theme": "light", + "toast": null, + "viewBackgroundColor": "#ffffff", + "viewModeEnabled": false, + "width": 1024, + "zenModeEnabled": false, + "zoom": Object { + "value": 1, + }, +} +`; + +exports[`regression tests deleting last but one element in editing group should unselect the group: [end of test] history 1`] = ` +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 [], + }, + Object { + "appState": Object { + "editingGroupId": null, + "editingLinearElement": null, + "name": "Untitled-201933152653", + "selectedElementIds": Object { + "id0": true, + }, + "selectedGroupIds": Object {}, + "viewBackgroundColor": "#ffffff", + }, + "elements": Array [ + Object { + "angle": 0, + "backgroundColor": "transparent", + "boundElements": null, + "fillStyle": "hachure", + "frameId": null, + "groupIds": Array [], + "height": 10, + "id": "id0", + "isDeleted": false, + "link": null, + "locked": false, + "opacity": 100, + "roughness": 1, + "roundness": Object { + "type": 3, + }, + "seed": 1278240551, + "strokeColor": "#1e1e1e", + "strokeStyle": "solid", + "strokeWidth": 1, + "type": "rectangle", + "updated": 1, + "version": 2, + "versionNonce": 453191, + "width": 10, + "x": 10, + "y": 0, + }, + ], + }, + Object { + "appState": Object { + "editingGroupId": null, + "editingLinearElement": null, + "name": "Untitled-201933152653", + "selectedElementIds": Object { + "id1": true, + }, + "selectedGroupIds": Object {}, + "viewBackgroundColor": "#ffffff", + }, + "elements": Array [ + Object { + "angle": 0, + "backgroundColor": "transparent", + "boundElements": null, + "fillStyle": "hachure", + "frameId": null, + "groupIds": Array [], + "height": 10, + "id": "id0", + "isDeleted": false, + "link": null, + "locked": false, + "opacity": 100, + "roughness": 1, + "roundness": Object { + "type": 3, + }, + "seed": 1278240551, + "strokeColor": "#1e1e1e", + "strokeStyle": "solid", + "strokeWidth": 1, + "type": "rectangle", + "updated": 1, + "version": 2, + "versionNonce": 453191, + "width": 10, + "x": 10, + "y": 0, + }, + Object { + "angle": 0, + "backgroundColor": "transparent", + "boundElements": null, + "fillStyle": "hachure", + "frameId": null, + "groupIds": Array [], + "height": 10, + "id": "id1", + "isDeleted": false, + "link": null, + "locked": false, + "opacity": 100, + "roughness": 1, + "roundness": Object { + "type": 3, + }, + "seed": 2019559783, + "strokeColor": "#1e1e1e", + "strokeStyle": "solid", + "strokeWidth": 1, + "type": "rectangle", + "updated": 1, + "version": 2, + "versionNonce": 1116226695, + "width": 10, + "x": 50, + "y": 0, + }, + ], + }, + Object { + "appState": Object { + "editingGroupId": null, + "editingLinearElement": null, + "name": "Untitled-201933152653", + "selectedElementIds": Object { + "id0": true, + "id1": true, + }, + "selectedGroupIds": Object { + "id4": true, + }, + "viewBackgroundColor": "#ffffff", + }, + "elements": Array [ + Object { + "angle": 0, + "backgroundColor": "transparent", + "boundElements": null, + "fillStyle": "hachure", + "frameId": null, + "groupIds": Array [ + "id4", + ], + "height": 10, + "id": "id0", + "isDeleted": false, + "link": null, + "locked": false, + "opacity": 100, + "roughness": 1, + "roundness": Object { + "type": 3, + }, + "seed": 1278240551, + "strokeColor": "#1e1e1e", + "strokeStyle": "solid", + "strokeWidth": 1, + "type": "rectangle", + "updated": 1, + "version": 3, + "versionNonce": 1604849351, + "width": 10, + "x": 10, + "y": 0, + }, + Object { + "angle": 0, + "backgroundColor": "transparent", + "boundElements": null, + "fillStyle": "hachure", + "frameId": null, + "groupIds": Array [ + "id4", + ], + "height": 10, + "id": "id1", + "isDeleted": false, + "link": null, + "locked": false, + "opacity": 100, + "roughness": 1, + "roundness": Object { + "type": 3, + }, + "seed": 2019559783, + "strokeColor": "#1e1e1e", + "strokeStyle": "solid", + "strokeWidth": 1, + "type": "rectangle", + "updated": 1, + "version": 3, + "versionNonce": 1505387817, + "width": 10, + "x": 50, + "y": 0, + }, + ], + }, + Object { + "appState": Object { + "editingGroupId": "id4", + "editingLinearElement": null, + "name": "Untitled-201933152653", + "selectedElementIds": Object { + "id1": true, + }, + "selectedGroupIds": Object {}, + "viewBackgroundColor": "#ffffff", + }, + "elements": Array [ + Object { + "angle": 0, + "backgroundColor": "transparent", + "boundElements": null, + "fillStyle": "hachure", + "frameId": null, + "groupIds": Array [ + "id4", + ], + "height": 10, + "id": "id0", + "isDeleted": true, + "link": null, + "locked": false, + "opacity": 100, + "roughness": 1, + "roundness": Object { + "type": 3, + }, + "seed": 1278240551, + "strokeColor": "#1e1e1e", + "strokeStyle": "solid", + "strokeWidth": 1, + "type": "rectangle", + "updated": 1, + "version": 4, + "versionNonce": 493213705, + "width": 10, + "x": 10, + "y": 0, + }, + Object { + "angle": 0, + "backgroundColor": "transparent", + "boundElements": null, + "fillStyle": "hachure", + "frameId": null, + "groupIds": Array [ + "id4", + ], + "height": 10, + "id": "id1", + "isDeleted": false, + "link": null, + "locked": false, + "opacity": 100, + "roughness": 1, + "roundness": Object { + "type": 3, + }, + "seed": 2019559783, + "strokeColor": "#1e1e1e", + "strokeStyle": "solid", + "strokeWidth": 1, + "type": "rectangle", + "updated": 1, + "version": 3, + "versionNonce": 1505387817, + "width": 10, + "x": 50, + "y": 0, + }, + ], + }, + Object { + "appState": Object { + "editingGroupId": null, + "editingLinearElement": null, + "name": "Untitled-201933152653", + "selectedElementIds": Object { + "id1": true, + }, + "selectedGroupIds": Object { + "id4": false, + }, + "viewBackgroundColor": "#ffffff", + }, + "elements": Array [ + Object { + "angle": 0, + "backgroundColor": "transparent", + "boundElements": null, + "fillStyle": "hachure", + "frameId": null, + "groupIds": Array [ + "id4", + ], + "height": 10, + "id": "id0", + "isDeleted": true, + "link": null, + "locked": false, + "opacity": 100, + "roughness": 1, + "roundness": Object { + "type": 3, + }, + "seed": 1278240551, + "strokeColor": "#1e1e1e", + "strokeStyle": "solid", + "strokeWidth": 1, + "type": "rectangle", + "updated": 1, + "version": 4, + "versionNonce": 493213705, + "width": 10, + "x": 10, + "y": 0, + }, + Object { + "angle": 0, + "backgroundColor": "transparent", + "boundElements": null, + "fillStyle": "hachure", + "frameId": null, + "groupIds": Array [ + "id4", + ], + "height": 10, + "id": "id1", + "isDeleted": false, + "link": null, + "locked": false, + "opacity": 100, + "roughness": 1, + "roundness": Object { + "type": 3, + }, + "seed": 2019559783, + "strokeColor": "#1e1e1e", + "strokeStyle": "solid", + "strokeWidth": 1, + "type": "rectangle", + "updated": 1, + "version": 3, + "versionNonce": 1505387817, + "width": 10, + "x": 50, + "y": 0, + }, + ], + }, + ], +} +`; + +exports[`regression tests deleting last but one element in editing group should unselect the group: [end of test] number of elements 1`] = `0`; + +exports[`regression tests deleting last but one element in editing group should unselect the group: [end of test] number of renders 1`] = `9`; exports[`regression tests deselects group of selected elements on pointer down when pointer doesn't hit any element: [end of test] appState 1`] = ` Object { @@ -4641,7 +5112,7 @@ Object { "roundness": Object { "type": 2, }, - "seed": 2019559783, + "seed": 400692809, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, @@ -4722,7 +5193,7 @@ Object { "roundness": Object { "type": 2, }, - "seed": 2019559783, + "seed": 400692809, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, @@ -4797,14 +5268,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 337897, + "seed": 1278240551, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 1278240551, + "versionNonce": 453191, "width": 10, "x": 0, "y": 0, @@ -4840,14 +5311,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 337897, + "seed": 1278240551, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 1278240551, + "versionNonce": 453191, "width": 10, "x": 0, "y": 0, @@ -4869,14 +5340,14 @@ Object { "roundness": Object { "type": 2, }, - "seed": 449462985, + "seed": 2019559783, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "ellipse", "updated": 1, "version": 2, - "versionNonce": 453191, + "versionNonce": 1116226695, "width": 10, "x": 110, "y": 110, @@ -4889,7 +5360,7 @@ Object { exports[`regression tests deselects group of selected elements on pointer down when pointer doesn't hit any element: [end of test] number of elements 1`] = `0`; -exports[`regression tests deselects group of selected elements on pointer down when pointer doesn't hit any element: [end of test] number of renders 1`] = `16`; +exports[`regression tests deselects group of selected elements on pointer down when pointer doesn't hit any element: [end of test] number of renders 1`] = `7`; exports[`regression tests deselects group of selected elements on pointer up when pointer hits common bounding box without hitting any element: [end of test] appState 1`] = ` Object { @@ -4934,7 +5405,7 @@ Object { "roundness": Object { "type": 2, }, - "seed": 2019559783, + "seed": 400692809, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, @@ -5062,14 +5533,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 337897, + "seed": 1278240551, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 1278240551, + "versionNonce": 453191, "width": 10, "x": 0, "y": 0, @@ -5105,14 +5576,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 337897, + "seed": 1278240551, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 1278240551, + "versionNonce": 453191, "width": 10, "x": 0, "y": 0, @@ -5134,14 +5605,14 @@ Object { "roundness": Object { "type": 2, }, - "seed": 449462985, + "seed": 2019559783, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "ellipse", "updated": 1, "version": 2, - "versionNonce": 453191, + "versionNonce": 1116226695, "width": 10, "x": 110, "y": 110, @@ -5154,7 +5625,7 @@ Object { exports[`regression tests deselects group of selected elements on pointer up when pointer hits common bounding box without hitting any element: [end of test] number of elements 1`] = `0`; -exports[`regression tests deselects group of selected elements on pointer up when pointer hits common bounding box without hitting any element: [end of test] number of renders 1`] = `17`; +exports[`regression tests deselects group of selected elements on pointer up when pointer hits common bounding box without hitting any element: [end of test] number of renders 1`] = `7`; exports[`regression tests deselects selected element on pointer down when pointer doesn't hit any element: [end of test] appState 1`] = ` Object { @@ -5199,7 +5670,7 @@ Object { "roundness": Object { "type": 2, }, - "seed": 449462985, + "seed": 2019559783, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, @@ -5279,7 +5750,7 @@ Object { "roundness": Object { "type": 2, }, - "seed": 449462985, + "seed": 2019559783, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, @@ -5354,14 +5825,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 337897, + "seed": 1278240551, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 1278240551, + "versionNonce": 453191, "width": 10, "x": 0, "y": 0, @@ -5374,7 +5845,7 @@ Object { exports[`regression tests deselects selected element on pointer down when pointer doesn't hit any element: [end of test] number of elements 1`] = `0`; -exports[`regression tests deselects selected element on pointer down when pointer doesn't hit any element: [end of test] number of renders 1`] = `10`; +exports[`regression tests deselects selected element on pointer down when pointer doesn't hit any element: [end of test] number of renders 1`] = `5`; exports[`regression tests deselects selected element, on pointer up, when click hits element bounding box but doesn't hit the element: [end of test] appState 1`] = ` Object { @@ -5518,14 +5989,14 @@ Object { "roundness": Object { "type": 2, }, - "seed": 337897, + "seed": 1278240551, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "ellipse", "updated": 1, "version": 2, - "versionNonce": 1278240551, + "versionNonce": 453191, "width": 100, "x": 0, "y": 0, @@ -5538,7 +6009,7 @@ Object { exports[`regression tests deselects selected element, on pointer up, when click hits element bounding box but doesn't hit the element: [end of test] number of elements 1`] = `0`; -exports[`regression tests deselects selected element, on pointer up, when click hits element bounding box but doesn't hit the element: [end of test] number of renders 1`] = `11`; +exports[`regression tests deselects selected element, on pointer up, when click hits element bounding box but doesn't hit the element: [end of test] number of renders 1`] = `5`; exports[`regression tests double click to edit a group: [end of test] appState 1`] = ` Object { @@ -5682,14 +6153,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 337897, + "seed": 1278240551, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 1278240551, + "versionNonce": 453191, "width": 10, "x": 10, "y": 10, @@ -5725,14 +6196,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 337897, + "seed": 1278240551, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 1278240551, + "versionNonce": 453191, "width": 10, "x": 10, "y": 10, @@ -5754,14 +6225,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 449462985, + "seed": 2019559783, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 453191, + "versionNonce": 1116226695, "width": 10, "x": 30, "y": 10, @@ -5797,14 +6268,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 337897, + "seed": 1278240551, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 1278240551, + "versionNonce": 453191, "width": 10, "x": 10, "y": 10, @@ -5826,14 +6297,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 449462985, + "seed": 2019559783, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 453191, + "versionNonce": 1116226695, "width": 10, "x": 30, "y": 10, @@ -5855,14 +6326,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 401146281, + "seed": 238820263, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 2019559783, + "versionNonce": 1604849351, "width": 10, "x": 50, "y": 10, @@ -5904,14 +6375,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 337897, + "seed": 1278240551, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 3, - "versionNonce": 1150084233, + "versionNonce": 23633383, "width": 10, "x": 10, "y": 10, @@ -5935,14 +6406,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 449462985, + "seed": 2019559783, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 3, - "versionNonce": 1116226695, + "versionNonce": 493213705, "width": 10, "x": 30, "y": 10, @@ -5966,14 +6437,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 401146281, + "seed": 238820263, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 3, - "versionNonce": 1014066025, + "versionNonce": 915032327, "width": 10, "x": 50, "y": 10, @@ -5986,7 +6457,7 @@ Object { exports[`regression tests double click to edit a group: [end of test] number of elements 1`] = `0`; -exports[`regression tests double click to edit a group: [end of test] number of renders 1`] = `20`; +exports[`regression tests double click to edit a group: [end of test] number of renders 1`] = `10`; exports[`regression tests drags selected elements from point inside common bounding box that doesn't hit any element and keeps elements selected after dragging: [end of test] appState 1`] = ` Object { @@ -6134,14 +6605,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 337897, + "seed": 1278240551, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 1278240551, + "versionNonce": 453191, "width": 10, "x": 0, "y": 0, @@ -6177,14 +6648,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 337897, + "seed": 1278240551, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 1278240551, + "versionNonce": 453191, "width": 10, "x": 0, "y": 0, @@ -6206,14 +6677,14 @@ Object { "roundness": Object { "type": 2, }, - "seed": 449462985, + "seed": 2019559783, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "ellipse", "updated": 1, "version": 2, - "versionNonce": 453191, + "versionNonce": 1116226695, "width": 10, "x": 110, "y": 110, @@ -6250,14 +6721,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 337897, + "seed": 1278240551, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 3, - "versionNonce": 1150084233, + "versionNonce": 1604849351, "width": 10, "x": 25, "y": 25, @@ -6279,14 +6750,14 @@ Object { "roundness": Object { "type": 2, }, - "seed": 449462985, + "seed": 2019559783, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "ellipse", "updated": 1, "version": 3, - "versionNonce": 1116226695, + "versionNonce": 23633383, "width": 10, "x": 135, "y": 135, @@ -6299,7 +6770,7 @@ Object { exports[`regression tests drags selected elements from point inside common bounding box that doesn't hit any element and keeps elements selected after dragging: [end of test] number of elements 1`] = `0`; -exports[`regression tests drags selected elements from point inside common bounding box that doesn't hit any element and keeps elements selected after dragging: [end of test] number of renders 1`] = `18`; +exports[`regression tests drags selected elements from point inside common bounding box that doesn't hit any element and keeps elements selected after dragging: [end of test] number of renders 1`] = `9`; exports[`regression tests draw every type of shape: [end of test] appState 1`] = ` Object { @@ -6441,14 +6912,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 337897, + "seed": 1278240551, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 1278240551, + "versionNonce": 453191, "width": 20, "x": 10, "y": -10, @@ -6484,14 +6955,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 337897, + "seed": 1278240551, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 1278240551, + "versionNonce": 453191, "width": 20, "x": 10, "y": -10, @@ -6513,14 +6984,14 @@ Object { "roundness": Object { "type": 2, }, - "seed": 449462985, + "seed": 2019559783, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "diamond", "updated": 1, "version": 2, - "versionNonce": 453191, + "versionNonce": 1116226695, "width": 20, "x": 40, "y": -10, @@ -6556,14 +7027,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 337897, + "seed": 1278240551, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 1278240551, + "versionNonce": 453191, "width": 20, "x": 10, "y": -10, @@ -6585,14 +7056,14 @@ Object { "roundness": Object { "type": 2, }, - "seed": 449462985, + "seed": 2019559783, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "diamond", "updated": 1, "version": 2, - "versionNonce": 453191, + "versionNonce": 1116226695, "width": 20, "x": 40, "y": -10, @@ -6614,14 +7085,14 @@ Object { "roundness": Object { "type": 2, }, - "seed": 401146281, + "seed": 238820263, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "ellipse", "updated": 1, "version": 2, - "versionNonce": 2019559783, + "versionNonce": 1604849351, "width": 20, "x": 70, "y": -10, @@ -6657,14 +7128,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 337897, + "seed": 1278240551, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 1278240551, + "versionNonce": 453191, "width": 20, "x": 10, "y": -10, @@ -6686,14 +7157,14 @@ Object { "roundness": Object { "type": 2, }, - "seed": 449462985, + "seed": 2019559783, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "diamond", "updated": 1, "version": 2, - "versionNonce": 453191, + "versionNonce": 1116226695, "width": 20, "x": 40, "y": -10, @@ -6715,14 +7186,14 @@ Object { "roundness": Object { "type": 2, }, - "seed": 401146281, + "seed": 238820263, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "ellipse", "updated": 1, "version": 2, - "versionNonce": 2019559783, + "versionNonce": 1604849351, "width": 20, "x": 70, "y": -10, @@ -6757,7 +7228,7 @@ Object { "roundness": Object { "type": 2, }, - "seed": 1150084233, + "seed": 23633383, "startArrowhead": null, "startBinding": null, "strokeColor": "#1e1e1e", @@ -6766,7 +7237,7 @@ Object { "type": "arrow", "updated": 1, "version": 3, - "versionNonce": 1014066025, + "versionNonce": 81784553, "width": 50, "x": 130, "y": -10, @@ -6802,14 +7273,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 337897, + "seed": 1278240551, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 1278240551, + "versionNonce": 453191, "width": 20, "x": 10, "y": -10, @@ -6831,14 +7302,14 @@ Object { "roundness": Object { "type": 2, }, - "seed": 449462985, + "seed": 2019559783, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "diamond", "updated": 1, "version": 2, - "versionNonce": 453191, + "versionNonce": 1116226695, "width": 20, "x": 40, "y": -10, @@ -6860,14 +7331,14 @@ Object { "roundness": Object { "type": 2, }, - "seed": 401146281, + "seed": 238820263, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "ellipse", "updated": 1, "version": 2, - "versionNonce": 2019559783, + "versionNonce": 1604849351, "width": 20, "x": 70, "y": -10, @@ -6902,7 +7373,7 @@ Object { "roundness": Object { "type": 2, }, - "seed": 1150084233, + "seed": 23633383, "startArrowhead": null, "startBinding": null, "strokeColor": "#1e1e1e", @@ -6911,7 +7382,7 @@ Object { "type": "arrow", "updated": 1, "version": 3, - "versionNonce": 1014066025, + "versionNonce": 81784553, "width": 50, "x": 130, "y": -10, @@ -6946,7 +7417,7 @@ Object { "roundness": Object { "type": 2, }, - "seed": 238820263, + "seed": 1723083209, "startArrowhead": null, "startBinding": null, "strokeColor": "#1e1e1e", @@ -6955,7 +7426,7 @@ Object { "type": "line", "updated": 1, "version": 3, - "versionNonce": 1604849351, + "versionNonce": 289600103, "width": 50, "x": 220, "y": -10, @@ -6991,14 +7462,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 337897, + "seed": 1278240551, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 1278240551, + "versionNonce": 453191, "width": 20, "x": 10, "y": -10, @@ -7020,14 +7491,14 @@ Object { "roundness": Object { "type": 2, }, - "seed": 449462985, + "seed": 2019559783, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "diamond", "updated": 1, "version": 2, - "versionNonce": 453191, + "versionNonce": 1116226695, "width": 20, "x": 40, "y": -10, @@ -7049,14 +7520,14 @@ Object { "roundness": Object { "type": 2, }, - "seed": 401146281, + "seed": 238820263, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "ellipse", "updated": 1, "version": 2, - "versionNonce": 2019559783, + "versionNonce": 1604849351, "width": 20, "x": 70, "y": -10, @@ -7091,7 +7562,7 @@ Object { "roundness": Object { "type": 2, }, - "seed": 1150084233, + "seed": 23633383, "startArrowhead": null, "startBinding": null, "strokeColor": "#1e1e1e", @@ -7100,7 +7571,7 @@ Object { "type": "arrow", "updated": 1, "version": 3, - "versionNonce": 1014066025, + "versionNonce": 81784553, "width": 50, "x": 130, "y": -10, @@ -7135,7 +7606,7 @@ Object { "roundness": Object { "type": 2, }, - "seed": 238820263, + "seed": 1723083209, "startArrowhead": null, "startBinding": null, "strokeColor": "#1e1e1e", @@ -7144,7 +7615,7 @@ Object { "type": "line", "updated": 1, "version": 3, - "versionNonce": 1604849351, + "versionNonce": 289600103, "width": 50, "x": 220, "y": -10, @@ -7182,7 +7653,7 @@ Object { "roundness": Object { "type": 2, }, - "seed": 1505387817, + "seed": 1898319239, "startArrowhead": null, "startBinding": null, "strokeColor": "#1e1e1e", @@ -7191,7 +7662,7 @@ Object { "type": "arrow", "updated": 1, "version": 5, - "versionNonce": 81784553, + "versionNonce": 1349943049, "width": 50, "x": 310, "y": -10, @@ -7227,14 +7698,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 337897, + "seed": 1278240551, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 1278240551, + "versionNonce": 453191, "width": 20, "x": 10, "y": -10, @@ -7256,14 +7727,14 @@ Object { "roundness": Object { "type": 2, }, - "seed": 449462985, + "seed": 2019559783, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "diamond", "updated": 1, "version": 2, - "versionNonce": 453191, + "versionNonce": 1116226695, "width": 20, "x": 40, "y": -10, @@ -7285,14 +7756,14 @@ Object { "roundness": Object { "type": 2, }, - "seed": 401146281, + "seed": 238820263, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "ellipse", "updated": 1, "version": 2, - "versionNonce": 2019559783, + "versionNonce": 1604849351, "width": 20, "x": 70, "y": -10, @@ -7327,7 +7798,7 @@ Object { "roundness": Object { "type": 2, }, - "seed": 1150084233, + "seed": 23633383, "startArrowhead": null, "startBinding": null, "strokeColor": "#1e1e1e", @@ -7336,7 +7807,7 @@ Object { "type": "arrow", "updated": 1, "version": 3, - "versionNonce": 1014066025, + "versionNonce": 81784553, "width": 50, "x": 130, "y": -10, @@ -7371,7 +7842,7 @@ Object { "roundness": Object { "type": 2, }, - "seed": 238820263, + "seed": 1723083209, "startArrowhead": null, "startBinding": null, "strokeColor": "#1e1e1e", @@ -7380,7 +7851,7 @@ Object { "type": "line", "updated": 1, "version": 3, - "versionNonce": 1604849351, + "versionNonce": 289600103, "width": 50, "x": 220, "y": -10, @@ -7422,7 +7893,7 @@ Object { "roundness": Object { "type": 2, }, - "seed": 1505387817, + "seed": 1898319239, "startArrowhead": null, "startBinding": null, "strokeColor": "#1e1e1e", @@ -7431,7 +7902,7 @@ Object { "type": "arrow", "updated": 1, "version": 7, - "versionNonce": 1723083209, + "versionNonce": 1292308681, "width": 80, "x": 310, "y": -10, @@ -7467,14 +7938,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 337897, + "seed": 1278240551, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 1278240551, + "versionNonce": 453191, "width": 20, "x": 10, "y": -10, @@ -7496,14 +7967,14 @@ Object { "roundness": Object { "type": 2, }, - "seed": 449462985, + "seed": 2019559783, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "diamond", "updated": 1, "version": 2, - "versionNonce": 453191, + "versionNonce": 1116226695, "width": 20, "x": 40, "y": -10, @@ -7525,14 +7996,14 @@ Object { "roundness": Object { "type": 2, }, - "seed": 401146281, + "seed": 238820263, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "ellipse", "updated": 1, "version": 2, - "versionNonce": 2019559783, + "versionNonce": 1604849351, "width": 20, "x": 70, "y": -10, @@ -7567,7 +8038,7 @@ Object { "roundness": Object { "type": 2, }, - "seed": 1150084233, + "seed": 23633383, "startArrowhead": null, "startBinding": null, "strokeColor": "#1e1e1e", @@ -7576,7 +8047,7 @@ Object { "type": "arrow", "updated": 1, "version": 3, - "versionNonce": 1014066025, + "versionNonce": 81784553, "width": 50, "x": 130, "y": -10, @@ -7611,7 +8082,7 @@ Object { "roundness": Object { "type": 2, }, - "seed": 238820263, + "seed": 1723083209, "startArrowhead": null, "startBinding": null, "strokeColor": "#1e1e1e", @@ -7620,7 +8091,7 @@ Object { "type": "line", "updated": 1, "version": 3, - "versionNonce": 1604849351, + "versionNonce": 289600103, "width": 50, "x": 220, "y": -10, @@ -7662,7 +8133,7 @@ Object { "roundness": Object { "type": 2, }, - "seed": 1505387817, + "seed": 1898319239, "startArrowhead": null, "startBinding": null, "strokeColor": "#1e1e1e", @@ -7671,7 +8142,7 @@ Object { "type": "arrow", "updated": 1, "version": 7, - "versionNonce": 1723083209, + "versionNonce": 1292308681, "width": 80, "x": 310, "y": -10, @@ -7709,7 +8180,7 @@ Object { "roundness": Object { "type": 2, }, - "seed": 760410951, + "seed": 1508694887, "startArrowhead": null, "startBinding": null, "strokeColor": "#1e1e1e", @@ -7718,7 +8189,7 @@ Object { "type": "line", "updated": 1, "version": 5, - "versionNonce": 1898319239, + "versionNonce": 1177973545, "width": 50, "x": 430, "y": -10, @@ -7754,14 +8225,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 337897, + "seed": 1278240551, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 1278240551, + "versionNonce": 453191, "width": 20, "x": 10, "y": -10, @@ -7783,14 +8254,14 @@ Object { "roundness": Object { "type": 2, }, - "seed": 449462985, + "seed": 2019559783, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "diamond", "updated": 1, "version": 2, - "versionNonce": 453191, + "versionNonce": 1116226695, "width": 20, "x": 40, "y": -10, @@ -7812,14 +8283,14 @@ Object { "roundness": Object { "type": 2, }, - "seed": 401146281, + "seed": 238820263, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "ellipse", "updated": 1, "version": 2, - "versionNonce": 2019559783, + "versionNonce": 1604849351, "width": 20, "x": 70, "y": -10, @@ -7854,7 +8325,7 @@ Object { "roundness": Object { "type": 2, }, - "seed": 1150084233, + "seed": 23633383, "startArrowhead": null, "startBinding": null, "strokeColor": "#1e1e1e", @@ -7863,7 +8334,7 @@ Object { "type": "arrow", "updated": 1, "version": 3, - "versionNonce": 1014066025, + "versionNonce": 81784553, "width": 50, "x": 130, "y": -10, @@ -7898,7 +8369,7 @@ Object { "roundness": Object { "type": 2, }, - "seed": 238820263, + "seed": 1723083209, "startArrowhead": null, "startBinding": null, "strokeColor": "#1e1e1e", @@ -7907,7 +8378,7 @@ Object { "type": "line", "updated": 1, "version": 3, - "versionNonce": 1604849351, + "versionNonce": 289600103, "width": 50, "x": 220, "y": -10, @@ -7949,7 +8420,7 @@ Object { "roundness": Object { "type": 2, }, - "seed": 1505387817, + "seed": 1898319239, "startArrowhead": null, "startBinding": null, "strokeColor": "#1e1e1e", @@ -7958,7 +8429,7 @@ Object { "type": "arrow", "updated": 1, "version": 7, - "versionNonce": 1723083209, + "versionNonce": 1292308681, "width": 80, "x": 310, "y": -10, @@ -8000,7 +8471,7 @@ Object { "roundness": Object { "type": 2, }, - "seed": 760410951, + "seed": 1508694887, "startArrowhead": null, "startBinding": null, "strokeColor": "#1e1e1e", @@ -8009,7 +8480,7 @@ Object { "type": "line", "updated": 1, "version": 7, - "versionNonce": 406373543, + "versionNonce": 271613161, "width": 80, "x": 430, "y": -10, @@ -8043,14 +8514,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 337897, + "seed": 1278240551, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 1278240551, + "versionNonce": 453191, "width": 20, "x": 10, "y": -10, @@ -8072,14 +8543,14 @@ Object { "roundness": Object { "type": 2, }, - "seed": 449462985, + "seed": 2019559783, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "diamond", "updated": 1, "version": 2, - "versionNonce": 453191, + "versionNonce": 1116226695, "width": 20, "x": 40, "y": -10, @@ -8101,14 +8572,14 @@ Object { "roundness": Object { "type": 2, }, - "seed": 401146281, + "seed": 238820263, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "ellipse", "updated": 1, "version": 2, - "versionNonce": 2019559783, + "versionNonce": 1604849351, "width": 20, "x": 70, "y": -10, @@ -8143,7 +8614,7 @@ Object { "roundness": Object { "type": 2, }, - "seed": 1150084233, + "seed": 23633383, "startArrowhead": null, "startBinding": null, "strokeColor": "#1e1e1e", @@ -8152,7 +8623,7 @@ Object { "type": "arrow", "updated": 1, "version": 3, - "versionNonce": 1014066025, + "versionNonce": 81784553, "width": 50, "x": 130, "y": -10, @@ -8187,7 +8658,7 @@ Object { "roundness": Object { "type": 2, }, - "seed": 238820263, + "seed": 1723083209, "startArrowhead": null, "startBinding": null, "strokeColor": "#1e1e1e", @@ -8196,7 +8667,7 @@ Object { "type": "line", "updated": 1, "version": 3, - "versionNonce": 1604849351, + "versionNonce": 289600103, "width": 50, "x": 220, "y": -10, @@ -8238,7 +8709,7 @@ Object { "roundness": Object { "type": 2, }, - "seed": 1505387817, + "seed": 1898319239, "startArrowhead": null, "startBinding": null, "strokeColor": "#1e1e1e", @@ -8247,7 +8718,7 @@ Object { "type": "arrow", "updated": 1, "version": 7, - "versionNonce": 1723083209, + "versionNonce": 1292308681, "width": 80, "x": 310, "y": -10, @@ -8289,7 +8760,7 @@ Object { "roundness": Object { "type": 2, }, - "seed": 760410951, + "seed": 1508694887, "startArrowhead": null, "startBinding": null, "strokeColor": "#1e1e1e", @@ -8298,7 +8769,7 @@ Object { "type": "line", "updated": 1, "version": 7, - "versionNonce": 406373543, + "versionNonce": 271613161, "width": 80, "x": 430, "y": -10, @@ -8341,7 +8812,7 @@ Object { ], "roughness": 1, "roundness": null, - "seed": 941653321, + "seed": 1189086535, "simulatePressure": false, "strokeColor": "#1e1e1e", "strokeStyle": "solid", @@ -8349,7 +8820,7 @@ Object { "type": "freedraw", "updated": 1, "version": 4, - "versionNonce": 1359939303, + "versionNonce": 1439318121, "width": 50, "x": 550, "y": -10, @@ -8362,7 +8833,7 @@ Object { exports[`regression tests draw every type of shape: [end of test] number of elements 1`] = `0`; -exports[`regression tests draw every type of shape: [end of test] number of renders 1`] = `57`; +exports[`regression tests draw every type of shape: [end of test] number of renders 1`] = `32`; exports[`regression tests given a group of selected elements with an element that is not selected inside the group common bounding box when element that is not selected is clicked should switch selection to not selected element on pointer up: [end of test] appState 1`] = ` Object { @@ -8509,14 +8980,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 337897, + "seed": 1278240551, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 1278240551, + "versionNonce": 453191, "width": 10, "x": 0, "y": 0, @@ -8552,14 +9023,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 337897, + "seed": 1278240551, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 1278240551, + "versionNonce": 453191, "width": 10, "x": 0, "y": 0, @@ -8581,14 +9052,14 @@ Object { "roundness": Object { "type": 2, }, - "seed": 449462985, + "seed": 2019559783, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "ellipse", "updated": 1, "version": 2, - "versionNonce": 453191, + "versionNonce": 1116226695, "width": 100, "x": 110, "y": 110, @@ -8624,14 +9095,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 337897, + "seed": 1278240551, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 1278240551, + "versionNonce": 453191, "width": 10, "x": 0, "y": 0, @@ -8653,14 +9124,14 @@ Object { "roundness": Object { "type": 2, }, - "seed": 449462985, + "seed": 2019559783, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "ellipse", "updated": 1, "version": 2, - "versionNonce": 453191, + "versionNonce": 1116226695, "width": 100, "x": 110, "y": 110, @@ -8682,14 +9153,14 @@ Object { "roundness": Object { "type": 2, }, - "seed": 401146281, + "seed": 238820263, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "diamond", "updated": 1, "version": 2, - "versionNonce": 2019559783, + "versionNonce": 1604849351, "width": 100, "x": 310, "y": 310, @@ -8702,7 +9173,7 @@ Object { exports[`regression tests given a group of selected elements with an element that is not selected inside the group common bounding box when element that is not selected is clicked should switch selection to not selected element on pointer up: [end of test] number of elements 1`] = `0`; -exports[`regression tests given a group of selected elements with an element that is not selected inside the group common bounding box when element that is not selected is clicked should switch selection to not selected element on pointer up: [end of test] number of renders 1`] = `21`; +exports[`regression tests given a group of selected elements with an element that is not selected inside the group common bounding box when element that is not selected is clicked should switch selection to not selected element on pointer up: [end of test] number of renders 1`] = `9`; exports[`regression tests given a selected element A and a not selected element B with higher z-index than A and given B partially overlaps A when there's a shift-click on the overlapped section B is added to the selection: [end of test] appState 1`] = ` Object { @@ -8849,14 +9320,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 337897, + "seed": 449462985, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 1278240551, + "versionNonce": 401146281, "width": 1000, "x": 0, "y": 0, @@ -8892,14 +9363,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 337897, + "seed": 449462985, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 1278240551, + "versionNonce": 401146281, "width": 1000, "x": 0, "y": 0, @@ -8921,14 +9392,14 @@ Object { "roundness": Object { "type": 2, }, - "seed": 449462985, + "seed": 1150084233, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "ellipse", "updated": 1, "version": 2, - "versionNonce": 453191, + "versionNonce": 1014066025, "width": 1000, "x": 500, "y": 500, @@ -8941,7 +9412,7 @@ Object { exports[`regression tests given a selected element A and a not selected element B with higher z-index than A and given B partially overlaps A when there's a shift-click on the overlapped section B is added to the selection: [end of test] number of elements 1`] = `0`; -exports[`regression tests given a selected element A and a not selected element B with higher z-index than A and given B partially overlaps A when there's a shift-click on the overlapped section B is added to the selection: [end of test] number of renders 1`] = `19`; +exports[`regression tests given a selected element A and a not selected element B with higher z-index than A and given B partially overlaps A when there's a shift-click on the overlapped section B is added to the selection: [end of test] number of renders 1`] = `8`; exports[`regression tests given selected element A with lower z-index than unselected element B and given B is partially over A when clicking intersection between A and B B should be selected on pointer up: [end of test] appState 1`] = ` Object { @@ -9087,7 +9558,7 @@ Object { "roundness": Object { "type": 3, }, - "seed": 337897, + "seed": 1278240551, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, @@ -9116,7 +9587,7 @@ Object { "roundness": Object { "type": 3, }, - "seed": 1278240551, + "seed": 449462985, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, @@ -9136,7 +9607,7 @@ Object { exports[`regression tests given selected element A with lower z-index than unselected element B and given B is partially over A when clicking intersection between A and B B should be selected on pointer up: [end of test] number of elements 1`] = `0`; -exports[`regression tests given selected element A with lower z-index than unselected element B and given B is partially over A when clicking intersection between A and B B should be selected on pointer up: [end of test] number of renders 1`] = `12`; +exports[`regression tests given selected element A with lower z-index than unselected element B and given B is partially over A when clicking intersection between A and B B should be selected on pointer up: [end of test] number of renders 1`] = `4`; exports[`regression tests given selected element A with lower z-index than unselected element B and given B is partially over A when dragging on intersection between A and B A should be dragged and keep being selected: [end of test] appState 1`] = ` Object { @@ -9282,7 +9753,7 @@ Object { "roundness": Object { "type": 3, }, - "seed": 337897, + "seed": 1278240551, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, @@ -9311,7 +9782,7 @@ Object { "roundness": Object { "type": 3, }, - "seed": 1278240551, + "seed": 449462985, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, @@ -9354,14 +9825,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 337897, + "seed": 1278240551, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 401146281, + "versionNonce": 1150084233, "width": 1000, "x": 100, "y": 100, @@ -9383,7 +9854,7 @@ Object { "roundness": Object { "type": 3, }, - "seed": 1278240551, + "seed": 449462985, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, @@ -9403,7 +9874,7 @@ Object { exports[`regression tests given selected element A with lower z-index than unselected element B and given B is partially over A when dragging on intersection between A and B A should be dragged and keep being selected: [end of test] number of elements 1`] = `0`; -exports[`regression tests given selected element A with lower z-index than unselected element B and given B is partially over A when dragging on intersection between A and B A should be dragged and keep being selected: [end of test] number of renders 1`] = `13`; +exports[`regression tests given selected element A with lower z-index than unselected element B and given B is partially over A when dragging on intersection between A and B A should be dragged and keep being selected: [end of test] number of renders 1`] = `6`; exports[`regression tests key 2 selects rectangle tool: [end of test] appState 1`] = ` Object { @@ -9547,14 +10018,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 337897, + "seed": 1278240551, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 1278240551, + "versionNonce": 453191, "width": 10, "x": 10, "y": 10, @@ -9567,7 +10038,7 @@ Object { exports[`regression tests key 2 selects rectangle tool: [end of test] number of elements 1`] = `0`; -exports[`regression tests key 2 selects rectangle tool: [end of test] number of renders 1`] = `9`; +exports[`regression tests key 2 selects rectangle tool: [end of test] number of renders 1`] = `5`; exports[`regression tests key 3 selects diamond tool: [end of test] appState 1`] = ` Object { @@ -9711,14 +10182,14 @@ Object { "roundness": Object { "type": 2, }, - "seed": 337897, + "seed": 1278240551, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "diamond", "updated": 1, "version": 2, - "versionNonce": 1278240551, + "versionNonce": 453191, "width": 10, "x": 10, "y": 10, @@ -9731,7 +10202,7 @@ Object { exports[`regression tests key 3 selects diamond tool: [end of test] number of elements 1`] = `0`; -exports[`regression tests key 3 selects diamond tool: [end of test] number of renders 1`] = `9`; +exports[`regression tests key 3 selects diamond tool: [end of test] number of renders 1`] = `5`; exports[`regression tests key 4 selects ellipse tool: [end of test] appState 1`] = ` Object { @@ -9875,14 +10346,14 @@ Object { "roundness": Object { "type": 2, }, - "seed": 337897, + "seed": 1278240551, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "ellipse", "updated": 1, "version": 2, - "versionNonce": 1278240551, + "versionNonce": 453191, "width": 10, "x": 10, "y": 10, @@ -9895,7 +10366,7 @@ Object { exports[`regression tests key 4 selects ellipse tool: [end of test] number of elements 1`] = `0`; -exports[`regression tests key 4 selects ellipse tool: [end of test] number of renders 1`] = `9`; +exports[`regression tests key 4 selects ellipse tool: [end of test] number of renders 1`] = `5`; exports[`regression tests key 5 selects arrow tool: [end of test] appState 1`] = ` Object { @@ -10075,7 +10546,7 @@ Object { "roundness": Object { "type": 2, }, - "seed": 337897, + "seed": 1278240551, "startArrowhead": null, "startBinding": null, "strokeColor": "#1e1e1e", @@ -10084,7 +10555,7 @@ Object { "type": "arrow", "updated": 1, "version": 3, - "versionNonce": 449462985, + "versionNonce": 401146281, "width": 10, "x": 10, "y": 10, @@ -10097,7 +10568,7 @@ Object { exports[`regression tests key 5 selects arrow tool: [end of test] number of elements 1`] = `0`; -exports[`regression tests key 5 selects arrow tool: [end of test] number of renders 1`] = `10`; +exports[`regression tests key 5 selects arrow tool: [end of test] number of renders 1`] = `6`; exports[`regression tests key 6 selects line tool: [end of test] appState 1`] = ` Object { @@ -10277,7 +10748,7 @@ Object { "roundness": Object { "type": 2, }, - "seed": 337897, + "seed": 1278240551, "startArrowhead": null, "startBinding": null, "strokeColor": "#1e1e1e", @@ -10286,7 +10757,7 @@ Object { "type": "line", "updated": 1, "version": 3, - "versionNonce": 449462985, + "versionNonce": 401146281, "width": 10, "x": 10, "y": 10, @@ -10299,7 +10770,7 @@ Object { exports[`regression tests key 6 selects line tool: [end of test] number of elements 1`] = `0`; -exports[`regression tests key 6 selects line tool: [end of test] number of renders 1`] = `9`; +exports[`regression tests key 6 selects line tool: [end of test] number of renders 1`] = `6`; exports[`regression tests key 7 selects freedraw tool: [end of test] appState 1`] = ` Object { @@ -10460,7 +10931,7 @@ Object { ], "roughness": 1, "roundness": null, - "seed": 337897, + "seed": 1278240551, "simulatePressure": false, "strokeColor": "#1e1e1e", "strokeStyle": "solid", @@ -10468,7 +10939,7 @@ Object { "type": "freedraw", "updated": 1, "version": 4, - "versionNonce": 453191, + "versionNonce": 1150084233, "width": 10, "x": 10, "y": 10, @@ -10481,7 +10952,7 @@ Object { exports[`regression tests key 7 selects freedraw tool: [end of test] number of elements 1`] = `0`; -exports[`regression tests key 7 selects freedraw tool: [end of test] number of renders 1`] = `9`; +exports[`regression tests key 7 selects freedraw tool: [end of test] number of renders 1`] = `6`; exports[`regression tests key a selects arrow tool: [end of test] appState 1`] = ` Object { @@ -10661,7 +11132,7 @@ Object { "roundness": Object { "type": 2, }, - "seed": 337897, + "seed": 1278240551, "startArrowhead": null, "startBinding": null, "strokeColor": "#1e1e1e", @@ -10670,7 +11141,7 @@ Object { "type": "arrow", "updated": 1, "version": 3, - "versionNonce": 449462985, + "versionNonce": 401146281, "width": 10, "x": 10, "y": 10, @@ -10683,7 +11154,7 @@ Object { exports[`regression tests key a selects arrow tool: [end of test] number of elements 1`] = `0`; -exports[`regression tests key a selects arrow tool: [end of test] number of renders 1`] = `10`; +exports[`regression tests key a selects arrow tool: [end of test] number of renders 1`] = `6`; exports[`regression tests key d selects diamond tool: [end of test] appState 1`] = ` Object { @@ -10827,14 +11298,14 @@ Object { "roundness": Object { "type": 2, }, - "seed": 337897, + "seed": 1278240551, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "diamond", "updated": 1, "version": 2, - "versionNonce": 1278240551, + "versionNonce": 453191, "width": 10, "x": 10, "y": 10, @@ -10847,7 +11318,7 @@ Object { exports[`regression tests key d selects diamond tool: [end of test] number of elements 1`] = `0`; -exports[`regression tests key d selects diamond tool: [end of test] number of renders 1`] = `9`; +exports[`regression tests key d selects diamond tool: [end of test] number of renders 1`] = `5`; exports[`regression tests key l selects line tool: [end of test] appState 1`] = ` Object { @@ -11027,7 +11498,7 @@ Object { "roundness": Object { "type": 2, }, - "seed": 337897, + "seed": 1278240551, "startArrowhead": null, "startBinding": null, "strokeColor": "#1e1e1e", @@ -11036,7 +11507,7 @@ Object { "type": "line", "updated": 1, "version": 3, - "versionNonce": 449462985, + "versionNonce": 401146281, "width": 10, "x": 10, "y": 10, @@ -11049,7 +11520,7 @@ Object { exports[`regression tests key l selects line tool: [end of test] number of elements 1`] = `0`; -exports[`regression tests key l selects line tool: [end of test] number of renders 1`] = `9`; +exports[`regression tests key l selects line tool: [end of test] number of renders 1`] = `6`; exports[`regression tests key o selects ellipse tool: [end of test] appState 1`] = ` Object { @@ -11193,14 +11664,14 @@ Object { "roundness": Object { "type": 2, }, - "seed": 337897, + "seed": 1278240551, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "ellipse", "updated": 1, "version": 2, - "versionNonce": 1278240551, + "versionNonce": 453191, "width": 10, "x": 10, "y": 10, @@ -11213,7 +11684,7 @@ Object { exports[`regression tests key o selects ellipse tool: [end of test] number of elements 1`] = `0`; -exports[`regression tests key o selects ellipse tool: [end of test] number of renders 1`] = `9`; +exports[`regression tests key o selects ellipse tool: [end of test] number of renders 1`] = `5`; exports[`regression tests key p selects freedraw tool: [end of test] appState 1`] = ` Object { @@ -11374,7 +11845,7 @@ Object { ], "roughness": 1, "roundness": null, - "seed": 337897, + "seed": 1278240551, "simulatePressure": false, "strokeColor": "#1e1e1e", "strokeStyle": "solid", @@ -11382,7 +11853,7 @@ Object { "type": "freedraw", "updated": 1, "version": 4, - "versionNonce": 453191, + "versionNonce": 1150084233, "width": 10, "x": 10, "y": 10, @@ -11395,7 +11866,7 @@ Object { exports[`regression tests key p selects freedraw tool: [end of test] number of elements 1`] = `0`; -exports[`regression tests key p selects freedraw tool: [end of test] number of renders 1`] = `9`; +exports[`regression tests key p selects freedraw tool: [end of test] number of renders 1`] = `6`; exports[`regression tests key r selects rectangle tool: [end of test] appState 1`] = ` Object { @@ -11539,14 +12010,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 337897, + "seed": 1278240551, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 1278240551, + "versionNonce": 453191, "width": 10, "x": 10, "y": 10, @@ -11559,7 +12030,7 @@ Object { exports[`regression tests key r selects rectangle tool: [end of test] number of elements 1`] = `0`; -exports[`regression tests key r selects rectangle tool: [end of test] number of renders 1`] = `9`; +exports[`regression tests key r selects rectangle tool: [end of test] number of renders 1`] = `5`; exports[`regression tests make a group and duplicate it: [end of test] appState 1`] = ` Object { @@ -11711,14 +12182,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 337897, + "seed": 1278240551, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 1278240551, + "versionNonce": 453191, "width": 10, "x": 10, "y": 10, @@ -11754,14 +12225,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 337897, + "seed": 1278240551, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 1278240551, + "versionNonce": 453191, "width": 10, "x": 10, "y": 10, @@ -11783,14 +12254,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 449462985, + "seed": 2019559783, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 453191, + "versionNonce": 1116226695, "width": 10, "x": 30, "y": 10, @@ -11826,14 +12297,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 337897, + "seed": 1278240551, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 1278240551, + "versionNonce": 453191, "width": 10, "x": 10, "y": 10, @@ -11855,14 +12326,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 449462985, + "seed": 2019559783, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 453191, + "versionNonce": 1116226695, "width": 10, "x": 30, "y": 10, @@ -11884,14 +12355,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 401146281, + "seed": 238820263, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 2019559783, + "versionNonce": 1604849351, "width": 10, "x": 50, "y": 10, @@ -11933,14 +12404,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 337897, + "seed": 1278240551, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 3, - "versionNonce": 1014066025, + "versionNonce": 915032327, "width": 10, "x": 10, "y": 10, @@ -11964,14 +12435,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 449462985, + "seed": 2019559783, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 3, - "versionNonce": 238820263, + "versionNonce": 81784553, "width": 10, "x": 30, "y": 10, @@ -11995,14 +12466,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 401146281, + "seed": 238820263, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 3, - "versionNonce": 400692809, + "versionNonce": 747212839, "width": 10, "x": 50, "y": 10, @@ -12044,14 +12515,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 915032327, + "seed": 941653321, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 5, - "versionNonce": 81784553, + "versionNonce": 908564423, "width": 10, "x": 10, "y": 10, @@ -12075,14 +12546,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 747212839, + "seed": 1402203177, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 5, - "versionNonce": 1723083209, + "versionNonce": 1359939303, "width": 10, "x": 30, "y": 10, @@ -12106,14 +12577,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 760410951, + "seed": 1349943049, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 5, - "versionNonce": 1006504105, + "versionNonce": 2004587015, "width": 10, "x": 50, "y": 10, @@ -12137,14 +12608,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 337897, + "seed": 1278240551, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 4, - "versionNonce": 1505387817, + "versionNonce": 1006504105, "width": 10, "x": 20, "y": 20, @@ -12168,14 +12639,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 449462985, + "seed": 2019559783, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 4, - "versionNonce": 23633383, + "versionNonce": 1315507081, "width": 10, "x": 40, "y": 20, @@ -12199,14 +12670,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 401146281, + "seed": 238820263, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 4, - "versionNonce": 493213705, + "versionNonce": 640725609, "width": 10, "x": 60, "y": 20, @@ -12219,7 +12690,7 @@ Object { exports[`regression tests make a group and duplicate it: [end of test] number of elements 1`] = `0`; -exports[`regression tests make a group and duplicate it: [end of test] number of renders 1`] = `24`; +exports[`regression tests make a group and duplicate it: [end of test] number of renders 1`] = `12`; exports[`regression tests noop interaction after undo shouldn't create history entry: [end of test] appState 1`] = ` Object { @@ -12365,14 +12836,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 337897, + "seed": 1278240551, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 1278240551, + "versionNonce": 453191, "width": 10, "x": 10, "y": 10, @@ -12408,14 +12879,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 337897, + "seed": 1278240551, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 1278240551, + "versionNonce": 453191, "width": 10, "x": 10, "y": 10, @@ -12437,14 +12908,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 449462985, + "seed": 2019559783, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 453191, + "versionNonce": 1116226695, "width": 10, "x": 30, "y": 10, @@ -12457,7 +12928,7 @@ Object { exports[`regression tests noop interaction after undo shouldn't create history entry: [end of test] number of elements 1`] = `0`; -exports[`regression tests noop interaction after undo shouldn't create history entry: [end of test] number of renders 1`] = `21`; +exports[`regression tests noop interaction after undo shouldn't create history entry: [end of test] number of renders 1`] = `10`; exports[`regression tests pinch-to-zoom works: [end of test] appState 1`] = ` Object { @@ -12576,7 +13047,7 @@ Object { exports[`regression tests pinch-to-zoom works: [end of test] number of elements 1`] = `0`; -exports[`regression tests pinch-to-zoom works: [end of test] number of renders 1`] = `11`; +exports[`regression tests pinch-to-zoom works: [end of test] number of renders 1`] = `6`; exports[`regression tests rerenders UI on language change: [end of test] appState 1`] = ` Object { @@ -12695,7 +13166,7 @@ Object { exports[`regression tests rerenders UI on language change: [end of test] number of elements 1`] = `0`; -exports[`regression tests rerenders UI on language change: [end of test] number of renders 1`] = `11`; +exports[`regression tests rerenders UI on language change: [end of test] number of renders 1`] = `3`; exports[`regression tests shift click on selected element should deselect it on pointer up: [end of test] appState 1`] = ` Object { @@ -12839,14 +13310,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 337897, + "seed": 1278240551, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 1278240551, + "versionNonce": 453191, "width": 10, "x": 0, "y": 0, @@ -12859,7 +13330,7 @@ Object { exports[`regression tests shift click on selected element should deselect it on pointer up: [end of test] number of elements 1`] = `0`; -exports[`regression tests shift click on selected element should deselect it on pointer up: [end of test] number of renders 1`] = `11`; +exports[`regression tests shift click on selected element should deselect it on pointer up: [end of test] number of renders 1`] = `5`; exports[`regression tests shift-click to multiselect, then drag: [end of test] appState 1`] = ` Object { @@ -13007,14 +13478,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 337897, + "seed": 1278240551, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 1278240551, + "versionNonce": 453191, "width": 10, "x": 10, "y": 10, @@ -13050,14 +13521,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 337897, + "seed": 1278240551, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 1278240551, + "versionNonce": 453191, "width": 10, "x": 10, "y": 10, @@ -13079,14 +13550,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 449462985, + "seed": 2019559783, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 453191, + "versionNonce": 1116226695, "width": 10, "x": 30, "y": 10, @@ -13123,14 +13594,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 337897, + "seed": 1278240551, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 3, - "versionNonce": 1116226695, + "versionNonce": 1505387817, "width": 10, "x": 20, "y": 20, @@ -13152,14 +13623,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 449462985, + "seed": 2019559783, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 3, - "versionNonce": 1014066025, + "versionNonce": 493213705, "width": 10, "x": 40, "y": 20, @@ -13172,7 +13643,7 @@ Object { exports[`regression tests shift-click to multiselect, then drag: [end of test] number of elements 1`] = `0`; -exports[`regression tests shift-click to multiselect, then drag: [end of test] number of renders 1`] = `20`; +exports[`regression tests shift-click to multiselect, then drag: [end of test] number of renders 1`] = `9`; exports[`regression tests should group elements and ungroup them: [end of test] appState 1`] = ` Object { @@ -13322,14 +13793,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 337897, + "seed": 1278240551, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 1278240551, + "versionNonce": 453191, "width": 10, "x": 10, "y": 10, @@ -13365,14 +13836,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 337897, + "seed": 1278240551, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 1278240551, + "versionNonce": 453191, "width": 10, "x": 10, "y": 10, @@ -13394,14 +13865,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 449462985, + "seed": 2019559783, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 453191, + "versionNonce": 1116226695, "width": 10, "x": 30, "y": 10, @@ -13437,14 +13908,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 337897, + "seed": 1278240551, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 1278240551, + "versionNonce": 453191, "width": 10, "x": 10, "y": 10, @@ -13466,14 +13937,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 449462985, + "seed": 2019559783, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 453191, + "versionNonce": 1116226695, "width": 10, "x": 30, "y": 10, @@ -13495,14 +13966,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 401146281, + "seed": 238820263, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 2019559783, + "versionNonce": 1604849351, "width": 10, "x": 50, "y": 10, @@ -13544,14 +14015,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 337897, + "seed": 1278240551, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 3, - "versionNonce": 1014066025, + "versionNonce": 915032327, "width": 10, "x": 10, "y": 10, @@ -13575,14 +14046,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 449462985, + "seed": 2019559783, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 3, - "versionNonce": 238820263, + "versionNonce": 81784553, "width": 10, "x": 30, "y": 10, @@ -13606,14 +14077,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 401146281, + "seed": 238820263, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 3, - "versionNonce": 400692809, + "versionNonce": 747212839, "width": 10, "x": 50, "y": 10, @@ -13651,14 +14122,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 337897, + "seed": 1278240551, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 4, - "versionNonce": 23633383, + "versionNonce": 289600103, "width": 10, "x": 10, "y": 10, @@ -13680,14 +14151,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 449462985, + "seed": 2019559783, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 4, - "versionNonce": 493213705, + "versionNonce": 1315507081, "width": 10, "x": 30, "y": 10, @@ -13709,14 +14180,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 401146281, + "seed": 238820263, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 4, - "versionNonce": 915032327, + "versionNonce": 1898319239, "width": 10, "x": 50, "y": 10, @@ -13729,7 +14200,7 @@ Object { exports[`regression tests should group elements and ungroup them: [end of test] number of elements 1`] = `0`; -exports[`regression tests should group elements and ungroup them: [end of test] number of renders 1`] = `25`; +exports[`regression tests should group elements and ungroup them: [end of test] number of renders 1`] = `11`; exports[`regression tests should show fill icons when element has non transparent background: [end of test] appState 1`] = ` Object { @@ -13873,14 +14344,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 337897, + "seed": 1278240551, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 1278240551, + "versionNonce": 453191, "width": 10, "x": 0, "y": 0, @@ -13916,14 +14387,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 337897, + "seed": 1278240551, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 3, - "versionNonce": 449462985, + "versionNonce": 2019559783, "width": 10, "x": 0, "y": 0, @@ -13936,7 +14407,7 @@ Object { exports[`regression tests should show fill icons when element has non transparent background: [end of test] number of elements 1`] = `0`; -exports[`regression tests should show fill icons when element has non transparent background: [end of test] number of renders 1`] = `13`; +exports[`regression tests should show fill icons when element has non transparent background: [end of test] number of renders 1`] = `7`; exports[`regression tests single-clicking on a subgroup of a selected group should not alter selection: [end of test] appState 1`] = ` Object { @@ -14090,14 +14561,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 337897, + "seed": 1278240551, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 1278240551, + "versionNonce": 453191, "width": 10, "x": 10, "y": 0, @@ -14133,14 +14604,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 337897, + "seed": 1278240551, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 1278240551, + "versionNonce": 453191, "width": 10, "x": 10, "y": 0, @@ -14162,14 +14633,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 449462985, + "seed": 2019559783, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 453191, + "versionNonce": 1116226695, "width": 10, "x": 50, "y": 0, @@ -14210,14 +14681,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 337897, + "seed": 1278240551, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 3, - "versionNonce": 1150084233, + "versionNonce": 1604849351, "width": 10, "x": 10, "y": 0, @@ -14241,14 +14712,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 449462985, + "seed": 2019559783, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 3, - "versionNonce": 1116226695, + "versionNonce": 1505387817, "width": 10, "x": 50, "y": 0, @@ -14286,14 +14757,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 337897, + "seed": 1278240551, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 3, - "versionNonce": 1150084233, + "versionNonce": 1604849351, "width": 10, "x": 10, "y": 0, @@ -14317,14 +14788,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 449462985, + "seed": 2019559783, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 3, - "versionNonce": 1116226695, + "versionNonce": 1505387817, "width": 10, "x": 50, "y": 0, @@ -14346,14 +14817,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 1014066025, + "seed": 493213705, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 238820263, + "versionNonce": 81784553, "width": 10, "x": 10, "y": 50, @@ -14391,14 +14862,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 337897, + "seed": 1278240551, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 3, - "versionNonce": 1150084233, + "versionNonce": 1604849351, "width": 10, "x": 10, "y": 0, @@ -14422,14 +14893,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 449462985, + "seed": 2019559783, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 3, - "versionNonce": 1116226695, + "versionNonce": 1505387817, "width": 10, "x": 50, "y": 0, @@ -14451,14 +14922,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 1014066025, + "seed": 493213705, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 238820263, + "versionNonce": 81784553, "width": 10, "x": 10, "y": 50, @@ -14480,14 +14951,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 400692809, + "seed": 1723083209, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 1604849351, + "versionNonce": 1006504105, "width": 10, "x": 50, "y": 50, @@ -14528,14 +14999,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 337897, + "seed": 1278240551, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 3, - "versionNonce": 1150084233, + "versionNonce": 1604849351, "width": 10, "x": 10, "y": 0, @@ -14559,14 +15030,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 449462985, + "seed": 2019559783, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 3, - "versionNonce": 1116226695, + "versionNonce": 1505387817, "width": 10, "x": 50, "y": 0, @@ -14590,14 +15061,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 1014066025, + "seed": 493213705, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 3, - "versionNonce": 493213705, + "versionNonce": 640725609, "width": 10, "x": 10, "y": 50, @@ -14621,14 +15092,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 400692809, + "seed": 1723083209, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 3, - "versionNonce": 915032327, + "versionNonce": 406373543, "width": 10, "x": 50, "y": 50, @@ -14672,14 +15143,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 337897, + "seed": 1278240551, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 4, - "versionNonce": 81784553, + "versionNonce": 908564423, "width": 10, "x": 10, "y": 0, @@ -14704,14 +15175,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 449462985, + "seed": 2019559783, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 4, - "versionNonce": 747212839, + "versionNonce": 1402203177, "width": 10, "x": 50, "y": 0, @@ -14736,14 +15207,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 1014066025, + "seed": 493213705, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 4, - "versionNonce": 1723083209, + "versionNonce": 1359939303, "width": 10, "x": 10, "y": 50, @@ -14768,14 +15239,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 400692809, + "seed": 1723083209, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 4, - "versionNonce": 760410951, + "versionNonce": 1349943049, "width": 10, "x": 50, "y": 50, @@ -14788,7 +15259,7 @@ Object { exports[`regression tests single-clicking on a subgroup of a selected group should not alter selection: [end of test] number of elements 1`] = `0`; -exports[`regression tests single-clicking on a subgroup of a selected group should not alter selection: [end of test] number of renders 1`] = `39`; +exports[`regression tests single-clicking on a subgroup of a selected group should not alter selection: [end of test] number of renders 1`] = `14`; exports[`regression tests spacebar + drag scrolls the canvas: [end of test] appState 1`] = ` Object { @@ -14907,7 +15378,7 @@ Object { exports[`regression tests spacebar + drag scrolls the canvas: [end of test] number of elements 1`] = `0`; -exports[`regression tests spacebar + drag scrolls the canvas: [end of test] number of renders 1`] = `8`; +exports[`regression tests spacebar + drag scrolls the canvas: [end of test] number of renders 1`] = `4`; exports[`regression tests supports nested groups: [end of test] appState 1`] = ` Object { @@ -15053,14 +15524,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 337897, + "seed": 1278240551, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 1278240551, + "versionNonce": 453191, "width": 50, "x": 0, "y": 0, @@ -15096,14 +15567,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 337897, + "seed": 1278240551, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 1278240551, + "versionNonce": 453191, "width": 50, "x": 0, "y": 0, @@ -15125,14 +15596,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 449462985, + "seed": 2019559783, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 453191, + "versionNonce": 1116226695, "width": 50, "x": 100, "y": 100, @@ -15168,14 +15639,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 337897, + "seed": 1278240551, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 1278240551, + "versionNonce": 453191, "width": 50, "x": 0, "y": 0, @@ -15197,14 +15668,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 449462985, + "seed": 2019559783, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 453191, + "versionNonce": 1116226695, "width": 50, "x": 100, "y": 100, @@ -15226,14 +15697,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 401146281, + "seed": 238820263, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 2019559783, + "versionNonce": 1604849351, "width": 50, "x": 200, "y": 200, @@ -15275,14 +15746,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 337897, + "seed": 1278240551, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 3, - "versionNonce": 1150084233, + "versionNonce": 23633383, "width": 50, "x": 0, "y": 0, @@ -15306,14 +15777,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 449462985, + "seed": 2019559783, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 3, - "versionNonce": 1116226695, + "versionNonce": 493213705, "width": 50, "x": 100, "y": 100, @@ -15337,14 +15808,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 401146281, + "seed": 238820263, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 3, - "versionNonce": 1014066025, + "versionNonce": 915032327, "width": 50, "x": 200, "y": 200, @@ -15383,14 +15854,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 337897, + "seed": 1278240551, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 3, - "versionNonce": 1150084233, + "versionNonce": 23633383, "width": 50, "x": 0, "y": 0, @@ -15414,14 +15885,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 449462985, + "seed": 2019559783, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 3, - "versionNonce": 1116226695, + "versionNonce": 493213705, "width": 50, "x": 100, "y": 100, @@ -15445,14 +15916,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 401146281, + "seed": 238820263, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 3, - "versionNonce": 1014066025, + "versionNonce": 915032327, "width": 50, "x": 200, "y": 200, @@ -15493,14 +15964,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 449462985, + "seed": 2019559783, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 3, - "versionNonce": 1116226695, + "versionNonce": 493213705, "width": 50, "x": 100, "y": 100, @@ -15525,14 +15996,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 337897, + "seed": 1278240551, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 4, - "versionNonce": 400692809, + "versionNonce": 1723083209, "width": 50, "x": 0, "y": 0, @@ -15557,14 +16028,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 401146281, + "seed": 238820263, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 4, - "versionNonce": 1604849351, + "versionNonce": 760410951, "width": 50, "x": 200, "y": 200, @@ -15606,14 +16077,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 449462985, + "seed": 2019559783, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 3, - "versionNonce": 1116226695, + "versionNonce": 493213705, "width": 50, "x": 100, "y": 100, @@ -15638,14 +16109,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 337897, + "seed": 1278240551, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 4, - "versionNonce": 400692809, + "versionNonce": 1723083209, "width": 50, "x": 0, "y": 0, @@ -15670,14 +16141,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 401146281, + "seed": 238820263, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 4, - "versionNonce": 1604849351, + "versionNonce": 760410951, "width": 50, "x": 200, "y": 200, @@ -15690,7 +16161,7 @@ Object { exports[`regression tests supports nested groups: [end of test] number of elements 1`] = `0`; -exports[`regression tests supports nested groups: [end of test] number of renders 1`] = `32`; +exports[`regression tests supports nested groups: [end of test] number of renders 1`] = `11`; exports[`regression tests switches from group of selected elements to another element on pointer down: [end of test] appState 1`] = ` Object { @@ -15735,7 +16206,7 @@ Object { "roundness": Object { "type": 2, }, - "seed": 1116226695, + "seed": 493213705, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, @@ -15818,7 +16289,7 @@ Object { "roundness": Object { "type": 2, }, - "seed": 1116226695, + "seed": 493213705, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, @@ -15893,14 +16364,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 337897, + "seed": 1278240551, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 1278240551, + "versionNonce": 453191, "width": 10, "x": 0, "y": 0, @@ -15936,14 +16407,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 337897, + "seed": 1278240551, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 1278240551, + "versionNonce": 453191, "width": 10, "x": 0, "y": 0, @@ -15965,14 +16436,14 @@ Object { "roundness": Object { "type": 2, }, - "seed": 449462985, + "seed": 2019559783, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "ellipse", "updated": 1, "version": 2, - "versionNonce": 453191, + "versionNonce": 1116226695, "width": 100, "x": 110, "y": 110, @@ -16008,14 +16479,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 337897, + "seed": 1278240551, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 1278240551, + "versionNonce": 453191, "width": 10, "x": 0, "y": 0, @@ -16037,14 +16508,14 @@ Object { "roundness": Object { "type": 2, }, - "seed": 449462985, + "seed": 2019559783, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "ellipse", "updated": 1, "version": 2, - "versionNonce": 453191, + "versionNonce": 1116226695, "width": 100, "x": 110, "y": 110, @@ -16066,14 +16537,14 @@ Object { "roundness": Object { "type": 2, }, - "seed": 401146281, + "seed": 238820263, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "diamond", "updated": 1, "version": 2, - "versionNonce": 2019559783, + "versionNonce": 1604849351, "width": 100, "x": 310, "y": 310, @@ -16086,7 +16557,7 @@ Object { exports[`regression tests switches from group of selected elements to another element on pointer down: [end of test] number of elements 1`] = `0`; -exports[`regression tests switches from group of selected elements to another element on pointer down: [end of test] number of renders 1`] = `20`; +exports[`regression tests switches from group of selected elements to another element on pointer down: [end of test] number of renders 1`] = `9`; exports[`regression tests switches selected element on pointer down: [end of test] appState 1`] = ` Object { @@ -16131,7 +16602,7 @@ Object { "roundness": Object { "type": 2, }, - "seed": 401146281, + "seed": 238820263, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, @@ -16213,7 +16684,7 @@ Object { "roundness": Object { "type": 2, }, - "seed": 401146281, + "seed": 238820263, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, @@ -16288,14 +16759,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 337897, + "seed": 1278240551, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 1278240551, + "versionNonce": 453191, "width": 10, "x": 0, "y": 0, @@ -16331,14 +16802,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 337897, + "seed": 1278240551, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 1278240551, + "versionNonce": 453191, "width": 10, "x": 0, "y": 0, @@ -16360,14 +16831,14 @@ Object { "roundness": Object { "type": 2, }, - "seed": 449462985, + "seed": 2019559783, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "ellipse", "updated": 1, "version": 2, - "versionNonce": 453191, + "versionNonce": 1116226695, "width": 10, "x": 20, "y": 20, @@ -16380,7 +16851,7 @@ Object { exports[`regression tests switches selected element on pointer down: [end of test] number of elements 1`] = `0`; -exports[`regression tests switches selected element on pointer down: [end of test] number of renders 1`] = `14`; +exports[`regression tests switches selected element on pointer down: [end of test] number of renders 1`] = `7`; exports[`regression tests two-finger scroll works: [end of test] appState 1`] = ` Object { @@ -16499,7 +16970,7 @@ Object { exports[`regression tests two-finger scroll works: [end of test] number of elements 1`] = `0`; -exports[`regression tests two-finger scroll works: [end of test] number of renders 1`] = `13`; +exports[`regression tests two-finger scroll works: [end of test] number of renders 1`] = `7`; exports[`regression tests undo/redo drawing an element: [end of test] appState 1`] = ` Object { @@ -16631,14 +17102,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 337897, + "seed": 1278240551, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 1278240551, + "versionNonce": 453191, "width": 20, "x": 10, "y": -10, @@ -16660,14 +17131,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 449462985, + "seed": 2019559783, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 453191, + "versionNonce": 1116226695, "width": 30, "x": 40, "y": 0, @@ -16709,7 +17180,7 @@ Object { "roundness": Object { "type": 2, }, - "seed": 401146281, + "seed": 238820263, "startArrowhead": null, "startBinding": null, "strokeColor": "#1e1e1e", @@ -16718,7 +17189,7 @@ Object { "type": "arrow", "updated": 1, "version": 7, - "versionNonce": 400692809, + "versionNonce": 1006504105, "width": 100, "x": 130, "y": 10, @@ -16754,14 +17225,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 337897, + "seed": 1278240551, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 1278240551, + "versionNonce": 453191, "width": 20, "x": 10, "y": -10, @@ -16783,14 +17254,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 449462985, + "seed": 2019559783, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 453191, + "versionNonce": 1116226695, "width": 30, "x": 40, "y": 0, @@ -16828,7 +17299,7 @@ Object { "roundness": Object { "type": 2, }, - "seed": 401146281, + "seed": 238820263, "startArrowhead": null, "startBinding": null, "strokeColor": "#1e1e1e", @@ -16837,7 +17308,7 @@ Object { "type": "arrow", "updated": 1, "version": 5, - "versionNonce": 1014066025, + "versionNonce": 81784553, "width": 60, "x": 130, "y": 10, @@ -16886,14 +17357,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 337897, + "seed": 1278240551, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 1278240551, + "versionNonce": 453191, "width": 20, "x": 10, "y": -10, @@ -16929,14 +17400,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 337897, + "seed": 1278240551, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 1278240551, + "versionNonce": 453191, "width": 20, "x": 10, "y": -10, @@ -16958,14 +17429,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 449462985, + "seed": 2019559783, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 453191, + "versionNonce": 1116226695, "width": 30, "x": 40, "y": 0, @@ -16978,7 +17449,7 @@ Object { exports[`regression tests undo/redo drawing an element: [end of test] number of elements 1`] = `0`; -exports[`regression tests undo/redo drawing an element: [end of test] number of renders 1`] = `31`; +exports[`regression tests undo/redo drawing an element: [end of test] number of renders 1`] = `18`; exports[`regression tests updates fontSize & fontFamily appState: [end of test] appState 1`] = ` Object { @@ -17097,7 +17568,7 @@ Object { exports[`regression tests updates fontSize & fontFamily appState: [end of test] number of elements 1`] = `0`; -exports[`regression tests updates fontSize & fontFamily appState: [end of test] number of renders 1`] = `7`; +exports[`regression tests updates fontSize & fontFamily appState: [end of test] number of renders 1`] = `4`; exports[`regression tests zoom hotkeys: [end of test] appState 1`] = ` Object { @@ -17216,4 +17687,4 @@ Object { exports[`regression tests zoom hotkeys: [end of test] number of elements 1`] = `0`; -exports[`regression tests zoom hotkeys: [end of test] number of renders 1`] = `7`; +exports[`regression tests zoom hotkeys: [end of test] number of renders 1`] = `5`; diff --git a/src/tests/__snapshots__/selection.test.tsx.snap b/src/tests/__snapshots__/selection.test.tsx.snap index fff176a63..12589d2c0 100644 --- a/src/tests/__snapshots__/selection.test.tsx.snap +++ b/src/tests/__snapshots__/selection.test.tsx.snap @@ -31,7 +31,7 @@ Object { "roundness": Object { "type": 2, }, - "seed": 337897, + "seed": 1278240551, "startArrowhead": null, "startBinding": null, "strokeColor": "#1e1e1e", @@ -40,7 +40,7 @@ Object { "type": "arrow", "updated": 1, "version": 3, - "versionNonce": 449462985, + "versionNonce": 401146281, "width": 30, "x": 10, "y": 10, @@ -78,7 +78,7 @@ Object { "roundness": Object { "type": 2, }, - "seed": 337897, + "seed": 1278240551, "startArrowhead": null, "startBinding": null, "strokeColor": "#1e1e1e", @@ -87,7 +87,7 @@ Object { "type": "line", "updated": 1, "version": 3, - "versionNonce": 449462985, + "versionNonce": 401146281, "width": 30, "x": 10, "y": 10, @@ -112,14 +112,14 @@ Object { "roundness": Object { "type": 2, }, - "seed": 337897, + "seed": 1278240551, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "diamond", "updated": 1, "version": 2, - "versionNonce": 1278240551, + "versionNonce": 453191, "width": 30, "x": 10, "y": 10, @@ -144,14 +144,14 @@ Object { "roundness": Object { "type": 2, }, - "seed": 337897, + "seed": 1278240551, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "ellipse", "updated": 1, "version": 2, - "versionNonce": 1278240551, + "versionNonce": 453191, "width": 30, "x": 10, "y": 10, @@ -176,14 +176,14 @@ Object { "roundness": Object { "type": 3, }, - "seed": 337897, + "seed": 1278240551, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 1, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 1278240551, + "versionNonce": 453191, "width": 30, "x": 10, "y": 10, diff --git a/src/tests/contextmenu.test.tsx b/src/tests/contextmenu.test.tsx index 9e89996af..c1dc74285 100644 --- a/src/tests/contextmenu.test.tsx +++ b/src/tests/contextmenu.test.tsx @@ -23,7 +23,7 @@ import { setDateTimeForTests } from "../utils"; import { LibraryItem } from "../types"; const checkpoint = (name: string) => { - expect(renderScene.mock.calls.length).toMatchSnapshot( + expect(renderStaticScene.mock.calls.length).toMatchSnapshot( `[${name}] number of renders`, ); expect(h.state).toMatchSnapshot(`[${name}] appState`); @@ -39,10 +39,10 @@ const mouse = new Pointer("mouse"); // Unmount ReactDOM from root ReactDOM.unmountComponentAtNode(document.getElementById("root")!); -const renderScene = jest.spyOn(Renderer, "renderScene"); +const renderStaticScene = jest.spyOn(Renderer, "renderStaticScene"); beforeEach(() => { localStorage.clear(); - renderScene.mockClear(); + renderStaticScene.mockClear(); reseed(7); }); @@ -51,7 +51,7 @@ const { h } = window; describe("contextMenu element", () => { beforeEach(async () => { localStorage.clear(); - renderScene.mockClear(); + renderStaticScene.mockClear(); reseed(7); setDateTimeForTests("201933152653"); @@ -74,7 +74,7 @@ describe("contextMenu element", () => { }); it("shows context menu for canvas", () => { - fireEvent.contextMenu(GlobalTestState.canvas, { + fireEvent.contextMenu(GlobalTestState.interactiveCanvas, { button: 2, clientX: 1, clientY: 1, @@ -104,7 +104,7 @@ describe("contextMenu element", () => { mouse.down(10, 10); mouse.up(20, 20); - fireEvent.contextMenu(GlobalTestState.canvas, { + fireEvent.contextMenu(GlobalTestState.interactiveCanvas, { button: 2, clientX: 1, clientY: 1, @@ -158,7 +158,7 @@ describe("contextMenu element", () => { API.setSelectedElements([rect1]); // lower z-index - fireEvent.contextMenu(GlobalTestState.canvas, { + fireEvent.contextMenu(GlobalTestState.interactiveCanvas, { button: 2, clientX: 100, clientY: 100, @@ -168,7 +168,7 @@ describe("contextMenu element", () => { // higher z-index API.setSelectedElements([rect2]); - fireEvent.contextMenu(GlobalTestState.canvas, { + fireEvent.contextMenu(GlobalTestState.interactiveCanvas, { button: 2, clientX: 100, clientY: 100, @@ -192,7 +192,7 @@ describe("contextMenu element", () => { mouse.click(20, 0); }); - fireEvent.contextMenu(GlobalTestState.canvas, { + fireEvent.contextMenu(GlobalTestState.interactiveCanvas, { button: 2, clientX: 1, clientY: 1, @@ -245,7 +245,7 @@ describe("contextMenu element", () => { Keyboard.keyPress(KEYS.G); }); - fireEvent.contextMenu(GlobalTestState.canvas, { + fireEvent.contextMenu(GlobalTestState.interactiveCanvas, { button: 2, clientX: 1, clientY: 1, @@ -284,7 +284,7 @@ describe("contextMenu element", () => { mouse.down(10, 10); mouse.up(20, 20); - fireEvent.contextMenu(GlobalTestState.canvas, { + fireEvent.contextMenu(GlobalTestState.interactiveCanvas, { button: 2, clientX: 1, clientY: 1, @@ -332,7 +332,7 @@ describe("contextMenu element", () => { mouse.reset(); // Copy styles of second rectangle - fireEvent.contextMenu(GlobalTestState.canvas, { + fireEvent.contextMenu(GlobalTestState.interactiveCanvas, { button: 2, clientX: 40, clientY: 40, @@ -345,7 +345,7 @@ describe("contextMenu element", () => { mouse.reset(); // Paste styles to first rectangle - fireEvent.contextMenu(GlobalTestState.canvas, { + fireEvent.contextMenu(GlobalTestState.interactiveCanvas, { button: 2, clientX: 10, clientY: 10, @@ -369,7 +369,7 @@ describe("contextMenu element", () => { mouse.down(10, 10); mouse.up(20, 20); - fireEvent.contextMenu(GlobalTestState.canvas, { + fireEvent.contextMenu(GlobalTestState.interactiveCanvas, { button: 2, clientX: 1, clientY: 1, @@ -385,7 +385,7 @@ describe("contextMenu element", () => { mouse.down(10, 10); mouse.up(20, 20); - fireEvent.contextMenu(GlobalTestState.canvas, { + fireEvent.contextMenu(GlobalTestState.interactiveCanvas, { button: 2, clientX: 1, clientY: 1, @@ -406,7 +406,7 @@ describe("contextMenu element", () => { mouse.down(10, 10); mouse.up(20, 20); - fireEvent.contextMenu(GlobalTestState.canvas, { + fireEvent.contextMenu(GlobalTestState.interactiveCanvas, { button: 2, clientX: 1, clientY: 1, @@ -429,7 +429,7 @@ describe("contextMenu element", () => { mouse.up(20, 20); mouse.reset(); - fireEvent.contextMenu(GlobalTestState.canvas, { + fireEvent.contextMenu(GlobalTestState.interactiveCanvas, { button: 2, clientX: 40, clientY: 40, @@ -451,7 +451,7 @@ describe("contextMenu element", () => { mouse.up(20, 20); mouse.reset(); - fireEvent.contextMenu(GlobalTestState.canvas, { + fireEvent.contextMenu(GlobalTestState.interactiveCanvas, { button: 2, clientX: 10, clientY: 10, @@ -473,7 +473,7 @@ describe("contextMenu element", () => { mouse.up(20, 20); mouse.reset(); - fireEvent.contextMenu(GlobalTestState.canvas, { + fireEvent.contextMenu(GlobalTestState.interactiveCanvas, { button: 2, clientX: 40, clientY: 40, @@ -494,7 +494,7 @@ describe("contextMenu element", () => { mouse.up(20, 20); mouse.reset(); - fireEvent.contextMenu(GlobalTestState.canvas, { + fireEvent.contextMenu(GlobalTestState.interactiveCanvas, { button: 2, clientX: 10, clientY: 10, @@ -519,7 +519,7 @@ describe("contextMenu element", () => { mouse.click(10, 10); }); - fireEvent.contextMenu(GlobalTestState.canvas, { + fireEvent.contextMenu(GlobalTestState.interactiveCanvas, { button: 2, clientX: 1, clientY: 1, @@ -549,7 +549,7 @@ describe("contextMenu element", () => { Keyboard.keyPress(KEYS.G); }); - fireEvent.contextMenu(GlobalTestState.canvas, { + fireEvent.contextMenu(GlobalTestState.interactiveCanvas, { button: 2, clientX: 1, clientY: 1, diff --git a/src/tests/dragCreate.test.tsx b/src/tests/dragCreate.test.tsx index 4e69f1ab8..a925dce91 100644 --- a/src/tests/dragCreate.test.tsx +++ b/src/tests/dragCreate.test.tsx @@ -14,10 +14,13 @@ import { reseed } from "../random"; // Unmount ReactDOM from root ReactDOM.unmountComponentAtNode(document.getElementById("root")!); -const renderScene = jest.spyOn(Renderer, "renderScene"); +const renderInteractiveScene = jest.spyOn(Renderer, "renderInteractiveScene"); +const renderStaticScene = jest.spyOn(Renderer, "renderStaticScene"); + beforeEach(() => { localStorage.clear(); - renderScene.mockClear(); + renderInteractiveScene.mockClear(); + renderStaticScene.mockClear(); reseed(7); }); @@ -31,7 +34,7 @@ describe("Test dragCreate", () => { const tool = getByToolName("rectangle"); fireEvent.click(tool); - const canvas = container.querySelector("canvas")!; + const canvas = container.querySelector("canvas.interactive")!; // start from (30, 20) fireEvent.pointerDown(canvas, { clientX: 30, clientY: 20 }); @@ -42,7 +45,8 @@ describe("Test dragCreate", () => { // finish (position does not matter) fireEvent.pointerUp(canvas); - expect(renderScene).toHaveBeenCalledTimes(9); + expect(renderInteractiveScene).toHaveBeenCalledTimes(6); + expect(renderStaticScene).toHaveBeenCalledTimes(4); expect(h.state.selectionElement).toBeNull(); expect(h.elements.length).toEqual(1); @@ -62,7 +66,7 @@ describe("Test dragCreate", () => { const tool = getByToolName("ellipse"); fireEvent.click(tool); - const canvas = container.querySelector("canvas")!; + const canvas = container.querySelector("canvas.interactive")!; // start from (30, 20) fireEvent.pointerDown(canvas, { clientX: 30, clientY: 20 }); @@ -73,7 +77,9 @@ describe("Test dragCreate", () => { // finish (position does not matter) fireEvent.pointerUp(canvas); - expect(renderScene).toHaveBeenCalledTimes(9); + expect(renderInteractiveScene).toHaveBeenCalledTimes(6); + expect(renderStaticScene).toHaveBeenCalledTimes(4); + expect(h.state.selectionElement).toBeNull(); expect(h.elements.length).toEqual(1); @@ -93,7 +99,7 @@ describe("Test dragCreate", () => { const tool = getByToolName("diamond"); fireEvent.click(tool); - const canvas = container.querySelector("canvas")!; + const canvas = container.querySelector("canvas.interactive")!; // start from (30, 20) fireEvent.pointerDown(canvas, { clientX: 30, clientY: 20 }); @@ -104,7 +110,8 @@ describe("Test dragCreate", () => { // finish (position does not matter) fireEvent.pointerUp(canvas); - expect(renderScene).toHaveBeenCalledTimes(9); + expect(renderInteractiveScene).toHaveBeenCalledTimes(6); + expect(renderStaticScene).toHaveBeenCalledTimes(4); expect(h.state.selectionElement).toBeNull(); expect(h.elements.length).toEqual(1); @@ -124,7 +131,7 @@ describe("Test dragCreate", () => { const tool = getByToolName("arrow"); fireEvent.click(tool); - const canvas = container.querySelector("canvas")!; + const canvas = container.querySelector("canvas.interactive")!; // start from (30, 20) fireEvent.pointerDown(canvas, { clientX: 30, clientY: 20 }); @@ -135,7 +142,8 @@ describe("Test dragCreate", () => { // finish (position does not matter) fireEvent.pointerUp(canvas); - expect(renderScene).toHaveBeenCalledTimes(9); + expect(renderInteractiveScene).toHaveBeenCalledTimes(6); + expect(renderStaticScene).toHaveBeenCalledTimes(5); expect(h.state.selectionElement).toBeNull(); expect(h.elements.length).toEqual(1); @@ -159,7 +167,7 @@ describe("Test dragCreate", () => { const tool = getByToolName("line"); fireEvent.click(tool); - const canvas = container.querySelector("canvas")!; + const canvas = container.querySelector("canvas.interactive")!; // start from (30, 20) fireEvent.pointerDown(canvas, { clientX: 30, clientY: 20 }); @@ -170,7 +178,8 @@ describe("Test dragCreate", () => { // finish (position does not matter) fireEvent.pointerUp(canvas); - expect(renderScene).toHaveBeenCalledTimes(9); + expect(renderInteractiveScene).toHaveBeenCalledTimes(6); + expect(renderStaticScene).toHaveBeenCalledTimes(5); expect(h.state.selectionElement).toBeNull(); expect(h.elements.length).toEqual(1); @@ -202,7 +211,7 @@ describe("Test dragCreate", () => { const tool = getByToolName("rectangle"); fireEvent.click(tool); - const canvas = container.querySelector("canvas")!; + const canvas = container.querySelector("canvas.interactive")!; // start from (30, 20) fireEvent.pointerDown(canvas, { clientX: 30, clientY: 20 }); @@ -210,7 +219,8 @@ describe("Test dragCreate", () => { // finish (position does not matter) fireEvent.pointerUp(canvas); - expect(renderScene).toHaveBeenCalledTimes(7); + expect(renderInteractiveScene).toHaveBeenCalledTimes(5); + expect(renderStaticScene).toHaveBeenCalledTimes(4); expect(h.state.selectionElement).toBeNull(); expect(h.elements.length).toEqual(0); }); @@ -221,7 +231,7 @@ describe("Test dragCreate", () => { const tool = getByToolName("ellipse"); fireEvent.click(tool); - const canvas = container.querySelector("canvas")!; + const canvas = container.querySelector("canvas.interactive")!; // start from (30, 20) fireEvent.pointerDown(canvas, { clientX: 30, clientY: 20 }); @@ -229,7 +239,8 @@ describe("Test dragCreate", () => { // finish (position does not matter) fireEvent.pointerUp(canvas); - expect(renderScene).toHaveBeenCalledTimes(7); + expect(renderInteractiveScene).toHaveBeenCalledTimes(5); + expect(renderStaticScene).toHaveBeenCalledTimes(4); expect(h.state.selectionElement).toBeNull(); expect(h.elements.length).toEqual(0); }); @@ -240,7 +251,7 @@ describe("Test dragCreate", () => { const tool = getByToolName("diamond"); fireEvent.click(tool); - const canvas = container.querySelector("canvas")!; + const canvas = container.querySelector("canvas.interactive")!; // start from (30, 20) fireEvent.pointerDown(canvas, { clientX: 30, clientY: 20 }); @@ -248,7 +259,8 @@ describe("Test dragCreate", () => { // finish (position does not matter) fireEvent.pointerUp(canvas); - expect(renderScene).toHaveBeenCalledTimes(7); + expect(renderInteractiveScene).toHaveBeenCalledTimes(5); + expect(renderStaticScene).toHaveBeenCalledTimes(4); expect(h.state.selectionElement).toBeNull(); expect(h.elements.length).toEqual(0); }); @@ -259,7 +271,7 @@ describe("Test dragCreate", () => { const tool = getByToolName("arrow"); fireEvent.click(tool); - const canvas = container.querySelector("canvas")!; + const canvas = container.querySelector("canvas.interactive")!; // start from (30, 20) fireEvent.pointerDown(canvas, { clientX: 30, clientY: 20 }); @@ -272,7 +284,8 @@ describe("Test dragCreate", () => { key: KEYS.ENTER, }); - expect(renderScene).toHaveBeenCalledTimes(8); + expect(renderInteractiveScene).toHaveBeenCalledTimes(6); + expect(renderStaticScene).toHaveBeenCalledTimes(5); expect(h.state.selectionElement).toBeNull(); expect(h.elements.length).toEqual(0); }); @@ -283,7 +296,7 @@ describe("Test dragCreate", () => { const tool = getByToolName("line"); fireEvent.click(tool); - const canvas = container.querySelector("canvas")!; + const canvas = container.querySelector("canvas.interactive")!; // start from (30, 20) fireEvent.pointerDown(canvas, { clientX: 30, clientY: 20 }); @@ -296,7 +309,8 @@ describe("Test dragCreate", () => { key: KEYS.ENTER, }); - expect(renderScene).toHaveBeenCalledTimes(8); + expect(renderInteractiveScene).toHaveBeenCalledTimes(6); + expect(renderStaticScene).toHaveBeenCalledTimes(5); expect(h.state.selectionElement).toBeNull(); expect(h.elements.length).toEqual(0); }); diff --git a/src/tests/helpers/api.ts b/src/tests/helpers/api.ts index 7bba8bdd3..0c30ff1d7 100644 --- a/src/tests/helpers/api.ts +++ b/src/tests/helpers/api.ts @@ -268,7 +268,7 @@ export class API { }; static drop = async (blob: Blob) => { - const fileDropEvent = createEvent.drop(GlobalTestState.canvas); + const fileDropEvent = createEvent.drop(GlobalTestState.interactiveCanvas); const text = await new Promise((resolve, reject) => { try { const reader = new FileReader(); @@ -295,6 +295,6 @@ export class API { }, }, }); - fireEvent(GlobalTestState.canvas, fileDropEvent); + fireEvent(GlobalTestState.interactiveCanvas, fileDropEvent); }; } diff --git a/src/tests/helpers/ui.ts b/src/tests/helpers/ui.ts index c88201133..f815b07dc 100644 --- a/src/tests/helpers/ui.ts +++ b/src/tests/helpers/ui.ts @@ -107,7 +107,7 @@ export class Pointer { restorePosition(x = 0, y = 0) { this.clientX = x; this.clientY = y; - fireEvent.pointerMove(GlobalTestState.canvas, this.getEvent()); + fireEvent.pointerMove(GlobalTestState.interactiveCanvas, this.getEvent()); } private getEvent() { @@ -129,18 +129,18 @@ export class Pointer { if (dx !== 0 || dy !== 0) { this.clientX += dx; this.clientY += dy; - fireEvent.pointerMove(GlobalTestState.canvas, this.getEvent()); + fireEvent.pointerMove(GlobalTestState.interactiveCanvas, this.getEvent()); } } down(dx = 0, dy = 0) { this.move(dx, dy); - fireEvent.pointerDown(GlobalTestState.canvas, this.getEvent()); + fireEvent.pointerDown(GlobalTestState.interactiveCanvas, this.getEvent()); } up(dx = 0, dy = 0) { this.move(dx, dy); - fireEvent.pointerUp(GlobalTestState.canvas, this.getEvent()); + fireEvent.pointerUp(GlobalTestState.interactiveCanvas, this.getEvent()); } click(dx = 0, dy = 0) { @@ -150,7 +150,7 @@ export class Pointer { doubleClick(dx = 0, dy = 0) { this.move(dx, dy); - fireEvent.doubleClick(GlobalTestState.canvas, this.getEvent()); + fireEvent.doubleClick(GlobalTestState.interactiveCanvas, this.getEvent()); } // absolute coords @@ -159,19 +159,19 @@ export class Pointer { moveTo(x: number = this.clientX, y: number = this.clientY) { this.clientX = x; this.clientY = y; - fireEvent.pointerMove(GlobalTestState.canvas, this.getEvent()); + fireEvent.pointerMove(GlobalTestState.interactiveCanvas, this.getEvent()); } downAt(x = this.clientX, y = this.clientY) { this.clientX = x; this.clientY = y; - fireEvent.pointerDown(GlobalTestState.canvas, this.getEvent()); + fireEvent.pointerDown(GlobalTestState.interactiveCanvas, this.getEvent()); } upAt(x = this.clientX, y = this.clientY) { this.clientX = x; this.clientY = y; - fireEvent.pointerUp(GlobalTestState.canvas, this.getEvent()); + fireEvent.pointerUp(GlobalTestState.interactiveCanvas, this.getEvent()); } clickAt(x: number, y: number) { @@ -180,7 +180,7 @@ export class Pointer { } rightClickAt(x: number, y: number) { - fireEvent.contextMenu(GlobalTestState.canvas, { + fireEvent.contextMenu(GlobalTestState.interactiveCanvas, { button: 2, clientX: x, clientY: y, @@ -189,7 +189,7 @@ export class Pointer { doubleClickAt(x: number, y: number) { this.moveTo(x, y); - fireEvent.doubleClick(GlobalTestState.canvas, this.getEvent()); + fireEvent.doubleClick(GlobalTestState.interactiveCanvas, this.getEvent()); } // --------------------------------------------------------------------------- @@ -327,6 +327,13 @@ export class UI { }); } + static ungroup(elements: ExcalidrawElement[]) { + mouse.select(elements); + Keyboard.withModifierKeys({ ctrl: true, shift: true }, () => { + Keyboard.keyPress(KEYS.G); + }); + } + static queryContextMenu = () => { return GlobalTestState.renderResult.container.querySelector( ".context-menu", diff --git a/src/tests/linearElementEditor.test.tsx b/src/tests/linearElementEditor.test.tsx index c71283a4c..111deca0e 100644 --- a/src/tests/linearElementEditor.test.tsx +++ b/src/tests/linearElementEditor.test.tsx @@ -25,7 +25,9 @@ import { import * as textElementUtils from "../element/textElement"; import { ROUNDNESS, VERTICAL_ALIGN } from "../constants"; -const renderScene = jest.spyOn(Renderer, "renderScene"); +// FIXME I: add specific tests for render of both components (when they should both, when just one, when just second, what is the order of renders - first static, then interactive, etc., all tests) +const renderInteractiveScene = jest.spyOn(Renderer, "renderInteractiveScene"); +const renderStaticScene = jest.spyOn(Renderer, "renderStaticScene"); const { h } = window; const font = "20px Cascadia, width: Segoe UI Emoji" as FontString; @@ -33,18 +35,23 @@ const font = "20px Cascadia, width: Segoe UI Emoji" as FontString; describe("Test Linear Elements", () => { let container: HTMLElement; let canvas: HTMLCanvasElement; + let interactiveCanvas: HTMLCanvasElement; beforeEach(async () => { // Unmount ReactDOM from root ReactDOM.unmountComponentAtNode(document.getElementById("root")!); localStorage.clear(); - renderScene.mockClear(); + renderInteractiveScene.mockClear(); + renderStaticScene.mockClear(); reseed(7); const comp = await render(); container = comp.container; - canvas = container.querySelector("canvas")!; + canvas = container.querySelector("canvas.static")!; canvas.width = 1000; canvas.height = 1000; + interactiveCanvas = container.querySelector("canvas.interactive")!; + interactiveCanvas.width = 1000; + interactiveCanvas.height = 1000; }); const p1: Point = [20, 20]; @@ -119,26 +126,26 @@ describe("Test Linear Elements", () => { }; const drag = (startPoint: Point, endPoint: Point) => { - fireEvent.pointerDown(canvas, { + fireEvent.pointerDown(interactiveCanvas, { clientX: startPoint[0], clientY: startPoint[1], }); - fireEvent.pointerMove(canvas, { + fireEvent.pointerMove(interactiveCanvas, { clientX: endPoint[0], clientY: endPoint[1], }); - fireEvent.pointerUp(canvas, { + fireEvent.pointerUp(interactiveCanvas, { clientX: endPoint[0], clientY: endPoint[1], }); }; const deletePoint = (point: Point) => { - fireEvent.pointerDown(canvas, { + fireEvent.pointerDown(interactiveCanvas, { clientX: point[0], clientY: point[1], }); - fireEvent.pointerUp(canvas, { + fireEvent.pointerUp(interactiveCanvas, { clientX: point[0], clientY: point[1], }); @@ -171,12 +178,14 @@ describe("Test Linear Elements", () => { createTwoPointerLinearElement("line"); const line = h.elements[0] as ExcalidrawLinearElement; - expect(renderScene).toHaveBeenCalledTimes(7); + expect(renderInteractiveScene).toHaveBeenCalledTimes(5); + expect(renderStaticScene).toHaveBeenCalledTimes(3); expect((h.elements[0] as ExcalidrawLinearElement).points.length).toEqual(2); // drag line from midpoint drag(midpoint, [midpoint[0] + delta, midpoint[1] + delta]); - expect(renderScene).toHaveBeenCalledTimes(11); + expect(renderInteractiveScene).toHaveBeenCalledTimes(9); + expect(renderStaticScene).toHaveBeenCalledTimes(4); expect(line.points.length).toEqual(3); expect(line.points).toMatchInlineSnapshot(` Array [ @@ -198,14 +207,14 @@ describe("Test Linear Elements", () => { it("should allow entering and exiting line editor via context menu", () => { createTwoPointerLinearElement("line"); - fireEvent.contextMenu(GlobalTestState.canvas, { + fireEvent.contextMenu(GlobalTestState.interactiveCanvas, { button: 2, clientX: midpoint[0], clientY: midpoint[1], }); // Enter line editor let contextMenu = document.querySelector(".context-menu"); - fireEvent.contextMenu(GlobalTestState.canvas, { + fireEvent.contextMenu(GlobalTestState.interactiveCanvas, { button: 2, clientX: midpoint[0], clientY: midpoint[1], @@ -215,13 +224,13 @@ describe("Test Linear Elements", () => { expect(h.state.editingLinearElement?.elementId).toEqual(h.elements[0].id); // Exiting line editor - fireEvent.contextMenu(GlobalTestState.canvas, { + fireEvent.contextMenu(GlobalTestState.interactiveCanvas, { button: 2, clientX: midpoint[0], clientY: midpoint[1], }); contextMenu = document.querySelector(".context-menu"); - fireEvent.contextMenu(GlobalTestState.canvas, { + fireEvent.contextMenu(GlobalTestState.interactiveCanvas, { button: 2, clientX: midpoint[0], clientY: midpoint[1], @@ -269,7 +278,8 @@ describe("Test Linear Elements", () => { // drag line from midpoint drag(midpoint, [midpoint[0] + delta, midpoint[1] + delta]); - expect(renderScene).toHaveBeenCalledTimes(15); + expect(renderInteractiveScene).toHaveBeenCalledTimes(12); + expect(renderStaticScene).toHaveBeenCalledTimes(4); expect(line.points.length).toEqual(3); expect(line.points).toMatchInlineSnapshot(` @@ -306,7 +316,9 @@ describe("Test Linear Elements", () => { // update roundness fireEvent.click(screen.getByTitle("Round")); - expect(renderScene).toHaveBeenCalledTimes(12); + expect(renderInteractiveScene).toHaveBeenCalledTimes(10); + expect(renderStaticScene).toHaveBeenCalledTimes(5); + const midPointsWithRoundEdge = LinearElementEditor.getEditorMidPoints( h.elements[0] as ExcalidrawLinearElement, h.state, @@ -350,7 +362,9 @@ describe("Test Linear Elements", () => { // Move the element drag(startPoint, endPoint); - expect(renderScene).toHaveBeenCalledTimes(16); + expect(renderInteractiveScene).toHaveBeenCalledTimes(13); + expect(renderStaticScene).toHaveBeenCalledTimes(6); + expect([line.x, line.y]).toEqual([ points[0][0] + deltaX, points[0][1] + deltaY, @@ -407,7 +421,9 @@ describe("Test Linear Elements", () => { lastSegmentMidpoint[1] + delta, ]); - expect(renderScene).toHaveBeenCalledTimes(21); + expect(renderInteractiveScene).toHaveBeenCalledTimes(17); + expect(renderStaticScene).toHaveBeenCalledTimes(6); + expect(line.points.length).toEqual(5); expect((h.elements[0] as ExcalidrawLinearElement).points) @@ -446,7 +462,8 @@ describe("Test Linear Elements", () => { // Drag from first point drag(hitCoords, [hitCoords[0] - delta, hitCoords[1] - delta]); - expect(renderScene).toHaveBeenCalledTimes(16); + expect(renderInteractiveScene).toHaveBeenCalledTimes(13); + expect(renderStaticScene).toHaveBeenCalledTimes(5); const newPoints = LinearElementEditor.getPointsGlobalCoordinates(line); expect([newPoints[0][0], newPoints[0][1]]).toEqual([ @@ -472,7 +489,8 @@ describe("Test Linear Elements", () => { // Drag from first point drag(hitCoords, [hitCoords[0] + delta, hitCoords[1] + delta]); - expect(renderScene).toHaveBeenCalledTimes(16); + expect(renderInteractiveScene).toHaveBeenCalledTimes(13); + expect(renderStaticScene).toHaveBeenCalledTimes(5); const newPoints = LinearElementEditor.getPointsGlobalCoordinates(line); expect([newPoints[0][0], newPoints[0][1]]).toEqual([ @@ -506,7 +524,8 @@ describe("Test Linear Elements", () => { // delete 3rd point deletePoint(points[2]); expect(line.points.length).toEqual(3); - expect(renderScene).toHaveBeenCalledTimes(22); + expect(renderInteractiveScene).toHaveBeenCalledTimes(19); + expect(renderStaticScene).toHaveBeenCalledTimes(6); const newMidPoints = LinearElementEditor.getEditorMidPoints( line, @@ -552,8 +571,8 @@ describe("Test Linear Elements", () => { lastSegmentMidpoint[0] + delta, lastSegmentMidpoint[1] + delta, ]); - expect(renderScene).toHaveBeenCalledTimes(21); - + expect(renderInteractiveScene).toHaveBeenCalledTimes(17); + expect(renderStaticScene).toHaveBeenCalledTimes(6); expect(line.points.length).toEqual(5); expect((h.elements[0] as ExcalidrawLinearElement).points) @@ -628,7 +647,8 @@ describe("Test Linear Elements", () => { // Drag from first point drag(hitCoords, [hitCoords[0] + delta, hitCoords[1] + delta]); - expect(renderScene).toHaveBeenCalledTimes(16); + expect(renderInteractiveScene).toHaveBeenCalledTimes(13); + expect(renderStaticScene).toHaveBeenCalledTimes(5); const newPoints = LinearElementEditor.getPointsGlobalCoordinates(line); expect([newPoints[0][0], newPoints[0][1]]).toEqual([ @@ -1205,7 +1225,7 @@ describe("Test Linear Elements", () => { const container = h.elements[0]; API.setSelectedElements([container, text]); - fireEvent.contextMenu(GlobalTestState.canvas, { + fireEvent.contextMenu(GlobalTestState.interactiveCanvas, { button: 2, clientX: 20, clientY: 30, @@ -1230,7 +1250,7 @@ describe("Test Linear Elements", () => { mouse.up(); API.setSelectedElements([h.elements[0], h.elements[1]]); - fireEvent.contextMenu(GlobalTestState.canvas, { + fireEvent.contextMenu(GlobalTestState.interactiveCanvas, { button: 2, clientX: 20, clientY: 30, diff --git a/src/tests/move.test.tsx b/src/tests/move.test.tsx index 3fbfceee3..916595185 100644 --- a/src/tests/move.test.tsx +++ b/src/tests/move.test.tsx @@ -16,10 +16,12 @@ import { KEYS } from "../keys"; // Unmount ReactDOM from root ReactDOM.unmountComponentAtNode(document.getElementById("root")!); -const renderScene = jest.spyOn(Renderer, "renderScene"); +const renderInteractiveScene = jest.spyOn(Renderer, "renderInteractiveScene"); +const renderStaticScene = jest.spyOn(Renderer, "renderStaticScene"); beforeEach(() => { localStorage.clear(); - renderScene.mockClear(); + renderInteractiveScene.mockClear(); + renderStaticScene.mockClear(); reseed(7); }); @@ -28,7 +30,7 @@ const { h } = window; describe("move element", () => { it("rectangle", async () => { const { getByToolName, container } = await render(); - const canvas = container.querySelector("canvas")!; + const canvas = container.querySelector("canvas.interactive")!; { // create element @@ -38,20 +40,23 @@ describe("move element", () => { fireEvent.pointerMove(canvas, { clientX: 60, clientY: 70 }); fireEvent.pointerUp(canvas); - expect(renderScene).toHaveBeenCalledTimes(9); + expect(renderInteractiveScene).toHaveBeenCalledTimes(6); + expect(renderStaticScene).toHaveBeenCalledTimes(4); expect(h.state.selectionElement).toBeNull(); expect(h.elements.length).toEqual(1); expect(h.state.selectedElementIds[h.elements[0].id]).toBeTruthy(); expect([h.elements[0].x, h.elements[0].y]).toEqual([30, 20]); - renderScene.mockClear(); + renderInteractiveScene.mockClear(); + renderStaticScene.mockClear(); } fireEvent.pointerDown(canvas, { clientX: 50, clientY: 20 }); fireEvent.pointerMove(canvas, { clientX: 20, clientY: 40 }); fireEvent.pointerUp(canvas); - expect(renderScene).toHaveBeenCalledTimes(3); + expect(renderInteractiveScene).toHaveBeenCalledTimes(3); + expect(renderStaticScene).toHaveBeenCalledTimes(2); expect(h.state.selectionElement).toBeNull(); expect(h.elements.length).toEqual(1); expect([h.elements[0].x, h.elements[0].y]).toEqual([0, 40]); @@ -77,7 +82,8 @@ describe("move element", () => { // select the second rectangles new Pointer("mouse").clickOn(rectB); - expect(renderScene).toHaveBeenCalledTimes(23); + expect(renderInteractiveScene).toHaveBeenCalledTimes(21); + expect(renderStaticScene).toHaveBeenCalledTimes(13); expect(h.state.selectionElement).toBeNull(); expect(h.elements.length).toEqual(3); expect(h.state.selectedElementIds[rectB.id]).toBeTruthy(); @@ -86,7 +92,8 @@ describe("move element", () => { expect([line.x, line.y]).toEqual([110, 50]); expect([line.width, line.height]).toEqual([80, 80]); - renderScene.mockClear(); + renderInteractiveScene.mockClear(); + renderStaticScene.mockClear(); // Move selected rectangle Keyboard.keyDown(KEYS.ARROW_RIGHT); @@ -94,7 +101,8 @@ describe("move element", () => { Keyboard.keyDown(KEYS.ARROW_DOWN); // Check that the arrow size has been changed according to moving the rectangle - expect(renderScene).toHaveBeenCalledTimes(3); + expect(renderInteractiveScene).toHaveBeenCalledTimes(3); + expect(renderStaticScene).toHaveBeenCalledTimes(3); expect(h.state.selectionElement).toBeNull(); expect(h.elements.length).toEqual(3); expect(h.state.selectedElementIds[rectB.id]).toBeTruthy(); @@ -110,7 +118,7 @@ describe("move element", () => { describe("duplicate element on move when ALT is clicked", () => { it("rectangle", async () => { const { getByToolName, container } = await render(); - const canvas = container.querySelector("canvas")!; + const canvas = container.querySelector("canvas.interactive")!; { // create element @@ -120,13 +128,15 @@ describe("duplicate element on move when ALT is clicked", () => { fireEvent.pointerMove(canvas, { clientX: 60, clientY: 70 }); fireEvent.pointerUp(canvas); - expect(renderScene).toHaveBeenCalledTimes(9); + expect(renderInteractiveScene).toHaveBeenCalledTimes(6); + expect(renderStaticScene).toHaveBeenCalledTimes(4); expect(h.state.selectionElement).toBeNull(); expect(h.elements.length).toEqual(1); expect(h.state.selectedElementIds[h.elements[0].id]).toBeTruthy(); expect([h.elements[0].x, h.elements[0].y]).toEqual([30, 20]); - renderScene.mockClear(); + renderInteractiveScene.mockClear(); + renderStaticScene.mockClear(); } fireEvent.pointerDown(canvas, { clientX: 50, clientY: 20 }); @@ -140,7 +150,8 @@ describe("duplicate element on move when ALT is clicked", () => { // TODO: This used to be 4, but binding made it go up to 5. Do we need // that additional render? - expect(renderScene).toHaveBeenCalledTimes(5); + expect(renderInteractiveScene).toHaveBeenCalledTimes(5); + expect(renderStaticScene).toHaveBeenCalledTimes(3); expect(h.state.selectionElement).toBeNull(); expect(h.elements.length).toEqual(2); diff --git a/src/tests/multiPointCreate.test.tsx b/src/tests/multiPointCreate.test.tsx index 0026041c7..f52d782e4 100644 --- a/src/tests/multiPointCreate.test.tsx +++ b/src/tests/multiPointCreate.test.tsx @@ -14,10 +14,12 @@ import { reseed } from "../random"; // Unmount ReactDOM from root ReactDOM.unmountComponentAtNode(document.getElementById("root")!); -const renderScene = jest.spyOn(Renderer, "renderScene"); +const renderInteractiveScene = jest.spyOn(Renderer, "renderInteractiveScene"); +const renderStaticScene = jest.spyOn(Renderer, "renderStaticScene"); beforeEach(() => { localStorage.clear(); - renderScene.mockClear(); + renderInteractiveScene.mockClear(); + renderStaticScene.mockClear(); reseed(7); }); @@ -38,11 +40,12 @@ describe("remove shape in non linear elements", () => { const tool = getByToolName("rectangle"); fireEvent.click(tool); - const canvas = container.querySelector("canvas")!; + const canvas = container.querySelector("canvas.interactive")!; fireEvent.pointerDown(canvas, { clientX: 30, clientY: 20 }); fireEvent.pointerUp(canvas, { clientX: 30, clientY: 30 }); - expect(renderScene).toHaveBeenCalledTimes(7); + expect(renderInteractiveScene).toHaveBeenCalledTimes(5); + expect(renderStaticScene).toHaveBeenCalledTimes(4); expect(h.elements.length).toEqual(0); }); @@ -52,11 +55,12 @@ describe("remove shape in non linear elements", () => { const tool = getByToolName("ellipse"); fireEvent.click(tool); - const canvas = container.querySelector("canvas")!; + const canvas = container.querySelector("canvas.interactive")!; fireEvent.pointerDown(canvas, { clientX: 30, clientY: 20 }); fireEvent.pointerUp(canvas, { clientX: 30, clientY: 30 }); - expect(renderScene).toHaveBeenCalledTimes(7); + expect(renderInteractiveScene).toHaveBeenCalledTimes(5); + expect(renderStaticScene).toHaveBeenCalledTimes(4); expect(h.elements.length).toEqual(0); }); @@ -66,11 +70,12 @@ describe("remove shape in non linear elements", () => { const tool = getByToolName("diamond"); fireEvent.click(tool); - const canvas = container.querySelector("canvas")!; + const canvas = container.querySelector("canvas.interactive")!; fireEvent.pointerDown(canvas, { clientX: 30, clientY: 20 }); fireEvent.pointerUp(canvas, { clientX: 30, clientY: 30 }); - expect(renderScene).toHaveBeenCalledTimes(7); + expect(renderInteractiveScene).toHaveBeenCalledTimes(5); + expect(renderStaticScene).toHaveBeenCalledTimes(4); expect(h.elements.length).toEqual(0); }); }); @@ -82,7 +87,7 @@ describe("multi point mode in linear elements", () => { const tool = getByToolName("arrow"); fireEvent.click(tool); - const canvas = container.querySelector("canvas")!; + const canvas = container.querySelector("canvas.interactive")!; // first point is added on pointer down fireEvent.pointerDown(canvas, { clientX: 30, clientY: 30 }); @@ -102,7 +107,8 @@ describe("multi point mode in linear elements", () => { key: KEYS.ENTER, }); - expect(renderScene).toHaveBeenCalledTimes(15); + expect(renderInteractiveScene).toHaveBeenCalledTimes(10); + expect(renderStaticScene).toHaveBeenCalledTimes(9); expect(h.elements.length).toEqual(1); const element = h.elements[0] as ExcalidrawLinearElement; @@ -125,7 +131,7 @@ describe("multi point mode in linear elements", () => { const tool = getByToolName("line"); fireEvent.click(tool); - const canvas = container.querySelector("canvas")!; + const canvas = container.querySelector("canvas.interactive")!; // first point is added on pointer down fireEvent.pointerDown(canvas, { clientX: 30, clientY: 30 }); @@ -145,7 +151,8 @@ describe("multi point mode in linear elements", () => { key: KEYS.ENTER, }); - expect(renderScene).toHaveBeenCalledTimes(15); + expect(renderInteractiveScene).toHaveBeenCalledTimes(10); + expect(renderStaticScene).toHaveBeenCalledTimes(9); expect(h.elements.length).toEqual(1); const element = h.elements[0] as ExcalidrawLinearElement; diff --git a/src/tests/packages/excalidraw.test.tsx b/src/tests/packages/excalidraw.test.tsx index 91fd9c09f..b6e70c170 100644 --- a/src/tests/packages/excalidraw.test.tsx +++ b/src/tests/packages/excalidraw.test.tsx @@ -23,7 +23,7 @@ describe("", () => { ).toBe(0); expect(h.state.zenModeEnabled).toBe(false); - fireEvent.contextMenu(GlobalTestState.canvas, { + fireEvent.contextMenu(GlobalTestState.interactiveCanvas, { button: 2, clientX: 1, clientY: 1, @@ -43,7 +43,7 @@ describe("", () => { ).toBe(0); expect(h.state.zenModeEnabled).toBe(true); - fireEvent.contextMenu(GlobalTestState.canvas, { + fireEvent.contextMenu(GlobalTestState.interactiveCanvas, { button: 2, clientX: 1, clientY: 1, @@ -93,7 +93,7 @@ describe("", () => { expect( container.getElementsByClassName("disable-zen-mode--visible").length, ).toBe(0); - fireEvent.contextMenu(GlobalTestState.canvas, { + fireEvent.contextMenu(GlobalTestState.interactiveCanvas, { button: 2, clientX: 1, clientY: 1, @@ -112,7 +112,7 @@ describe("", () => { expect( container.getElementsByClassName("disable-zen-mode--visible").length, ).toBe(0); - fireEvent.contextMenu(GlobalTestState.canvas, { + fireEvent.contextMenu(GlobalTestState.interactiveCanvas, { button: 2, clientX: 1, clientY: 1, diff --git a/src/tests/regressionTests.test.tsx b/src/tests/regressionTests.test.tsx index 2b9094888..ff0641575 100644 --- a/src/tests/regressionTests.test.tsx +++ b/src/tests/regressionTests.test.tsx @@ -20,7 +20,7 @@ import { FONT_FAMILY } from "../constants"; const { h } = window; -const renderScene = jest.spyOn(Renderer, "renderScene"); +const renderStaticScene = jest.spyOn(Renderer, "renderStaticScene"); const mouse = new Pointer("mouse"); const finger1 = new Pointer("touch", 1); @@ -32,7 +32,7 @@ const finger2 = new Pointer("touch", 2); * to debug where a test failure came from. */ const checkpoint = (name: string) => { - expect(renderScene.mock.calls.length).toMatchSnapshot( + expect(renderStaticScene.mock.calls.length).toMatchSnapshot( `[${name}] number of renders`, ); expect(h.state).toMatchSnapshot(`[${name}] appState`); @@ -47,7 +47,7 @@ beforeEach(async () => { ReactDOM.unmountComponentAtNode(document.getElementById("root")!); localStorage.clear(); - renderScene.mockClear(); + renderStaticScene.mockClear(); reseed(7); setDateTimeForTests("201933152653"); @@ -1054,6 +1054,28 @@ describe("regression tests", () => { expect(API.getSelectedElements()).toEqual(selectedElements_prev); }); + it("deleting last but one element in editing group should unselect the group", () => { + const rect1 = UI.createElement("rectangle", { x: 10 }); + const rect2 = UI.createElement("rectangle", { x: 50 }); + + UI.group([rect1, rect2]); + + mouse.doubleClickOn(rect1); + Keyboard.keyDown(KEYS.DELETE); + + // Clicking on the deleted element, hence in the empty space + mouse.clickOn(rect1); + + expect(h.state.selectedGroupIds).toEqual({}); + expect(API.getSelectedElements()).toEqual([]); + + // Clicking back in and expecting no group selection + mouse.clickOn(rect2); + + expect(h.state.selectedGroupIds).toEqual({ [rect2.groupIds[0]]: false }); + expect(API.getSelectedElements()).toEqual([rect2.get()]); + }); + it("Cmd/Ctrl-click exclusively select element under pointer", () => { const rect1 = UI.createElement("rectangle", { x: 0 }); const rect2 = UI.createElement("rectangle", { x: 30 }); diff --git a/src/tests/resize.test.tsx b/src/tests/resize.test.tsx index 12fb86bea..1a3aa1b10 100644 --- a/src/tests/resize.test.tsx +++ b/src/tests/resize.test.tsx @@ -13,10 +13,10 @@ import { KEYS } from "../keys"; // Unmount ReactDOM from root ReactDOM.unmountComponentAtNode(document.getElementById("root")!); -const renderScene = jest.spyOn(Renderer, "renderScene"); +const renderStaticScene = jest.spyOn(Renderer, "renderStaticScene"); beforeEach(() => { localStorage.clear(); - renderScene.mockClear(); + renderStaticScene.mockClear(); reseed(7); }); diff --git a/src/tests/selection.test.tsx b/src/tests/selection.test.tsx index 7bfd6e9b3..5824511eb 100644 --- a/src/tests/selection.test.tsx +++ b/src/tests/selection.test.tsx @@ -17,10 +17,12 @@ import { SHAPES } from "../shapes"; // Unmount ReactDOM from root ReactDOM.unmountComponentAtNode(document.getElementById("root")!); -const renderScene = jest.spyOn(Renderer, "renderScene"); +const renderInteractiveScene = jest.spyOn(Renderer, "renderInteractiveScene"); +const renderStaticScene = jest.spyOn(Renderer, "renderStaticScene"); beforeEach(() => { localStorage.clear(); - renderScene.mockClear(); + renderInteractiveScene.mockClear(); + renderStaticScene.mockClear(); reseed(7); }); @@ -132,7 +134,7 @@ describe("inner box-selection", () => { }); h.elements = [rect1, rect2, rect3]; Keyboard.withModifierKeys({ ctrl: true }, () => { - mouse.downAt(rect2.x - 20, rect2.x - 20); + mouse.downAt(rect2.x - 20, rect2.y - 20); mouse.moveTo(rect2.x + rect2.width + 10, rect2.y + rect2.height + 10); assertSelectedElements([rect2.id, rect3.id]); expect(h.state.selectedGroupIds).toEqual({ A: true }); @@ -151,10 +153,11 @@ describe("selection element", () => { const tool = getByToolName("selection"); fireEvent.click(tool); - const canvas = container.querySelector("canvas")!; + const canvas = container.querySelector("canvas.interactive")!; fireEvent.pointerDown(canvas, { clientX: 60, clientY: 100 }); - expect(renderScene).toHaveBeenCalledTimes(5); + expect(renderInteractiveScene).toHaveBeenCalledTimes(3); + expect(renderStaticScene).toHaveBeenCalledTimes(2); const selectionElement = h.state.selectionElement!; expect(selectionElement).not.toBeNull(); expect(selectionElement.type).toEqual("selection"); @@ -171,11 +174,12 @@ describe("selection element", () => { const tool = getByToolName("selection"); fireEvent.click(tool); - const canvas = container.querySelector("canvas")!; + const canvas = container.querySelector("canvas.interactive")!; fireEvent.pointerDown(canvas, { clientX: 60, clientY: 100 }); fireEvent.pointerMove(canvas, { clientX: 150, clientY: 30 }); - expect(renderScene).toHaveBeenCalledTimes(6); + expect(renderInteractiveScene).toHaveBeenCalledTimes(4); + expect(renderStaticScene).toHaveBeenCalledTimes(2); const selectionElement = h.state.selectionElement!; expect(selectionElement).not.toBeNull(); expect(selectionElement.type).toEqual("selection"); @@ -192,12 +196,13 @@ describe("selection element", () => { const tool = getByToolName("selection"); fireEvent.click(tool); - const canvas = container.querySelector("canvas")!; + const canvas = container.querySelector("canvas.interactive")!; fireEvent.pointerDown(canvas, { clientX: 60, clientY: 100 }); fireEvent.pointerMove(canvas, { clientX: 150, clientY: 30 }); fireEvent.pointerUp(canvas); - expect(renderScene).toHaveBeenCalledTimes(7); + expect(renderInteractiveScene).toHaveBeenCalledTimes(5); + expect(renderStaticScene).toHaveBeenCalledTimes(2); expect(h.state.selectionElement).toBeNull(); }); }); @@ -213,7 +218,7 @@ describe("select single element on the scene", () => { it("rectangle", async () => { const { getByToolName, container } = await render(); - const canvas = container.querySelector("canvas")!; + const canvas = container.querySelector("canvas.interactive")!; { // create element const tool = getByToolName("rectangle"); @@ -232,7 +237,8 @@ describe("select single element on the scene", () => { fireEvent.pointerDown(canvas, { clientX: 45, clientY: 20 }); fireEvent.pointerUp(canvas); - expect(renderScene).toHaveBeenCalledTimes(11); + expect(renderInteractiveScene).toHaveBeenCalledTimes(9); + expect(renderStaticScene).toHaveBeenCalledTimes(5); expect(h.state.selectionElement).toBeNull(); expect(h.elements.length).toEqual(1); expect(h.state.selectedElementIds[h.elements[0].id]).toBeTruthy(); @@ -242,7 +248,7 @@ describe("select single element on the scene", () => { it("diamond", async () => { const { getByToolName, container } = await render(); - const canvas = container.querySelector("canvas")!; + const canvas = container.querySelector("canvas.interactive")!; { // create element const tool = getByToolName("diamond"); @@ -261,7 +267,8 @@ describe("select single element on the scene", () => { fireEvent.pointerDown(canvas, { clientX: 45, clientY: 20 }); fireEvent.pointerUp(canvas); - expect(renderScene).toHaveBeenCalledTimes(11); + expect(renderInteractiveScene).toHaveBeenCalledTimes(9); + expect(renderStaticScene).toHaveBeenCalledTimes(5); expect(h.state.selectionElement).toBeNull(); expect(h.elements.length).toEqual(1); expect(h.state.selectedElementIds[h.elements[0].id]).toBeTruthy(); @@ -271,7 +278,7 @@ describe("select single element on the scene", () => { it("ellipse", async () => { const { getByToolName, container } = await render(); - const canvas = container.querySelector("canvas")!; + const canvas = container.querySelector("canvas.interactive")!; { // create element const tool = getByToolName("ellipse"); @@ -290,7 +297,8 @@ describe("select single element on the scene", () => { fireEvent.pointerDown(canvas, { clientX: 45, clientY: 20 }); fireEvent.pointerUp(canvas); - expect(renderScene).toHaveBeenCalledTimes(11); + expect(renderInteractiveScene).toHaveBeenCalledTimes(9); + expect(renderStaticScene).toHaveBeenCalledTimes(5); expect(h.state.selectionElement).toBeNull(); expect(h.elements.length).toEqual(1); expect(h.state.selectedElementIds[h.elements[0].id]).toBeTruthy(); @@ -300,7 +308,7 @@ describe("select single element on the scene", () => { it("arrow", async () => { const { getByToolName, container } = await render(); - const canvas = container.querySelector("canvas")!; + const canvas = container.querySelector("canvas.interactive")!; { // create element const tool = getByToolName("arrow"); @@ -332,7 +340,8 @@ describe("select single element on the scene", () => { fireEvent.pointerDown(canvas, { clientX: 40, clientY: 40 }); fireEvent.pointerUp(canvas); - expect(renderScene).toHaveBeenCalledTimes(11); + expect(renderInteractiveScene).toHaveBeenCalledTimes(9); + expect(renderStaticScene).toHaveBeenCalledTimes(6); expect(h.state.selectionElement).toBeNull(); expect(h.elements.length).toEqual(1); expect(h.state.selectedElementIds[h.elements[0].id]).toBeTruthy(); @@ -341,7 +350,7 @@ describe("select single element on the scene", () => { it("arrow escape", async () => { const { getByToolName, container } = await render(); - const canvas = container.querySelector("canvas")!; + const canvas = container.querySelector("canvas.interactive")!; { // create element const tool = getByToolName("line"); @@ -373,7 +382,8 @@ describe("select single element on the scene", () => { fireEvent.pointerDown(canvas, { clientX: 40, clientY: 40 }); fireEvent.pointerUp(canvas); - expect(renderScene).toHaveBeenCalledTimes(11); + expect(renderInteractiveScene).toHaveBeenCalledTimes(9); + expect(renderStaticScene).toHaveBeenCalledTimes(6); expect(h.state.selectionElement).toBeNull(); expect(h.elements.length).toEqual(1); expect(h.state.selectedElementIds[h.elements[0].id]).toBeTruthy(); diff --git a/src/tests/test-utils.ts b/src/tests/test-utils.ts index bb771b196..97d54c644 100644 --- a/src/tests/test-utils.ts +++ b/src/tests/test-utils.ts @@ -49,15 +49,30 @@ const renderApp: TestRenderFn = async (ui, options) => { // child App component isn't likely mounted yet (and thus canvas not // present in DOM) get() { - return renderResult.container.querySelector("canvas")!; + return renderResult.container.querySelector("canvas.static")!; + }, + }); + + Object.defineProperty(GlobalTestState, "interactiveCanvas", { + // must be a getter because at the time of ExcalidrawApp render the + // child App component isn't likely mounted yet (and thus canvas not + // present in DOM) + get() { + return renderResult.container.querySelector("canvas.interactive")!; }, }); await waitFor(() => { - const canvas = renderResult.container.querySelector("canvas"); + const canvas = renderResult.container.querySelector("canvas.static"); if (!canvas) { throw new Error("not initialized yet"); } + + const interactiveCanvas = + renderResult.container.querySelector("canvas.interactive"); + if (!interactiveCanvas) { + throw new Error("not initialized yet"); + } }); return renderResult; @@ -81,11 +96,17 @@ export class GlobalTestState { */ static renderResult: RenderResult = null!; /** - * retrieves canvas for currently rendered app instance + * retrieves static canvas for currently rendered app instance */ static get canvas(): HTMLCanvasElement { return null!; } + /** + * retrieves interactive canvas for currently rendered app instance + */ + static get interactiveCanvas(): HTMLCanvasElement { + return null!; + } } const initLocalStorage = (data: ImportedDataState) => { diff --git a/src/tests/viewMode.test.tsx b/src/tests/viewMode.test.tsx index 277b306ee..bc29f81c3 100644 --- a/src/tests/viewMode.test.tsx +++ b/src/tests/viewMode.test.tsx @@ -17,7 +17,9 @@ describe("view mode", () => { it("after switching to view mode – cursor type should be pointer", async () => { h.setState({ viewModeEnabled: true }); - expect(GlobalTestState.canvas.style.cursor).toBe(CURSOR_TYPE.GRAB); + expect(GlobalTestState.interactiveCanvas.style.cursor).toBe( + CURSOR_TYPE.GRAB, + ); }); it("after switching to view mode, moving, clicking, and pressing space key – cursor type should be pointer", async () => { @@ -29,7 +31,9 @@ describe("view mode", () => { pointer.move(100, 100); pointer.click(); Keyboard.keyPress(KEYS.SPACE); - expect(GlobalTestState.canvas.style.cursor).toBe(CURSOR_TYPE.GRAB); + expect(GlobalTestState.interactiveCanvas.style.cursor).toBe( + CURSOR_TYPE.GRAB, + ); }); }); @@ -45,13 +49,19 @@ describe("view mode", () => { pointer.moveTo(50, 50); // eslint-disable-next-line dot-notation if (pointerType["pointerType"] === "mouse") { - expect(GlobalTestState.canvas.style.cursor).toBe(CURSOR_TYPE.MOVE); + expect(GlobalTestState.interactiveCanvas.style.cursor).toBe( + CURSOR_TYPE.MOVE, + ); } else { - expect(GlobalTestState.canvas.style.cursor).toBe(CURSOR_TYPE.GRAB); + expect(GlobalTestState.interactiveCanvas.style.cursor).toBe( + CURSOR_TYPE.GRAB, + ); } h.setState({ viewModeEnabled: true }); - expect(GlobalTestState.canvas.style.cursor).toBe(CURSOR_TYPE.GRAB); + expect(GlobalTestState.interactiveCanvas.style.cursor).toBe( + CURSOR_TYPE.GRAB, + ); }); }); }); diff --git a/src/tests/zindex.test.tsx b/src/tests/zindex.test.tsx index de421338b..2047c032e 100644 --- a/src/tests/zindex.test.tsx +++ b/src/tests/zindex.test.tsx @@ -94,7 +94,7 @@ const populateElements = ( ), ...appState, selectedElementIds, - }); + } as AppState); return selectedElementIds; }; diff --git a/src/types.ts b/src/types.ts index 40f54831d..72227f5c5 100644 --- a/src/types.ts +++ b/src/types.ts @@ -98,6 +98,51 @@ export type LastActiveTool = export type SidebarName = string; export type SidebarTabName = string; +export type CommonCanvasAppState = { + zoom: AppState["zoom"]; + scrollX: AppState["scrollX"]; + scrollY: AppState["scrollY"]; + width: AppState["width"]; + height: AppState["height"]; + viewModeEnabled: AppState["viewModeEnabled"]; + editingElement: AppState["editingElement"]; + editingGroupId: AppState["editingGroupId"]; // TODO: move to interactive canvas if possible + selectedElementIds: AppState["selectedElementIds"]; // TODO: move to interactive canvas if possible + frameToHighlight: AppState["frameToHighlight"]; // TODO: move to interactive canvas if possible + offsetLeft: AppState["offsetLeft"]; + offsetTop: AppState["offsetTop"]; + theme: AppState["theme"]; + pendingImageElementId: AppState["pendingImageElementId"]; +}; + +export type StaticCanvasAppState = CommonCanvasAppState & { + shouldCacheIgnoreZoom: AppState["shouldCacheIgnoreZoom"]; + /** null indicates transparent bg */ + viewBackgroundColor?: AppState["viewBackgroundColor"]; + exportScale: AppState["exportScale"]; + selectedElementsAreBeingDragged: AppState["selectedElementsAreBeingDragged"]; + gridSize: AppState["gridSize"]; + frameRendering: AppState["frameRendering"]; +}; + +export type InteractiveCanvasAppState = CommonCanvasAppState & { + // renderInteractiveScene + editingLinearElement: AppState["editingLinearElement"]; + selectionElement: AppState["selectionElement"]; + selectedGroupIds: AppState["selectedGroupIds"]; + selectedLinearElement: AppState["selectedLinearElement"]; + multiElement: AppState["multiElement"]; + isBindingEnabled: AppState["isBindingEnabled"]; + suggestedBindings: AppState["suggestedBindings"]; + isRotating: AppState["isRotating"]; + elementsToHighlight: AppState["elementsToHighlight"]; + // App + openSidebar: AppState["openSidebar"]; + showHyperlinkPopup: AppState["showHyperlinkPopup"]; + // Collaborators + collaborators: AppState["collaborators"]; +}; + export type AppState = { contextMenu: { items: ContextMenuItems; @@ -434,6 +479,7 @@ export type AppProps = Merge< export type AppClassProperties = { props: AppProps; canvas: HTMLCanvasElement | null; + interactiveCanvas: HTMLCanvasElement | null; focusContainer(): void; library: Library; imageCache: Map< diff --git a/src/utils.ts b/src/utils.ts index c644efd81..877f9835d 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -392,22 +392,25 @@ export const updateActiveTool = ( }; }; -export const resetCursor = (canvas: HTMLCanvasElement | null) => { - if (canvas) { - canvas.style.cursor = ""; +export const resetCursor = (interactiveCanvas: HTMLCanvasElement | null) => { + if (interactiveCanvas) { + interactiveCanvas.style.cursor = ""; } }; -export const setCursor = (canvas: HTMLCanvasElement | null, cursor: string) => { - if (canvas) { - canvas.style.cursor = cursor; +export const setCursor = ( + interactiveCanvas: HTMLCanvasElement | null, + cursor: string, +) => { + if (interactiveCanvas) { + interactiveCanvas.style.cursor = cursor; } }; let eraserCanvasCache: any; let previewDataURL: string; export const setEraserCursor = ( - canvas: HTMLCanvasElement | null, + interactiveCanvas: HTMLCanvasElement | null, theme: AppState["theme"], ) => { const cursorImageSizePx = 20; @@ -439,7 +442,7 @@ export const setEraserCursor = ( } setCursor( - canvas, + interactiveCanvas, `url(${previewDataURL}) ${cursorImageSizePx / 2} ${ cursorImageSizePx / 2 }, auto`, @@ -447,23 +450,23 @@ export const setEraserCursor = ( }; export const setCursorForShape = ( - canvas: HTMLCanvasElement | null, + interactiveCanvas: HTMLCanvasElement | null, appState: Pick, ) => { - if (!canvas) { + if (!interactiveCanvas) { return; } if (appState.activeTool.type === "selection") { - resetCursor(canvas); + resetCursor(interactiveCanvas); } else if (isHandToolActive(appState)) { - canvas.style.cursor = CURSOR_TYPE.GRAB; + interactiveCanvas.style.cursor = CURSOR_TYPE.GRAB; } else if (isEraserActive(appState)) { - setEraserCursor(canvas, appState.theme); + setEraserCursor(interactiveCanvas, appState.theme); // do nothing if image tool is selected which suggests there's // a image-preview set as the cursor // Ignore custom type as well and let host decide } else if (!["image", "custom"].includes(appState.activeTool.type)) { - canvas.style.cursor = CURSOR_TYPE.CROSSHAIR; + interactiveCanvas.style.cursor = CURSOR_TYPE.CROSSHAIR; } };