diff --git a/excalidraw-app/tests/collab.test.tsx b/excalidraw-app/tests/collab.test.tsx index 3572303f4..4549c2128 100644 --- a/excalidraw-app/tests/collab.test.tsx +++ b/excalidraw-app/tests/collab.test.tsx @@ -8,6 +8,13 @@ import { API } from "@excalidraw/excalidraw/tests/helpers/api"; import { act, render, waitFor } from "@excalidraw/excalidraw/tests/test-utils"; import { vi } from "vitest"; +import { StoreIncrement } from "@excalidraw/element/store"; + +import type { + DurableIncrement, + EphemeralIncrement, +} from "@excalidraw/element/store"; + import ExcalidrawApp from "../App"; const { h } = window; @@ -65,6 +72,79 @@ vi.mock("socket.io-client", () => { * i.e. multiplayer history tests could be a good first candidate, as we could test both history stacks simultaneously. */ describe("collaboration", () => { + it("should emit two ephemeral increments even though updates get batched", async () => { + const durableIncrements: DurableIncrement[] = []; + const ephemeralIncrements: EphemeralIncrement[] = []; + + await render(); + + h.store.onStoreIncrementEmitter.on((increment) => { + if (StoreIncrement.isDurable(increment)) { + durableIncrements.push(increment); + } else { + ephemeralIncrements.push(increment); + } + }); + + // eslint-disable-next-line dot-notation + expect(h.store["scheduledMicroActions"].length).toBe(0); + expect(durableIncrements.length).toBe(0); + expect(ephemeralIncrements.length).toBe(0); + + const rectProps = { + type: "rectangle", + id: "A", + height: 200, + width: 100, + x: 0, + y: 0, + } as const; + + const rect = API.createElement({ ...rectProps }); + + API.updateScene({ + elements: [rect], + captureUpdate: CaptureUpdateAction.IMMEDIATELY, + }); + + await waitFor(() => { + // expect(commitSpy).toHaveBeenCalledTimes(1); + expect(durableIncrements.length).toBe(1); + }); + + // simulate two batched remote updates + act(() => { + h.app.updateScene({ + elements: [newElementWith(h.elements[0], { x: 100 })], + captureUpdate: CaptureUpdateAction.NEVER, + }); + h.app.updateScene({ + elements: [newElementWith(h.elements[0], { x: 200 })], + captureUpdate: CaptureUpdateAction.NEVER, + }); + + // we scheduled two micro actions, + // which confirms they are going to be executed as part of one batched component update + // eslint-disable-next-line dot-notation + expect(h.store["scheduledMicroActions"].length).toBe(2); + }); + + await waitFor(() => { + // altough the updates get batched, + // we expect two ephemeral increments for each update, + // and each such update should have the expected change + expect(ephemeralIncrements.length).toBe(2); + expect(ephemeralIncrements[0].change.elements.A).toEqual( + expect.objectContaining({ x: 100 }), + ); + expect(ephemeralIncrements[1].change.elements.A).toEqual( + expect.objectContaining({ x: 200 }), + ); + // eslint-disable-next-line dot-notation + expect(h.store["scheduledMicroActions"].length).toBe(0); + }); + }); + it("should allow to undo / redo even on force-deleted elements", async () => { await render(); const rect1Props = { @@ -122,7 +202,7 @@ describe("collaboration", () => { expect(h.elements).toEqual([expect.objectContaining(rect1Props)]); }); - const undoAction = createUndoAction(h.history, h.store); + const undoAction = createUndoAction(h.history); act(() => h.app.actionManager.executeAction(undoAction)); // with explicit undo (as addition) we expect our item to be restored from the snapshot! @@ -154,7 +234,7 @@ describe("collaboration", () => { expect(h.elements).toEqual([expect.objectContaining(rect1Props)]); }); - const redoAction = createRedoAction(h.history, h.store); + const redoAction = createRedoAction(h.history); act(() => h.app.actionManager.executeAction(redoAction)); // with explicit redo (as removal) we again restore the element from the snapshot! diff --git a/package.json b/package.json index 6f57f7e7c..02d989cd2 100644 --- a/package.json +++ b/package.json @@ -33,6 +33,7 @@ "pepjs": "0.5.3", "prettier": "2.6.2", "rewire": "6.0.0", + "rimraf": "^5.0.0", "typescript": "4.9.4", "vite": "5.0.12", "vite-plugin-checker": "0.7.2", @@ -78,8 +79,8 @@ "autorelease": "node scripts/autorelease.js", "prerelease:excalidraw": "node scripts/prerelease.js", "release:excalidraw": "node scripts/release.js", - "rm:build": "rm -rf excalidraw-app/{build,dist,dev-dist} && rm -rf packages/*/{dist,build} && rm -rf examples/*/{build,dist}", - "rm:node_modules": "rm -rf node_modules && rm -rf excalidraw-app/node_modules && rm -rf packages/*/node_modules", + "rm:build": "rimraf --glob excalidraw-app/build excalidraw-app/dist excalidraw-app/dev-dist packages/*/dist packages/*/build examples/*/build examples/*/dist", + "rm:node_modules": "rimraf --glob node_modules excalidraw-app/node_modules packages/*/node_modules", "clean-install": "yarn rm:node_modules && yarn install" }, "resolutions": { diff --git a/packages/common/package.json b/packages/common/package.json index 3e5f6413a..32cffc717 100644 --- a/packages/common/package.json +++ b/packages/common/package.json @@ -50,7 +50,7 @@ "bugs": "https://github.com/excalidraw/excalidraw/issues", "repository": "https://github.com/excalidraw/excalidraw", "scripts": { - "gen:types": "rm -rf types && tsc", - "build:esm": "rm -rf dist && node ../../scripts/buildBase.js && yarn gen:types" + "gen:types": "rimraf types && tsc", + "build:esm": "rimraf dist && node ../../scripts/buildBase.js && yarn gen:types" } } diff --git a/packages/excalidraw/emitter.ts b/packages/common/src/emitter.ts similarity index 94% rename from packages/excalidraw/emitter.ts rename to packages/common/src/emitter.ts index 938269728..7c069a5a0 100644 --- a/packages/excalidraw/emitter.ts +++ b/packages/common/src/emitter.ts @@ -1,4 +1,4 @@ -import type { UnsubscribeCallback } from "./types"; +import type { UnsubscribeCallback } from "@excalidraw/excalidraw/types"; type Subscriber = (...payload: T) => void; diff --git a/packages/common/src/index.ts b/packages/common/src/index.ts index d896ba98e..79f243f4f 100644 --- a/packages/common/src/index.ts +++ b/packages/common/src/index.ts @@ -9,3 +9,4 @@ export * from "./promise-pool"; export * from "./random"; export * from "./url"; export * from "./utils"; +export * from "./emitter"; diff --git a/packages/common/src/utility-types.ts b/packages/common/src/utility-types.ts index d4804d195..b5b68a978 100644 --- a/packages/common/src/utility-types.ts +++ b/packages/common/src/utility-types.ts @@ -68,3 +68,12 @@ export type MaybePromise = T | Promise; // get union of all keys from the union of types export type AllPossibleKeys = T extends any ? keyof T : never; + +/** Strip all the methods or functions from a type */ +export type DTO = { + [K in keyof T as T[K] extends Function ? never : K]: T[K]; +}; + +export type MapEntry> = M extends Map + ? [K, V] + : never; diff --git a/packages/common/src/utils.ts b/packages/common/src/utils.ts index e7053b181..6bf309c62 100644 --- a/packages/common/src/utils.ts +++ b/packages/common/src/utils.ts @@ -735,6 +735,25 @@ export const arrayToList = (array: readonly T[]): Node[] => return acc; }, [] as Node[]); +/** + * Converts a readonly array or map into an iterable. + * Useful for avoiding entry allocations when iterating object / map on each iteration. + */ +export const toIterable = ( + values: readonly T[] | ReadonlyMap, +): Iterable => { + return Array.isArray(values) ? values : values.values(); +}; + +/** + * Converts a readonly array or map into an array. + */ +export const toArray = ( + values: readonly T[] | ReadonlyMap, +): T[] => { + return Array.isArray(values) ? values : Array.from(toIterable(values)); +}; + export const isTestEnv = () => import.meta.env.MODE === ENV.TEST; export const isDevEnv = () => import.meta.env.MODE === ENV.DEVELOPMENT; diff --git a/packages/element/package.json b/packages/element/package.json index ae810e374..1eec60742 100644 --- a/packages/element/package.json +++ b/packages/element/package.json @@ -50,7 +50,7 @@ "bugs": "https://github.com/excalidraw/excalidraw/issues", "repository": "https://github.com/excalidraw/excalidraw", "scripts": { - "gen:types": "rm -rf types && tsc", - "build:esm": "rm -rf dist && node ../../scripts/buildBase.js && yarn gen:types" + "gen:types": "rimraf types && tsc", + "build:esm": "rimraf dist && node ../../scripts/buildBase.js && yarn gen:types" } } diff --git a/packages/element/src/Scene.ts b/packages/element/src/Scene.ts index b35c54cae..a31c96264 100644 --- a/packages/element/src/Scene.ts +++ b/packages/element/src/Scene.ts @@ -6,14 +6,13 @@ import { toBrandedType, isDevEnv, isTestEnv, - isReadonlyArray, + toArray, } from "@excalidraw/common"; import { isNonDeletedElement } from "@excalidraw/element"; import { isFrameLikeElement } from "@excalidraw/element/typeChecks"; import { getElementsInGroup } from "@excalidraw/element/groups"; import { - orderByFractionalIndex, syncInvalidIndices, syncMovedIndices, validateFractionalIndices, @@ -268,19 +267,13 @@ class Scene { } replaceAllElements(nextElements: ElementsMapOrArray) { - // ts doesn't like `Array.isArray` of `instanceof Map` - if (!isReadonlyArray(nextElements)) { - // need to order by fractional indices to get the correct order - nextElements = orderByFractionalIndex( - Array.from(nextElements.values()) as OrderedExcalidrawElement[], - ); - } - + // we do trust the insertion order on the map, though maybe we shouldn't and should prefer order defined by fractional indices + const _nextElements = toArray(nextElements); const nextFrameLikes: ExcalidrawFrameLikeElement[] = []; - validateIndicesThrottled(nextElements); + validateIndicesThrottled(_nextElements); - this.elements = syncInvalidIndices(nextElements); + this.elements = syncInvalidIndices(_nextElements); this.elementsMap.clear(); this.elements.forEach((element) => { if (isFrameLikeElement(element)) { diff --git a/packages/element/src/binding.ts b/packages/element/src/binding.ts index ee5d037a8..58f378998 100644 --- a/packages/element/src/binding.ts +++ b/packages/element/src/binding.ts @@ -33,7 +33,7 @@ import type { LocalPoint, Radians } from "@excalidraw/math"; import type { AppState } from "@excalidraw/excalidraw/types"; -import type { Mutable } from "@excalidraw/common/utility-types"; +import type { MapEntry, Mutable } from "@excalidraw/common/utility-types"; import { getCenterForBounds, @@ -84,6 +84,7 @@ import type { ExcalidrawElbowArrowElement, FixedPoint, FixedPointBinding, + PointsPositionUpdates, } from "./types"; export type SuggestedBinding = @@ -801,28 +802,22 @@ export const updateBoundElements = ( bindableElement, elementsMap, ); + if (point) { - return { - index: - bindingProp === "startBinding" ? 0 : element.points.length - 1, - point, - }; + return [ + bindingProp === "startBinding" ? 0 : element.points.length - 1, + { point }, + ] as MapEntry; } } return null; }, ).filter( - ( - update, - ): update is NonNullable<{ - index: number; - point: LocalPoint; - isDragging?: boolean; - }> => update !== null, + (update): update is MapEntry => update !== null, ); - LinearElementEditor.movePoints(element, scene, updates, { + LinearElementEditor.movePoints(element, scene, new Map(updates), { ...(changedElement.id === element.startBinding?.elementId ? { startBinding: bindings.startBinding } : {}), @@ -1171,6 +1166,48 @@ export const snapToMid = ( center, angle, ); + } else if (element.type === "diamond") { + const distance = FIXED_BINDING_DISTANCE - 1; + const topLeft = pointFrom( + x + width / 4 - distance, + y + height / 4 - distance, + ); + const topRight = pointFrom( + x + (3 * width) / 4 + distance, + y + height / 4 - distance, + ); + const bottomLeft = pointFrom( + x + width / 4 - distance, + y + (3 * height) / 4 + distance, + ); + const bottomRight = pointFrom( + x + (3 * width) / 4 + distance, + y + (3 * height) / 4 + distance, + ); + if ( + pointDistance(topLeft, nonRotated) < + Math.max(horizontalThrehsold, verticalThrehsold) + ) { + return pointRotateRads(topLeft, center, angle); + } + if ( + pointDistance(topRight, nonRotated) < + Math.max(horizontalThrehsold, verticalThrehsold) + ) { + return pointRotateRads(topRight, center, angle); + } + if ( + pointDistance(bottomLeft, nonRotated) < + Math.max(horizontalThrehsold, verticalThrehsold) + ) { + return pointRotateRads(bottomLeft, center, angle); + } + if ( + pointDistance(bottomRight, nonRotated) < + Math.max(horizontalThrehsold, verticalThrehsold) + ) { + return pointRotateRads(bottomRight, center, angle); + } } return p; diff --git a/packages/element/src/bounds.ts b/packages/element/src/bounds.ts index d0c071f2c..a5b91922b 100644 --- a/packages/element/src/bounds.ts +++ b/packages/element/src/bounds.ts @@ -1,17 +1,17 @@ import rough from "roughjs/bin/rough"; import { - rescalePoints, arrayToMap, invariant, + rescalePoints, sizeOf, } from "@excalidraw/common"; import { degreesToRadians, lineSegment, - pointFrom, pointDistance, + pointFrom, pointFromArray, pointRotateRads, } from "@excalidraw/math"; @@ -33,8 +33,8 @@ import type { AppState } from "@excalidraw/excalidraw/types"; import type { Mutable } from "@excalidraw/common/utility-types"; -import { ShapeCache } from "./ShapeCache"; import { generateRoughOptions } from "./Shape"; +import { ShapeCache } from "./ShapeCache"; import { LinearElementEditor } from "./linearElementEditor"; import { getBoundTextElement, getContainerElement } from "./textElement"; import { @@ -52,20 +52,20 @@ import { deconstructRectanguloidElement, } from "./utils"; -import type { - ExcalidrawElement, - ExcalidrawLinearElement, - Arrowhead, - ExcalidrawFreeDrawElement, - NonDeleted, - ExcalidrawTextElementWithContainer, - ElementsMap, - ExcalidrawRectanguloidElement, - ExcalidrawEllipseElement, - ElementsMapOrArray, -} from "./types"; import type { Drawable, Op } from "roughjs/bin/core"; import type { Point as RoughPoint } from "roughjs/bin/geometry"; +import type { + Arrowhead, + ElementsMap, + ElementsMapOrArray, + ExcalidrawElement, + ExcalidrawEllipseElement, + ExcalidrawFreeDrawElement, + ExcalidrawLinearElement, + ExcalidrawRectanguloidElement, + ExcalidrawTextElementWithContainer, + NonDeleted, +} from "./types"; export type RectangleBox = { x: number; diff --git a/packages/excalidraw/change.ts b/packages/element/src/delta.ts similarity index 81% rename from packages/excalidraw/change.ts rename to packages/element/src/delta.ts index e7ba76f60..2499f7d66 100644 --- a/packages/excalidraw/change.ts +++ b/packages/element/src/delta.ts @@ -5,43 +5,7 @@ import { isDevEnv, isShallowEqual, isTestEnv, - toBrandedType, } from "@excalidraw/common"; -import { - BoundElement, - BindableElement, - bindingProperties, - updateBoundElements, -} from "@excalidraw/element/binding"; -import { LinearElementEditor } from "@excalidraw/element/linearElementEditor"; -import { - mutateElement, - newElementWith, -} from "@excalidraw/element/mutateElement"; -import { - getBoundTextElementId, - redrawTextBoundingBox, -} from "@excalidraw/element/textElement"; -import { - hasBoundTextElement, - isBindableElement, - isBoundToContainer, - isImageElement, - isTextElement, -} from "@excalidraw/element/typeChecks"; - -import { getNonDeletedGroupIds } from "@excalidraw/element/groups"; - -import { - orderByFractionalIndex, - syncMovedIndices, -} from "@excalidraw/element/fractionalIndex"; - -import Scene from "@excalidraw/element/Scene"; - -import type { BindableProp, BindingProp } from "@excalidraw/element/binding"; - -import type { ElementUpdate } from "@excalidraw/element/mutateElement"; import type { ExcalidrawElement, @@ -54,16 +18,42 @@ import type { SceneElementsMap, } from "@excalidraw/element/types"; -import type { SubtypeOf, ValueOf } from "@excalidraw/common/utility-types"; - -import { getObservedAppState } from "./store"; +import type { DTO, SubtypeOf, ValueOf } from "@excalidraw/common/utility-types"; import type { AppState, ObservedAppState, ObservedElementsAppState, ObservedStandaloneAppState, -} from "./types"; +} from "@excalidraw/excalidraw/types"; + +import { getObservedAppState } from "./store"; + +import { + BoundElement, + BindableElement, + bindingProperties, + updateBoundElements, +} from "./binding"; +import { LinearElementEditor } from "./linearElementEditor"; +import { mutateElement, newElementWith } from "./mutateElement"; +import { getBoundTextElementId, redrawTextBoundingBox } from "./textElement"; +import { + hasBoundTextElement, + isBindableElement, + isBoundToContainer, + isTextElement, +} from "./typeChecks"; + +import { getNonDeletedGroupIds } from "./groups"; + +import { orderByFractionalIndex, syncMovedIndices } from "./fractionalIndex"; + +import Scene from "./Scene"; + +import type { BindableProp, BindingProp } from "./binding"; + +import type { ElementUpdate } from "./mutateElement"; /** * Represents the difference between two objects of the same type. @@ -74,7 +64,7 @@ import type { * * Keeping it as pure object (without transient state, side-effects, etc.), so we won't have to instantiate it on load. */ -class Delta { +export class Delta { private constructor( public readonly deleted: Partial, public readonly inserted: Partial, @@ -326,7 +316,7 @@ class Delta { } /** - * Returns all the object1 keys that have distinct values. + * Returns sorted object1 keys that have distinct values. */ public static getLeftDifferences( object1: T, @@ -335,11 +325,11 @@ class Delta { ) { return Array.from( this.distinctKeysIterator("left", object1, object2, skipShallowCompare), - ); + ).sort(); } /** - * Returns all the object2 keys that have distinct values. + * Returns sorted object2 keys that have distinct values. */ public static getRightDifferences( object1: T, @@ -348,7 +338,7 @@ class Delta { ) { return Array.from( this.distinctKeysIterator("right", object1, object2, skipShallowCompare), - ); + ).sort(); } /** @@ -409,51 +399,57 @@ class Delta { } /** - * Encapsulates the modifications captured as `Delta`/s. + * Encapsulates a set of application-level `Delta`s. */ -interface Change { +export interface DeltaContainer { /** - * Inverses the `Delta`s inside while creating a new `Change`. + * Inverses the `Delta`s while creating a new `DeltaContainer` instance. */ - inverse(): Change; + inverse(): DeltaContainer; /** - * Applies the `Change` to the previous object. + * Applies the `Delta`s to the previous object. * - * @returns a tuple of the next object `T` with applied change, and `boolean`, indicating whether the applied change resulted in a visible change. + * @returns a tuple of the next object `T` with applied `Delta`s, and `boolean`, indicating whether the applied deltas resulted in a visible change. */ applyTo(previous: T, ...options: unknown[]): [T, boolean]; /** - * Checks whether there are actually `Delta`s. + * Checks whether all `Delta`s are empty. */ isEmpty(): boolean; } -export class AppStateChange implements Change { - private constructor(private readonly delta: Delta) {} +export class AppStateDelta implements DeltaContainer { + private constructor(public readonly delta: Delta) {} public static calculate( prevAppState: T, nextAppState: T, - ): AppStateChange { + ): AppStateDelta { const delta = Delta.calculate( prevAppState, nextAppState, - undefined, - AppStateChange.postProcess, + // making the order of keys in deltas stable for hashing purposes + AppStateDelta.orderAppStateKeys, + AppStateDelta.postProcess, ); - return new AppStateChange(delta); + return new AppStateDelta(delta); + } + + public static restore(appStateDeltaDTO: DTO): AppStateDelta { + const { delta } = appStateDeltaDTO; + return new AppStateDelta(delta); } public static empty() { - return new AppStateChange(Delta.create({}, {})); + return new AppStateDelta(Delta.create({}, {})); } - public inverse(): AppStateChange { + public inverse(): AppStateDelta { const inversedDelta = Delta.create(this.delta.inserted, this.delta.deleted); - return new AppStateChange(inversedDelta); + return new AppStateDelta(inversedDelta); } public applyTo( @@ -544,40 +540,6 @@ export class AppStateChange implements Change { return Delta.isEmpty(this.delta); } - /** - * It is necessary to post process the partials in case of reference values, - * for which we need to calculate the real diff between `deleted` and `inserted`. - */ - private static postProcess( - deleted: Partial, - inserted: Partial, - ): [Partial, Partial] { - try { - Delta.diffObjects( - deleted, - inserted, - "selectedElementIds", - // ts language server has a bit trouble resolving this, so we are giving it a little push - (_) => true as ValueOf, - ); - Delta.diffObjects( - deleted, - inserted, - "selectedGroupIds", - (prevValue) => (prevValue ?? false) as ValueOf, - ); - } catch (e) { - // if postprocessing fails it does not make sense to bubble up, but let's make sure we know about it - console.error(`Couldn't postprocess appstate change deltas.`); - - if (isTestEnv() || isDevEnv()) { - throw e; - } - } finally { - return [deleted, inserted]; - } - } - /** * Mutates `nextAppState` be filtering out state related to deleted elements. * @@ -594,13 +556,13 @@ export class AppStateChange implements Change { const nextObservedAppState = getObservedAppState(nextAppState); const containsStandaloneDifference = Delta.isRightDifferent( - AppStateChange.stripElementsProps(prevObservedAppState), - AppStateChange.stripElementsProps(nextObservedAppState), + AppStateDelta.stripElementsProps(prevObservedAppState), + AppStateDelta.stripElementsProps(nextObservedAppState), ); const containsElementsDifference = Delta.isRightDifferent( - AppStateChange.stripStandaloneProps(prevObservedAppState), - AppStateChange.stripStandaloneProps(nextObservedAppState), + AppStateDelta.stripStandaloneProps(prevObservedAppState), + AppStateDelta.stripStandaloneProps(nextObservedAppState), ); if (!containsStandaloneDifference && !containsElementsDifference) { @@ -615,8 +577,8 @@ export class AppStateChange implements Change { if (containsElementsDifference) { // filter invisible changes on each iteration const changedElementsProps = Delta.getRightDifferences( - AppStateChange.stripStandaloneProps(prevObservedAppState), - AppStateChange.stripStandaloneProps(nextObservedAppState), + AppStateDelta.stripStandaloneProps(prevObservedAppState), + AppStateDelta.stripStandaloneProps(nextObservedAppState), ) as Array; let nonDeletedGroupIds = new Set(); @@ -633,7 +595,7 @@ export class AppStateChange implements Change { for (const key of changedElementsProps) { switch (key) { case "selectedElementIds": - nextAppState[key] = AppStateChange.filterSelectedElements( + nextAppState[key] = AppStateDelta.filterSelectedElements( nextAppState[key], nextElements, visibleDifferenceFlag, @@ -641,7 +603,7 @@ export class AppStateChange implements Change { break; case "selectedGroupIds": - nextAppState[key] = AppStateChange.filterSelectedGroups( + nextAppState[key] = AppStateDelta.filterSelectedGroups( nextAppState[key], nonDeletedGroupIds, visibleDifferenceFlag, @@ -677,7 +639,7 @@ export class AppStateChange implements Change { break; case "selectedLinearElementId": case "editingLinearElementId": - const appStateKey = AppStateChange.convertToAppStateKey(key); + const appStateKey = AppStateDelta.convertToAppStateKey(key); const linearElement = nextAppState[appStateKey]; if (!linearElement) { @@ -812,6 +774,51 @@ export class AppStateChange implements Change { ObservedElementsAppState >; } + + /** + * It is necessary to post process the partials in case of reference values, + * for which we need to calculate the real diff between `deleted` and `inserted`. + */ + private static postProcess( + deleted: Partial, + inserted: Partial, + ): [Partial, Partial] { + try { + Delta.diffObjects( + deleted, + inserted, + "selectedElementIds", + // ts language server has a bit trouble resolving this, so we are giving it a little push + (_) => true as ValueOf, + ); + Delta.diffObjects( + deleted, + inserted, + "selectedGroupIds", + (prevValue) => (prevValue ?? false) as ValueOf, + ); + } catch (e) { + // if postprocessing fails it does not make sense to bubble up, but let's make sure we know about it + console.error(`Couldn't postprocess appstate change deltas.`); + + if (isTestEnv() || isDevEnv()) { + throw e; + } + } finally { + return [deleted, inserted]; + } + } + + private static orderAppStateKeys(partial: Partial) { + const orderedPartial: { [key: string]: unknown } = {}; + + for (const key of Object.keys(partial).sort()) { + // relying on insertion order + orderedPartial[key] = partial[key as keyof ObservedAppState]; + } + + return orderedPartial as Partial; + } } type ElementPartial = Omit< @@ -823,50 +830,63 @@ type ElementPartial = Omit< * Elements change is a low level primitive to capture a change between two sets of elements. * It does so by encapsulating forward and backward `Delta`s, allowing to time-travel in both directions. */ -export class ElementsChange implements Change { +export class ElementsDelta implements DeltaContainer { private constructor( - private readonly added: Map>, - private readonly removed: Map>, - private readonly updated: Map>, + public readonly added: Record>, + public readonly removed: Record>, + public readonly updated: Record>, ) {} public static create( - added: Map>, - removed: Map>, - updated: Map>, - options = { shouldRedistribute: false }, + added: Record>, + removed: Record>, + updated: Record>, + options: { + shouldRedistribute: boolean; + } = { + shouldRedistribute: false, + }, ) { - let change: ElementsChange; + let delta: ElementsDelta; if (options.shouldRedistribute) { - const nextAdded = new Map>(); - const nextRemoved = new Map>(); - const nextUpdated = new Map>(); + const nextAdded: Record> = {}; + const nextRemoved: Record> = {}; + const nextUpdated: Record> = {}; - const deltas = [...added, ...removed, ...updated]; + const deltas = [ + ...Object.entries(added), + ...Object.entries(removed), + ...Object.entries(updated), + ]; for (const [id, delta] of deltas) { if (this.satisfiesAddition(delta)) { - nextAdded.set(id, delta); + nextAdded[id] = delta; } else if (this.satisfiesRemoval(delta)) { - nextRemoved.set(id, delta); + nextRemoved[id] = delta; } else { - nextUpdated.set(id, delta); + nextUpdated[id] = delta; } } - change = new ElementsChange(nextAdded, nextRemoved, nextUpdated); + delta = new ElementsDelta(nextAdded, nextRemoved, nextUpdated); } else { - change = new ElementsChange(added, removed, updated); + delta = new ElementsDelta(added, removed, updated); } if (isTestEnv() || isDevEnv()) { - ElementsChange.validate(change, "added", this.satisfiesAddition); - ElementsChange.validate(change, "removed", this.satisfiesRemoval); - ElementsChange.validate(change, "updated", this.satisfiesUpdate); + ElementsDelta.validate(delta, "added", this.satisfiesAddition); + ElementsDelta.validate(delta, "removed", this.satisfiesRemoval); + ElementsDelta.validate(delta, "updated", this.satisfiesUpdate); } - return change; + return delta; + } + + public static restore(elementsDeltaDTO: DTO): ElementsDelta { + const { added, removed, updated } = elementsDeltaDTO; + return ElementsDelta.create(added, removed, updated); } private static satisfiesAddition = ({ @@ -888,17 +908,17 @@ export class ElementsChange implements Change { }: Delta) => !!deleted.isDeleted === !!inserted.isDeleted; private static validate( - change: ElementsChange, + elementsDelta: ElementsDelta, type: "added" | "removed" | "updated", satifies: (delta: Delta) => boolean, ) { - for (const [id, delta] of change[type].entries()) { + for (const [id, delta] of Object.entries(elementsDelta[type])) { if (!satifies(delta)) { console.error( `Broken invariant for "${type}" delta, element "${id}", delta:`, delta, ); - throw new Error(`ElementsChange invariant broken for element "${id}".`); + throw new Error(`ElementsDelta invariant broken for element "${id}".`); } } } @@ -909,19 +929,19 @@ export class ElementsChange implements Change { * @param prevElements - Map representing the previous state of elements. * @param nextElements - Map representing the next state of elements. * - * @returns `ElementsChange` instance representing the `Delta` changes between the two sets of elements. + * @returns `ElementsDelta` instance representing the `Delta` changes between the two sets of elements. */ public static calculate( prevElements: Map, nextElements: Map, - ): ElementsChange { + ): ElementsDelta { if (prevElements === nextElements) { - return ElementsChange.empty(); + return ElementsDelta.empty(); } - const added = new Map>(); - const removed = new Map>(); - const updated = new Map>(); + const added: Record> = {}; + const removed: Record> = {}; + const updated: Record> = {}; // this might be needed only in same edge cases, like during collab, when `isDeleted` elements get removed or when we (un)intentionally remove the elements for (const prevElement of prevElements.values()) { @@ -934,10 +954,10 @@ export class ElementsChange implements Change { const delta = Delta.create( deleted, inserted, - ElementsChange.stripIrrelevantProps, + ElementsDelta.stripIrrelevantProps, ); - removed.set(prevElement.id, delta); + removed[prevElement.id] = delta; } } @@ -954,10 +974,10 @@ export class ElementsChange implements Change { const delta = Delta.create( deleted, inserted, - ElementsChange.stripIrrelevantProps, + ElementsDelta.stripIrrelevantProps, ); - added.set(nextElement.id, delta); + added[nextElement.id] = delta; continue; } @@ -966,8 +986,8 @@ export class ElementsChange implements Change { const delta = Delta.calculate( prevElement, nextElement, - ElementsChange.stripIrrelevantProps, - ElementsChange.postProcess, + ElementsDelta.stripIrrelevantProps, + ElementsDelta.postProcess, ); if ( @@ -978,9 +998,9 @@ export class ElementsChange implements Change { ) { // notice that other props could have been updated as well if (prevElement.isDeleted && !nextElement.isDeleted) { - added.set(nextElement.id, delta); + added[nextElement.id] = delta; } else { - removed.set(nextElement.id, delta); + removed[nextElement.id] = delta; } continue; @@ -988,24 +1008,24 @@ export class ElementsChange implements Change { // making sure there are at least some changes if (!Delta.isEmpty(delta)) { - updated.set(nextElement.id, delta); + updated[nextElement.id] = delta; } } } - return ElementsChange.create(added, removed, updated); + return ElementsDelta.create(added, removed, updated); } public static empty() { - return ElementsChange.create(new Map(), new Map(), new Map()); + return ElementsDelta.create({}, {}, {}); } - public inverse(): ElementsChange { - const inverseInternal = (deltas: Map>) => { - const inversedDeltas = new Map>(); + public inverse(): ElementsDelta { + const inverseInternal = (deltas: Record>) => { + const inversedDeltas: Record> = {}; - for (const [id, delta] of deltas.entries()) { - inversedDeltas.set(id, Delta.create(delta.inserted, delta.deleted)); + for (const [id, delta] of Object.entries(deltas)) { + inversedDeltas[id] = Delta.create(delta.inserted, delta.deleted); } return inversedDeltas; @@ -1016,14 +1036,14 @@ export class ElementsChange implements Change { const updated = inverseInternal(this.updated); // notice we inverse removed with added not to break the invariants - return ElementsChange.create(removed, added, updated); + return ElementsDelta.create(removed, added, updated); } public isEmpty(): boolean { return ( - this.added.size === 0 && - this.removed.size === 0 && - this.updated.size === 0 + Object.keys(this.added).length === 0 && + Object.keys(this.removed).length === 0 && + Object.keys(this.updated).length === 0 ); } @@ -1034,7 +1054,10 @@ export class ElementsChange implements Change { * @param modifierOptions defines which of the delta (`deleted` or `inserted`) will be updated * @returns new instance with modified delta/s */ - public applyLatestChanges(elements: SceneElementsMap): ElementsChange { + public applyLatestChanges( + elements: SceneElementsMap, + modifierOptions: "deleted" | "inserted", + ): ElementsDelta { const modifier = (element: OrderedExcalidrawElement) => (partial: ElementPartial) => { const latestPartial: { [key: string]: unknown } = {}; @@ -1055,11 +1078,11 @@ export class ElementsChange implements Change { }; const applyLatestChangesInternal = ( - deltas: Map>, + deltas: Record>, ) => { - const modifiedDeltas = new Map>(); + const modifiedDeltas: Record> = {}; - for (const [id, delta] of deltas.entries()) { + for (const [id, delta] of Object.entries(deltas)) { const existingElement = elements.get(id); if (existingElement) { @@ -1067,12 +1090,12 @@ export class ElementsChange implements Change { delta.deleted, delta.inserted, modifier(existingElement), - "inserted", + modifierOptions, ); - modifiedDeltas.set(id, modifiedDelta); + modifiedDeltas[id] = modifiedDelta; } else { - modifiedDeltas.set(id, delta); + modifiedDeltas[id] = delta; } } @@ -1083,16 +1106,16 @@ export class ElementsChange implements Change { const removed = applyLatestChangesInternal(this.removed); const updated = applyLatestChangesInternal(this.updated); - return ElementsChange.create(added, removed, updated, { + return ElementsDelta.create(added, removed, updated, { shouldRedistribute: true, // redistribute the deltas as `isDeleted` could have been updated }); } public applyTo( elements: SceneElementsMap, - snapshot: Map, + elementsSnapshot: Map, ): [SceneElementsMap, boolean] { - let nextElements = toBrandedType(new Map(elements)); + let nextElements = new Map(elements) as SceneElementsMap; let changedElements: Map; const flags = { @@ -1102,15 +1125,15 @@ export class ElementsChange implements Change { // mimic a transaction by applying deltas into `nextElements` (always new instance, no mutation) try { - const applyDeltas = ElementsChange.createApplier( + const applyDeltas = ElementsDelta.createApplier( nextElements, - snapshot, + elementsSnapshot, flags, ); - const addedElements = applyDeltas(this.added); - const removedElements = applyDeltas(this.removed); - const updatedElements = applyDeltas(this.updated); + const addedElements = applyDeltas("added", this.added); + const removedElements = applyDeltas("removed", this.removed); + const updatedElements = applyDeltas("updated", this.updated); const affectedElements = this.resolveConflicts(elements, nextElements); @@ -1122,7 +1145,7 @@ export class ElementsChange implements Change { ...affectedElements, ]); } catch (e) { - console.error(`Couldn't apply elements change`, e); + console.error(`Couldn't apply elements delta`, e); if (isTestEnv() || isDevEnv()) { throw e; @@ -1138,7 +1161,7 @@ export class ElementsChange implements Change { try { // the following reorder performs also mutations, but only on new instances of changed elements // (unless something goes really bad and it fallbacks to fixing all invalid indices) - nextElements = ElementsChange.reorderElements( + nextElements = ElementsDelta.reorderElements( nextElements, changedElements, flags, @@ -1149,9 +1172,9 @@ export class ElementsChange implements Change { // so we are creating a temp scene just to query and mutate elements const tempScene = new Scene(nextElements); - ElementsChange.redrawTextBoundingBoxes(tempScene, changedElements); + ElementsDelta.redrawTextBoundingBoxes(tempScene, changedElements); // Need ordered nextElements to avoid z-index binding issues - ElementsChange.redrawBoundArrows(tempScene, changedElements); + ElementsDelta.redrawBoundArrows(tempScene, changedElements); } catch (e) { console.error( `Couldn't mutate elements after applying elements change`, @@ -1166,36 +1189,42 @@ export class ElementsChange implements Change { } } - private static createApplier = ( - nextElements: SceneElementsMap, - snapshot: Map, - flags: { - containsVisibleDifference: boolean; - containsZindexDifference: boolean; - }, - ) => { - const getElement = ElementsChange.createGetter( - nextElements, - snapshot, - flags, - ); + private static createApplier = + ( + nextElements: SceneElementsMap, + snapshot: Map, + flags: { + containsVisibleDifference: boolean; + containsZindexDifference: boolean; + }, + ) => + ( + type: "added" | "removed" | "updated", + deltas: Record>, + ) => { + const getElement = ElementsDelta.createGetter( + type, + nextElements, + snapshot, + flags, + ); - return (deltas: Map>) => - Array.from(deltas.entries()).reduce((acc, [id, delta]) => { + return Object.entries(deltas).reduce((acc, [id, delta]) => { const element = getElement(id, delta.inserted); if (element) { - const newElement = ElementsChange.applyDelta(element, delta, flags); + const newElement = ElementsDelta.applyDelta(element, delta, flags); nextElements.set(newElement.id, newElement); acc.set(newElement.id, newElement); } return acc; }, new Map()); - }; + }; private static createGetter = ( + type: "added" | "removed" | "updated", elements: SceneElementsMap, snapshot: Map, flags: { @@ -1221,6 +1250,14 @@ export class ElementsChange implements Change { ) { flags.containsVisibleDifference = true; } + } else { + // not in elements, not in snapshot? element might have been added remotely! + element = newElementWith( + { id, version: 1 } as OrderedExcalidrawElement, + { + ...partial, + }, + ); } } @@ -1257,7 +1294,8 @@ export class ElementsChange implements Change { }); } - if (isImageElement(element)) { + // TODO: this looks wrong, shouldn't be here + if (element.type === "image") { const _delta = delta as Delta>; // we want to override `crop` only if modified so that we don't reset // when undoing/redoing unrelated change @@ -1270,10 +1308,12 @@ export class ElementsChange implements Change { } if (!flags.containsVisibleDifference) { - // strip away fractional as even if it would be different, it doesn't have to result in visible change + // strip away fractional index, as even if it would be different, it doesn't have to result in visible change const { index, ...rest } = directlyApplicablePartial; - const containsVisibleDifference = - ElementsChange.checkForVisibleDifference(element, rest); + const containsVisibleDifference = ElementsDelta.checkForVisibleDifference( + element, + rest, + ); flags.containsVisibleDifference = containsVisibleDifference; } @@ -1316,6 +1356,8 @@ export class ElementsChange implements Change { * Resolves conflicts for all previously added, removed and updated elements. * Updates the previous deltas with all the changes after conflict resolution. * + * // TODO: revisit since some bound arrows seem to be often redrawn incorrectly + * * @returns all elements affected by the conflict resolution */ private resolveConflicts( @@ -1346,7 +1388,7 @@ export class ElementsChange implements Change { nextElement, nextElements, updates as ElementUpdate, - ) as OrderedExcalidrawElement; + ); } nextAffectedElements.set(affectedElement.id, affectedElement); @@ -1354,20 +1396,21 @@ export class ElementsChange implements Change { }; // removed delta is affecting the bindings always, as all the affected elements of the removed elements need to be unbound - for (const [id] of this.removed) { - ElementsChange.unbindAffected(prevElements, nextElements, id, updater); + for (const id of Object.keys(this.removed)) { + ElementsDelta.unbindAffected(prevElements, nextElements, id, updater); } // added delta is affecting the bindings always, all the affected elements of the added elements need to be rebound - for (const [id] of this.added) { - ElementsChange.rebindAffected(prevElements, nextElements, id, updater); + for (const id of Object.keys(this.added)) { + ElementsDelta.rebindAffected(prevElements, nextElements, id, updater); } // updated delta is affecting the binding only in case it contains changed binding or bindable property - for (const [id] of Array.from(this.updated).filter(([_, delta]) => - Object.keys({ ...delta.deleted, ...delta.inserted }).find((prop) => - bindingProperties.has(prop as BindingProp | BindableProp), - ), + for (const [id] of Array.from(Object.entries(this.updated)).filter( + ([_, delta]) => + Object.keys({ ...delta.deleted, ...delta.inserted }).find((prop) => + bindingProperties.has(prop as BindingProp | BindableProp), + ), )) { const updatedElement = nextElements.get(id); if (!updatedElement || updatedElement.isDeleted) { @@ -1375,7 +1418,7 @@ export class ElementsChange implements Change { continue; } - ElementsChange.rebindAffected(prevElements, nextElements, id, updater); + ElementsDelta.rebindAffected(prevElements, nextElements, id, updater); } // filter only previous elements, which were now affected @@ -1385,21 +1428,21 @@ export class ElementsChange implements Change { // calculate complete deltas for affected elements, and assign them back to all the deltas // technically we could do better here if perf. would become an issue - const { added, removed, updated } = ElementsChange.calculate( + const { added, removed, updated } = ElementsDelta.calculate( prevAffectedElements, nextAffectedElements, ); - for (const [id, delta] of added) { - this.added.set(id, delta); + for (const [id, delta] of Object.entries(added)) { + this.added[id] = delta; } - for (const [id, delta] of removed) { - this.removed.set(id, delta); + for (const [id, delta] of Object.entries(removed)) { + this.removed[id] = delta; } - for (const [id, delta] of updated) { - this.updated.set(id, delta); + for (const [id, delta] of Object.entries(updated)) { + this.updated[id] = delta; } return nextAffectedElements; @@ -1572,7 +1615,7 @@ export class ElementsChange implements Change { Delta.diffArrays(deleted, inserted, "boundElements", (x) => x.id); } catch (e) { // if postprocessing fails, it does not make sense to bubble up, but let's make sure we know about it - console.error(`Couldn't postprocess elements change deltas.`); + console.error(`Couldn't postprocess elements delta.`); if (isTestEnv() || isDevEnv()) { throw e; @@ -1585,8 +1628,7 @@ export class ElementsChange implements Change { private static stripIrrelevantProps( partial: Partial, ): ElementPartial { - const { id, updated, version, versionNonce, seed, ...strippedPartial } = - partial; + const { id, updated, version, versionNonce, ...strippedPartial } = partial; return strippedPartial; } diff --git a/packages/element/src/flowchart.ts b/packages/element/src/flowchart.ts index 62acd1c4e..c537cb719 100644 --- a/packages/element/src/flowchart.ts +++ b/packages/element/src/flowchart.ts @@ -462,12 +462,18 @@ const createBindingArrow = ( bindingArrow as OrderedExcalidrawElement, ); - LinearElementEditor.movePoints(bindingArrow, scene, [ - { - index: 1, - point: bindingArrow.points[1], - }, - ]); + LinearElementEditor.movePoints( + bindingArrow, + scene, + new Map([ + [ + 1, + { + point: bindingArrow.points[1], + }, + ], + ]), + ); const update = updateElbowArrowPoints( bindingArrow, diff --git a/packages/element/src/index.ts b/packages/element/src/index.ts index d7edec8ae..eafa609c4 100644 --- a/packages/element/src/index.ts +++ b/packages/element/src/index.ts @@ -1,3 +1,5 @@ +import { toIterable } from "@excalidraw/common"; + import { isInvisiblySmallElement } from "./sizeHelpers"; import { isLinearElementType } from "./typeChecks"; @@ -5,6 +7,7 @@ import type { ExcalidrawElement, NonDeletedExcalidrawElement, NonDeleted, + ElementsMapOrArray, } from "./types"; /** @@ -16,12 +19,10 @@ export const getSceneVersion = (elements: readonly ExcalidrawElement[]) => /** * Hashes elements' versionNonce (using djb2 algo). Order of elements matters. */ -export const hashElementsVersion = ( - elements: readonly ExcalidrawElement[], -): number => { +export const hashElementsVersion = (elements: ElementsMapOrArray): number => { let hash = 5381; - for (let i = 0; i < elements.length; i++) { - hash = (hash << 5) + hash + elements[i].versionNonce; + for (const element of toIterable(elements)) { + hash = (hash << 5) + hash + element.versionNonce; } return hash >>> 0; // Ensure unsigned 32-bit integer }; diff --git a/packages/element/src/linearElementEditor.ts b/packages/element/src/linearElementEditor.ts index 65546d1ae..87621259d 100644 --- a/packages/element/src/linearElementEditor.ts +++ b/packages/element/src/linearElementEditor.ts @@ -20,7 +20,7 @@ import { tupleToCoors, } from "@excalidraw/common"; -import type { Store } from "@excalidraw/excalidraw/store"; +import type { Store } from "@excalidraw/element/store"; import type { Radians } from "@excalidraw/math"; @@ -85,6 +85,7 @@ import type { FixedPointBinding, FixedSegment, ExcalidrawElbowArrowElement, + PointsPositionUpdates, } from "./types"; const editorMidPointsCache: { @@ -309,16 +310,22 @@ export class LinearElementEditor { event[KEYS.CTRL_OR_CMD] ? null : app.getEffectiveGridSize(), ); - LinearElementEditor.movePoints(element, scene, [ - { - index: selectedIndex, - point: pointFrom( - width + referencePoint[0], - height + referencePoint[1], - ), - isDragging: selectedIndex === lastClickedPoint, - }, - ]); + LinearElementEditor.movePoints( + element, + scene, + new Map([ + [ + selectedIndex, + { + point: pointFrom( + width + referencePoint[0], + height + referencePoint[1], + ), + isDragging: selectedIndex === lastClickedPoint, + }, + ], + ]), + ); } else { const newDraggingPointPosition = LinearElementEditor.createPointAt( element, @@ -334,26 +341,32 @@ export class LinearElementEditor { LinearElementEditor.movePoints( element, scene, - selectedPointsIndices.map((pointIndex) => { - const newPointPosition: LocalPoint = - pointIndex === lastClickedPoint - ? LinearElementEditor.createPointAt( - element, - elementsMap, - scenePointerX - linearElementEditor.pointerOffset.x, - scenePointerY - linearElementEditor.pointerOffset.y, - event[KEYS.CTRL_OR_CMD] ? null : app.getEffectiveGridSize(), - ) - : pointFrom( - element.points[pointIndex][0] + deltaX, - element.points[pointIndex][1] + deltaY, - ); - return { - index: pointIndex, - point: newPointPosition, - isDragging: pointIndex === lastClickedPoint, - }; - }), + new Map( + selectedPointsIndices.map((pointIndex) => { + const newPointPosition: LocalPoint = + pointIndex === lastClickedPoint + ? LinearElementEditor.createPointAt( + element, + elementsMap, + scenePointerX - linearElementEditor.pointerOffset.x, + scenePointerY - linearElementEditor.pointerOffset.y, + event[KEYS.CTRL_OR_CMD] + ? null + : app.getEffectiveGridSize(), + ) + : pointFrom( + element.points[pointIndex][0] + deltaX, + element.points[pointIndex][1] + deltaY, + ); + return [ + pointIndex, + { + point: newPointPosition, + isDragging: pointIndex === lastClickedPoint, + }, + ]; + }), + ), ); } @@ -466,15 +479,21 @@ export class LinearElementEditor { }, ); } - LinearElementEditor.movePoints(element, scene, [ - { - index: selectedPoint, - point: - selectedPoint === 0 - ? element.points[element.points.length - 1] - : element.points[0], - }, - ]); + LinearElementEditor.movePoints( + element, + scene, + new Map([ + [ + selectedPoint, + { + point: + selectedPoint === 0 + ? element.points[element.points.length - 1] + : element.points[0], + }, + ], + ]), + ); } const bindingElement = isBindingEnabled(appState) @@ -822,7 +841,7 @@ export class LinearElementEditor { }); ret.didAddPoint = true; } - store.shouldCaptureIncrement(); + store.scheduleCapture(); ret.linearElementEditor = { ...linearElementEditor, pointerDownState: { @@ -1001,12 +1020,18 @@ export class LinearElementEditor { } if (lastPoint === lastUncommittedPoint) { - LinearElementEditor.movePoints(element, app.scene, [ - { - index: element.points.length - 1, - point: newPoint, - }, - ]); + LinearElementEditor.movePoints( + element, + app.scene, + new Map([ + [ + element.points.length - 1, + { + point: newPoint, + }, + ], + ]), + ); } else { LinearElementEditor.addPoints(element, app.scene, [{ point: newPoint }]); } @@ -1240,12 +1265,16 @@ export class LinearElementEditor { // potentially expanding the bounding box if (pointAddedToEnd) { const lastPoint = element.points[element.points.length - 1]; - LinearElementEditor.movePoints(element, scene, [ - { - index: element.points.length - 1, - point: pointFrom(lastPoint[0] + 30, lastPoint[1] + 30), - }, - ]); + LinearElementEditor.movePoints( + element, + scene, + new Map([ + [ + element.points.length - 1, + { point: pointFrom(lastPoint[0] + 30, lastPoint[1] + 30) }, + ], + ]), + ); } return { @@ -1333,7 +1362,7 @@ export class LinearElementEditor { static movePoints( element: NonDeleted, scene: Scene, - targetPoints: { index: number; point: LocalPoint; isDragging?: boolean }[], + pointUpdates: PointsPositionUpdates, otherUpdates?: { startBinding?: PointBinding | null; endBinding?: PointBinding | null; @@ -1343,14 +1372,11 @@ export class LinearElementEditor { // Handle loop lock behavior if (isLineElement(element) && element.loopLock) { - const firstPointUpdate = targetPoints.find(({ index }) => index === 0); - const lastPointUpdate = targetPoints.find( - ({ index }) => index === points.length - 1, - ); + const firstPointUpdate = pointUpdates.get(0); + const lastPointUpdate = pointUpdates.get(points.length - 1); if (firstPointUpdate) { - targetPoints.push({ - index: points.length - 1, + pointUpdates.set(points.length - 1, { point: pointFrom( firstPointUpdate.point[0], firstPointUpdate.point[1], @@ -1358,8 +1384,7 @@ export class LinearElementEditor { isDragging: firstPointUpdate.isDragging, }); } else if (lastPointUpdate) { - targetPoints.push({ - index: 0, + pointUpdates.set(0, { point: pointFrom(lastPointUpdate.point[0], lastPointUpdate.point[1]), isDragging: lastPointUpdate.isDragging, }); @@ -1372,8 +1397,7 @@ export class LinearElementEditor { // offset it. We do the same with actual element.x/y position, so // this hacks are completely transparent to the user. const [deltaX, deltaY] = - targetPoints.find(({ index }) => index === 0)?.point ?? - pointFrom(0, 0); + pointUpdates.get(0)?.point ?? pointFrom(0, 0); const [offsetX, offsetY] = pointFrom( deltaX - points[0][0], deltaY - points[0][1], @@ -1381,12 +1405,12 @@ export class LinearElementEditor { const nextPoints = isElbowArrow(element) ? [ - targetPoints.find((t) => t.index === 0)?.point ?? points[0], - targetPoints.find((t) => t.index === points.length - 1)?.point ?? + pointUpdates.get(0)?.point ?? points[0], + pointUpdates.get(points.length - 1)?.point ?? points[points.length - 1], ] : points.map((p, idx) => { - const current = targetPoints.find((t) => t.index === idx)?.point ?? p; + const current = pointUpdates.get(idx)?.point ?? p; return pointFrom( current[0] - offsetX, @@ -1402,11 +1426,7 @@ export class LinearElementEditor { offsetY, otherUpdates, { - isDragging: targetPoints.reduce( - (dragging, targetPoint): boolean => - dragging || targetPoint.isDragging === true, - false, - ), + isDragging: Array.from(pointUpdates.values()).some((t) => t.isDragging), }, ); } diff --git a/packages/element/src/resizeElements.ts b/packages/element/src/resizeElements.ts index 8a1702afa..43a5e7211 100644 --- a/packages/element/src/resizeElements.ts +++ b/packages/element/src/resizeElements.ts @@ -962,11 +962,6 @@ export const resizeSingleElement = ( isDragging: false, }); - updateBoundElements(latestElement, scene, { - // TODO: confirm with MARK if this actually makes sense - newSize: { width: nextWidth, height: nextHeight }, - }); - if (boundTextElement && boundTextFont != null) { scene.mutateElement(boundTextElement, { fontSize: boundTextFont.fontSize, @@ -978,6 +973,11 @@ export const resizeSingleElement = ( handleDirection, shouldMaintainAspectRatio, ); + + updateBoundElements(latestElement, scene, { + // TODO: confirm with MARK if this actually makes sense + newSize: { width: nextWidth, height: nextHeight }, + }); } }; diff --git a/packages/element/src/store.ts b/packages/element/src/store.ts new file mode 100644 index 000000000..e50a94a86 --- /dev/null +++ b/packages/element/src/store.ts @@ -0,0 +1,968 @@ +import { + assertNever, + COLOR_PALETTE, + isDevEnv, + isTestEnv, + randomId, + Emitter, + toIterable, +} from "@excalidraw/common"; + +import type App from "@excalidraw/excalidraw/components/App"; + +import type { DTO, ValueOf } from "@excalidraw/common/utility-types"; + +import type { AppState, ObservedAppState } from "@excalidraw/excalidraw/types"; + +import { deepCopyElement } from "./duplicate"; +import { newElementWith } from "./mutateElement"; + +import { ElementsDelta, AppStateDelta, Delta } from "./delta"; + +import { hashElementsVersion, hashString } from "./index"; + +import type { OrderedExcalidrawElement, SceneElementsMap } from "./types"; + +export const CaptureUpdateAction = { + /** + * Immediately undoable. + * + * Use for updates which should be captured. + * Should be used for most of the local updates, except ephemerals such as dragging or resizing. + * + * These updates will _immediately_ make it to the local undo / redo stacks. + */ + IMMEDIATELY: "IMMEDIATELY", + /** + * Never undoable. + * + * Use for updates which should never be recorded, such as remote updates + * or scene initialization. + * + * These updates will _never_ make it to the local undo / redo stacks. + */ + NEVER: "NEVER", + /** + * Eventually undoable. + * + * Use for updates which should not be captured immediately - likely + * exceptions which are part of some async multi-step process. Otherwise, all + * such updates would end up being captured with the next + * `CaptureUpdateAction.IMMEDIATELY` - triggered either by the next `updateScene` + * or internally by the editor. + * + * These updates will _eventually_ make it to the local undo / redo stacks. + */ + EVENTUALLY: "EVENTUALLY", +} as const; + +export type CaptureUpdateActionType = ValueOf; + +type MicroActionsQueue = (() => void)[]; + +/** + * Store which captures the observed changes and emits them as `StoreIncrement` events. + */ +export class Store { + // internally used by history + public readonly onDurableIncrementEmitter = new Emitter<[DurableIncrement]>(); + public readonly onStoreIncrementEmitter = new Emitter< + [DurableIncrement | EphemeralIncrement] + >(); + + private scheduledMacroActions: Set = new Set(); + private scheduledMicroActions: MicroActionsQueue = []; + + private _snapshot = StoreSnapshot.empty(); + + public get snapshot() { + return this._snapshot; + } + + public set snapshot(snapshot: StoreSnapshot) { + this._snapshot = snapshot; + } + + constructor(private readonly app: App) {} + + public scheduleAction(action: CaptureUpdateActionType) { + this.scheduledMacroActions.add(action); + this.satisfiesScheduledActionsInvariant(); + } + + /** + * Use to schedule a delta calculation, which will consquentially be emitted as `DurableStoreIncrement` and pushed in the undo stack. + */ + // TODO: Suspicious that this is called so many places. Seems error-prone. + public scheduleCapture() { + this.scheduleAction(CaptureUpdateAction.IMMEDIATELY); + } + + /** + * Schedule special "micro" actions, to-be executed before the next commit, before it executes a scheduled "macro" action. + */ + public scheduleMicroAction( + params: + | { + action: CaptureUpdateActionType; + elements: SceneElementsMap | undefined; + appState: AppState | ObservedAppState | undefined; + } + | { + action: typeof CaptureUpdateAction.IMMEDIATELY; + change: StoreChange; + delta: StoreDelta; + } + | { + action: + | typeof CaptureUpdateAction.NEVER + | typeof CaptureUpdateAction.EVENTUALLY; + change: StoreChange; + }, + ) { + const { action } = params; + + let change: StoreChange; + + if ("change" in params) { + change = params.change; + } else { + // immediately create an immutable change of the scheduled updates, + // compared to the current state, so that they won't mutate later on during batching + const currentSnapshot = StoreSnapshot.create( + this.app.scene.getElementsMapIncludingDeleted(), + this.app.state, + ); + const scheduledSnapshot = currentSnapshot.maybeClone( + action, + params.elements, + params.appState, + ); + + change = StoreChange.create(currentSnapshot, scheduledSnapshot); + } + + const delta = "delta" in params ? params.delta : undefined; + + this.scheduledMicroActions.push(() => + this.processAction({ + action, + change, + delta, + }), + ); + } + + /** + * Performs the incoming `CaptureUpdateAction` and emits the corresponding `StoreIncrement`. + * Emits `DurableStoreIncrement` when action is "capture", emits `EphemeralStoreIncrement` otherwise. + * + * @emits StoreIncrement + */ + public commit( + elements: SceneElementsMap | undefined, + appState: AppState | ObservedAppState | undefined, + ): void { + // execute all scheduled micro actions first + // similar to microTasks, there can be many + this.flushMicroActions(); + + try { + // execute a single scheduled "macro" function + // similar to macro tasks, there can be only one within a single commit (loop) + const action = this.getScheduledMacroAction(); + this.processAction({ action, elements, appState }); + } finally { + this.satisfiesScheduledActionsInvariant(); + // defensively reset all scheduled "macro" actions, possibly cleans up other runtime garbage + this.scheduledMacroActions = new Set(); + } + } + + /** + * Clears the store instance. + */ + public clear(): void { + this.snapshot = StoreSnapshot.empty(); + this.scheduledMacroActions = new Set(); + } + + /** + * Performs delta & change calculation and emits a durable increment. + * + * @emits StoreIncrement. + */ + private emitDurableIncrement( + snapshot: StoreSnapshot, + change: StoreChange | undefined = undefined, + delta: StoreDelta | undefined = undefined, + ) { + const prevSnapshot = this.snapshot; + + let storeChange: StoreChange; + let storeDelta: StoreDelta; + + if (change) { + storeChange = change; + } else { + storeChange = StoreChange.create(prevSnapshot, snapshot); + } + + if (delta) { + // we might have the delta already (i.e. when applying history entry), thus we don't need to calculate it again + // using the same instance, since in history we have a check against `HistoryEntry`, so that we don't re-record the same delta again + storeDelta = delta; + } else { + // calculate the deltas based on the previous and next snapshot + const elementsDelta = snapshot.metadata.didElementsChange + ? ElementsDelta.calculate(prevSnapshot.elements, snapshot.elements) + : ElementsDelta.empty(); + + const appStateDelta = snapshot.metadata.didAppStateChange + ? AppStateDelta.calculate(prevSnapshot.appState, snapshot.appState) + : AppStateDelta.empty(); + + storeDelta = StoreDelta.create(elementsDelta, appStateDelta); + } + + if (!storeDelta.isEmpty()) { + const increment = new DurableIncrement(storeChange, storeDelta); + + // Notify listeners with the increment + this.onDurableIncrementEmitter.trigger(increment); + this.onStoreIncrementEmitter.trigger(increment); + } + } + + /** + * Performs change calculation and emits an ephemeral increment. + * + * @emits EphemeralStoreIncrement + */ + private emitEphemeralIncrement( + snapshot: StoreSnapshot, + change: StoreChange | undefined = undefined, + ) { + let storeChange: StoreChange; + + if (change) { + storeChange = change; + } else { + const prevSnapshot = this.snapshot; + storeChange = StoreChange.create(prevSnapshot, snapshot); + } + + const increment = new EphemeralIncrement(storeChange); + + // Notify listeners with the increment + this.onStoreIncrementEmitter.trigger(increment); + } + + private applyChangeToSnapshot(change: StoreChange) { + const prevSnapshot = this.snapshot; + const nextSnapshot = this.snapshot.applyChange(change); + + if (prevSnapshot === nextSnapshot) { + return null; + } + + return nextSnapshot; + } + + /** + * Clones the snapshot if there are changes detected. + */ + private maybeCloneSnapshot( + action: CaptureUpdateActionType, + elements: SceneElementsMap | undefined, + appState: AppState | ObservedAppState | undefined, + ) { + if (!elements && !appState) { + return null; + } + + const prevSnapshot = this.snapshot; + const nextSnapshot = this.snapshot.maybeClone(action, elements, appState); + + if (prevSnapshot === nextSnapshot) { + return null; + } + + return nextSnapshot; + } + + private flushMicroActions() { + for (const microAction of this.scheduledMicroActions) { + try { + microAction(); + } catch (error) { + console.error(`Failed to execute scheduled micro action`, error); + } + } + + this.scheduledMicroActions = []; + } + + private processAction( + params: + | { + action: CaptureUpdateActionType; + elements: SceneElementsMap | undefined; + appState: AppState | ObservedAppState | undefined; + } + | { + action: CaptureUpdateActionType; + change: StoreChange; + delta: StoreDelta | undefined; + }, + ) { + const { action } = params; + + // perf. optimisation, since "EVENTUALLY" does not update the snapshot, + // so if nobody is listening for increments, we don't need to even clone the snapshot + // as it's only needed for `StoreChange` computation inside `EphemeralIncrement` + if ( + action === CaptureUpdateAction.EVENTUALLY && + !this.onStoreIncrementEmitter.subscribers.length + ) { + return; + } + + let nextSnapshot: StoreSnapshot | null; + + if ("change" in params) { + nextSnapshot = this.applyChangeToSnapshot(params.change); + } else { + nextSnapshot = this.maybeCloneSnapshot( + action, + params.elements, + params.appState, + ); + } + + if (!nextSnapshot) { + // don't continue if there is not change detected + return; + } + + const change = "change" in params ? params.change : undefined; + const delta = "delta" in params ? params.delta : undefined; + + try { + switch (action) { + // only immediately emits a durable increment + case CaptureUpdateAction.IMMEDIATELY: + this.emitDurableIncrement(nextSnapshot, change, delta); + break; + // both never and eventually emit an ephemeral increment + case CaptureUpdateAction.NEVER: + case CaptureUpdateAction.EVENTUALLY: + this.emitEphemeralIncrement(nextSnapshot, change); + break; + default: + assertNever(action, `Unknown store action`); + } + } finally { + // update the snapshot no-matter what, as it would mess up with the next action + switch (action) { + // both immediately and never update the snapshot, unlike eventually + case CaptureUpdateAction.IMMEDIATELY: + case CaptureUpdateAction.NEVER: + this.snapshot = nextSnapshot; + break; + } + } + } + + /** + * Returns the scheduled macro action. + */ + private getScheduledMacroAction() { + let scheduledAction: CaptureUpdateActionType; + + if (this.scheduledMacroActions.has(CaptureUpdateAction.IMMEDIATELY)) { + // Capture has a precedence over update, since it also performs snapshot update + scheduledAction = CaptureUpdateAction.IMMEDIATELY; + } else if (this.scheduledMacroActions.has(CaptureUpdateAction.NEVER)) { + // Update has a precedence over none, since it also emits an (ephemeral) increment + scheduledAction = CaptureUpdateAction.NEVER; + } else { + // Default is to emit ephemeral increment and don't update the snapshot + scheduledAction = CaptureUpdateAction.EVENTUALLY; + } + + return scheduledAction; + } + + /** + * Ensures that the scheduled actions invariant is satisfied. + */ + private satisfiesScheduledActionsInvariant() { + if ( + !( + this.scheduledMacroActions.size >= 0 && + this.scheduledMacroActions.size <= + Object.keys(CaptureUpdateAction).length + ) + ) { + const message = `There can be at most three store actions scheduled at the same time, but there are "${this.scheduledMacroActions.size}".`; + console.error(message, this.scheduledMacroActions.values()); + + if (isTestEnv() || isDevEnv()) { + throw new Error(message); + } + } + } +} + +/** + * Repsents a change to the store containing changed elements and appState. + */ +export class StoreChange { + // so figuring out what has changed should ideally be just quick reference checks + // TODO: we might need to have binary files here as well, in order to be drop-in replacement for `onChange` + private constructor( + public readonly elements: Record, + public readonly appState: Partial, + ) {} + + public static create( + prevSnapshot: StoreSnapshot, + nextSnapshot: StoreSnapshot, + ) { + const changedElements = nextSnapshot.getChangedElements(prevSnapshot); + const changedAppState = nextSnapshot.getChangedAppState(prevSnapshot); + + return new StoreChange(changedElements, changedAppState); + } +} + +/** + * Encpasulates any change to the store (durable or ephemeral). + */ +export abstract class StoreIncrement { + protected constructor( + public readonly type: "durable" | "ephemeral", + public readonly change: StoreChange, + ) {} + + public static isDurable( + increment: StoreIncrement, + ): increment is DurableIncrement { + return increment.type === "durable"; + } + + public static isEphemeral( + increment: StoreIncrement, + ): increment is EphemeralIncrement { + return increment.type === "ephemeral"; + } +} + +/** + * Represents a durable change to the store. + */ +export class DurableIncrement extends StoreIncrement { + constructor( + public readonly change: StoreChange, + public readonly delta: StoreDelta, + ) { + super("durable", change); + } +} + +/** + * Represents an ephemeral change to the store. + */ +export class EphemeralIncrement extends StoreIncrement { + constructor(public readonly change: StoreChange) { + super("ephemeral", change); + } +} + +/** + * Represents a captured delta by the Store. + */ +export class StoreDelta { + protected constructor( + public readonly id: string, + public readonly elements: ElementsDelta, + public readonly appState: AppStateDelta, + ) {} + + /** + * Create a new instance of `StoreDelta`. + */ + public static create( + elements: ElementsDelta, + appState: AppStateDelta, + opts: { + id: string; + } = { + id: randomId(), + }, + ) { + return new this(opts.id, elements, appState); + } + + /** + * Restore a store delta instance from a DTO. + */ + public static restore(storeDeltaDTO: DTO) { + const { id, elements, appState } = storeDeltaDTO; + return new this( + id, + ElementsDelta.restore(elements), + AppStateDelta.restore(appState), + ); + } + + /** + * Parse and load the delta from the remote payload. + */ + public static load({ + id, + elements: { added, removed, updated }, + }: DTO) { + const elements = ElementsDelta.create(added, removed, updated, { + shouldRedistribute: false, + }); + + return new this(id, elements, AppStateDelta.empty()); + } + + /** + * Inverse store delta, creates new instance of `StoreDelta`. + */ + public static inverse(delta: StoreDelta): StoreDelta { + return this.create(delta.elements.inverse(), delta.appState.inverse()); + } + + /** + * Apply latest (remote) changes to the delta, creates new instance of `StoreDelta`. + */ + public static applyLatestChanges( + delta: StoreDelta, + elements: SceneElementsMap, + modifierOptions: "deleted" | "inserted", + ): StoreDelta { + return this.create( + delta.elements.applyLatestChanges(elements, modifierOptions), + delta.appState, + { + id: delta.id, + }, + ); + } + + /** + * Apply the delta to the passed elements and appState, does not modify the snapshot. + */ + public static applyTo( + delta: StoreDelta, + elements: SceneElementsMap, + appState: AppState, + prevSnapshot: StoreSnapshot = StoreSnapshot.empty(), + ): [SceneElementsMap, AppState, boolean] { + const [nextElements, elementsContainVisibleChange] = delta.elements.applyTo( + elements, + prevSnapshot.elements, + ); + + const [nextAppState, appStateContainsVisibleChange] = + delta.appState.applyTo(appState, nextElements); + + const appliedVisibleChanges = + elementsContainVisibleChange || appStateContainsVisibleChange; + + return [nextElements, nextAppState, appliedVisibleChanges]; + } + + public isEmpty() { + return this.elements.isEmpty() && this.appState.isEmpty(); + } +} + +/** + * Represents a snapshot of the captured or updated changes in the store, + * used for producing deltas and emitting `DurableStoreIncrement`s. + */ +export class StoreSnapshot { + private _lastChangedElementsHash: number = 0; + private _lastChangedAppStateHash: number = 0; + + private constructor( + public readonly elements: SceneElementsMap, + public readonly appState: ObservedAppState, + public readonly metadata: { + didElementsChange: boolean; + didAppStateChange: boolean; + isEmpty?: boolean; + } = { + didElementsChange: false, + didAppStateChange: false, + isEmpty: false, + }, + ) {} + + public static create( + elements: SceneElementsMap, + appState: AppState | ObservedAppState, + metadata: { + didElementsChange: boolean; + didAppStateChange: boolean; + } = { + didElementsChange: false, + didAppStateChange: false, + }, + ) { + return new StoreSnapshot( + elements, + isObservedAppState(appState) ? appState : getObservedAppState(appState), + metadata, + ); + } + + public static empty() { + return new StoreSnapshot( + new Map() as SceneElementsMap, + getDefaultObservedAppState(), + { + didElementsChange: false, + didAppStateChange: false, + isEmpty: true, + }, + ); + } + + public getChangedElements(prevSnapshot: StoreSnapshot) { + const changedElements: Record = {}; + + for (const prevElement of toIterable(prevSnapshot.elements)) { + const nextElement = this.elements.get(prevElement.id); + + if (!nextElement) { + changedElements[prevElement.id] = newElementWith(prevElement, { + isDeleted: true, + }); + } + } + + for (const nextElement of toIterable(this.elements)) { + // Due to the structural clone inside `maybeClone`, we can perform just these reference checks + if (prevSnapshot.elements.get(nextElement.id) !== nextElement) { + changedElements[nextElement.id] = nextElement; + } + } + + return changedElements; + } + + public getChangedAppState( + prevSnapshot: StoreSnapshot, + ): Partial { + return Delta.getRightDifferences( + prevSnapshot.appState, + this.appState, + ).reduce( + (acc, key) => + Object.assign(acc, { + [key]: this.appState[key as keyof ObservedAppState], + }), + {} as Partial, + ); + } + + public isEmpty() { + return this.metadata.isEmpty; + } + + /** + * Apply the change and return a new snapshot instance. + */ + public applyChange(change: StoreChange): StoreSnapshot { + const nextElements = new Map(this.elements) as SceneElementsMap; + + for (const [id, changedElement] of Object.entries(change.elements)) { + nextElements.set(id, changedElement); + } + + const nextAppState = Object.assign( + {}, + this.appState, + change.appState, + ) as ObservedAppState; + + return StoreSnapshot.create(nextElements, nextAppState, { + // by default we assume that change is different from what we have in the snapshot + // so that we trigger the delta calculation and if it isn't different, delta will be empty + didElementsChange: Object.keys(change.elements).length > 0, + didAppStateChange: Object.keys(change.appState).length > 0, + }); + } + + /** + * Efficiently clone the existing snapshot, only if we detected changes. + * + * @returns same instance if there are no changes detected, new instance otherwise. + */ + public maybeClone( + action: CaptureUpdateActionType, + elements: SceneElementsMap | undefined, + appState: AppState | ObservedAppState | undefined, + ) { + const options = { + shouldCompareHashes: false, + }; + + if (action === CaptureUpdateAction.EVENTUALLY) { + // actions that do not update the snapshot immediately, must be additionally checked for changes against the latest hash + // as we are always comparing against the latest snapshot, so they would emit elements or appState as changed on every component update + // instead of just the first time the elements or appState actually changed + options.shouldCompareHashes = true; + } + + const nextElementsSnapshot = this.maybeCreateElementsSnapshot( + elements, + options, + ); + const nextAppStateSnapshot = this.maybeCreateAppStateSnapshot( + appState, + options, + ); + + let didElementsChange = false; + let didAppStateChange = false; + + if (this.elements !== nextElementsSnapshot) { + didElementsChange = true; + } + + if (this.appState !== nextAppStateSnapshot) { + didAppStateChange = true; + } + + if (!didElementsChange && !didAppStateChange) { + return this; + } + + const snapshot = new StoreSnapshot( + nextElementsSnapshot, + nextAppStateSnapshot, + { + didElementsChange, + didAppStateChange, + }, + ); + + return snapshot; + } + + private maybeCreateAppStateSnapshot( + appState: AppState | ObservedAppState | undefined, + options: { + shouldCompareHashes: boolean; + } = { + shouldCompareHashes: false, + }, + ): ObservedAppState { + if (!appState) { + return this.appState; + } + + // Not watching over everything from the app state, just the relevant props + const nextAppStateSnapshot = !isObservedAppState(appState) + ? getObservedAppState(appState) + : appState; + + const didAppStateChange = this.detectChangedAppState( + nextAppStateSnapshot, + options, + ); + + if (!didAppStateChange) { + return this.appState; + } + + return nextAppStateSnapshot; + } + + private maybeCreateElementsSnapshot( + elements: SceneElementsMap | undefined, + options: { + shouldCompareHashes: boolean; + } = { + shouldCompareHashes: false, + }, + ): SceneElementsMap { + if (!elements) { + return this.elements; + } + + const changedElements = this.detectChangedElements(elements, options); + + if (!changedElements?.size) { + return this.elements; + } + + const elementsSnapshot = this.createElementsSnapshot(changedElements); + return elementsSnapshot; + } + + private detectChangedAppState( + nextObservedAppState: ObservedAppState, + options: { + shouldCompareHashes: boolean; + } = { + shouldCompareHashes: false, + }, + ): boolean | undefined { + if (this.appState === nextObservedAppState) { + return; + } + + const didAppStateChange = Delta.isRightDifferent( + this.appState, + nextObservedAppState, + ); + + if (!didAppStateChange) { + return; + } + + const changedAppStateHash = hashString( + JSON.stringify(nextObservedAppState), + ); + + if ( + options.shouldCompareHashes && + this._lastChangedAppStateHash === changedAppStateHash + ) { + return; + } + + this._lastChangedAppStateHash = changedAppStateHash; + + return didAppStateChange; + } + + /** + * Detect if there any changed elements. + */ + private detectChangedElements( + nextElements: SceneElementsMap, + options: { + shouldCompareHashes: boolean; + } = { + shouldCompareHashes: false, + }, + ): SceneElementsMap | undefined { + if (this.elements === nextElements) { + return; + } + + const changedElements: SceneElementsMap = new Map() as SceneElementsMap; + + for (const prevElement of toIterable(this.elements)) { + const nextElement = nextElements.get(prevElement.id); + + if (!nextElement) { + // element was deleted + changedElements.set( + prevElement.id, + newElementWith(prevElement, { isDeleted: true }), + ); + } + } + + for (const nextElement of toIterable(nextElements)) { + const prevElement = this.elements.get(nextElement.id); + + if ( + !prevElement || // element was added + prevElement.version < nextElement.version // element was updated + ) { + changedElements.set(nextElement.id, nextElement); + } + } + + if (!changedElements.size) { + return; + } + + const changedElementsHash = hashElementsVersion(changedElements); + + if ( + options.shouldCompareHashes && + this._lastChangedElementsHash === changedElementsHash + ) { + return; + } + + this._lastChangedElementsHash = changedElementsHash; + + return changedElements; + } + + /** + * Perform structural clone, deep cloning only elements that changed. + */ + private createElementsSnapshot(changedElements: SceneElementsMap) { + const clonedElements = new Map() as SceneElementsMap; + + for (const prevElement of toIterable(this.elements)) { + // Clone previous elements, never delete, in case nextElements would be just a subset of previous elements + // i.e. during collab, persist or whenenever isDeleted elements get cleared + clonedElements.set(prevElement.id, prevElement); + } + + for (const changedElement of toIterable(changedElements)) { + // TODO: consider just creating new instance, once we can ensure that all reference properties on every element are immutable + // TODO: consider creating a lazy deep clone, having a one-time-usage proxy over the snapshotted element and deep cloning only if it gets mutated + clonedElements.set(changedElement.id, deepCopyElement(changedElement)); + } + + return clonedElements; + } +} + +// hidden non-enumerable property for runtime checks +const hiddenObservedAppStateProp = "__observedAppState"; + +const getDefaultObservedAppState = (): ObservedAppState => { + return { + name: null, + editingGroupId: null, + viewBackgroundColor: COLOR_PALETTE.white, + selectedElementIds: {}, + selectedGroupIds: {}, + editingLinearElementId: null, + selectedLinearElementId: null, + croppingElementId: null, + }; +}; + +export const getObservedAppState = (appState: AppState): ObservedAppState => { + const observedAppState = { + name: appState.name, + editingGroupId: appState.editingGroupId, + viewBackgroundColor: appState.viewBackgroundColor, + selectedElementIds: appState.selectedElementIds, + selectedGroupIds: appState.selectedGroupIds, + editingLinearElementId: appState.editingLinearElement?.elementId || null, + selectedLinearElementId: appState.selectedLinearElement?.elementId || null, + croppingElementId: appState.croppingElementId, + }; + + Reflect.defineProperty(observedAppState, hiddenObservedAppStateProp, { + value: true, + enumerable: false, + }); + + return observedAppState; +}; + +const isObservedAppState = ( + appState: AppState | ObservedAppState, +): appState is ObservedAppState => + !!Reflect.get(appState, hiddenObservedAppStateProp); diff --git a/packages/element/src/types.ts b/packages/element/src/types.ts index 0a4d01122..5fab88729 100644 --- a/packages/element/src/types.ts +++ b/packages/element/src/types.ts @@ -296,6 +296,11 @@ export type FixedPointBinding = Merge< } >; +export type PointsPositionUpdates = Map< + number, + { point: LocalPoint; isDragging?: boolean } +>; + export type Arrowhead = | "arrow" | "bar" diff --git a/packages/element/tests/delta.test.tsx b/packages/element/tests/delta.test.tsx new file mode 100644 index 000000000..48e925c30 --- /dev/null +++ b/packages/element/tests/delta.test.tsx @@ -0,0 +1,143 @@ +import type { ObservedAppState } from "@excalidraw/excalidraw/types"; +import type { LinearElementEditor } from "@excalidraw/element/linearElementEditor"; + +import { AppStateDelta } from "../src/delta"; + +describe("AppStateDelta", () => { + describe("ensure stable delta properties order", () => { + it("should maintain stable order for root properties", () => { + const name = "untitled scene"; + const selectedLinearElementId = "id1" as LinearElementEditor["elementId"]; + + const commonAppState = { + viewBackgroundColor: "#ffffff", + selectedElementIds: {}, + selectedGroupIds: {}, + editingGroupId: null, + croppingElementId: null, + editingLinearElementId: null, + }; + + const prevAppState1: ObservedAppState = { + ...commonAppState, + name: "", + selectedLinearElementId: null, + }; + + const nextAppState1: ObservedAppState = { + ...commonAppState, + name, + selectedLinearElementId, + }; + + const prevAppState2: ObservedAppState = { + selectedLinearElementId: null, + name: "", + ...commonAppState, + }; + + const nextAppState2: ObservedAppState = { + selectedLinearElementId, + name, + ...commonAppState, + }; + + const delta1 = AppStateDelta.calculate(prevAppState1, nextAppState1); + const delta2 = AppStateDelta.calculate(prevAppState2, nextAppState2); + + expect(JSON.stringify(delta1)).toBe(JSON.stringify(delta2)); + }); + + it("should maintain stable order for selectedElementIds", () => { + const commonAppState = { + name: "", + viewBackgroundColor: "#ffffff", + selectedGroupIds: {}, + editingGroupId: null, + croppingElementId: null, + selectedLinearElementId: null, + editingLinearElementId: null, + }; + + const prevAppState1: ObservedAppState = { + ...commonAppState, + selectedElementIds: { id5: true, id2: true, id4: true }, + }; + + const nextAppState1: ObservedAppState = { + ...commonAppState, + selectedElementIds: { + id1: true, + id2: true, + id3: true, + }, + }; + + const prevAppState2: ObservedAppState = { + ...commonAppState, + selectedElementIds: { id4: true, id2: true, id5: true }, + }; + + const nextAppState2: ObservedAppState = { + ...commonAppState, + selectedElementIds: { + id3: true, + id2: true, + id1: true, + }, + }; + + const delta1 = AppStateDelta.calculate(prevAppState1, nextAppState1); + const delta2 = AppStateDelta.calculate(prevAppState2, nextAppState2); + + expect(JSON.stringify(delta1)).toBe(JSON.stringify(delta2)); + }); + + it("should maintain stable order for selectedGroupIds", () => { + const commonAppState = { + name: "", + viewBackgroundColor: "#ffffff", + selectedElementIds: {}, + editingGroupId: null, + croppingElementId: null, + selectedLinearElementId: null, + editingLinearElementId: null, + }; + + const prevAppState1: ObservedAppState = { + ...commonAppState, + selectedGroupIds: { id5: false, id2: true, id4: true, id0: true }, + }; + + const nextAppState1: ObservedAppState = { + ...commonAppState, + selectedGroupIds: { + id0: true, + id1: true, + id2: false, + id3: true, + }, + }; + + const prevAppState2: ObservedAppState = { + ...commonAppState, + selectedGroupIds: { id0: true, id4: true, id2: true, id5: false }, + }; + + const nextAppState2: ObservedAppState = { + ...commonAppState, + selectedGroupIds: { + id3: true, + id2: false, + id1: true, + id0: true, + }, + }; + + const delta1 = AppStateDelta.calculate(prevAppState1, nextAppState1); + const delta2 = AppStateDelta.calculate(prevAppState2, nextAppState2); + + expect(JSON.stringify(delta1)).toBe(JSON.stringify(delta2)); + }); + }); +}); diff --git a/packages/excalidraw/actions/actionAddToLibrary.ts b/packages/excalidraw/actions/actionAddToLibrary.ts index 9216e52c2..cb45f64d6 100644 --- a/packages/excalidraw/actions/actionAddToLibrary.ts +++ b/packages/excalidraw/actions/actionAddToLibrary.ts @@ -1,8 +1,9 @@ import { LIBRARY_DISABLED_TYPES, randomId } from "@excalidraw/common"; import { deepCopyElement } from "@excalidraw/element/duplicate"; +import { CaptureUpdateAction } from "@excalidraw/element/store"; + import { t } from "../i18n"; -import { CaptureUpdateAction } from "../store"; import { register } from "./register"; diff --git a/packages/excalidraw/actions/actionAlign.tsx b/packages/excalidraw/actions/actionAlign.tsx index 0ef938c67..918bdd8f4 100644 --- a/packages/excalidraw/actions/actionAlign.tsx +++ b/packages/excalidraw/actions/actionAlign.tsx @@ -8,6 +8,8 @@ import { KEYS, arrayToMap, getShortcutKey } from "@excalidraw/common"; import { alignElements } from "@excalidraw/element/align"; +import { CaptureUpdateAction } from "@excalidraw/element/store"; + import type { ExcalidrawElement } from "@excalidraw/element/types"; import type { Alignment } from "@excalidraw/element/align"; @@ -25,7 +27,6 @@ import { import { t } from "../i18n"; import { isSomeElementSelected } from "../scene"; -import { CaptureUpdateAction } from "../store"; import { register } from "./register"; diff --git a/packages/excalidraw/actions/actionBoundText.tsx b/packages/excalidraw/actions/actionBoundText.tsx index c7843656c..c740d6e90 100644 --- a/packages/excalidraw/actions/actionBoundText.tsx +++ b/packages/excalidraw/actions/actionBoundText.tsx @@ -33,6 +33,8 @@ import { syncMovedIndices } from "@excalidraw/element/fractionalIndex"; import { newElement } from "@excalidraw/element/newElement"; +import { CaptureUpdateAction } from "@excalidraw/element/store"; + import type { ExcalidrawElement, ExcalidrawLinearElement, @@ -44,8 +46,6 @@ import type { Mutable } from "@excalidraw/common/utility-types"; import type { Radians } from "@excalidraw/math"; -import { CaptureUpdateAction } from "../store"; - import { register } from "./register"; import type { AppState } from "../types"; diff --git a/packages/excalidraw/actions/actionCanvas.tsx b/packages/excalidraw/actions/actionCanvas.tsx index a8bd56e82..7d6bb6aad 100644 --- a/packages/excalidraw/actions/actionCanvas.tsx +++ b/packages/excalidraw/actions/actionCanvas.tsx @@ -17,6 +17,8 @@ import { getNonDeletedElements } from "@excalidraw/element"; import { newElementWith } from "@excalidraw/element/mutateElement"; import { getCommonBounds, type SceneBounds } from "@excalidraw/element/bounds"; +import { CaptureUpdateAction } from "@excalidraw/element/store"; + import type { ExcalidrawElement } from "@excalidraw/element/types"; import { @@ -44,7 +46,6 @@ import { t } from "../i18n"; import { getNormalizedZoom } from "../scene"; import { centerScrollOn } from "../scene/scroll"; import { getStateForZoom } from "../scene/zoom"; -import { CaptureUpdateAction } from "../store"; import { register } from "./register"; diff --git a/packages/excalidraw/actions/actionClipboard.tsx b/packages/excalidraw/actions/actionClipboard.tsx index 9de6d70f4..2494595a8 100644 --- a/packages/excalidraw/actions/actionClipboard.tsx +++ b/packages/excalidraw/actions/actionClipboard.tsx @@ -3,6 +3,8 @@ import { getTextFromElements } from "@excalidraw/element/textElement"; import { CODES, KEYS, isFirefox } from "@excalidraw/common"; +import { CaptureUpdateAction } from "@excalidraw/element/store"; + import { copyTextToSystemClipboard, copyToClipboard, @@ -15,8 +17,6 @@ import { DuplicateIcon, cutIcon, pngIcon, svgIcon } from "../components/icons"; import { exportCanvas, prepareElementsForExport } from "../data/index"; import { t } from "../i18n"; -import { CaptureUpdateAction } from "../store"; - import { actionDeleteSelected } from "./actionDeleteSelected"; import { register } from "./register"; diff --git a/packages/excalidraw/actions/actionCropEditor.tsx b/packages/excalidraw/actions/actionCropEditor.tsx index 1a7b6da69..b6e801785 100644 --- a/packages/excalidraw/actions/actionCropEditor.tsx +++ b/packages/excalidraw/actions/actionCropEditor.tsx @@ -1,11 +1,12 @@ import { isImageElement } from "@excalidraw/element/typeChecks"; +import { CaptureUpdateAction } from "@excalidraw/element/store"; + import type { ExcalidrawImageElement } from "@excalidraw/element/types"; import { ToolButton } from "../components/ToolButton"; import { cropIcon } from "../components/icons"; import { t } from "../i18n"; -import { CaptureUpdateAction } from "../store"; import { register } from "./register"; diff --git a/packages/excalidraw/actions/actionDeleteSelected.tsx b/packages/excalidraw/actions/actionDeleteSelected.tsx index 442ee8545..d2d49ca23 100644 --- a/packages/excalidraw/actions/actionDeleteSelected.tsx +++ b/packages/excalidraw/actions/actionDeleteSelected.tsx @@ -17,11 +17,12 @@ import { selectGroupsForSelectedElements, } from "@excalidraw/element/groups"; +import { CaptureUpdateAction } from "@excalidraw/element/store"; + import type { ExcalidrawElement } from "@excalidraw/element/types"; import { t } from "../i18n"; import { getSelectedElements, isSomeElementSelected } from "../scene"; -import { CaptureUpdateAction } from "../store"; import { TrashIcon } from "../components/icons"; import { ToolButton } from "../components/ToolButton"; diff --git a/packages/excalidraw/actions/actionDistribute.tsx b/packages/excalidraw/actions/actionDistribute.tsx index 9f05ab6bf..ab964d3b3 100644 --- a/packages/excalidraw/actions/actionDistribute.tsx +++ b/packages/excalidraw/actions/actionDistribute.tsx @@ -8,6 +8,8 @@ import { updateFrameMembershipOfSelectedElements } from "@excalidraw/element/fra import { distributeElements } from "@excalidraw/element/distribute"; +import { CaptureUpdateAction } from "@excalidraw/element/store"; + import type { ExcalidrawElement } from "@excalidraw/element/types"; import type { Distribution } from "@excalidraw/element/distribute"; @@ -21,7 +23,6 @@ import { import { t } from "../i18n"; import { isSomeElementSelected } from "../scene"; -import { CaptureUpdateAction } from "../store"; import { register } from "./register"; diff --git a/packages/excalidraw/actions/actionDuplicateSelection.tsx b/packages/excalidraw/actions/actionDuplicateSelection.tsx index 034edf543..882f8716a 100644 --- a/packages/excalidraw/actions/actionDuplicateSelection.tsx +++ b/packages/excalidraw/actions/actionDuplicateSelection.tsx @@ -18,12 +18,13 @@ import { syncMovedIndices } from "@excalidraw/element/fractionalIndex"; import { duplicateElements } from "@excalidraw/element/duplicate"; +import { CaptureUpdateAction } from "@excalidraw/element/store"; + import { ToolButton } from "../components/ToolButton"; import { DuplicateIcon } from "../components/icons"; import { t } from "../i18n"; import { isSomeElementSelected } from "../scene"; -import { CaptureUpdateAction } from "../store"; import { register } from "./register"; diff --git a/packages/excalidraw/actions/actionElementLink.ts b/packages/excalidraw/actions/actionElementLink.ts index 24ea8bbd6..ad8d01687 100644 --- a/packages/excalidraw/actions/actionElementLink.ts +++ b/packages/excalidraw/actions/actionElementLink.ts @@ -4,11 +4,12 @@ import { getLinkIdAndTypeFromSelection, } from "@excalidraw/element/elementLink"; +import { CaptureUpdateAction } from "@excalidraw/element/store"; + import { copyTextToSystemClipboard } from "../clipboard"; import { copyIcon, elementLinkIcon } from "../components/icons"; import { t } from "../i18n"; import { getSelectedElements } from "../scene"; -import { CaptureUpdateAction } from "../store"; import { register } from "./register"; diff --git a/packages/excalidraw/actions/actionElementLock.ts b/packages/excalidraw/actions/actionElementLock.ts index 6bc238a59..0e97f1955 100644 --- a/packages/excalidraw/actions/actionElementLock.ts +++ b/packages/excalidraw/actions/actionElementLock.ts @@ -4,12 +4,13 @@ import { newElementWith } from "@excalidraw/element/mutateElement"; import { isFrameLikeElement } from "@excalidraw/element/typeChecks"; +import { CaptureUpdateAction } from "@excalidraw/element/store"; + import type { ExcalidrawElement } from "@excalidraw/element/types"; import { LockedIcon, UnlockedIcon } from "../components/icons"; import { getSelectedElements } from "../scene"; -import { CaptureUpdateAction } from "../store"; import { register } from "./register"; diff --git a/packages/excalidraw/actions/actionEmbeddable.ts b/packages/excalidraw/actions/actionEmbeddable.ts index 556652240..987b2b45a 100644 --- a/packages/excalidraw/actions/actionEmbeddable.ts +++ b/packages/excalidraw/actions/actionEmbeddable.ts @@ -1,7 +1,8 @@ import { updateActiveTool } from "@excalidraw/common"; +import { CaptureUpdateAction } from "@excalidraw/element/store"; + import { setCursorForShape } from "../cursor"; -import { CaptureUpdateAction } from "../store"; import { register } from "./register"; diff --git a/packages/excalidraw/actions/actionExport.tsx b/packages/excalidraw/actions/actionExport.tsx index 8fcaea21b..f8a9dca82 100644 --- a/packages/excalidraw/actions/actionExport.tsx +++ b/packages/excalidraw/actions/actionExport.tsx @@ -7,6 +7,8 @@ import { import { getNonDeletedElements } from "@excalidraw/element"; +import { CaptureUpdateAction } from "@excalidraw/element/store"; + import type { Theme } from "@excalidraw/element/types"; import { useDevice } from "../components/App"; @@ -24,7 +26,6 @@ import { resaveAsImageWithScene } from "../data/resave"; import { t } from "../i18n"; import { getSelectedElements, isSomeElementSelected } from "../scene"; import { getExportSize } from "../scene/export"; -import { CaptureUpdateAction } from "../store"; import "../components/ToolIcon.scss"; diff --git a/packages/excalidraw/actions/actionFinalize.tsx b/packages/excalidraw/actions/actionFinalize.tsx index 22638ee91..6ddb8ab52 100644 --- a/packages/excalidraw/actions/actionFinalize.tsx +++ b/packages/excalidraw/actions/actionFinalize.tsx @@ -16,11 +16,12 @@ import { isPathALoop } from "@excalidraw/element/shapes"; import { isInvisiblySmallElement } from "@excalidraw/element/sizeHelpers"; +import { CaptureUpdateAction } from "@excalidraw/element/store"; + import { t } from "../i18n"; import { resetCursor } from "../cursor"; import { done } from "../components/icons"; import { ToolButton } from "../components/ToolButton"; -import { CaptureUpdateAction } from "../store"; import { register } from "./register"; diff --git a/packages/excalidraw/actions/actionFlip.ts b/packages/excalidraw/actions/actionFlip.ts index becc8a976..f6c4f0c71 100644 --- a/packages/excalidraw/actions/actionFlip.ts +++ b/packages/excalidraw/actions/actionFlip.ts @@ -15,6 +15,8 @@ import { import { updateFrameMembershipOfSelectedElements } from "@excalidraw/element/frame"; import { CODES, KEYS, arrayToMap } from "@excalidraw/common"; +import { CaptureUpdateAction } from "@excalidraw/element/store"; + import type { ExcalidrawArrowElement, ExcalidrawElbowArrowElement, @@ -24,7 +26,6 @@ import type { } from "@excalidraw/element/types"; import { getSelectedElements } from "../scene"; -import { CaptureUpdateAction } from "../store"; import { flipHorizontal, flipVertical } from "../components/icons"; diff --git a/packages/excalidraw/actions/actionFrame.ts b/packages/excalidraw/actions/actionFrame.ts index 7882d26f6..f5e91fd93 100644 --- a/packages/excalidraw/actions/actionFrame.ts +++ b/packages/excalidraw/actions/actionFrame.ts @@ -14,12 +14,13 @@ import { getElementsInGroup } from "@excalidraw/element/groups"; import { getCommonBounds } from "@excalidraw/element/bounds"; +import { CaptureUpdateAction } from "@excalidraw/element/store"; + import type { ExcalidrawElement } from "@excalidraw/element/types"; import { setCursorForShape } from "../cursor"; import { frameToolIcon } from "../components/icons"; import { getSelectedElements } from "../scene"; -import { CaptureUpdateAction } from "../store"; import { register } from "./register"; diff --git a/packages/excalidraw/actions/actionGroup.tsx b/packages/excalidraw/actions/actionGroup.tsx index 6b47ef969..de3f6b266 100644 --- a/packages/excalidraw/actions/actionGroup.tsx +++ b/packages/excalidraw/actions/actionGroup.tsx @@ -28,6 +28,8 @@ import { import { syncMovedIndices } from "@excalidraw/element/fractionalIndex"; +import { CaptureUpdateAction } from "@excalidraw/element/store"; + import type { ExcalidrawElement, ExcalidrawTextElement, @@ -40,7 +42,6 @@ import { UngroupIcon, GroupIcon } from "../components/icons"; import { t } from "../i18n"; import { isSomeElementSelected } from "../scene"; -import { CaptureUpdateAction } from "../store"; import { register } from "./register"; diff --git a/packages/excalidraw/actions/actionHistory.tsx b/packages/excalidraw/actions/actionHistory.tsx index a0dfb85df..6477f795f 100644 --- a/packages/excalidraw/actions/actionHistory.tsx +++ b/packages/excalidraw/actions/actionHistory.tsx @@ -1,5 +1,9 @@ import { isWindows, KEYS, matchKey, arrayToMap } from "@excalidraw/common"; +import { CaptureUpdateAction } from "@excalidraw/element/store"; + +import { orderByFractionalIndex } from "@excalidraw/element/fractionalIndex"; + import type { SceneElementsMap } from "@excalidraw/element/types"; import { ToolButton } from "../components/ToolButton"; @@ -7,10 +11,8 @@ import { UndoIcon, RedoIcon } from "../components/icons"; import { HistoryChangedEvent } from "../history"; import { useEmitter } from "../hooks/useEmitter"; import { t } from "../i18n"; -import { CaptureUpdateAction } from "../store"; import type { History } from "../history"; -import type { Store } from "../store"; import type { AppClassProperties, AppState } from "../types"; import type { Action, ActionResult } from "./types"; @@ -35,7 +37,11 @@ const executeHistoryAction = ( } const [nextElementsMap, nextAppState] = result; - const nextElements = Array.from(nextElementsMap.values()); + + // order by fractional indices in case the map was accidently modified in the meantime + const nextElements = orderByFractionalIndex( + Array.from(nextElementsMap.values()), + ); return { appState: nextAppState, @@ -47,9 +53,9 @@ const executeHistoryAction = ( return { captureUpdate: CaptureUpdateAction.EVENTUALLY }; }; -type ActionCreator = (history: History, store: Store) => Action; +type ActionCreator = (history: History) => Action; -export const createUndoAction: ActionCreator = (history, store) => ({ +export const createUndoAction: ActionCreator = (history) => ({ name: "undo", label: "buttons.undo", icon: UndoIcon, @@ -57,11 +63,7 @@ export const createUndoAction: ActionCreator = (history, store) => ({ viewMode: false, perform: (elements, appState, value, app) => executeHistoryAction(app, appState, () => - history.undo( - arrayToMap(elements) as SceneElementsMap, // TODO: #7348 refactor action manager to already include `SceneElementsMap` - appState, - store.snapshot, - ), + history.undo(arrayToMap(elements) as SceneElementsMap, appState), ), keyTest: (event) => event[KEYS.CTRL_OR_CMD] && matchKey(event, KEYS.Z) && !event.shiftKey, @@ -88,19 +90,15 @@ export const createUndoAction: ActionCreator = (history, store) => ({ }, }); -export const createRedoAction: ActionCreator = (history, store) => ({ +export const createRedoAction: ActionCreator = (history) => ({ name: "redo", label: "buttons.redo", icon: RedoIcon, trackEvent: { category: "history" }, viewMode: false, - perform: (elements, appState, _, app) => + perform: (elements, appState, __, app) => executeHistoryAction(app, appState, () => - history.redo( - arrayToMap(elements) as SceneElementsMap, // TODO: #7348 refactor action manager to already include `SceneElementsMap` - appState, - store.snapshot, - ), + history.redo(arrayToMap(elements) as SceneElementsMap, appState), ), keyTest: (event) => (event[KEYS.CTRL_OR_CMD] && event.shiftKey && matchKey(event, KEYS.Z)) || diff --git a/packages/excalidraw/actions/actionLinearEditor.tsx b/packages/excalidraw/actions/actionLinearEditor.tsx index 441962137..a558fa836 100644 --- a/packages/excalidraw/actions/actionLinearEditor.tsx +++ b/packages/excalidraw/actions/actionLinearEditor.tsx @@ -6,6 +6,8 @@ import { } from "@excalidraw/element/typeChecks"; import { arrayToMap } from "@excalidraw/common"; +import { CaptureUpdateAction } from "@excalidraw/element/store"; + import type { ExcalidrawLinearElement, ExcalidrawLineElement, @@ -15,7 +17,6 @@ import { DEFAULT_CATEGORIES } from "../components/CommandPalette/CommandPalette" import { ToolButton } from "../components/ToolButton"; import { lineEditorIcon, polygonIcon } from "../components/icons"; import { t } from "../i18n"; -import { CaptureUpdateAction } from "../store"; import { ButtonIcon } from "../components/ButtonIcon"; diff --git a/packages/excalidraw/actions/actionLink.tsx b/packages/excalidraw/actions/actionLink.tsx index 71426267d..d7a5ca7d2 100644 --- a/packages/excalidraw/actions/actionLink.tsx +++ b/packages/excalidraw/actions/actionLink.tsx @@ -2,13 +2,14 @@ import { isEmbeddableElement } from "@excalidraw/element/typeChecks"; import { KEYS, getShortcutKey } from "@excalidraw/common"; +import { CaptureUpdateAction } from "@excalidraw/element/store"; + import { ToolButton } from "../components/ToolButton"; import { getContextMenuLabel } from "../components/hyperlink/Hyperlink"; import { LinkIcon } from "../components/icons"; import { t } from "../i18n"; import { getSelectedElements } from "../scene"; -import { CaptureUpdateAction } from "../store"; import { register } from "./register"; diff --git a/packages/excalidraw/actions/actionMenu.tsx b/packages/excalidraw/actions/actionMenu.tsx index 67863e020..8cdc489b4 100644 --- a/packages/excalidraw/actions/actionMenu.tsx +++ b/packages/excalidraw/actions/actionMenu.tsx @@ -4,12 +4,12 @@ import { getNonDeletedElements } from "@excalidraw/element"; import { showSelectedShapeActions } from "@excalidraw/element/showSelectedShapeActions"; +import { CaptureUpdateAction } from "@excalidraw/element/store"; + import { ToolButton } from "../components/ToolButton"; import { HamburgerMenuIcon, HelpIconThin, palette } from "../components/icons"; import { t } from "../i18n"; -import { CaptureUpdateAction } from "../store"; - import { register } from "./register"; export const actionToggleCanvasMenu = register({ diff --git a/packages/excalidraw/actions/actionNavigate.tsx b/packages/excalidraw/actions/actionNavigate.tsx index 738386839..637df0450 100644 --- a/packages/excalidraw/actions/actionNavigate.tsx +++ b/packages/excalidraw/actions/actionNavigate.tsx @@ -1,5 +1,7 @@ import clsx from "clsx"; +import { CaptureUpdateAction } from "@excalidraw/element/store"; + import { getClientColor } from "../clients"; import { Avatar } from "../components/Avatar"; import { @@ -8,7 +10,6 @@ import { microphoneMutedIcon, } from "../components/icons"; import { t } from "../i18n"; -import { CaptureUpdateAction } from "../store"; import { register } from "./register"; diff --git a/packages/excalidraw/actions/actionProperties.tsx b/packages/excalidraw/actions/actionProperties.tsx index b1835dadf..40df8046c 100644 --- a/packages/excalidraw/actions/actionProperties.tsx +++ b/packages/excalidraw/actions/actionProperties.tsx @@ -56,6 +56,8 @@ import { hasStrokeColor } from "@excalidraw/element/comparisons"; import { updateElbowArrowPoints } from "@excalidraw/element/elbowArrow"; +import { CaptureUpdateAction } from "@excalidraw/element/store"; + import type { LocalPoint } from "@excalidraw/math"; import type { @@ -72,6 +74,8 @@ import type { import type Scene from "@excalidraw/element/Scene"; +import type { CaptureUpdateActionType } from "@excalidraw/element/store"; + import { trackEvent } from "../analytics"; import { ButtonIconSelect } from "../components/ButtonIconSelect"; import { ColorPicker } from "../components/ColorPicker/ColorPicker"; @@ -133,13 +137,11 @@ import { getTargetElements, isSomeElementSelected, } from "../scene"; -import { CaptureUpdateAction } from "../store"; import { toggleLinePolygonState } from "../../element/src/shapes"; import { register } from "./register"; -import type { CaptureUpdateActionType } from "../store"; import type { AppClassProperties, AppState, Primitive } from "../types"; const FONT_SIZE_RELATIVE_INCREASE_STEP = 0.1; diff --git a/packages/excalidraw/actions/actionSelectAll.ts b/packages/excalidraw/actions/actionSelectAll.ts index ea13636b7..9386bc9a1 100644 --- a/packages/excalidraw/actions/actionSelectAll.ts +++ b/packages/excalidraw/actions/actionSelectAll.ts @@ -6,9 +6,9 @@ import { arrayToMap, KEYS } from "@excalidraw/common"; import { selectGroupsForSelectedElements } from "@excalidraw/element/groups"; -import type { ExcalidrawElement } from "@excalidraw/element/types"; +import { CaptureUpdateAction } from "@excalidraw/element/store"; -import { CaptureUpdateAction } from "../store"; +import type { ExcalidrawElement } from "@excalidraw/element/types"; import { selectAllIcon } from "../components/icons"; diff --git a/packages/excalidraw/actions/actionStyles.ts b/packages/excalidraw/actions/actionStyles.ts index 08b32e227..f80a56990 100644 --- a/packages/excalidraw/actions/actionStyles.ts +++ b/packages/excalidraw/actions/actionStyles.ts @@ -24,13 +24,14 @@ import { redrawTextBoundingBox, } from "@excalidraw/element/textElement"; +import { CaptureUpdateAction } from "@excalidraw/element/store"; + import type { ExcalidrawTextElement } from "@excalidraw/element/types"; import { paintIcon } from "../components/icons"; import { t } from "../i18n"; import { getSelectedElements } from "../scene"; -import { CaptureUpdateAction } from "../store"; import { register } from "./register"; diff --git a/packages/excalidraw/actions/actionTextAutoResize.ts b/packages/excalidraw/actions/actionTextAutoResize.ts index 4a36cab40..15af36658 100644 --- a/packages/excalidraw/actions/actionTextAutoResize.ts +++ b/packages/excalidraw/actions/actionTextAutoResize.ts @@ -5,8 +5,9 @@ import { measureText } from "@excalidraw/element/textMeasurements"; import { isTextElement } from "@excalidraw/element/typeChecks"; +import { CaptureUpdateAction } from "@excalidraw/element/store"; + import { getSelectedElements } from "../scene"; -import { CaptureUpdateAction } from "../store"; import { register } from "./register"; diff --git a/packages/excalidraw/actions/actionToggleGridMode.tsx b/packages/excalidraw/actions/actionToggleGridMode.tsx index 9415051f3..543485d8a 100644 --- a/packages/excalidraw/actions/actionToggleGridMode.tsx +++ b/packages/excalidraw/actions/actionToggleGridMode.tsx @@ -1,7 +1,8 @@ import { CODES, KEYS } from "@excalidraw/common"; +import { CaptureUpdateAction } from "@excalidraw/element/store"; + import { gridIcon } from "../components/icons"; -import { CaptureUpdateAction } from "../store"; import { register } from "./register"; diff --git a/packages/excalidraw/actions/actionToggleObjectsSnapMode.tsx b/packages/excalidraw/actions/actionToggleObjectsSnapMode.tsx index ba092bff8..1eef483aa 100644 --- a/packages/excalidraw/actions/actionToggleObjectsSnapMode.tsx +++ b/packages/excalidraw/actions/actionToggleObjectsSnapMode.tsx @@ -1,7 +1,8 @@ import { CODES, KEYS } from "@excalidraw/common"; +import { CaptureUpdateAction } from "@excalidraw/element/store"; + import { magnetIcon } from "../components/icons"; -import { CaptureUpdateAction } from "../store"; import { register } from "./register"; diff --git a/packages/excalidraw/actions/actionToggleSearchMenu.ts b/packages/excalidraw/actions/actionToggleSearchMenu.ts index ce384fc66..b7821bce4 100644 --- a/packages/excalidraw/actions/actionToggleSearchMenu.ts +++ b/packages/excalidraw/actions/actionToggleSearchMenu.ts @@ -5,8 +5,9 @@ import { DEFAULT_SIDEBAR, } from "@excalidraw/common"; +import { CaptureUpdateAction } from "@excalidraw/element/store"; + import { searchIcon } from "../components/icons"; -import { CaptureUpdateAction } from "../store"; import { register } from "./register"; diff --git a/packages/excalidraw/actions/actionToggleShapeSwitch.tsx b/packages/excalidraw/actions/actionToggleShapeSwitch.tsx index 39e7566fb..aea8f986e 100644 --- a/packages/excalidraw/actions/actionToggleShapeSwitch.tsx +++ b/packages/excalidraw/actions/actionToggleShapeSwitch.tsx @@ -1,3 +1,5 @@ +import { CaptureUpdateAction } from "@excalidraw/element/store"; + import type { ExcalidrawElement } from "@excalidraw/element/types"; import { @@ -5,7 +7,6 @@ import { convertElementTypePopupAtom, } from "../components/ConvertElementTypePopup"; import { editorJotaiStore } from "../editor-jotai"; -import { CaptureUpdateAction } from "../store"; import { register } from "./register"; diff --git a/packages/excalidraw/actions/actionToggleStats.tsx b/packages/excalidraw/actions/actionToggleStats.tsx index ffa812e96..d044c01fb 100644 --- a/packages/excalidraw/actions/actionToggleStats.tsx +++ b/packages/excalidraw/actions/actionToggleStats.tsx @@ -1,7 +1,8 @@ import { CODES, KEYS } from "@excalidraw/common"; +import { CaptureUpdateAction } from "@excalidraw/element/store"; + import { abacusIcon } from "../components/icons"; -import { CaptureUpdateAction } from "../store"; import { register } from "./register"; diff --git a/packages/excalidraw/actions/actionToggleViewMode.tsx b/packages/excalidraw/actions/actionToggleViewMode.tsx index e42a7a102..f511ec619 100644 --- a/packages/excalidraw/actions/actionToggleViewMode.tsx +++ b/packages/excalidraw/actions/actionToggleViewMode.tsx @@ -1,7 +1,8 @@ import { CODES, KEYS } from "@excalidraw/common"; +import { CaptureUpdateAction } from "@excalidraw/element/store"; + import { eyeIcon } from "../components/icons"; -import { CaptureUpdateAction } from "../store"; import { register } from "./register"; diff --git a/packages/excalidraw/actions/actionToggleZenMode.tsx b/packages/excalidraw/actions/actionToggleZenMode.tsx index e56e02ca7..a9dc8dd1f 100644 --- a/packages/excalidraw/actions/actionToggleZenMode.tsx +++ b/packages/excalidraw/actions/actionToggleZenMode.tsx @@ -1,7 +1,8 @@ import { CODES, KEYS } from "@excalidraw/common"; +import { CaptureUpdateAction } from "@excalidraw/element/store"; + import { coffeeIcon } from "../components/icons"; -import { CaptureUpdateAction } from "../store"; import { register } from "./register"; diff --git a/packages/excalidraw/actions/actionZindex.tsx b/packages/excalidraw/actions/actionZindex.tsx index 8eb5a50f2..753e42321 100644 --- a/packages/excalidraw/actions/actionZindex.tsx +++ b/packages/excalidraw/actions/actionZindex.tsx @@ -7,6 +7,8 @@ import { moveAllRight, } from "@excalidraw/element/zindex"; +import { CaptureUpdateAction } from "@excalidraw/element/store"; + import { BringForwardIcon, BringToFrontIcon, @@ -14,7 +16,6 @@ import { SendToBackIcon, } from "../components/icons"; import { t } from "../i18n"; -import { CaptureUpdateAction } from "../store"; import { register } from "./register"; diff --git a/packages/excalidraw/actions/types.ts b/packages/excalidraw/actions/types.ts index 71cfd7508..809234f4d 100644 --- a/packages/excalidraw/actions/types.ts +++ b/packages/excalidraw/actions/types.ts @@ -3,7 +3,8 @@ import type { OrderedExcalidrawElement, } from "@excalidraw/element/types"; -import type { CaptureUpdateActionType } from "../store"; +import type { CaptureUpdateActionType } from "@excalidraw/element/store"; + import type { AppClassProperties, AppState, diff --git a/packages/excalidraw/components/App.tsx b/packages/excalidraw/components/App.tsx index ddb071981..d94d39e77 100644 --- a/packages/excalidraw/components/App.tsx +++ b/packages/excalidraw/components/App.tsx @@ -101,6 +101,7 @@ import { type EXPORT_IMAGE_TYPES, randomInteger, CLASSES, + Emitter, } from "@excalidraw/common"; import { @@ -303,6 +304,8 @@ import { isNonDeletedElement } from "@excalidraw/element"; import Scene from "@excalidraw/element/Scene"; +import { Store, CaptureUpdateAction } from "@excalidraw/element/store"; + import type { ElementUpdate } from "@excalidraw/element/mutateElement"; import type { LocalPoint, Radians } from "@excalidraw/math"; @@ -331,6 +334,7 @@ import type { ExcalidrawNonSelectionElement, ExcalidrawArrowElement, ExcalidrawElbowArrowElement, + SceneElementsMap, } from "@excalidraw/element/types"; import type { Mutable, ValueOf } from "@excalidraw/common/utility-types"; @@ -454,9 +458,7 @@ import { resetCursor, setCursorForShape, } from "../cursor"; -import { Emitter } from "../emitter"; import { ElementCanvasButtons } from "../components/ElementCanvasButtons"; -import { Store, CaptureUpdateAction } from "../store"; import { LaserTrails } from "../laser-trails"; import { withBatchedUpdates, withBatchedUpdatesThrottled } from "../reactUtils"; import { textWysiwyg } from "../wysiwyg/textWysiwyg"; @@ -761,8 +763,8 @@ class App extends React.Component { this.renderer = new Renderer(this.scene); this.visibleElements = []; - this.store = new Store(); - this.history = new History(); + this.store = new Store(this); + this.history = new History(this.store); if (excalidrawAPI) { const api: ExcalidrawImperativeAPI = { @@ -792,6 +794,7 @@ class App extends React.Component { updateFrameRendering: this.updateFrameRendering, toggleSidebar: this.toggleSidebar, onChange: (cb) => this.onChangeEmitter.on(cb), + onIncrement: (cb) => this.store.onStoreIncrementEmitter.on(cb), onPointerDown: (cb) => this.onPointerDownEmitter.on(cb), onPointerUp: (cb) => this.onPointerUpEmitter.on(cb), onScrollChange: (cb) => this.onScrollChangeEmitter.on(cb), @@ -810,15 +813,11 @@ class App extends React.Component { }; this.fonts = new Fonts(this.scene); - this.history = new History(); + this.history = new History(this.store); this.actionManager.registerAll(actions); - this.actionManager.registerAction( - createUndoAction(this.history, this.store), - ); - this.actionManager.registerAction( - createRedoAction(this.history, this.store), - ); + this.actionManager.registerAction(createUndoAction(this.history)); + this.actionManager.registerAction(createRedoAction(this.history)); } updateEditorAtom = ( @@ -1899,6 +1898,10 @@ class App extends React.Component { return this.scene.getElementsIncludingDeleted(); }; + public getSceneElementsMapIncludingDeleted = () => { + return this.scene.getElementsMapIncludingDeleted(); + }; + public getSceneElements = () => { return this.scene.getNonDeletedElements(); }; @@ -2215,11 +2218,7 @@ class App extends React.Component { return; } - if (actionResult.captureUpdate === CaptureUpdateAction.NEVER) { - this.store.shouldUpdateSnapshot(); - } else if (actionResult.captureUpdate === CaptureUpdateAction.IMMEDIATELY) { - this.store.shouldCaptureIncrement(); - } + this.store.scheduleAction(actionResult.captureUpdate); let didUpdate = false; @@ -2292,10 +2291,7 @@ class App extends React.Component { didUpdate = true; } - if ( - !didUpdate && - actionResult.captureUpdate !== CaptureUpdateAction.EVENTUALLY - ) { + if (!didUpdate) { this.scene.triggerUpdate(); } }); @@ -2547,10 +2543,19 @@ class App extends React.Component { }); } - this.store.onStoreIncrementEmitter.on((increment) => { - this.history.record(increment.elementsChange, increment.appStateChange); + this.store.onDurableIncrementEmitter.on((increment) => { + this.history.record(increment.delta); }); + const { onIncrement } = this.props; + + // per. optimmisation, only subscribe if there is the `onIncrement` prop registered, to avoid unnecessary computation + if (onIncrement) { + this.store.onStoreIncrementEmitter.on((increment) => { + onIncrement(increment); + }); + } + this.scene.onUpdate(this.triggerRender); this.addEventListeners(); @@ -2610,6 +2615,7 @@ class App extends React.Component { this.eraserTrail.stop(); this.onChangeEmitter.clear(); this.store.onStoreIncrementEmitter.clear(); + this.store.onDurableIncrementEmitter.clear(); ShapeCache.destroy(); SnapCache.destroy(); clearTimeout(touchTimeout); @@ -2903,7 +2909,7 @@ class App extends React.Component { this.state.editingLinearElement && !this.state.selectedElementIds[this.state.editingLinearElement.elementId] ) { - // defer so that the shouldCaptureIncrement flag isn't reset via current update + // defer so that the scheduleCapture flag isn't reset via current update setTimeout(() => { // execute only if the condition still holds when the deferred callback // executes (it can be scheduled multiple times depending on how @@ -3358,7 +3364,7 @@ class App extends React.Component { this.addMissingFiles(opts.files); } - this.store.shouldCaptureIncrement(); + this.store.scheduleCapture(); const nextElementsToSelect = excludeElementsInFramesFromSelection(duplicatedElements); @@ -3619,7 +3625,7 @@ class App extends React.Component { PLAIN_PASTE_TOAST_SHOWN = true; } - this.store.shouldCaptureIncrement(); + this.store.scheduleCapture(); } setAppState: React.Component["setState"] = ( @@ -3975,51 +3981,37 @@ class App extends React.Component { */ captureUpdate?: SceneData["captureUpdate"]; }) => { - const nextElements = syncInvalidIndices(sceneData.elements ?? []); + const { elements, appState, collaborators, captureUpdate } = sceneData; - if ( - sceneData.captureUpdate && - sceneData.captureUpdate !== CaptureUpdateAction.EVENTUALLY - ) { - const prevCommittedAppState = this.store.snapshot.appState; - const prevCommittedElements = this.store.snapshot.elements; + const nextElements = elements ? syncInvalidIndices(elements) : undefined; - const nextCommittedAppState = sceneData.appState - ? Object.assign({}, prevCommittedAppState, sceneData.appState) // new instance, with partial appstate applied to previously captured one, including hidden prop inside `prevCommittedAppState` - : prevCommittedAppState; + if (captureUpdate) { + const nextElementsMap = elements + ? (arrayToMap(nextElements ?? []) as SceneElementsMap) + : undefined; - const nextCommittedElements = sceneData.elements - ? this.store.filterUncomittedElements( - this.scene.getElementsMapIncludingDeleted(), // Only used to detect uncomitted local elements - arrayToMap(nextElements), // We expect all (already reconciled) elements - ) - : prevCommittedElements; + const nextAppState = appState + ? // new instance, with partial appstate applied to previously captured one, including hidden prop inside `prevCommittedAppState` + Object.assign({}, this.store.snapshot.appState, appState) + : undefined; - // WARN: store action always performs deep clone of changed elements, for ephemeral remote updates (i.e. remote dragging, resizing, drawing) we might consider doing something smarter - // do NOT schedule store actions (execute after re-render), as it might cause unexpected concurrency issues if not handled well - if (sceneData.captureUpdate === CaptureUpdateAction.IMMEDIATELY) { - this.store.captureIncrement( - nextCommittedElements, - nextCommittedAppState, - ); - } else if (sceneData.captureUpdate === CaptureUpdateAction.NEVER) { - this.store.updateSnapshot( - nextCommittedElements, - nextCommittedAppState, - ); - } + this.store.scheduleMicroAction({ + action: captureUpdate, + elements: nextElementsMap, + appState: nextAppState, + }); } - if (sceneData.appState) { - this.setState(sceneData.appState); + if (appState) { + this.setState(appState); } - if (sceneData.elements) { + if (nextElements) { this.scene.replaceAllElements(nextElements); } - if (sceneData.collaborators) { - this.setState({ collaborators: sceneData.collaborators }); + if (collaborators) { + this.setState({ collaborators }); } }, ); @@ -4202,7 +4194,7 @@ class App extends React.Component { direction: event.shiftKey ? "left" : "right", }) ) { - this.store.shouldCaptureIncrement(); + this.store.scheduleCapture(); } } if (conversionType) { @@ -4519,7 +4511,7 @@ class App extends React.Component { this.state.editingLinearElement.elementId !== selectedElements[0].id ) { - this.store.shouldCaptureIncrement(); + this.store.scheduleCapture(); if (!isElbowArrow(selectedElement)) { this.setState({ editingLinearElement: new LinearElementEditor( @@ -4845,7 +4837,7 @@ class App extends React.Component { } as const; if (nextActiveTool.type === "freedraw") { - this.store.shouldCaptureIncrement(); + this.store.scheduleCapture(); } if (nextActiveTool.type === "lasso") { @@ -5062,7 +5054,7 @@ class App extends React.Component { ]); } if (!isDeleted || isExistingElement) { - this.store.shouldCaptureIncrement(); + this.store.scheduleCapture(); } flushSync(() => { @@ -5475,7 +5467,7 @@ class App extends React.Component { }; private startImageCropping = (image: ExcalidrawImageElement) => { - this.store.shouldCaptureIncrement(); + this.store.scheduleCapture(); this.setState({ croppingElementId: image.id, }); @@ -5483,7 +5475,7 @@ class App extends React.Component { private finishImageCropping = () => { if (this.state.croppingElementId) { - this.store.shouldCaptureIncrement(); + this.store.scheduleCapture(); this.setState({ croppingElementId: null, }); @@ -5518,7 +5510,7 @@ class App extends React.Component { selectedElements[0].id) && !isElbowArrow(selectedElements[0]) ) { - this.store.shouldCaptureIncrement(); + this.store.scheduleCapture(); this.setState({ editingLinearElement: new LinearElementEditor( selectedElements[0], @@ -5546,7 +5538,7 @@ class App extends React.Component { : -1; if (midPoint && midPoint > -1) { - this.store.shouldCaptureIncrement(); + this.store.scheduleCapture(); LinearElementEditor.deleteFixedSegment( selectedElements[0], this.scene, @@ -5608,7 +5600,7 @@ class App extends React.Component { getSelectedGroupIdForElement(hitElement, this.state.selectedGroupIds); if (selectedGroupId) { - this.store.shouldCaptureIncrement(); + this.store.scheduleCapture(); this.setState((prevState) => ({ ...prevState, ...selectGroupsForSelectedElements( @@ -9131,7 +9123,7 @@ class App extends React.Component { if (isLinearElement(newElement)) { if (newElement!.points.length > 1) { - this.store.shouldCaptureIncrement(); + this.store.scheduleCapture(); } const pointerCoords = viewportCoordsToSceneCoords( childEvent, @@ -9404,7 +9396,7 @@ class App extends React.Component { } if (resizingElement) { - this.store.shouldCaptureIncrement(); + this.store.scheduleCapture(); } if (resizingElement && isInvisiblySmallElement(resizingElement)) { @@ -9744,7 +9736,7 @@ class App extends React.Component { this.state.selectedElementIds, ) ) { - this.store.shouldCaptureIncrement(); + this.store.scheduleCapture(); } if ( @@ -9837,7 +9829,7 @@ class App extends React.Component { this.elementsPendingErasure = new Set(); if (didChange) { - this.store.shouldCaptureIncrement(); + this.store.scheduleCapture(); this.scene.replaceAllElements(elements); } }; @@ -10517,8 +10509,13 @@ class App extends React.Component { // restore the fractional indices by mutating elements syncInvalidIndices(elements.concat(ret.data.elements)); - // update the store snapshot for old elements, otherwise we would end up with duplicated fractional indices on undo - this.store.updateSnapshot(arrayToMap(elements), this.state); + // don't capture and only update the store snapshot for old elements, + // otherwise we would end up with duplicated fractional indices on undo + this.store.scheduleMicroAction({ + action: CaptureUpdateAction.NEVER, + elements: arrayToMap(elements) as SceneElementsMap, + appState: undefined, + }); this.setState({ isLoading: true }); this.syncActionResult({ diff --git a/packages/excalidraw/components/Stats/DragInput.tsx b/packages/excalidraw/components/Stats/DragInput.tsx index 6fdf909b2..208b48f6c 100644 --- a/packages/excalidraw/components/Stats/DragInput.tsx +++ b/packages/excalidraw/components/Stats/DragInput.tsx @@ -5,11 +5,12 @@ import { EVENT, KEYS, cloneJSON } from "@excalidraw/common"; import { deepCopyElement } from "@excalidraw/element/duplicate"; +import { CaptureUpdateAction } from "@excalidraw/element/store"; + import type { ElementsMap, ExcalidrawElement } from "@excalidraw/element/types"; import type Scene from "@excalidraw/element/Scene"; -import { CaptureUpdateAction } from "../../store"; import { useApp } from "../App"; import { InlineIcon } from "../InlineIcon"; diff --git a/packages/excalidraw/components/Stats/utils.ts b/packages/excalidraw/components/Stats/utils.ts index 769601a46..c30777e42 100644 --- a/packages/excalidraw/components/Stats/utils.ts +++ b/packages/excalidraw/components/Stats/utils.ts @@ -215,23 +215,6 @@ export const moveElement = ( updateBindings(latestChildElement, scene, { simultaneouslyUpdated: originalChildren, }); - - const boundTextElement = getBoundTextElement( - latestChildElement, - originalElementsMap, - ); - if (boundTextElement) { - const latestBoundTextElement = elementsMap.get(boundTextElement.id); - latestBoundTextElement && - scene.mutateElement( - latestBoundTextElement, - { - x: boundTextElement.x + changeInX, - y: boundTextElement.y + changeInY, - }, - { informMutation: shouldInformMutation, isDragging: false }, - ); - } }); } }; diff --git a/packages/excalidraw/components/TTDDialog/TTDDialogInput.tsx b/packages/excalidraw/components/TTDDialog/TTDDialogInput.tsx index 9bd80b681..24427d52d 100644 --- a/packages/excalidraw/components/TTDDialog/TTDDialogInput.tsx +++ b/packages/excalidraw/components/TTDDialog/TTDDialogInput.tsx @@ -34,6 +34,7 @@ export const TTDDialogInput = ({ callbackRef.current?.(); } }; + textarea.focus(); textarea.addEventListener(EVENT.KEYDOWN, handleKeyDown); return () => { textarea.removeEventListener(EVENT.KEYDOWN, handleKeyDown); @@ -47,7 +48,6 @@ export const TTDDialogInput = ({ onChange={onChange} value={input} placeholder={placeholder} - autoFocus ref={ref} /> ); diff --git a/packages/excalidraw/data/library.ts b/packages/excalidraw/data/library.ts index 74252657e..5a9b7fc15 100644 --- a/packages/excalidraw/data/library.ts +++ b/packages/excalidraw/data/library.ts @@ -14,6 +14,7 @@ import { resolvablePromise, toValidURL, Queue, + Emitter, } from "@excalidraw/common"; import { hashElementsVersion, hashString } from "@excalidraw/element"; @@ -26,7 +27,6 @@ import type { MaybePromise } from "@excalidraw/common/utility-types"; import { atom, editorJotaiStore } from "../editor-jotai"; -import { Emitter } from "../emitter"; import { AbortError } from "../errors"; import { libraryItemSvgsCache } from "../hooks/useLibraryItemSvg"; import { t } from "../i18n"; diff --git a/packages/excalidraw/history.ts b/packages/excalidraw/history.ts index 0481c8411..f3022fd41 100644 --- a/packages/excalidraw/history.ts +++ b/packages/excalidraw/history.ts @@ -1,12 +1,17 @@ +import { Emitter } from "@excalidraw/common"; + +import { + CaptureUpdateAction, + StoreChange, + StoreDelta, + type Store, +} from "@excalidraw/element/store"; + import type { SceneElementsMap } from "@excalidraw/element/types"; -import { Emitter } from "./emitter"; - -import type { AppStateChange, ElementsChange } from "./change"; -import type { Snapshot } from "./store"; import type { AppState } from "./types"; -type HistoryStack = HistoryEntry[]; +class HistoryEntry extends StoreDelta {} export class HistoryChangedEvent { constructor( @@ -20,8 +25,8 @@ export class History { [HistoryChangedEvent] >(); - private readonly undoStack: HistoryStack = []; - private readonly redoStack: HistoryStack = []; + public readonly undoStack: HistoryEntry[] = []; + public readonly redoStack: HistoryEntry[] = []; public get isUndoStackEmpty() { return this.undoStack.length === 0; @@ -31,60 +36,52 @@ export class History { return this.redoStack.length === 0; } + constructor(private readonly store: Store) {} + public clear() { this.undoStack.length = 0; this.redoStack.length = 0; } /** - * Record a local change which will go into the history + * Record a non-empty local durable increment, which will go into the undo stack.. + * Do not re-record history entries, which were already pushed to undo / redo stack, as part of history action. */ - public record( - elementsChange: ElementsChange, - appStateChange: AppStateChange, - ) { - const entry = HistoryEntry.create(appStateChange, elementsChange); - - if (!entry.isEmpty()) { - // we have the latest changes, no need to `applyLatest`, which is done within `History.push` - this.undoStack.push(entry.inverse()); - - if (!entry.elementsChange.isEmpty()) { - // don't reset redo stack on local appState changes, - // as a simple click (unselect) could lead to losing all the redo entries - // only reset on non empty elements changes! - this.redoStack.length = 0; - } - - this.onHistoryChangedEmitter.trigger( - new HistoryChangedEvent(this.isUndoStackEmpty, this.isRedoStackEmpty), - ); + public record(delta: StoreDelta) { + if (delta.isEmpty() || delta instanceof HistoryEntry) { + return; } + + // construct history entry, so once it's emitted, it's not recorded again + const entry = HistoryEntry.inverse(delta); + + this.undoStack.push(entry); + + if (!entry.elements.isEmpty()) { + // don't reset redo stack on local appState changes, + // as a simple click (unselect) could lead to losing all the redo entries + // only reset on non empty elements changes! + this.redoStack.length = 0; + } + + this.onHistoryChangedEmitter.trigger( + new HistoryChangedEvent(this.isUndoStackEmpty, this.isRedoStackEmpty), + ); } - public undo( - elements: SceneElementsMap, - appState: AppState, - snapshot: Readonly, - ) { + public undo(elements: SceneElementsMap, appState: AppState) { return this.perform( elements, appState, - snapshot, () => History.pop(this.undoStack), (entry: HistoryEntry) => History.push(this.redoStack, entry, elements), ); } - public redo( - elements: SceneElementsMap, - appState: AppState, - snapshot: Readonly, - ) { + public redo(elements: SceneElementsMap, appState: AppState) { return this.perform( elements, appState, - snapshot, () => History.pop(this.redoStack), (entry: HistoryEntry) => History.push(this.undoStack, entry, elements), ); @@ -93,7 +90,6 @@ export class History { private perform( elements: SceneElementsMap, appState: AppState, - snapshot: Readonly, pop: () => HistoryEntry | null, push: (entry: HistoryEntry) => void, ): [SceneElementsMap, AppState] | void { @@ -104,6 +100,10 @@ export class History { return; } + const action = CaptureUpdateAction.IMMEDIATELY; + + let prevSnapshot = this.store.snapshot; + let nextElements = elements; let nextAppState = appState; let containsVisibleChange = false; @@ -112,9 +112,29 @@ export class History { while (historyEntry) { try { [nextElements, nextAppState, containsVisibleChange] = - historyEntry.applyTo(nextElements, nextAppState, snapshot); + StoreDelta.applyTo( + historyEntry, + nextElements, + nextAppState, + prevSnapshot, + ); + + const nextSnapshot = prevSnapshot.maybeClone( + action, + nextElements, + nextAppState, + ); + + // schedule immediate capture, so that it's emitted for the sync purposes + this.store.scheduleMicroAction({ + action, + change: StoreChange.create(prevSnapshot, nextSnapshot), + delta: historyEntry, + }); + + prevSnapshot = nextSnapshot; } finally { - // make sure to always push / pop, even if the increment is corrupted + // make sure to always push, even if the delta is corrupted push(historyEntry); } @@ -135,7 +155,7 @@ export class History { } } - private static pop(stack: HistoryStack): HistoryEntry | null { + private static pop(stack: HistoryEntry[]): HistoryEntry | null { if (!stack.length) { return null; } @@ -150,63 +170,17 @@ export class History { } private static push( - stack: HistoryStack, + stack: HistoryEntry[], entry: HistoryEntry, prevElements: SceneElementsMap, ) { - const updatedEntry = entry.inverse().applyLatestChanges(prevElements); + const inversedEntry = HistoryEntry.inverse(entry); + const updatedEntry = HistoryEntry.applyLatestChanges( + inversedEntry, + prevElements, + "inserted", + ); + return stack.push(updatedEntry); } } - -export class HistoryEntry { - private constructor( - public readonly appStateChange: AppStateChange, - public readonly elementsChange: ElementsChange, - ) {} - - public static create( - appStateChange: AppStateChange, - elementsChange: ElementsChange, - ) { - return new HistoryEntry(appStateChange, elementsChange); - } - - public inverse(): HistoryEntry { - return new HistoryEntry( - this.appStateChange.inverse(), - this.elementsChange.inverse(), - ); - } - - public applyTo( - elements: SceneElementsMap, - appState: AppState, - snapshot: Readonly, - ): [SceneElementsMap, AppState, boolean] { - const [nextElements, elementsContainVisibleChange] = - this.elementsChange.applyTo(elements, snapshot.elements); - - const [nextAppState, appStateContainsVisibleChange] = - this.appStateChange.applyTo(appState, nextElements); - - const appliedVisibleChanges = - elementsContainVisibleChange || appStateContainsVisibleChange; - - return [nextElements, nextAppState, appliedVisibleChanges]; - } - - /** - * Apply latest (remote) changes to the history entry, creates new instance of `HistoryEntry`. - */ - public applyLatestChanges(elements: SceneElementsMap): HistoryEntry { - const updatedElementsChange = - this.elementsChange.applyLatestChanges(elements); - - return HistoryEntry.create(this.appStateChange, updatedElementsChange); - } - - public isEmpty(): boolean { - return this.appStateChange.isEmpty() && this.elementsChange.isEmpty(); - } -} diff --git a/packages/excalidraw/hooks/useEmitter.ts b/packages/excalidraw/hooks/useEmitter.ts index eebbaaf30..3ecb24796 100644 --- a/packages/excalidraw/hooks/useEmitter.ts +++ b/packages/excalidraw/hooks/useEmitter.ts @@ -1,6 +1,6 @@ import { useEffect, useState } from "react"; -import type { Emitter } from "../emitter"; +import type { Emitter } from "@excalidraw/common"; export const useEmitter = ( emitter: Emitter<[TEvent]>, diff --git a/packages/excalidraw/index.tsx b/packages/excalidraw/index.tsx index 17c59a1b5..07fbdfdbb 100644 --- a/packages/excalidraw/index.tsx +++ b/packages/excalidraw/index.tsx @@ -23,6 +23,7 @@ polyfill(); const ExcalidrawBase = (props: ExcalidrawProps) => { const { onChange, + onIncrement, initialData, excalidrawAPI, isCollaborating = false, @@ -114,6 +115,7 @@ const ExcalidrawBase = (props: ExcalidrawProps) => { = points.length ? points.length - 1 : i + 1]; + + for (let t = 0; t <= 1; t += 1 / segments) { + const t2 = t * t; + + const x = + (1 - t) * (1 - t) * p0[0] + 2 * (1 - t) * t * p1[0] + t2 * p2[0]; + + const y = + (1 - t) * (1 - t) * p0[1] + 2 * (1 - t) * t * p1[1] + t2 * p2[1]; + + ctx.lineTo(x, y); + } + } +} + +function drawCatmullRomCubicApprox( + ctx: CanvasRenderingContext2D, + points: GlobalPoint[], + segments = 20, +) { + ctx.lineTo(points[0][0], points[0][1]); + + for (let i = 0; i < points.length - 1; i++) { + const p0 = points[i - 1 < 0 ? 0 : i - 1]; + const p1 = points[i]; + const p2 = points[i + 1 >= points.length ? points.length - 1 : i + 1]; + const p3 = points[i + 2 >= points.length ? points.length - 1 : i + 2]; + + for (let t = 0; t <= 1; t += 1 / segments) { + const t2 = t * t; + const t3 = t2 * t; + + const x = + 0.5 * + (2 * p1[0] + + (-p0[0] + p2[0]) * t + + (2 * p0[0] - 5 * p1[0] + 4 * p2[0] - p3[0]) * t2 + + (-p0[0] + 3 * p1[0] - 3 * p2[0] + p3[0]) * t3); + + const y = + 0.5 * + (2 * p1[1] + + (-p0[1] + p2[1]) * t + + (2 * p0[1] - 5 * p1[1] + 4 * p2[1] - p3[1]) * t2 + + (-p0[1] + 3 * p1[1] - 3 * p2[1] + p3[1]) * t3); + + ctx.lineTo(x, y); + } + } +} + +export const drawHighlightForRectWithRotation = ( + context: CanvasRenderingContext2D, + element: ExcalidrawRectanguloidElement, + padding: number, +) => { + const [x, y] = pointRotateRads( + pointFrom(element.x, element.y), + elementCenterPoint(element), + element.angle, + ); + + context.save(); + context.translate(x, y); + context.rotate(element.angle); + + let radius = getCornerRadius( + Math.min(element.width, element.height), + element, + ); + if (radius === 0) { + radius = 0.01; + } + + context.beginPath(); + + { + const topLeftApprox = offsetQuadraticBezier( + pointFrom(0, 0 + radius), + pointFrom(0, 0), + pointFrom(0 + radius, 0), + padding, + ); + const topRightApprox = offsetQuadraticBezier( + pointFrom(element.width - radius, 0), + pointFrom(element.width, 0), + pointFrom(element.width, radius), + padding, + ); + const bottomRightApprox = offsetQuadraticBezier( + pointFrom(element.width, element.height - radius), + pointFrom(element.width, element.height), + pointFrom(element.width - radius, element.height), + padding, + ); + const bottomLeftApprox = offsetQuadraticBezier( + pointFrom(radius, element.height), + pointFrom(0, element.height), + pointFrom(0, element.height - radius), + padding, + ); + + context.moveTo( + topLeftApprox[topLeftApprox.length - 1][0], + topLeftApprox[topLeftApprox.length - 1][1], + ); + context.lineTo(topRightApprox[0][0], topRightApprox[0][1]); + drawCatmullRomQuadraticApprox(context, topRightApprox); + context.lineTo(bottomRightApprox[0][0], bottomRightApprox[0][1]); + drawCatmullRomQuadraticApprox(context, bottomRightApprox); + context.lineTo(bottomLeftApprox[0][0], bottomLeftApprox[0][1]); + drawCatmullRomQuadraticApprox(context, bottomLeftApprox); + context.lineTo(topLeftApprox[0][0], topLeftApprox[0][1]); + drawCatmullRomQuadraticApprox(context, topLeftApprox); + } + + // Counter-clockwise for the cutout in the middle. We need to have an "inverse + // mask" on a filled shape for the diamond highlight, because stroking creates + // sharp inset edges on line joins < 90 degrees. + { + const topLeftApprox = offsetQuadraticBezier( + pointFrom(0 + radius, 0), + pointFrom(0, 0), + pointFrom(0, 0 + radius), + -FIXED_BINDING_DISTANCE, + ); + const topRightApprox = offsetQuadraticBezier( + pointFrom(element.width, radius), + pointFrom(element.width, 0), + pointFrom(element.width - radius, 0), + -FIXED_BINDING_DISTANCE, + ); + const bottomRightApprox = offsetQuadraticBezier( + pointFrom(element.width - radius, element.height), + pointFrom(element.width, element.height), + pointFrom(element.width, element.height - radius), + -FIXED_BINDING_DISTANCE, + ); + const bottomLeftApprox = offsetQuadraticBezier( + pointFrom(0, element.height - radius), + pointFrom(0, element.height), + pointFrom(radius, element.height), + -FIXED_BINDING_DISTANCE, + ); + + context.moveTo( + topLeftApprox[topLeftApprox.length - 1][0], + topLeftApprox[topLeftApprox.length - 1][1], + ); + context.lineTo(bottomLeftApprox[0][0], bottomLeftApprox[0][1]); + drawCatmullRomQuadraticApprox(context, bottomLeftApprox); + context.lineTo(bottomRightApprox[0][0], bottomRightApprox[0][1]); + drawCatmullRomQuadraticApprox(context, bottomRightApprox); + context.lineTo(topRightApprox[0][0], topRightApprox[0][1]); + drawCatmullRomQuadraticApprox(context, topRightApprox); + context.lineTo(topLeftApprox[0][0], topLeftApprox[0][1]); + drawCatmullRomQuadraticApprox(context, topLeftApprox); + } + + context.closePath(); + context.fill(); + + context.restore(); +}; + +export const strokeEllipseWithRotation = ( + context: CanvasRenderingContext2D, + width: number, + height: number, + cx: number, + cy: number, + angle: number, +) => { + context.beginPath(); + context.ellipse(cx, cy, width / 2, height / 2, angle, 0, Math.PI * 2); + context.stroke(); +}; + +export const strokeRectWithRotation = ( + context: CanvasRenderingContext2D, + x: number, + y: number, + width: number, + height: number, + cx: number, + cy: number, + angle: number, + fill: boolean = false, + /** should account for zoom */ + radius: number = 0, +) => { + context.save(); + context.translate(cx, cy); + context.rotate(angle); + if (fill) { + context.fillRect(x - cx, y - cy, width, height); + } + if (radius && context.roundRect) { + context.beginPath(); + context.roundRect(x - cx, y - cy, width, height, radius); + context.stroke(); + context.closePath(); + } else { + context.strokeRect(x - cx, y - cy, width, height); + } + context.restore(); +}; + +export const drawHighlightForDiamondWithRotation = ( + context: CanvasRenderingContext2D, + padding: number, + element: ExcalidrawDiamondElement, +) => { + const [x, y] = pointRotateRads( + pointFrom(element.x, element.y), + elementCenterPoint(element), + element.angle, + ); + context.save(); + context.translate(x, y); + context.rotate(element.angle); + + { + context.beginPath(); + + const [topX, topY, rightX, rightY, bottomX, bottomY, leftX, leftY] = + getDiamondPoints(element); + const verticalRadius = element.roundness + ? getCornerRadius(Math.abs(topX - leftX), element) + : (topX - leftX) * 0.01; + const horizontalRadius = element.roundness + ? getCornerRadius(Math.abs(rightY - topY), element) + : (rightY - topY) * 0.01; + const topApprox = offsetCubicBezier( + pointFrom(topX - verticalRadius, topY + horizontalRadius), + pointFrom(topX, topY), + pointFrom(topX, topY), + pointFrom(topX + verticalRadius, topY + horizontalRadius), + padding, + ); + const rightApprox = offsetCubicBezier( + pointFrom(rightX - verticalRadius, rightY - horizontalRadius), + pointFrom(rightX, rightY), + pointFrom(rightX, rightY), + pointFrom(rightX - verticalRadius, rightY + horizontalRadius), + padding, + ); + const bottomApprox = offsetCubicBezier( + pointFrom(bottomX + verticalRadius, bottomY - horizontalRadius), + pointFrom(bottomX, bottomY), + pointFrom(bottomX, bottomY), + pointFrom(bottomX - verticalRadius, bottomY - horizontalRadius), + padding, + ); + const leftApprox = offsetCubicBezier( + pointFrom(leftX + verticalRadius, leftY + horizontalRadius), + pointFrom(leftX, leftY), + pointFrom(leftX, leftY), + pointFrom(leftX + verticalRadius, leftY - horizontalRadius), + padding, + ); + + context.moveTo( + topApprox[topApprox.length - 1][0], + topApprox[topApprox.length - 1][1], + ); + context.lineTo(rightApprox[0][0], rightApprox[0][1]); + drawCatmullRomCubicApprox(context, rightApprox); + context.lineTo(bottomApprox[0][0], bottomApprox[0][1]); + drawCatmullRomCubicApprox(context, bottomApprox); + context.lineTo(leftApprox[0][0], leftApprox[0][1]); + drawCatmullRomCubicApprox(context, leftApprox); + context.lineTo(topApprox[0][0], topApprox[0][1]); + drawCatmullRomCubicApprox(context, topApprox); + } + + // Counter-clockwise for the cutout in the middle. We need to have an "inverse + // mask" on a filled shape for the diamond highlight, because stroking creates + // sharp inset edges on line joins < 90 degrees. + { + const [topX, topY, rightX, rightY, bottomX, bottomY, leftX, leftY] = + getDiamondPoints(element); + const verticalRadius = element.roundness + ? getCornerRadius(Math.abs(topX - leftX), element) + : (topX - leftX) * 0.01; + const horizontalRadius = element.roundness + ? getCornerRadius(Math.abs(rightY - topY), element) + : (rightY - topY) * 0.01; + const topApprox = offsetCubicBezier( + pointFrom(topX + verticalRadius, topY + horizontalRadius), + pointFrom(topX, topY), + pointFrom(topX, topY), + pointFrom(topX - verticalRadius, topY + horizontalRadius), + -FIXED_BINDING_DISTANCE, + ); + const rightApprox = offsetCubicBezier( + pointFrom(rightX - verticalRadius, rightY + horizontalRadius), + pointFrom(rightX, rightY), + pointFrom(rightX, rightY), + pointFrom(rightX - verticalRadius, rightY - horizontalRadius), + -FIXED_BINDING_DISTANCE, + ); + const bottomApprox = offsetCubicBezier( + pointFrom(bottomX - verticalRadius, bottomY - horizontalRadius), + pointFrom(bottomX, bottomY), + pointFrom(bottomX, bottomY), + pointFrom(bottomX + verticalRadius, bottomY - horizontalRadius), + -FIXED_BINDING_DISTANCE, + ); + const leftApprox = offsetCubicBezier( + pointFrom(leftX + verticalRadius, leftY - horizontalRadius), + pointFrom(leftX, leftY), + pointFrom(leftX, leftY), + pointFrom(leftX + verticalRadius, leftY + horizontalRadius), + -FIXED_BINDING_DISTANCE, + ); + + context.moveTo( + topApprox[topApprox.length - 1][0], + topApprox[topApprox.length - 1][1], + ); + context.lineTo(leftApprox[0][0], leftApprox[0][1]); + drawCatmullRomCubicApprox(context, leftApprox); + context.lineTo(bottomApprox[0][0], bottomApprox[0][1]); + drawCatmullRomCubicApprox(context, bottomApprox); + context.lineTo(rightApprox[0][0], rightApprox[0][1]); + drawCatmullRomCubicApprox(context, rightApprox); + context.lineTo(topApprox[0][0], topApprox[0][1]); + drawCatmullRomCubicApprox(context, topApprox); + } + context.closePath(); + context.fill(); + context.restore(); +}; + +function offsetCubicBezier( + p0: GlobalPoint, + p1: GlobalPoint, + p2: GlobalPoint, + p3: GlobalPoint, + offsetDist: number, + steps = 20, +) { + const offsetPoints = []; + + for (let i = 0; i <= steps; i++) { + const t = i / steps; + const c = curve(p0, p1, p2, p3); + const point = bezierEquation(c, t); + const tangent = vectorNormalize(curveTangent(c, t)); + const normal = vectorNormal(tangent); + + offsetPoints.push(pointFromVector(vectorScale(normal, offsetDist), point)); + } + + return offsetPoints; +} + +function offsetQuadraticBezier( + p0: GlobalPoint, + p1: GlobalPoint, + p2: GlobalPoint, + offsetDist: number, + steps = 20, +) { + const offsetPoints = []; + + for (let i = 0; i <= steps; i++) { + const t = i / steps; + const t1 = 1 - t; + const point = pointFrom( + t1 * t1 * p0[0] + 2 * t1 * t * p1[0] + t * t * p2[0], + t1 * t1 * p0[1] + 2 * t1 * t * p1[1] + t * t * p2[1], + ); + const tangentX = 2 * (1 - t) * (p1[0] - p0[0]) + 2 * t * (p2[0] - p1[0]); + const tangentY = 2 * (1 - t) * (p1[1] - p0[1]) + 2 * t * (p2[1] - p1[1]); + const tangent = vectorNormalize(vector(tangentX, tangentY)); + const normal = vectorNormal(tangent); + + offsetPoints.push(pointFromVector(vectorScale(normal, offsetDist), point)); + } + + return offsetPoints; +} diff --git a/packages/excalidraw/renderer/interactiveScene.ts b/packages/excalidraw/renderer/interactiveScene.ts index 5d84a150d..407fb5a11 100644 --- a/packages/excalidraw/renderer/interactiveScene.ts +++ b/packages/excalidraw/renderer/interactiveScene.ts @@ -1,4 +1,3 @@ -import oc from "open-color"; import { pointFrom, pointsEqual, @@ -6,19 +5,19 @@ import { type LocalPoint, type Radians, } from "@excalidraw/math"; +import oc from "open-color"; import { + arrayToMap, DEFAULT_TRANSFORM_HANDLE_SPACING, FRAME_STYLE, - THEME, - arrayToMap, invariant, + THEME, throttleRAF, } from "@excalidraw/common"; import { - BINDING_HIGHLIGHT_OFFSET, - BINDING_HIGHLIGHT_THICKNESS, + FIXED_BINDING_DISTANCE, maxBindingGap, } from "@excalidraw/element/binding"; import { LinearElementEditor } from "@excalidraw/element/linearElementEditor"; @@ -37,14 +36,12 @@ import { isTextElement, } from "@excalidraw/element/typeChecks"; -import { getCornerRadius } from "@excalidraw/element/shapes"; - import { renderSelectionElement } from "@excalidraw/element/renderElement"; import { - isSelectedViaGroup, - getSelectedGroupIds, getElementsInGroup, + getSelectedGroupIds, + isSelectedViaGroup, selectGroupsFromGivenElements, } from "@excalidraw/element/groups"; @@ -88,8 +85,12 @@ import { getClientColor, renderRemoteCursors } from "../clients"; import { bootstrapCanvas, + drawHighlightForDiamondWithRotation, + drawHighlightForRectWithRotation, fillCircle, getNormalizedCanvasDimensions, + strokeEllipseWithRotation, + strokeRectWithRotation, } from "./helpers"; import type { @@ -162,57 +163,6 @@ const highlightPoint = ( ); }; -const strokeRectWithRotation = ( - context: CanvasRenderingContext2D, - x: number, - y: number, - width: number, - height: number, - cx: number, - cy: number, - angle: number, - fill: boolean = false, - /** should account for zoom */ - radius: number = 0, -) => { - context.save(); - context.translate(cx, cy); - context.rotate(angle); - if (fill) { - context.fillRect(x - cx, y - cy, width, height); - } - if (radius && context.roundRect) { - context.beginPath(); - context.roundRect(x - cx, y - cy, width, height, radius); - context.stroke(); - context.closePath(); - } else { - context.strokeRect(x - cx, y - cy, width, height); - } - context.restore(); -}; - -const strokeDiamondWithRotation = ( - context: CanvasRenderingContext2D, - width: number, - height: number, - cx: number, - cy: number, - angle: number, -) => { - context.save(); - context.translate(cx, cy); - context.rotate(angle); - context.beginPath(); - context.moveTo(0, height / 2); - context.lineTo(width / 2, 0); - context.lineTo(0, -height / 2); - context.lineTo(-width / 2, 0); - context.closePath(); - context.stroke(); - context.restore(); -}; - const renderSingleLinearPoint = ( context: CanvasRenderingContext2D, appState: InteractiveCanvasAppState, @@ -243,19 +193,6 @@ const renderSingleLinearPoint = ( ); }; -const strokeEllipseWithRotation = ( - context: CanvasRenderingContext2D, - width: number, - height: number, - cx: number, - cy: number, - angle: number, -) => { - context.beginPath(); - context.ellipse(cx, cy, width / 2, height / 2, angle, 0, Math.PI * 2); - context.stroke(); -}; - const renderBindingHighlightForBindableElement = ( context: CanvasRenderingContext2D, element: ExcalidrawBindableElement, @@ -267,16 +204,10 @@ const renderBindingHighlightForBindableElement = ( const height = y2 - y1; context.strokeStyle = "rgba(0,0,0,.05)"; - // When zooming out, make line width greater for visibility - const zoomValue = zoom.value < 1 ? zoom.value : 1; - context.lineWidth = BINDING_HIGHLIGHT_THICKNESS / zoomValue; - // To ensure the binding highlight doesn't overlap the element itself - const padding = context.lineWidth / 2 + BINDING_HIGHLIGHT_OFFSET; + context.fillStyle = "rgba(0,0,0,.05)"; - const radius = getCornerRadius( - Math.min(element.width, element.height), - element, - ); + // To ensure the binding highlight doesn't overlap the element itself + const padding = maxBindingGap(element, element.width, element.height, zoom); switch (element.type) { case "rectangle": @@ -286,37 +217,20 @@ const renderBindingHighlightForBindableElement = ( case "embeddable": case "frame": case "magicframe": - strokeRectWithRotation( - context, - x1 - padding, - y1 - padding, - width + padding * 2, - height + padding * 2, - x1 + width / 2, - y1 + height / 2, - element.angle, - undefined, - radius, - ); + drawHighlightForRectWithRotation(context, element, padding); break; case "diamond": - const side = Math.hypot(width, height); - const wPadding = (padding * side) / height; - const hPadding = (padding * side) / width; - strokeDiamondWithRotation( - context, - width + wPadding * 2, - height + hPadding * 2, - x1 + width / 2, - y1 + height / 2, - element.angle, - ); + drawHighlightForDiamondWithRotation(context, padding, element); break; case "ellipse": + context.lineWidth = + maxBindingGap(element, element.width, element.height, zoom) - + FIXED_BINDING_DISTANCE; + strokeEllipseWithRotation( context, - width + padding * 2, - height + padding * 2, + width + padding + FIXED_BINDING_DISTANCE, + height + padding + FIXED_BINDING_DISTANCE, x1 + width / 2, y1 + height / 2, element.angle, diff --git a/packages/excalidraw/store.ts b/packages/excalidraw/store.ts deleted file mode 100644 index 7a5590e54..000000000 --- a/packages/excalidraw/store.ts +++ /dev/null @@ -1,449 +0,0 @@ -import { isDevEnv, isShallowEqual, isTestEnv } from "@excalidraw/common"; - -import { deepCopyElement } from "@excalidraw/element/duplicate"; - -import { newElementWith } from "@excalidraw/element/mutateElement"; - -import type { OrderedExcalidrawElement } from "@excalidraw/element/types"; - -import type { ValueOf } from "@excalidraw/common/utility-types"; - -import { getDefaultAppState } from "./appState"; -import { AppStateChange, ElementsChange } from "./change"; - -import { Emitter } from "./emitter"; - -import type { AppState, ObservedAppState } from "./types"; - -// hidden non-enumerable property for runtime checks -const hiddenObservedAppStateProp = "__observedAppState"; - -export const getObservedAppState = (appState: AppState): ObservedAppState => { - const observedAppState = { - name: appState.name, - editingGroupId: appState.editingGroupId, - viewBackgroundColor: appState.viewBackgroundColor, - selectedElementIds: appState.selectedElementIds, - selectedGroupIds: appState.selectedGroupIds, - editingLinearElementId: appState.editingLinearElement?.elementId || null, - selectedLinearElementId: appState.selectedLinearElement?.elementId || null, - croppingElementId: appState.croppingElementId, - }; - - Reflect.defineProperty(observedAppState, hiddenObservedAppStateProp, { - value: true, - enumerable: false, - }); - - return observedAppState; -}; - -const isObservedAppState = ( - appState: AppState | ObservedAppState, -): appState is ObservedAppState => - !!Reflect.get(appState, hiddenObservedAppStateProp); - -export const CaptureUpdateAction = { - /** - * Immediately undoable. - * - * Use for updates which should be captured. - * Should be used for most of the local updates. - * - * These updates will _immediately_ make it to the local undo / redo stacks. - */ - IMMEDIATELY: "IMMEDIATELY", - /** - * Never undoable. - * - * Use for updates which should never be recorded, such as remote updates - * or scene initialization. - * - * These updates will _never_ make it to the local undo / redo stacks. - */ - NEVER: "NEVER", - /** - * Eventually undoable. - * - * Use for updates which should not be captured immediately - likely - * exceptions which are part of some async multi-step process. Otherwise, all - * such updates would end up being captured with the next - * `CaptureUpdateAction.IMMEDIATELY` - triggered either by the next `updateScene` - * or internally by the editor. - * - * These updates will _eventually_ make it to the local undo / redo stacks. - */ - EVENTUALLY: "EVENTUALLY", -} as const; - -export type CaptureUpdateActionType = ValueOf; - -/** - * Represent an increment to the Store. - */ -class StoreIncrementEvent { - constructor( - public readonly elementsChange: ElementsChange, - public readonly appStateChange: AppStateChange, - ) {} -} - -/** - * Store which captures the observed changes and emits them as `StoreIncrementEvent` events. - * - * @experimental this interface is experimental and subject to change. - */ -export interface IStore { - onStoreIncrementEmitter: Emitter<[StoreIncrementEvent]>; - get snapshot(): Snapshot; - set snapshot(snapshot: Snapshot); - - /** - * Use to schedule update of the snapshot, useful on updates for which we don't need to calculate increments (i.e. remote updates). - */ - shouldUpdateSnapshot(): void; - - /** - * Use to schedule calculation of a store increment. - */ - shouldCaptureIncrement(): void; - - /** - * Based on the scheduled operation, either only updates store snapshot or also calculates increment and emits the result as a `StoreIncrementEvent`. - * - * @emits StoreIncrementEvent when increment is calculated. - */ - commit( - elements: Map | undefined, - appState: AppState | ObservedAppState | undefined, - ): void; - - /** - * Clears the store instance. - */ - clear(): void; - - /** - * Filters out yet uncomitted elements from `nextElements`, which are part of in-progress local async actions (ephemerals) and thus were not yet commited to the snapshot. - * - * This is necessary in updates in which we receive reconciled elements, already containing elements which were not yet captured by the local store (i.e. collab). - */ - filterUncomittedElements( - prevElements: Map, - nextElements: Map, - ): Map; -} - -export class Store implements IStore { - public readonly onStoreIncrementEmitter = new Emitter< - [StoreIncrementEvent] - >(); - - private scheduledActions: Set = new Set(); - private _snapshot = Snapshot.empty(); - - public get snapshot() { - return this._snapshot; - } - - public set snapshot(snapshot: Snapshot) { - this._snapshot = snapshot; - } - - // TODO: Suspicious that this is called so many places. Seems error-prone. - public shouldCaptureIncrement = () => { - this.scheduleAction(CaptureUpdateAction.IMMEDIATELY); - }; - - public shouldUpdateSnapshot = () => { - this.scheduleAction(CaptureUpdateAction.NEVER); - }; - - private scheduleAction = (action: CaptureUpdateActionType) => { - this.scheduledActions.add(action); - this.satisfiesScheduledActionsInvariant(); - }; - - public commit = ( - elements: Map | undefined, - appState: AppState | ObservedAppState | undefined, - ): void => { - try { - // Capture has precedence since it also performs update - if (this.scheduledActions.has(CaptureUpdateAction.IMMEDIATELY)) { - this.captureIncrement(elements, appState); - } else if (this.scheduledActions.has(CaptureUpdateAction.NEVER)) { - this.updateSnapshot(elements, appState); - } - } finally { - this.satisfiesScheduledActionsInvariant(); - // Defensively reset all scheduled actions, potentially cleans up other runtime garbage - this.scheduledActions = new Set(); - } - }; - - public captureIncrement = ( - elements: Map | undefined, - appState: AppState | ObservedAppState | undefined, - ) => { - const prevSnapshot = this.snapshot; - const nextSnapshot = this.snapshot.maybeClone(elements, appState); - - // Optimisation, don't continue if nothing has changed - if (prevSnapshot !== nextSnapshot) { - // Calculate and record the changes based on the previous and next snapshot - const elementsChange = nextSnapshot.meta.didElementsChange - ? ElementsChange.calculate(prevSnapshot.elements, nextSnapshot.elements) - : ElementsChange.empty(); - - const appStateChange = nextSnapshot.meta.didAppStateChange - ? AppStateChange.calculate(prevSnapshot.appState, nextSnapshot.appState) - : AppStateChange.empty(); - - if (!elementsChange.isEmpty() || !appStateChange.isEmpty()) { - // Notify listeners with the increment - this.onStoreIncrementEmitter.trigger( - new StoreIncrementEvent(elementsChange, appStateChange), - ); - } - - // Update snapshot - this.snapshot = nextSnapshot; - } - }; - - public updateSnapshot = ( - elements: Map | undefined, - appState: AppState | ObservedAppState | undefined, - ) => { - const nextSnapshot = this.snapshot.maybeClone(elements, appState); - - if (this.snapshot !== nextSnapshot) { - // Update snapshot - this.snapshot = nextSnapshot; - } - }; - - public filterUncomittedElements = ( - prevElements: Map, - nextElements: Map, - ) => { - for (const [id, prevElement] of prevElements.entries()) { - const nextElement = nextElements.get(id); - - if (!nextElement) { - // Nothing to care about here, elements were forcefully deleted - continue; - } - - const elementSnapshot = this.snapshot.elements.get(id); - - // Checks for in progress async user action - if (!elementSnapshot) { - // Detected yet uncomitted local element - nextElements.delete(id); - } else if (elementSnapshot.version < prevElement.version) { - // Element was already commited, but the snapshot version is lower than current current local version - nextElements.set(id, elementSnapshot); - } - } - - return nextElements; - }; - - public clear = (): void => { - this.snapshot = Snapshot.empty(); - this.scheduledActions = new Set(); - }; - - private satisfiesScheduledActionsInvariant = () => { - if (!(this.scheduledActions.size >= 0 && this.scheduledActions.size <= 3)) { - const message = `There can be at most three store actions scheduled at the same time, but there are "${this.scheduledActions.size}".`; - console.error(message, this.scheduledActions.values()); - - if (isTestEnv() || isDevEnv()) { - throw new Error(message); - } - } - }; -} - -export class Snapshot { - private constructor( - public readonly elements: Map, - public readonly appState: ObservedAppState, - public readonly meta: { - didElementsChange: boolean; - didAppStateChange: boolean; - isEmpty?: boolean; - } = { - didElementsChange: false, - didAppStateChange: false, - isEmpty: false, - }, - ) {} - - public static empty() { - return new Snapshot( - new Map(), - getObservedAppState(getDefaultAppState() as AppState), - { didElementsChange: false, didAppStateChange: false, isEmpty: true }, - ); - } - - public isEmpty() { - return this.meta.isEmpty; - } - - /** - * Efficiently clone the existing snapshot, only if we detected changes. - * - * @returns same instance if there are no changes detected, new instance otherwise. - */ - public maybeClone( - elements: Map | undefined, - appState: AppState | ObservedAppState | undefined, - ) { - const nextElementsSnapshot = this.maybeCreateElementsSnapshot(elements); - const nextAppStateSnapshot = this.maybeCreateAppStateSnapshot(appState); - - let didElementsChange = false; - let didAppStateChange = false; - - if (this.elements !== nextElementsSnapshot) { - didElementsChange = true; - } - - if (this.appState !== nextAppStateSnapshot) { - didAppStateChange = true; - } - - if (!didElementsChange && !didAppStateChange) { - return this; - } - - const snapshot = new Snapshot(nextElementsSnapshot, nextAppStateSnapshot, { - didElementsChange, - didAppStateChange, - }); - - return snapshot; - } - - private maybeCreateAppStateSnapshot( - appState: AppState | ObservedAppState | undefined, - ) { - if (!appState) { - return this.appState; - } - - // Not watching over everything from the app state, just the relevant props - const nextAppStateSnapshot = !isObservedAppState(appState) - ? getObservedAppState(appState) - : appState; - - const didAppStateChange = this.detectChangedAppState(nextAppStateSnapshot); - - if (!didAppStateChange) { - return this.appState; - } - - return nextAppStateSnapshot; - } - - private detectChangedAppState(nextObservedAppState: ObservedAppState) { - return !isShallowEqual(this.appState, nextObservedAppState, { - selectedElementIds: isShallowEqual, - selectedGroupIds: isShallowEqual, - }); - } - - private maybeCreateElementsSnapshot( - elements: Map | undefined, - ) { - if (!elements) { - return this.elements; - } - - const didElementsChange = this.detectChangedElements(elements); - - if (!didElementsChange) { - return this.elements; - } - - const elementsSnapshot = this.createElementsSnapshot(elements); - return elementsSnapshot; - } - - /** - * Detect if there any changed elements. - * - * NOTE: we shouldn't just use `sceneVersionNonce` instead, as we need to call this before the scene updates. - */ - private detectChangedElements( - nextElements: Map, - ) { - if (this.elements === nextElements) { - return false; - } - - if (this.elements.size !== nextElements.size) { - return true; - } - - // loop from right to left as changes are likelier to happen on new elements - const keys = Array.from(nextElements.keys()); - - for (let i = keys.length - 1; i >= 0; i--) { - const prev = this.elements.get(keys[i]); - const next = nextElements.get(keys[i]); - if ( - !prev || - !next || - prev.id !== next.id || - prev.versionNonce !== next.versionNonce - ) { - return true; - } - } - - return false; - } - - /** - * Perform structural clone, cloning only elements that changed. - */ - private createElementsSnapshot( - nextElements: Map, - ) { - const clonedElements = new Map(); - - for (const [id, prevElement] of this.elements.entries()) { - // Clone previous elements, never delete, in case nextElements would be just a subset of previous elements - // i.e. during collab, persist or whenenever isDeleted elements get cleared - if (!nextElements.get(id)) { - // When we cannot find the prev element in the next elements, we mark it as deleted - clonedElements.set( - id, - newElementWith(prevElement, { isDeleted: true }), - ); - } else { - clonedElements.set(id, prevElement); - } - } - - for (const [id, nextElement] of nextElements.entries()) { - const prevElement = clonedElements.get(id); - - // At this point our elements are reconcilled already, meaning the next element is always newer - if ( - !prevElement || // element was added - (prevElement && prevElement.versionNonce !== nextElement.versionNonce) // element was updated - ) { - clonedElements.set(id, deepCopyElement(nextElement)); - } - } - - return clonedElements; - } -} diff --git a/packages/excalidraw/tests/__snapshots__/contextmenu.test.tsx.snap b/packages/excalidraw/tests/__snapshots__/contextmenu.test.tsx.snap index 349dd9e64..87fb23abd 100644 --- a/packages/excalidraw/tests/__snapshots__/contextmenu.test.tsx.snap +++ b/packages/excalidraw/tests/__snapshots__/contextmenu.test.tsx.snap @@ -1067,23 +1067,14 @@ exports[`contextMenu element > right-clicking on a group should select whole gro } `; -exports[`contextMenu element > right-clicking on a group should select whole group > [end of test] history 1`] = ` -History { - "onHistoryChangedEmitter": Emitter { - "subscribers": [ - [Function], - [Function], - ], - }, - "redoStack": [], - "undoStack": [], -} -`; - 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`] = `5`; +exports[`contextMenu element > right-clicking on a group should select whole group > [end of test] redo stack 1`] = `[]`; + +exports[`contextMenu element > right-clicking on a group should select whole group > [end of test] undo stack 1`] = `[]`; + exports[`contextMenu element > selecting 'Add to library' in context menu adds element to library > [end of test] appState 1`] = ` { "activeEmbeddable": null, @@ -1235,75 +1226,69 @@ exports[`contextMenu element > selecting 'Add to library' in context menu adds e } `; -exports[`contextMenu element > selecting 'Add to library' in context menu adds element to library > [end of test] history 1`] = ` -History { - "onHistoryChangedEmitter": Emitter { - "subscribers": [ - [Function], - [Function], - ], - }, - "redoStack": [], - "undoStack": [ - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": { - "selectedElementIds": { - "id0": true, - }, - }, - "inserted": { - "selectedElementIds": {}, - }, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map { - "id0" => Delta { - "deleted": { - "angle": 0, - "backgroundColor": "transparent", - "boundElements": null, - "customData": undefined, - "fillStyle": "solid", - "frameId": null, - "groupIds": [], - "height": 20, - "index": "a0", - "isDeleted": false, - "link": null, - "locked": false, - "opacity": 100, - "roughness": 1, - "roundness": { - "type": 3, - }, - "strokeColor": "#1e1e1e", - "strokeStyle": "solid", - "strokeWidth": 2, - "type": "rectangle", - "width": 20, - "x": -10, - "y": 0, - }, - "inserted": { - "isDeleted": true, - }, - }, - }, - "updated": Map {}, - }, - }, - ], -} -`; - 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`] = `5`; +exports[`contextMenu element > selecting 'Add to library' in context menu adds element to library > [end of test] redo stack 1`] = `[]`; + +exports[`contextMenu element > selecting 'Add to library' in context menu adds element to library > [end of test] undo stack 1`] = ` +[ + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": { + "selectedElementIds": { + "id0": true, + }, + }, + "inserted": { + "selectedElementIds": {}, + }, + }, + }, + "elements": { + "added": {}, + "removed": { + "id0": { + "deleted": { + "angle": 0, + "backgroundColor": "transparent", + "boundElements": null, + "customData": undefined, + "fillStyle": "solid", + "frameId": null, + "groupIds": [], + "height": 20, + "index": "a0", + "isDeleted": false, + "link": null, + "locked": false, + "opacity": 100, + "roughness": 1, + "roundness": { + "type": 3, + }, + "strokeColor": "#1e1e1e", + "strokeStyle": "solid", + "strokeWidth": 2, + "type": "rectangle", + "width": 20, + "x": -10, + "y": 0, + }, + "inserted": { + "isDeleted": true, + }, + }, + }, + "updated": {}, + }, + "id": "id2", + }, +] +`; + exports[`contextMenu element > selecting 'Bring forward' in context menu brings element forward > [end of test] appState 1`] = ` { "activeEmbeddable": null, @@ -1429,7 +1414,7 @@ exports[`contextMenu element > selecting 'Bring forward' in context menu brings "frameId": null, "groupIds": [], "height": 20, - "id": "id1", + "id": "id3", "index": "a1", "isDeleted": false, "link": null, @@ -1487,158 +1472,154 @@ exports[`contextMenu element > selecting 'Bring forward' in context menu brings } `; -exports[`contextMenu element > selecting 'Bring forward' in context menu brings element forward > [end of test] history 1`] = ` -History { - "onHistoryChangedEmitter": Emitter { - "subscribers": [ - [Function], - [Function], - ], - }, - "redoStack": [], - "undoStack": [ - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": { - "selectedElementIds": { - "id0": true, - }, - }, - "inserted": { - "selectedElementIds": {}, - }, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map { - "id0" => Delta { - "deleted": { - "angle": 0, - "backgroundColor": "transparent", - "boundElements": null, - "customData": undefined, - "fillStyle": "solid", - "frameId": null, - "groupIds": [], - "height": 20, - "index": "a0", - "isDeleted": false, - "link": null, - "locked": false, - "opacity": 100, - "roughness": 1, - "roundness": { - "type": 3, - }, - "strokeColor": "#1e1e1e", - "strokeStyle": "solid", - "strokeWidth": 2, - "type": "rectangle", - "width": 20, - "x": -10, - "y": 0, - }, - "inserted": { - "isDeleted": true, - }, - }, - }, - "updated": Map {}, - }, - }, - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": { - "selectedElementIds": { - "id1": true, - }, - }, - "inserted": { - "selectedElementIds": { - "id0": true, - }, - }, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map { - "id1" => Delta { - "deleted": { - "angle": 0, - "backgroundColor": "transparent", - "boundElements": null, - "customData": undefined, - "fillStyle": "solid", - "frameId": null, - "groupIds": [], - "height": 20, - "index": "a1", - "isDeleted": false, - "link": null, - "locked": false, - "opacity": 100, - "roughness": 1, - "roundness": { - "type": 3, - }, - "strokeColor": "#1e1e1e", - "strokeStyle": "solid", - "strokeWidth": 2, - "type": "rectangle", - "width": 20, - "x": 20, - "y": 30, - }, - "inserted": { - "isDeleted": true, - }, - }, - }, - "updated": Map {}, - }, - }, - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": { - "selectedElementIds": { - "id0": true, - }, - }, - "inserted": { - "selectedElementIds": { - "id1": true, - }, - }, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map {}, - "updated": Map { - "id0" => Delta { - "deleted": { - "index": "a2", - }, - "inserted": { - "index": "a0", - }, - }, - }, - }, - }, - ], -} -`; - 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`] = `10`; +exports[`contextMenu element > selecting 'Bring forward' in context menu brings element forward > [end of test] redo stack 1`] = `[]`; + +exports[`contextMenu element > selecting 'Bring forward' in context menu brings element forward > [end of test] undo stack 1`] = ` +[ + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": { + "selectedElementIds": { + "id0": true, + }, + }, + "inserted": { + "selectedElementIds": {}, + }, + }, + }, + "elements": { + "added": {}, + "removed": { + "id0": { + "deleted": { + "angle": 0, + "backgroundColor": "transparent", + "boundElements": null, + "customData": undefined, + "fillStyle": "solid", + "frameId": null, + "groupIds": [], + "height": 20, + "index": "a0", + "isDeleted": false, + "link": null, + "locked": false, + "opacity": 100, + "roughness": 1, + "roundness": { + "type": 3, + }, + "strokeColor": "#1e1e1e", + "strokeStyle": "solid", + "strokeWidth": 2, + "type": "rectangle", + "width": 20, + "x": -10, + "y": 0, + }, + "inserted": { + "isDeleted": true, + }, + }, + }, + "updated": {}, + }, + "id": "id2", + }, + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": { + "selectedElementIds": { + "id3": true, + }, + }, + "inserted": { + "selectedElementIds": { + "id0": true, + }, + }, + }, + }, + "elements": { + "added": {}, + "removed": { + "id3": { + "deleted": { + "angle": 0, + "backgroundColor": "transparent", + "boundElements": null, + "customData": undefined, + "fillStyle": "solid", + "frameId": null, + "groupIds": [], + "height": 20, + "index": "a1", + "isDeleted": false, + "link": null, + "locked": false, + "opacity": 100, + "roughness": 1, + "roundness": { + "type": 3, + }, + "strokeColor": "#1e1e1e", + "strokeStyle": "solid", + "strokeWidth": 2, + "type": "rectangle", + "width": 20, + "x": 20, + "y": 30, + }, + "inserted": { + "isDeleted": true, + }, + }, + }, + "updated": {}, + }, + "id": "id5", + }, + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": { + "selectedElementIds": { + "id0": true, + }, + }, + "inserted": { + "selectedElementIds": { + "id3": true, + }, + }, + }, + }, + "elements": { + "added": {}, + "removed": {}, + "updated": { + "id0": { + "deleted": { + "index": "a2", + }, + "inserted": { + "index": "a0", + }, + }, + }, + }, + "id": "id7", + }, +] +`; + exports[`contextMenu element > selecting 'Bring to front' in context menu brings element to front > [end of test] appState 1`] = ` { "activeEmbeddable": null, @@ -1764,7 +1745,7 @@ exports[`contextMenu element > selecting 'Bring to front' in context menu brings "frameId": null, "groupIds": [], "height": 20, - "id": "id1", + "id": "id3", "index": "a1", "isDeleted": false, "link": null, @@ -1822,158 +1803,154 @@ exports[`contextMenu element > selecting 'Bring to front' in context menu brings } `; -exports[`contextMenu element > selecting 'Bring to front' in context menu brings element to front > [end of test] history 1`] = ` -History { - "onHistoryChangedEmitter": Emitter { - "subscribers": [ - [Function], - [Function], - ], - }, - "redoStack": [], - "undoStack": [ - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": { - "selectedElementIds": { - "id0": true, - }, - }, - "inserted": { - "selectedElementIds": {}, - }, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map { - "id0" => Delta { - "deleted": { - "angle": 0, - "backgroundColor": "transparent", - "boundElements": null, - "customData": undefined, - "fillStyle": "solid", - "frameId": null, - "groupIds": [], - "height": 20, - "index": "a0", - "isDeleted": false, - "link": null, - "locked": false, - "opacity": 100, - "roughness": 1, - "roundness": { - "type": 3, - }, - "strokeColor": "#1e1e1e", - "strokeStyle": "solid", - "strokeWidth": 2, - "type": "rectangle", - "width": 20, - "x": -10, - "y": 0, - }, - "inserted": { - "isDeleted": true, - }, - }, - }, - "updated": Map {}, - }, - }, - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": { - "selectedElementIds": { - "id1": true, - }, - }, - "inserted": { - "selectedElementIds": { - "id0": true, - }, - }, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map { - "id1" => Delta { - "deleted": { - "angle": 0, - "backgroundColor": "transparent", - "boundElements": null, - "customData": undefined, - "fillStyle": "solid", - "frameId": null, - "groupIds": [], - "height": 20, - "index": "a1", - "isDeleted": false, - "link": null, - "locked": false, - "opacity": 100, - "roughness": 1, - "roundness": { - "type": 3, - }, - "strokeColor": "#1e1e1e", - "strokeStyle": "solid", - "strokeWidth": 2, - "type": "rectangle", - "width": 20, - "x": 20, - "y": 30, - }, - "inserted": { - "isDeleted": true, - }, - }, - }, - "updated": Map {}, - }, - }, - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": { - "selectedElementIds": { - "id0": true, - }, - }, - "inserted": { - "selectedElementIds": { - "id1": true, - }, - }, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map {}, - "updated": Map { - "id0" => Delta { - "deleted": { - "index": "a2", - }, - "inserted": { - "index": "a0", - }, - }, - }, - }, - }, - ], -} -`; - 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`] = `10`; +exports[`contextMenu element > selecting 'Bring to front' in context menu brings element to front > [end of test] redo stack 1`] = `[]`; + +exports[`contextMenu element > selecting 'Bring to front' in context menu brings element to front > [end of test] undo stack 1`] = ` +[ + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": { + "selectedElementIds": { + "id0": true, + }, + }, + "inserted": { + "selectedElementIds": {}, + }, + }, + }, + "elements": { + "added": {}, + "removed": { + "id0": { + "deleted": { + "angle": 0, + "backgroundColor": "transparent", + "boundElements": null, + "customData": undefined, + "fillStyle": "solid", + "frameId": null, + "groupIds": [], + "height": 20, + "index": "a0", + "isDeleted": false, + "link": null, + "locked": false, + "opacity": 100, + "roughness": 1, + "roundness": { + "type": 3, + }, + "strokeColor": "#1e1e1e", + "strokeStyle": "solid", + "strokeWidth": 2, + "type": "rectangle", + "width": 20, + "x": -10, + "y": 0, + }, + "inserted": { + "isDeleted": true, + }, + }, + }, + "updated": {}, + }, + "id": "id2", + }, + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": { + "selectedElementIds": { + "id3": true, + }, + }, + "inserted": { + "selectedElementIds": { + "id0": true, + }, + }, + }, + }, + "elements": { + "added": {}, + "removed": { + "id3": { + "deleted": { + "angle": 0, + "backgroundColor": "transparent", + "boundElements": null, + "customData": undefined, + "fillStyle": "solid", + "frameId": null, + "groupIds": [], + "height": 20, + "index": "a1", + "isDeleted": false, + "link": null, + "locked": false, + "opacity": 100, + "roughness": 1, + "roundness": { + "type": 3, + }, + "strokeColor": "#1e1e1e", + "strokeStyle": "solid", + "strokeWidth": 2, + "type": "rectangle", + "width": 20, + "x": 20, + "y": 30, + }, + "inserted": { + "isDeleted": true, + }, + }, + }, + "updated": {}, + }, + "id": "id5", + }, + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": { + "selectedElementIds": { + "id0": true, + }, + }, + "inserted": { + "selectedElementIds": { + "id3": true, + }, + }, + }, + }, + "elements": { + "added": {}, + "removed": {}, + "updated": { + "id0": { + "deleted": { + "index": "a2", + }, + "inserted": { + "index": "a0", + }, + }, + }, + }, + "id": "id7", + }, +] +`; + exports[`contextMenu element > selecting 'Copy styles' in context menu copies styles > [end of test] appState 1`] = ` { "activeEmbeddable": null, @@ -2125,75 +2102,69 @@ exports[`contextMenu element > selecting 'Copy styles' in context menu copies st } `; -exports[`contextMenu element > selecting 'Copy styles' in context menu copies styles > [end of test] history 1`] = ` -History { - "onHistoryChangedEmitter": Emitter { - "subscribers": [ - [Function], - [Function], - ], - }, - "redoStack": [], - "undoStack": [ - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": { - "selectedElementIds": { - "id0": true, - }, - }, - "inserted": { - "selectedElementIds": {}, - }, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map { - "id0" => Delta { - "deleted": { - "angle": 0, - "backgroundColor": "transparent", - "boundElements": null, - "customData": undefined, - "fillStyle": "solid", - "frameId": null, - "groupIds": [], - "height": 20, - "index": "a0", - "isDeleted": false, - "link": null, - "locked": false, - "opacity": 100, - "roughness": 1, - "roundness": { - "type": 3, - }, - "strokeColor": "#1e1e1e", - "strokeStyle": "solid", - "strokeWidth": 2, - "type": "rectangle", - "width": 20, - "x": -10, - "y": 0, - }, - "inserted": { - "isDeleted": true, - }, - }, - }, - "updated": Map {}, - }, - }, - ], -} -`; - 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`] = `5`; +exports[`contextMenu element > selecting 'Copy styles' in context menu copies styles > [end of test] redo stack 1`] = `[]`; + +exports[`contextMenu element > selecting 'Copy styles' in context menu copies styles > [end of test] undo stack 1`] = ` +[ + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": { + "selectedElementIds": { + "id0": true, + }, + }, + "inserted": { + "selectedElementIds": {}, + }, + }, + }, + "elements": { + "added": {}, + "removed": { + "id0": { + "deleted": { + "angle": 0, + "backgroundColor": "transparent", + "boundElements": null, + "customData": undefined, + "fillStyle": "solid", + "frameId": null, + "groupIds": [], + "height": 20, + "index": "a0", + "isDeleted": false, + "link": null, + "locked": false, + "opacity": 100, + "roughness": 1, + "roundness": { + "type": 3, + }, + "strokeColor": "#1e1e1e", + "strokeStyle": "solid", + "strokeWidth": 2, + "type": "rectangle", + "width": 20, + "x": -10, + "y": 0, + }, + "inserted": { + "isDeleted": true, + }, + }, + }, + "updated": {}, + }, + "id": "id2", + }, +] +`; + exports[`contextMenu element > selecting 'Delete' in context menu deletes element > [end of test] appState 1`] = ` { "activeEmbeddable": null, @@ -2341,103 +2312,98 @@ exports[`contextMenu element > selecting 'Delete' in context menu deletes elemen } `; -exports[`contextMenu element > selecting 'Delete' in context menu deletes element > [end of test] history 1`] = ` -History { - "onHistoryChangedEmitter": Emitter { - "subscribers": [ - [Function], - [Function], - ], - }, - "redoStack": [], - "undoStack": [ - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": { - "selectedElementIds": { - "id0": true, - }, - }, - "inserted": { - "selectedElementIds": {}, - }, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map { - "id0" => Delta { - "deleted": { - "angle": 0, - "backgroundColor": "transparent", - "boundElements": null, - "customData": undefined, - "fillStyle": "solid", - "frameId": null, - "groupIds": [], - "height": 20, - "index": "a0", - "isDeleted": false, - "link": null, - "locked": false, - "opacity": 100, - "roughness": 1, - "roundness": { - "type": 3, - }, - "strokeColor": "#1e1e1e", - "strokeStyle": "solid", - "strokeWidth": 2, - "type": "rectangle", - "width": 20, - "x": -10, - "y": 0, - }, - "inserted": { - "isDeleted": true, - }, - }, - }, - "updated": Map {}, - }, - }, - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": { - "selectedElementIds": {}, - }, - "inserted": { - "selectedElementIds": { - "id0": true, - }, - }, - }, - }, - "elementsChange": ElementsChange { - "added": Map { - "id0" => Delta { - "deleted": { - "isDeleted": true, - }, - "inserted": { - "isDeleted": false, - }, - }, - }, - "removed": Map {}, - "updated": Map {}, - }, - }, - ], -} -`; - 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`] = `6`; +exports[`contextMenu element > selecting 'Delete' in context menu deletes element > [end of test] redo stack 1`] = `[]`; + +exports[`contextMenu element > selecting 'Delete' in context menu deletes element > [end of test] undo stack 1`] = ` +[ + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": { + "selectedElementIds": { + "id0": true, + }, + }, + "inserted": { + "selectedElementIds": {}, + }, + }, + }, + "elements": { + "added": {}, + "removed": { + "id0": { + "deleted": { + "angle": 0, + "backgroundColor": "transparent", + "boundElements": null, + "customData": undefined, + "fillStyle": "solid", + "frameId": null, + "groupIds": [], + "height": 20, + "index": "a0", + "isDeleted": false, + "link": null, + "locked": false, + "opacity": 100, + "roughness": 1, + "roundness": { + "type": 3, + }, + "strokeColor": "#1e1e1e", + "strokeStyle": "solid", + "strokeWidth": 2, + "type": "rectangle", + "width": 20, + "x": -10, + "y": 0, + }, + "inserted": { + "isDeleted": true, + }, + }, + }, + "updated": {}, + }, + "id": "id2", + }, + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": { + "selectedElementIds": {}, + }, + "inserted": { + "selectedElementIds": { + "id0": true, + }, + }, + }, + }, + "elements": { + "added": { + "id0": { + "deleted": { + "isDeleted": true, + }, + "inserted": { + "isDeleted": false, + }, + }, + }, + "removed": {}, + "updated": {}, + }, + "id": "id4", + }, +] +`; + exports[`contextMenu element > selecting 'Duplicate' in context menu duplicates element > [end of test] appState 1`] = ` { "activeEmbeddable": null, @@ -2524,7 +2490,7 @@ exports[`contextMenu element > selecting 'Duplicate' in context menu duplicates "scrolledOutside": false, "searchMatches": [], "selectedElementIds": { - "id1": true, + "id3": true, }, "selectedElementsAreBeingDragged": false, "selectedGroupIds": {}, @@ -2597,7 +2563,7 @@ exports[`contextMenu element > selecting 'Duplicate' in context menu duplicates "frameId": null, "groupIds": [], "height": 20, - "id": "id1", + "id": "id3", "index": "a1", "isDeleted": false, "link": null, @@ -2621,128 +2587,123 @@ exports[`contextMenu element > selecting 'Duplicate' in context menu duplicates } `; -exports[`contextMenu element > selecting 'Duplicate' in context menu duplicates element > [end of test] history 1`] = ` -History { - "onHistoryChangedEmitter": Emitter { - "subscribers": [ - [Function], - [Function], - ], - }, - "redoStack": [], - "undoStack": [ - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": { - "selectedElementIds": { - "id0": true, - }, - }, - "inserted": { - "selectedElementIds": {}, - }, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map { - "id0" => Delta { - "deleted": { - "angle": 0, - "backgroundColor": "transparent", - "boundElements": null, - "customData": undefined, - "fillStyle": "solid", - "frameId": null, - "groupIds": [], - "height": 20, - "index": "a0", - "isDeleted": false, - "link": null, - "locked": false, - "opacity": 100, - "roughness": 1, - "roundness": { - "type": 3, - }, - "strokeColor": "#1e1e1e", - "strokeStyle": "solid", - "strokeWidth": 2, - "type": "rectangle", - "width": 20, - "x": -10, - "y": 0, - }, - "inserted": { - "isDeleted": true, - }, - }, - }, - "updated": Map {}, - }, - }, - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": { - "selectedElementIds": { - "id1": true, - }, - }, - "inserted": { - "selectedElementIds": { - "id0": true, - }, - }, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map { - "id1" => Delta { - "deleted": { - "angle": 0, - "backgroundColor": "transparent", - "boundElements": null, - "customData": undefined, - "fillStyle": "solid", - "frameId": null, - "groupIds": [], - "height": 20, - "index": "a1", - "isDeleted": false, - "link": null, - "locked": false, - "opacity": 100, - "roughness": 1, - "roundness": { - "type": 3, - }, - "strokeColor": "#1e1e1e", - "strokeStyle": "solid", - "strokeWidth": 2, - "type": "rectangle", - "width": 20, - "x": 0, - "y": 10, - }, - "inserted": { - "isDeleted": true, - }, - }, - }, - "updated": Map {}, - }, - }, - ], -} -`; - 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`] = `6`; +exports[`contextMenu element > selecting 'Duplicate' in context menu duplicates element > [end of test] redo stack 1`] = `[]`; + +exports[`contextMenu element > selecting 'Duplicate' in context menu duplicates element > [end of test] undo stack 1`] = ` +[ + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": { + "selectedElementIds": { + "id0": true, + }, + }, + "inserted": { + "selectedElementIds": {}, + }, + }, + }, + "elements": { + "added": {}, + "removed": { + "id0": { + "deleted": { + "angle": 0, + "backgroundColor": "transparent", + "boundElements": null, + "customData": undefined, + "fillStyle": "solid", + "frameId": null, + "groupIds": [], + "height": 20, + "index": "a0", + "isDeleted": false, + "link": null, + "locked": false, + "opacity": 100, + "roughness": 1, + "roundness": { + "type": 3, + }, + "strokeColor": "#1e1e1e", + "strokeStyle": "solid", + "strokeWidth": 2, + "type": "rectangle", + "width": 20, + "x": -10, + "y": 0, + }, + "inserted": { + "isDeleted": true, + }, + }, + }, + "updated": {}, + }, + "id": "id2", + }, + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": { + "selectedElementIds": { + "id3": true, + }, + }, + "inserted": { + "selectedElementIds": { + "id0": true, + }, + }, + }, + }, + "elements": { + "added": {}, + "removed": { + "id3": { + "deleted": { + "angle": 0, + "backgroundColor": "transparent", + "boundElements": null, + "customData": undefined, + "fillStyle": "solid", + "frameId": null, + "groupIds": [], + "height": 20, + "index": "a1", + "isDeleted": false, + "link": null, + "locked": false, + "opacity": 100, + "roughness": 1, + "roundness": { + "type": 3, + }, + "strokeColor": "#1e1e1e", + "strokeStyle": "solid", + "strokeWidth": 2, + "type": "rectangle", + "width": 20, + "x": 0, + "y": 10, + }, + "inserted": { + "isDeleted": true, + }, + }, + }, + "updated": {}, + }, + "id": "id5", + }, +] +`; + exports[`contextMenu element > selecting 'Group selection' in context menu groups selected elements > [end of test] appState 1`] = ` { "activeEmbeddable": null, @@ -2823,7 +2784,7 @@ exports[`contextMenu element > selecting 'Group selection' in context menu group "penMode": false, "pendingImageElementId": null, "previousSelectedElementIds": { - "id1": true, + "id3": true, }, "resizingElement": null, "scrollX": 0, @@ -2832,11 +2793,11 @@ exports[`contextMenu element > selecting 'Group selection' in context menu group "searchMatches": [], "selectedElementIds": { "id0": true, - "id1": true, + "id3": true, }, "selectedElementsAreBeingDragged": false, "selectedGroupIds": { - "id3": true, + "id9": true, }, "selectedLinearElement": null, "selectionElement": null, @@ -2872,7 +2833,7 @@ exports[`contextMenu element > selecting 'Group selection' in context menu group "fillStyle": "solid", "frameId": null, "groupIds": [ - "id3", + "id9", ], "height": 20, "id": "id0", @@ -2908,10 +2869,10 @@ exports[`contextMenu element > selecting 'Group selection' in context menu group "fillStyle": "solid", "frameId": null, "groupIds": [ - "id3", + "id9", ], "height": 20, - "id": "id1", + "id": "id3", "index": "a1", "isDeleted": false, "link": null, @@ -2935,187 +2896,184 @@ exports[`contextMenu element > selecting 'Group selection' in context menu group } `; -exports[`contextMenu element > selecting 'Group selection' in context menu groups selected elements > [end of test] history 1`] = ` -History { - "onHistoryChangedEmitter": Emitter { - "subscribers": [ - [Function], - [Function], - ], - }, - "redoStack": [], - "undoStack": [ - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": { - "selectedElementIds": { - "id0": true, - }, - }, - "inserted": { - "selectedElementIds": {}, - }, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map { - "id0" => Delta { - "deleted": { - "angle": 0, - "backgroundColor": "transparent", - "boundElements": null, - "customData": undefined, - "fillStyle": "solid", - "frameId": null, - "groupIds": [], - "height": 20, - "index": "a0", - "isDeleted": false, - "link": null, - "locked": false, - "opacity": 100, - "roughness": 1, - "roundness": { - "type": 3, - }, - "strokeColor": "#1e1e1e", - "strokeStyle": "solid", - "strokeWidth": 2, - "type": "rectangle", - "width": 20, - "x": -10, - "y": 0, - }, - "inserted": { - "isDeleted": true, - }, - }, - }, - "updated": Map {}, - }, - }, - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": { - "selectedElementIds": { - "id1": true, - }, - }, - "inserted": { - "selectedElementIds": { - "id0": true, - }, - }, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map { - "id1" => Delta { - "deleted": { - "angle": 0, - "backgroundColor": "transparent", - "boundElements": null, - "customData": undefined, - "fillStyle": "solid", - "frameId": null, - "groupIds": [], - "height": 20, - "index": "a1", - "isDeleted": false, - "link": null, - "locked": false, - "opacity": 100, - "roughness": 1, - "roundness": { - "type": 3, - }, - "strokeColor": "#1e1e1e", - "strokeStyle": "solid", - "strokeWidth": 2, - "type": "rectangle", - "width": 20, - "x": 20, - "y": 30, - }, - "inserted": { - "isDeleted": true, - }, - }, - }, - "updated": Map {}, - }, - }, - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": { - "selectedElementIds": { - "id0": true, - }, - }, - "inserted": { - "selectedElementIds": {}, - }, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map {}, - "updated": Map {}, - }, - }, - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": { - "selectedGroupIds": { - "id3": true, - }, - }, - "inserted": { - "selectedGroupIds": {}, - }, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map {}, - "updated": Map { - "id0" => Delta { - "deleted": { - "groupIds": [ - "id3", - ], - }, - "inserted": { - "groupIds": [], - }, - }, - "id1" => Delta { - "deleted": { - "groupIds": [ - "id3", - ], - }, - "inserted": { - "groupIds": [], - }, - }, - }, - }, - }, - ], -} -`; - 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`] = `10`; +exports[`contextMenu element > selecting 'Group selection' in context menu groups selected elements > [end of test] redo stack 1`] = `[]`; + +exports[`contextMenu element > selecting 'Group selection' in context menu groups selected elements > [end of test] undo stack 1`] = ` +[ + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": { + "selectedElementIds": { + "id0": true, + }, + }, + "inserted": { + "selectedElementIds": {}, + }, + }, + }, + "elements": { + "added": {}, + "removed": { + "id0": { + "deleted": { + "angle": 0, + "backgroundColor": "transparent", + "boundElements": null, + "customData": undefined, + "fillStyle": "solid", + "frameId": null, + "groupIds": [], + "height": 20, + "index": "a0", + "isDeleted": false, + "link": null, + "locked": false, + "opacity": 100, + "roughness": 1, + "roundness": { + "type": 3, + }, + "strokeColor": "#1e1e1e", + "strokeStyle": "solid", + "strokeWidth": 2, + "type": "rectangle", + "width": 20, + "x": -10, + "y": 0, + }, + "inserted": { + "isDeleted": true, + }, + }, + }, + "updated": {}, + }, + "id": "id2", + }, + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": { + "selectedElementIds": { + "id3": true, + }, + }, + "inserted": { + "selectedElementIds": { + "id0": true, + }, + }, + }, + }, + "elements": { + "added": {}, + "removed": { + "id3": { + "deleted": { + "angle": 0, + "backgroundColor": "transparent", + "boundElements": null, + "customData": undefined, + "fillStyle": "solid", + "frameId": null, + "groupIds": [], + "height": 20, + "index": "a1", + "isDeleted": false, + "link": null, + "locked": false, + "opacity": 100, + "roughness": 1, + "roundness": { + "type": 3, + }, + "strokeColor": "#1e1e1e", + "strokeStyle": "solid", + "strokeWidth": 2, + "type": "rectangle", + "width": 20, + "x": 20, + "y": 30, + }, + "inserted": { + "isDeleted": true, + }, + }, + }, + "updated": {}, + }, + "id": "id5", + }, + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": { + "selectedElementIds": { + "id0": true, + }, + }, + "inserted": { + "selectedElementIds": {}, + }, + }, + }, + "elements": { + "added": {}, + "removed": {}, + "updated": {}, + }, + "id": "id8", + }, + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": { + "selectedGroupIds": { + "id9": true, + }, + }, + "inserted": { + "selectedGroupIds": {}, + }, + }, + }, + "elements": { + "added": {}, + "removed": {}, + "updated": { + "id0": { + "deleted": { + "groupIds": [ + "id9", + ], + }, + "inserted": { + "groupIds": [], + }, + }, + "id3": { + "deleted": { + "groupIds": [ + "id9", + ], + }, + "inserted": { + "groupIds": [], + }, + }, + }, + }, + "id": "id11", + }, +] +`; + exports[`contextMenu element > selecting 'Paste styles' in context menu pastes styles > [end of test] appState 1`] = ` { "activeEmbeddable": null, @@ -3277,7 +3235,7 @@ exports[`contextMenu element > selecting 'Paste styles' in context menu pastes s "frameId": null, "groupIds": [], "height": 20, - "id": "id1", + "id": "id3", "index": "a1", "isDeleted": false, "link": null, @@ -3301,300 +3259,302 @@ exports[`contextMenu element > selecting 'Paste styles' in context menu pastes s } `; -exports[`contextMenu element > selecting 'Paste styles' in context menu pastes styles > [end of test] history 1`] = ` -History { - "onHistoryChangedEmitter": Emitter { - "subscribers": [ - [Function], - [Function], - ], - }, - "redoStack": [], - "undoStack": [ - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": { - "selectedElementIds": { - "id0": true, - }, - }, - "inserted": { - "selectedElementIds": {}, - }, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map { - "id0" => Delta { - "deleted": { - "angle": 0, - "backgroundColor": "transparent", - "boundElements": null, - "customData": undefined, - "fillStyle": "solid", - "frameId": null, - "groupIds": [], - "height": 20, - "index": "a0", - "isDeleted": false, - "link": null, - "locked": false, - "opacity": 100, - "roughness": 1, - "roundness": { - "type": 3, - }, - "strokeColor": "#1e1e1e", - "strokeStyle": "solid", - "strokeWidth": 2, - "type": "rectangle", - "width": 20, - "x": -10, - "y": 0, - }, - "inserted": { - "isDeleted": true, - }, - }, - }, - "updated": Map {}, - }, - }, - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": { - "selectedElementIds": { - "id1": true, - }, - }, - "inserted": { - "selectedElementIds": { - "id0": true, - }, - }, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map { - "id1" => Delta { - "deleted": { - "angle": 0, - "backgroundColor": "transparent", - "boundElements": null, - "customData": undefined, - "fillStyle": "solid", - "frameId": null, - "groupIds": [], - "height": 20, - "index": "a1", - "isDeleted": false, - "link": null, - "locked": false, - "opacity": 100, - "roughness": 1, - "roundness": { - "type": 3, - }, - "strokeColor": "#1e1e1e", - "strokeStyle": "solid", - "strokeWidth": 2, - "type": "rectangle", - "width": 20, - "x": 20, - "y": 30, - }, - "inserted": { - "isDeleted": true, - }, - }, - }, - "updated": Map {}, - }, - }, - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": {}, - "inserted": {}, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map {}, - "updated": Map { - "id1" => Delta { - "deleted": { - "strokeColor": "#e03131", - }, - "inserted": { - "strokeColor": "#1e1e1e", - }, - }, - }, - }, - }, - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": {}, - "inserted": {}, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map {}, - "updated": Map { - "id1" => Delta { - "deleted": { - "backgroundColor": "#a5d8ff", - }, - "inserted": { - "backgroundColor": "transparent", - }, - }, - }, - }, - }, - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": {}, - "inserted": {}, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map {}, - "updated": Map { - "id1" => Delta { - "deleted": { - "fillStyle": "cross-hatch", - }, - "inserted": { - "fillStyle": "solid", - }, - }, - }, - }, - }, - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": {}, - "inserted": {}, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map {}, - "updated": Map { - "id1" => Delta { - "deleted": { - "strokeStyle": "dotted", - }, - "inserted": { - "strokeStyle": "solid", - }, - }, - }, - }, - }, - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": {}, - "inserted": {}, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map {}, - "updated": Map { - "id1" => Delta { - "deleted": { - "roughness": 2, - }, - "inserted": { - "roughness": 1, - }, - }, - }, - }, - }, - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": {}, - "inserted": {}, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map {}, - "updated": Map { - "id1" => Delta { - "deleted": { - "opacity": 60, - }, - "inserted": { - "opacity": 100, - }, - }, - }, - }, - }, - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": { - "selectedElementIds": { - "id0": true, - }, - }, - "inserted": { - "selectedElementIds": { - "id1": true, - }, - }, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map {}, - "updated": Map { - "id0" => Delta { - "deleted": { - "backgroundColor": "#a5d8ff", - "fillStyle": "cross-hatch", - "opacity": 60, - "roughness": 2, - "strokeColor": "#e03131", - "strokeStyle": "dotted", - }, - "inserted": { - "backgroundColor": "transparent", - "fillStyle": "solid", - "opacity": 100, - "roughness": 1, - "strokeColor": "#1e1e1e", - "strokeStyle": "solid", - }, - }, - }, - }, - }, - ], -} -`; - 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`] = `16`; +exports[`contextMenu element > selecting 'Paste styles' in context menu pastes styles > [end of test] redo stack 1`] = `[]`; + +exports[`contextMenu element > selecting 'Paste styles' in context menu pastes styles > [end of test] undo stack 1`] = ` +[ + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": { + "selectedElementIds": { + "id0": true, + }, + }, + "inserted": { + "selectedElementIds": {}, + }, + }, + }, + "elements": { + "added": {}, + "removed": { + "id0": { + "deleted": { + "angle": 0, + "backgroundColor": "transparent", + "boundElements": null, + "customData": undefined, + "fillStyle": "solid", + "frameId": null, + "groupIds": [], + "height": 20, + "index": "a0", + "isDeleted": false, + "link": null, + "locked": false, + "opacity": 100, + "roughness": 1, + "roundness": { + "type": 3, + }, + "strokeColor": "#1e1e1e", + "strokeStyle": "solid", + "strokeWidth": 2, + "type": "rectangle", + "width": 20, + "x": -10, + "y": 0, + }, + "inserted": { + "isDeleted": true, + }, + }, + }, + "updated": {}, + }, + "id": "id2", + }, + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": { + "selectedElementIds": { + "id3": true, + }, + }, + "inserted": { + "selectedElementIds": { + "id0": true, + }, + }, + }, + }, + "elements": { + "added": {}, + "removed": { + "id3": { + "deleted": { + "angle": 0, + "backgroundColor": "transparent", + "boundElements": null, + "customData": undefined, + "fillStyle": "solid", + "frameId": null, + "groupIds": [], + "height": 20, + "index": "a1", + "isDeleted": false, + "link": null, + "locked": false, + "opacity": 100, + "roughness": 1, + "roundness": { + "type": 3, + }, + "strokeColor": "#1e1e1e", + "strokeStyle": "solid", + "strokeWidth": 2, + "type": "rectangle", + "width": 20, + "x": 20, + "y": 30, + }, + "inserted": { + "isDeleted": true, + }, + }, + }, + "updated": {}, + }, + "id": "id5", + }, + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": {}, + "inserted": {}, + }, + }, + "elements": { + "added": {}, + "removed": {}, + "updated": { + "id3": { + "deleted": { + "strokeColor": "#e03131", + }, + "inserted": { + "strokeColor": "#1e1e1e", + }, + }, + }, + }, + "id": "id7", + }, + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": {}, + "inserted": {}, + }, + }, + "elements": { + "added": {}, + "removed": {}, + "updated": { + "id3": { + "deleted": { + "backgroundColor": "#a5d8ff", + }, + "inserted": { + "backgroundColor": "transparent", + }, + }, + }, + }, + "id": "id9", + }, + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": {}, + "inserted": {}, + }, + }, + "elements": { + "added": {}, + "removed": {}, + "updated": { + "id3": { + "deleted": { + "fillStyle": "cross-hatch", + }, + "inserted": { + "fillStyle": "solid", + }, + }, + }, + }, + "id": "id11", + }, + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": {}, + "inserted": {}, + }, + }, + "elements": { + "added": {}, + "removed": {}, + "updated": { + "id3": { + "deleted": { + "strokeStyle": "dotted", + }, + "inserted": { + "strokeStyle": "solid", + }, + }, + }, + }, + "id": "id13", + }, + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": {}, + "inserted": {}, + }, + }, + "elements": { + "added": {}, + "removed": {}, + "updated": { + "id3": { + "deleted": { + "roughness": 2, + }, + "inserted": { + "roughness": 1, + }, + }, + }, + }, + "id": "id15", + }, + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": {}, + "inserted": {}, + }, + }, + "elements": { + "added": {}, + "removed": {}, + "updated": { + "id3": { + "deleted": { + "opacity": 60, + }, + "inserted": { + "opacity": 100, + }, + }, + }, + }, + "id": "id17", + }, + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": { + "selectedElementIds": { + "id0": true, + }, + }, + "inserted": { + "selectedElementIds": { + "id3": true, + }, + }, + }, + }, + "elements": { + "added": {}, + "removed": {}, + "updated": { + "id0": { + "deleted": { + "backgroundColor": "#a5d8ff", + "fillStyle": "cross-hatch", + "opacity": 60, + "roughness": 2, + "strokeColor": "#e03131", + "strokeStyle": "dotted", + }, + "inserted": { + "backgroundColor": "transparent", + "fillStyle": "solid", + "opacity": 100, + "roughness": 1, + "strokeColor": "#1e1e1e", + "strokeStyle": "solid", + }, + }, + }, + }, + "id": "id19", + }, +] +`; + exports[`contextMenu element > selecting 'Send backward' in context menu sends element backward > [end of test] appState 1`] = ` { "activeEmbeddable": null, @@ -3681,7 +3641,7 @@ exports[`contextMenu element > selecting 'Send backward' in context menu sends e "scrolledOutside": false, "searchMatches": [], "selectedElementIds": { - "id1": true, + "id3": true, }, "selectedElementsAreBeingDragged": false, "selectedGroupIds": {}, @@ -3720,7 +3680,7 @@ exports[`contextMenu element > selecting 'Send backward' in context menu sends e "frameId": null, "groupIds": [], "height": 20, - "id": "id1", + "id": "id3", "index": "Zz", "isDeleted": false, "link": null, @@ -3778,150 +3738,146 @@ exports[`contextMenu element > selecting 'Send backward' in context menu sends e } `; -exports[`contextMenu element > selecting 'Send backward' in context menu sends element backward > [end of test] history 1`] = ` -History { - "onHistoryChangedEmitter": Emitter { - "subscribers": [ - [Function], - [Function], - ], - }, - "redoStack": [], - "undoStack": [ - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": { - "selectedElementIds": { - "id0": true, - }, - }, - "inserted": { - "selectedElementIds": {}, - }, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map { - "id0" => Delta { - "deleted": { - "angle": 0, - "backgroundColor": "transparent", - "boundElements": null, - "customData": undefined, - "fillStyle": "solid", - "frameId": null, - "groupIds": [], - "height": 20, - "index": "a0", - "isDeleted": false, - "link": null, - "locked": false, - "opacity": 100, - "roughness": 1, - "roundness": { - "type": 3, - }, - "strokeColor": "#1e1e1e", - "strokeStyle": "solid", - "strokeWidth": 2, - "type": "rectangle", - "width": 20, - "x": -10, - "y": 0, - }, - "inserted": { - "isDeleted": true, - }, - }, - }, - "updated": Map {}, - }, - }, - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": { - "selectedElementIds": { - "id1": true, - }, - }, - "inserted": { - "selectedElementIds": { - "id0": true, - }, - }, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map { - "id1" => Delta { - "deleted": { - "angle": 0, - "backgroundColor": "transparent", - "boundElements": null, - "customData": undefined, - "fillStyle": "solid", - "frameId": null, - "groupIds": [], - "height": 20, - "index": "a1", - "isDeleted": false, - "link": null, - "locked": false, - "opacity": 100, - "roughness": 1, - "roundness": { - "type": 3, - }, - "strokeColor": "#1e1e1e", - "strokeStyle": "solid", - "strokeWidth": 2, - "type": "rectangle", - "width": 20, - "x": 20, - "y": 30, - }, - "inserted": { - "isDeleted": true, - }, - }, - }, - "updated": Map {}, - }, - }, - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": {}, - "inserted": {}, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map {}, - "updated": Map { - "id1" => Delta { - "deleted": { - "index": "Zz", - }, - "inserted": { - "index": "a1", - }, - }, - }, - }, - }, - ], -} -`; - 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`] = `9`; +exports[`contextMenu element > selecting 'Send backward' in context menu sends element backward > [end of test] redo stack 1`] = `[]`; + +exports[`contextMenu element > selecting 'Send backward' in context menu sends element backward > [end of test] undo stack 1`] = ` +[ + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": { + "selectedElementIds": { + "id0": true, + }, + }, + "inserted": { + "selectedElementIds": {}, + }, + }, + }, + "elements": { + "added": {}, + "removed": { + "id0": { + "deleted": { + "angle": 0, + "backgroundColor": "transparent", + "boundElements": null, + "customData": undefined, + "fillStyle": "solid", + "frameId": null, + "groupIds": [], + "height": 20, + "index": "a0", + "isDeleted": false, + "link": null, + "locked": false, + "opacity": 100, + "roughness": 1, + "roundness": { + "type": 3, + }, + "strokeColor": "#1e1e1e", + "strokeStyle": "solid", + "strokeWidth": 2, + "type": "rectangle", + "width": 20, + "x": -10, + "y": 0, + }, + "inserted": { + "isDeleted": true, + }, + }, + }, + "updated": {}, + }, + "id": "id2", + }, + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": { + "selectedElementIds": { + "id3": true, + }, + }, + "inserted": { + "selectedElementIds": { + "id0": true, + }, + }, + }, + }, + "elements": { + "added": {}, + "removed": { + "id3": { + "deleted": { + "angle": 0, + "backgroundColor": "transparent", + "boundElements": null, + "customData": undefined, + "fillStyle": "solid", + "frameId": null, + "groupIds": [], + "height": 20, + "index": "a1", + "isDeleted": false, + "link": null, + "locked": false, + "opacity": 100, + "roughness": 1, + "roundness": { + "type": 3, + }, + "strokeColor": "#1e1e1e", + "strokeStyle": "solid", + "strokeWidth": 2, + "type": "rectangle", + "width": 20, + "x": 20, + "y": 30, + }, + "inserted": { + "isDeleted": true, + }, + }, + }, + "updated": {}, + }, + "id": "id5", + }, + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": {}, + "inserted": {}, + }, + }, + "elements": { + "added": {}, + "removed": {}, + "updated": { + "id3": { + "deleted": { + "index": "Zz", + }, + "inserted": { + "index": "a1", + }, + }, + }, + }, + "id": "id7", + }, +] +`; + exports[`contextMenu element > selecting 'Send to back' in context menu sends element to back > [end of test] appState 1`] = ` { "activeEmbeddable": null, @@ -4008,7 +3964,7 @@ exports[`contextMenu element > selecting 'Send to back' in context menu sends el "scrolledOutside": false, "searchMatches": [], "selectedElementIds": { - "id1": true, + "id3": true, }, "selectedElementsAreBeingDragged": false, "selectedGroupIds": {}, @@ -4047,7 +4003,7 @@ exports[`contextMenu element > selecting 'Send to back' in context menu sends el "frameId": null, "groupIds": [], "height": 20, - "id": "id1", + "id": "id3", "index": "Zz", "isDeleted": false, "link": null, @@ -4105,150 +4061,146 @@ exports[`contextMenu element > selecting 'Send to back' in context menu sends el } `; -exports[`contextMenu element > selecting 'Send to back' in context menu sends element to back > [end of test] history 1`] = ` -History { - "onHistoryChangedEmitter": Emitter { - "subscribers": [ - [Function], - [Function], - ], - }, - "redoStack": [], - "undoStack": [ - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": { - "selectedElementIds": { - "id0": true, - }, - }, - "inserted": { - "selectedElementIds": {}, - }, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map { - "id0" => Delta { - "deleted": { - "angle": 0, - "backgroundColor": "transparent", - "boundElements": null, - "customData": undefined, - "fillStyle": "solid", - "frameId": null, - "groupIds": [], - "height": 20, - "index": "a0", - "isDeleted": false, - "link": null, - "locked": false, - "opacity": 100, - "roughness": 1, - "roundness": { - "type": 3, - }, - "strokeColor": "#1e1e1e", - "strokeStyle": "solid", - "strokeWidth": 2, - "type": "rectangle", - "width": 20, - "x": -10, - "y": 0, - }, - "inserted": { - "isDeleted": true, - }, - }, - }, - "updated": Map {}, - }, - }, - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": { - "selectedElementIds": { - "id1": true, - }, - }, - "inserted": { - "selectedElementIds": { - "id0": true, - }, - }, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map { - "id1" => Delta { - "deleted": { - "angle": 0, - "backgroundColor": "transparent", - "boundElements": null, - "customData": undefined, - "fillStyle": "solid", - "frameId": null, - "groupIds": [], - "height": 20, - "index": "a1", - "isDeleted": false, - "link": null, - "locked": false, - "opacity": 100, - "roughness": 1, - "roundness": { - "type": 3, - }, - "strokeColor": "#1e1e1e", - "strokeStyle": "solid", - "strokeWidth": 2, - "type": "rectangle", - "width": 20, - "x": 20, - "y": 30, - }, - "inserted": { - "isDeleted": true, - }, - }, - }, - "updated": Map {}, - }, - }, - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": {}, - "inserted": {}, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map {}, - "updated": Map { - "id1" => Delta { - "deleted": { - "index": "Zz", - }, - "inserted": { - "index": "a1", - }, - }, - }, - }, - }, - ], -} -`; - 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`] = `9`; +exports[`contextMenu element > selecting 'Send to back' in context menu sends element to back > [end of test] redo stack 1`] = `[]`; + +exports[`contextMenu element > selecting 'Send to back' in context menu sends element to back > [end of test] undo stack 1`] = ` +[ + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": { + "selectedElementIds": { + "id0": true, + }, + }, + "inserted": { + "selectedElementIds": {}, + }, + }, + }, + "elements": { + "added": {}, + "removed": { + "id0": { + "deleted": { + "angle": 0, + "backgroundColor": "transparent", + "boundElements": null, + "customData": undefined, + "fillStyle": "solid", + "frameId": null, + "groupIds": [], + "height": 20, + "index": "a0", + "isDeleted": false, + "link": null, + "locked": false, + "opacity": 100, + "roughness": 1, + "roundness": { + "type": 3, + }, + "strokeColor": "#1e1e1e", + "strokeStyle": "solid", + "strokeWidth": 2, + "type": "rectangle", + "width": 20, + "x": -10, + "y": 0, + }, + "inserted": { + "isDeleted": true, + }, + }, + }, + "updated": {}, + }, + "id": "id2", + }, + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": { + "selectedElementIds": { + "id3": true, + }, + }, + "inserted": { + "selectedElementIds": { + "id0": true, + }, + }, + }, + }, + "elements": { + "added": {}, + "removed": { + "id3": { + "deleted": { + "angle": 0, + "backgroundColor": "transparent", + "boundElements": null, + "customData": undefined, + "fillStyle": "solid", + "frameId": null, + "groupIds": [], + "height": 20, + "index": "a1", + "isDeleted": false, + "link": null, + "locked": false, + "opacity": 100, + "roughness": 1, + "roundness": { + "type": 3, + }, + "strokeColor": "#1e1e1e", + "strokeStyle": "solid", + "strokeWidth": 2, + "type": "rectangle", + "width": 20, + "x": 20, + "y": 30, + }, + "inserted": { + "isDeleted": true, + }, + }, + }, + "updated": {}, + }, + "id": "id5", + }, + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": {}, + "inserted": {}, + }, + }, + "elements": { + "added": {}, + "removed": {}, + "updated": { + "id3": { + "deleted": { + "index": "Zz", + }, + "inserted": { + "index": "a1", + }, + }, + }, + }, + "id": "id7", + }, +] +`; + exports[`contextMenu element > selecting 'Ungroup selection' in context menu ungroups selected group > [end of test] appState 1`] = ` { "activeEmbeddable": null, @@ -4329,7 +4281,7 @@ exports[`contextMenu element > selecting 'Ungroup selection' in context menu ung "penMode": false, "pendingImageElementId": null, "previousSelectedElementIds": { - "id1": true, + "id3": true, }, "resizingElement": null, "scrollX": 0, @@ -4338,7 +4290,7 @@ exports[`contextMenu element > selecting 'Ungroup selection' in context menu ung "searchMatches": [], "selectedElementIds": { "id0": true, - "id1": true, + "id3": true, }, "selectedElementsAreBeingDragged": false, "selectedGroupIds": {}, @@ -4411,7 +4363,7 @@ exports[`contextMenu element > selecting 'Ungroup selection' in context menu ung "frameId": null, "groupIds": [], "height": 20, - "id": "id1", + "id": "id3", "index": "a1", "isDeleted": false, "link": null, @@ -4435,227 +4387,225 @@ exports[`contextMenu element > selecting 'Ungroup selection' in context menu ung } `; -exports[`contextMenu element > selecting 'Ungroup selection' in context menu ungroups selected group > [end of test] history 1`] = ` -History { - "onHistoryChangedEmitter": Emitter { - "subscribers": [ - [Function], - [Function], - ], - }, - "redoStack": [], - "undoStack": [ - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": { - "selectedElementIds": { - "id0": true, - }, - }, - "inserted": { - "selectedElementIds": {}, - }, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map { - "id0" => Delta { - "deleted": { - "angle": 0, - "backgroundColor": "transparent", - "boundElements": null, - "customData": undefined, - "fillStyle": "solid", - "frameId": null, - "groupIds": [], - "height": 20, - "index": "a0", - "isDeleted": false, - "link": null, - "locked": false, - "opacity": 100, - "roughness": 1, - "roundness": { - "type": 3, - }, - "strokeColor": "#1e1e1e", - "strokeStyle": "solid", - "strokeWidth": 2, - "type": "rectangle", - "width": 20, - "x": -10, - "y": 0, - }, - "inserted": { - "isDeleted": true, - }, - }, - }, - "updated": Map {}, - }, - }, - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": { - "selectedElementIds": { - "id1": true, - }, - }, - "inserted": { - "selectedElementIds": { - "id0": true, - }, - }, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map { - "id1" => Delta { - "deleted": { - "angle": 0, - "backgroundColor": "transparent", - "boundElements": null, - "customData": undefined, - "fillStyle": "solid", - "frameId": null, - "groupIds": [], - "height": 20, - "index": "a1", - "isDeleted": false, - "link": null, - "locked": false, - "opacity": 100, - "roughness": 1, - "roundness": { - "type": 3, - }, - "strokeColor": "#1e1e1e", - "strokeStyle": "solid", - "strokeWidth": 2, - "type": "rectangle", - "width": 20, - "x": 20, - "y": 30, - }, - "inserted": { - "isDeleted": true, - }, - }, - }, - "updated": Map {}, - }, - }, - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": { - "selectedElementIds": { - "id0": true, - }, - }, - "inserted": { - "selectedElementIds": {}, - }, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map {}, - "updated": Map {}, - }, - }, - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": { - "selectedGroupIds": { - "id3": true, - }, - }, - "inserted": { - "selectedGroupIds": {}, - }, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map {}, - "updated": Map { - "id0" => Delta { - "deleted": { - "groupIds": [ - "id3", - ], - }, - "inserted": { - "groupIds": [], - }, - }, - "id1" => Delta { - "deleted": { - "groupIds": [ - "id3", - ], - }, - "inserted": { - "groupIds": [], - }, - }, - }, - }, - }, - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": { - "selectedGroupIds": {}, - }, - "inserted": { - "selectedGroupIds": { - "id3": true, - }, - }, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map {}, - "updated": Map { - "id0" => Delta { - "deleted": { - "groupIds": [], - }, - "inserted": { - "groupIds": [ - "id3", - ], - }, - }, - "id1" => Delta { - "deleted": { - "groupIds": [], - }, - "inserted": { - "groupIds": [ - "id3", - ], - }, - }, - }, - }, - }, - ], -} -`; - 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`] = `11`; +exports[`contextMenu element > selecting 'Ungroup selection' in context menu ungroups selected group > [end of test] redo stack 1`] = `[]`; + +exports[`contextMenu element > selecting 'Ungroup selection' in context menu ungroups selected group > [end of test] undo stack 1`] = ` +[ + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": { + "selectedElementIds": { + "id0": true, + }, + }, + "inserted": { + "selectedElementIds": {}, + }, + }, + }, + "elements": { + "added": {}, + "removed": { + "id0": { + "deleted": { + "angle": 0, + "backgroundColor": "transparent", + "boundElements": null, + "customData": undefined, + "fillStyle": "solid", + "frameId": null, + "groupIds": [], + "height": 20, + "index": "a0", + "isDeleted": false, + "link": null, + "locked": false, + "opacity": 100, + "roughness": 1, + "roundness": { + "type": 3, + }, + "strokeColor": "#1e1e1e", + "strokeStyle": "solid", + "strokeWidth": 2, + "type": "rectangle", + "width": 20, + "x": -10, + "y": 0, + }, + "inserted": { + "isDeleted": true, + }, + }, + }, + "updated": {}, + }, + "id": "id2", + }, + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": { + "selectedElementIds": { + "id3": true, + }, + }, + "inserted": { + "selectedElementIds": { + "id0": true, + }, + }, + }, + }, + "elements": { + "added": {}, + "removed": { + "id3": { + "deleted": { + "angle": 0, + "backgroundColor": "transparent", + "boundElements": null, + "customData": undefined, + "fillStyle": "solid", + "frameId": null, + "groupIds": [], + "height": 20, + "index": "a1", + "isDeleted": false, + "link": null, + "locked": false, + "opacity": 100, + "roughness": 1, + "roundness": { + "type": 3, + }, + "strokeColor": "#1e1e1e", + "strokeStyle": "solid", + "strokeWidth": 2, + "type": "rectangle", + "width": 20, + "x": 20, + "y": 30, + }, + "inserted": { + "isDeleted": true, + }, + }, + }, + "updated": {}, + }, + "id": "id5", + }, + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": { + "selectedElementIds": { + "id0": true, + }, + }, + "inserted": { + "selectedElementIds": {}, + }, + }, + }, + "elements": { + "added": {}, + "removed": {}, + "updated": {}, + }, + "id": "id8", + }, + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": { + "selectedGroupIds": { + "id9": true, + }, + }, + "inserted": { + "selectedGroupIds": {}, + }, + }, + }, + "elements": { + "added": {}, + "removed": {}, + "updated": { + "id0": { + "deleted": { + "groupIds": [ + "id9", + ], + }, + "inserted": { + "groupIds": [], + }, + }, + "id3": { + "deleted": { + "groupIds": [ + "id9", + ], + }, + "inserted": { + "groupIds": [], + }, + }, + }, + }, + "id": "id11", + }, + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": { + "selectedGroupIds": {}, + }, + "inserted": { + "selectedGroupIds": { + "id9": true, + }, + }, + }, + }, + "elements": { + "added": {}, + "removed": {}, + "updated": { + "id0": { + "deleted": { + "groupIds": [], + }, + "inserted": { + "groupIds": [ + "id9", + ], + }, + }, + "id3": { + "deleted": { + "groupIds": [], + }, + "inserted": { + "groupIds": [ + "id9", + ], + }, + }, + }, + }, + "id": "id13", + }, +] +`; + exports[`contextMenu element > shows 'Group selection' in context menu for multiple selected elements > [end of test] appState 1`] = ` { "activeEmbeddable": null, @@ -5619,7 +5569,7 @@ exports[`contextMenu element > shows 'Group selection' in context menu for multi "searchMatches": [], "selectedElementIds": { "id0": true, - "id1": true, + "id3": true, }, "selectedElementsAreBeingDragged": false, "selectedGroupIds": {}, @@ -5692,7 +5642,7 @@ exports[`contextMenu element > shows 'Group selection' in context menu for multi "frameId": null, "groupIds": [], "height": 10, - "id": "id1", + "id": "id3", "index": "a1", "isDeleted": false, "link": null, @@ -5716,168 +5666,165 @@ exports[`contextMenu element > shows 'Group selection' in context menu for multi } `; -exports[`contextMenu element > shows 'Group selection' in context menu for multiple selected elements > [end of test] history 1`] = ` -History { - "onHistoryChangedEmitter": Emitter { - "subscribers": [ - [Function], - [Function], - ], - }, - "redoStack": [], - "undoStack": [ - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": { - "selectedElementIds": { - "id0": true, - }, - }, - "inserted": { - "selectedElementIds": {}, - }, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map { - "id0" => Delta { - "deleted": { - "angle": 0, - "backgroundColor": "transparent", - "boundElements": null, - "customData": undefined, - "fillStyle": "solid", - "frameId": null, - "groupIds": [], - "height": 10, - "index": "a0", - "isDeleted": false, - "link": null, - "locked": false, - "opacity": 100, - "roughness": 1, - "roundness": { - "type": 3, - }, - "strokeColor": "#1e1e1e", - "strokeStyle": "solid", - "strokeWidth": 2, - "type": "rectangle", - "width": 10, - "x": -10, - "y": 0, - }, - "inserted": { - "isDeleted": true, - }, - }, - }, - "updated": Map {}, - }, - }, - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": { - "selectedElementIds": { - "id1": true, - }, - }, - "inserted": { - "selectedElementIds": { - "id0": true, - }, - }, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map { - "id1" => Delta { - "deleted": { - "angle": 0, - "backgroundColor": "transparent", - "boundElements": null, - "customData": undefined, - "fillStyle": "solid", - "frameId": null, - "groupIds": [], - "height": 10, - "index": "a1", - "isDeleted": false, - "link": null, - "locked": false, - "opacity": 100, - "roughness": 1, - "roundness": { - "type": 3, - }, - "strokeColor": "#1e1e1e", - "strokeStyle": "solid", - "strokeWidth": 2, - "type": "rectangle", - "width": 10, - "x": 12, - "y": 0, - }, - "inserted": { - "isDeleted": true, - }, - }, - }, - "updated": Map {}, - }, - }, - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": { - "selectedElementIds": { - "id0": true, - }, - }, - "inserted": { - "selectedElementIds": { - "id1": true, - }, - }, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map {}, - "updated": Map {}, - }, - }, - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": { - "selectedElementIds": { - "id1": true, - }, - }, - "inserted": { - "selectedElementIds": {}, - }, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map {}, - "updated": Map {}, - }, - }, - ], -} -`; - 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`] = `10`; +exports[`contextMenu element > shows 'Group selection' in context menu for multiple selected elements > [end of test] redo stack 1`] = `[]`; + +exports[`contextMenu element > shows 'Group selection' in context menu for multiple selected elements > [end of test] undo stack 1`] = ` +[ + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": { + "selectedElementIds": { + "id0": true, + }, + }, + "inserted": { + "selectedElementIds": {}, + }, + }, + }, + "elements": { + "added": {}, + "removed": { + "id0": { + "deleted": { + "angle": 0, + "backgroundColor": "transparent", + "boundElements": null, + "customData": undefined, + "fillStyle": "solid", + "frameId": null, + "groupIds": [], + "height": 10, + "index": "a0", + "isDeleted": false, + "link": null, + "locked": false, + "opacity": 100, + "roughness": 1, + "roundness": { + "type": 3, + }, + "strokeColor": "#1e1e1e", + "strokeStyle": "solid", + "strokeWidth": 2, + "type": "rectangle", + "width": 10, + "x": -10, + "y": 0, + }, + "inserted": { + "isDeleted": true, + }, + }, + }, + "updated": {}, + }, + "id": "id2", + }, + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": { + "selectedElementIds": { + "id3": true, + }, + }, + "inserted": { + "selectedElementIds": { + "id0": true, + }, + }, + }, + }, + "elements": { + "added": {}, + "removed": { + "id3": { + "deleted": { + "angle": 0, + "backgroundColor": "transparent", + "boundElements": null, + "customData": undefined, + "fillStyle": "solid", + "frameId": null, + "groupIds": [], + "height": 10, + "index": "a1", + "isDeleted": false, + "link": null, + "locked": false, + "opacity": 100, + "roughness": 1, + "roundness": { + "type": 3, + }, + "strokeColor": "#1e1e1e", + "strokeStyle": "solid", + "strokeWidth": 2, + "type": "rectangle", + "width": 10, + "x": 12, + "y": 0, + }, + "inserted": { + "isDeleted": true, + }, + }, + }, + "updated": {}, + }, + "id": "id5", + }, + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": { + "selectedElementIds": { + "id0": true, + }, + }, + "inserted": { + "selectedElementIds": { + "id3": true, + }, + }, + }, + }, + "elements": { + "added": {}, + "removed": {}, + "updated": {}, + }, + "id": "id8", + }, + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": { + "selectedElementIds": { + "id3": true, + }, + }, + "inserted": { + "selectedElementIds": {}, + }, + }, + }, + "elements": { + "added": {}, + "removed": {}, + "updated": {}, + }, + "id": "id11", + }, +] +`; + exports[`contextMenu element > shows 'Ungroup selection' in context menu for group inside selected elements > [end of test] appState 1`] = ` { "activeEmbeddable": null, @@ -6841,11 +6788,11 @@ exports[`contextMenu element > shows 'Ungroup selection' in context menu for gro "searchMatches": [], "selectedElementIds": { "id0": true, - "id1": true, + "id3": true, }, "selectedElementsAreBeingDragged": false, "selectedGroupIds": { - "id4": true, + "id12": true, }, "selectedLinearElement": null, "selectionElement": null, @@ -6881,7 +6828,7 @@ exports[`contextMenu element > shows 'Ungroup selection' in context menu for gro "fillStyle": "solid", "frameId": null, "groupIds": [ - "id4", + "id12", ], "height": 10, "id": "id0", @@ -6917,10 +6864,10 @@ exports[`contextMenu element > shows 'Ungroup selection' in context menu for gro "fillStyle": "solid", "frameId": null, "groupIds": [ - "id4", + "id12", ], "height": 10, - "id": "id1", + "id": "id3", "index": "a1", "isDeleted": false, "link": null, @@ -6944,208 +6891,206 @@ exports[`contextMenu element > shows 'Ungroup selection' in context menu for gro } `; -exports[`contextMenu element > shows 'Ungroup selection' in context menu for group inside selected elements > [end of test] history 1`] = ` -History { - "onHistoryChangedEmitter": Emitter { - "subscribers": [ - [Function], - [Function], - ], - }, - "redoStack": [], - "undoStack": [ - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": { - "selectedElementIds": { - "id0": true, - }, - }, - "inserted": { - "selectedElementIds": {}, - }, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map { - "id0" => Delta { - "deleted": { - "angle": 0, - "backgroundColor": "transparent", - "boundElements": null, - "customData": undefined, - "fillStyle": "solid", - "frameId": null, - "groupIds": [], - "height": 10, - "index": "a0", - "isDeleted": false, - "link": null, - "locked": false, - "opacity": 100, - "roughness": 1, - "roundness": { - "type": 3, - }, - "strokeColor": "#1e1e1e", - "strokeStyle": "solid", - "strokeWidth": 2, - "type": "rectangle", - "width": 10, - "x": -10, - "y": 0, - }, - "inserted": { - "isDeleted": true, - }, - }, - }, - "updated": Map {}, - }, - }, - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": { - "selectedElementIds": { - "id1": true, - }, - }, - "inserted": { - "selectedElementIds": { - "id0": true, - }, - }, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map { - "id1" => Delta { - "deleted": { - "angle": 0, - "backgroundColor": "transparent", - "boundElements": null, - "customData": undefined, - "fillStyle": "solid", - "frameId": null, - "groupIds": [], - "height": 10, - "index": "a1", - "isDeleted": false, - "link": null, - "locked": false, - "opacity": 100, - "roughness": 1, - "roundness": { - "type": 3, - }, - "strokeColor": "#1e1e1e", - "strokeStyle": "solid", - "strokeWidth": 2, - "type": "rectangle", - "width": 10, - "x": 12, - "y": 0, - }, - "inserted": { - "isDeleted": true, - }, - }, - }, - "updated": Map {}, - }, - }, - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": { - "selectedElementIds": { - "id0": true, - }, - }, - "inserted": { - "selectedElementIds": { - "id1": true, - }, - }, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map {}, - "updated": Map {}, - }, - }, - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": { - "selectedElementIds": { - "id1": true, - }, - }, - "inserted": { - "selectedElementIds": {}, - }, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map {}, - "updated": Map {}, - }, - }, - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": { - "selectedGroupIds": { - "id4": true, - }, - }, - "inserted": { - "selectedGroupIds": {}, - }, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map {}, - "updated": Map { - "id0" => Delta { - "deleted": { - "groupIds": [ - "id4", - ], - }, - "inserted": { - "groupIds": [], - }, - }, - "id1" => Delta { - "deleted": { - "groupIds": [ - "id4", - ], - }, - "inserted": { - "groupIds": [], - }, - }, - }, - }, - }, - ], -} -`; - 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`] = `11`; +exports[`contextMenu element > shows 'Ungroup selection' in context menu for group inside selected elements > [end of test] redo stack 1`] = `[]`; + +exports[`contextMenu element > shows 'Ungroup selection' in context menu for group inside selected elements > [end of test] undo stack 1`] = ` +[ + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": { + "selectedElementIds": { + "id0": true, + }, + }, + "inserted": { + "selectedElementIds": {}, + }, + }, + }, + "elements": { + "added": {}, + "removed": { + "id0": { + "deleted": { + "angle": 0, + "backgroundColor": "transparent", + "boundElements": null, + "customData": undefined, + "fillStyle": "solid", + "frameId": null, + "groupIds": [], + "height": 10, + "index": "a0", + "isDeleted": false, + "link": null, + "locked": false, + "opacity": 100, + "roughness": 1, + "roundness": { + "type": 3, + }, + "strokeColor": "#1e1e1e", + "strokeStyle": "solid", + "strokeWidth": 2, + "type": "rectangle", + "width": 10, + "x": -10, + "y": 0, + }, + "inserted": { + "isDeleted": true, + }, + }, + }, + "updated": {}, + }, + "id": "id2", + }, + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": { + "selectedElementIds": { + "id3": true, + }, + }, + "inserted": { + "selectedElementIds": { + "id0": true, + }, + }, + }, + }, + "elements": { + "added": {}, + "removed": { + "id3": { + "deleted": { + "angle": 0, + "backgroundColor": "transparent", + "boundElements": null, + "customData": undefined, + "fillStyle": "solid", + "frameId": null, + "groupIds": [], + "height": 10, + "index": "a1", + "isDeleted": false, + "link": null, + "locked": false, + "opacity": 100, + "roughness": 1, + "roundness": { + "type": 3, + }, + "strokeColor": "#1e1e1e", + "strokeStyle": "solid", + "strokeWidth": 2, + "type": "rectangle", + "width": 10, + "x": 12, + "y": 0, + }, + "inserted": { + "isDeleted": true, + }, + }, + }, + "updated": {}, + }, + "id": "id5", + }, + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": { + "selectedElementIds": { + "id0": true, + }, + }, + "inserted": { + "selectedElementIds": { + "id3": true, + }, + }, + }, + }, + "elements": { + "added": {}, + "removed": {}, + "updated": {}, + }, + "id": "id8", + }, + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": { + "selectedElementIds": { + "id3": true, + }, + }, + "inserted": { + "selectedElementIds": {}, + }, + }, + }, + "elements": { + "added": {}, + "removed": {}, + "updated": {}, + }, + "id": "id11", + }, + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": { + "selectedGroupIds": { + "id12": true, + }, + }, + "inserted": { + "selectedGroupIds": {}, + }, + }, + }, + "elements": { + "added": {}, + "removed": {}, + "updated": { + "id0": { + "deleted": { + "groupIds": [ + "id12", + ], + }, + "inserted": { + "groupIds": [], + }, + }, + "id3": { + "deleted": { + "groupIds": [ + "id12", + ], + }, + "inserted": { + "groupIds": [], + }, + }, + }, + }, + "id": "id14", + }, +] +`; + exports[`contextMenu element > shows context menu for canvas > [end of test] appState 1`] = ` { "activeEmbeddable": null, @@ -7803,23 +7748,14 @@ exports[`contextMenu element > shows context menu for canvas > [end of test] app } `; -exports[`contextMenu element > shows context menu for canvas > [end of test] history 1`] = ` -History { - "onHistoryChangedEmitter": Emitter { - "subscribers": [ - [Function], - [Function], - ], - }, - "redoStack": [], - "undoStack": [], -} -`; - 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`] = `3`; +exports[`contextMenu element > shows context menu for canvas > [end of test] redo stack 1`] = `[]`; + +exports[`contextMenu element > shows context menu for canvas > [end of test] undo stack 1`] = `[]`; + exports[`contextMenu element > shows context menu for element > [end of test] appState 1`] = ` { "activeEmbeddable": null, @@ -9903,84 +9839,6 @@ exports[`contextMenu element > shows context menu for element > [end of test] el } `; -exports[`contextMenu element > shows context menu for element > [end of test] history 1`] = ` -History { - "onHistoryChangedEmitter": Emitter { - "subscribers": [ - [Function], - [Function], - ], - }, - "redoStack": [], - "undoStack": [ - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": { - "selectedElementIds": { - "id0": true, - }, - }, - "inserted": { - "selectedElementIds": {}, - }, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map { - "id0" => Delta { - "deleted": { - "angle": 0, - "backgroundColor": "transparent", - "boundElements": null, - "customData": undefined, - "fillStyle": "solid", - "frameId": null, - "groupIds": [], - "height": 20, - "index": "a0", - "isDeleted": false, - "link": null, - "locked": false, - "opacity": 100, - "roughness": 1, - "roundness": { - "type": 3, - }, - "strokeColor": "#1e1e1e", - "strokeStyle": "solid", - "strokeWidth": 2, - "type": "rectangle", - "width": 20, - "x": -10, - "y": 0, - }, - "inserted": { - "isDeleted": true, - }, - }, - }, - "updated": Map {}, - }, - }, - ], -} -`; - -exports[`contextMenu element > shows context menu for element > [end of test] history 2`] = ` -History { - "onHistoryChangedEmitter": Emitter { - "subscribers": [ - [Function], - [Function], - ], - }, - "redoStack": [], - "undoStack": [], -} -`; - exports[`contextMenu element > shows context menu for element > [end of test] number of elements 1`] = `1`; exports[`contextMenu element > shows context menu for element > [end of test] number of elements 2`] = `2`; @@ -9988,3 +9846,66 @@ exports[`contextMenu element > shows context menu for element > [end of test] nu exports[`contextMenu element > shows context menu for element > [end of test] number of renders 1`] = `5`; exports[`contextMenu element > shows context menu for element > [end of test] number of renders 2`] = `6`; + +exports[`contextMenu element > shows context menu for element > [end of test] redo stack 1`] = `[]`; + +exports[`contextMenu element > shows context menu for element > [end of test] redo stack 2`] = `[]`; + +exports[`contextMenu element > shows context menu for element > [end of test] undo stack 1`] = ` +[ + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": { + "selectedElementIds": { + "id0": true, + }, + }, + "inserted": { + "selectedElementIds": {}, + }, + }, + }, + "elements": { + "added": {}, + "removed": { + "id0": { + "deleted": { + "angle": 0, + "backgroundColor": "transparent", + "boundElements": null, + "customData": undefined, + "fillStyle": "solid", + "frameId": null, + "groupIds": [], + "height": 20, + "index": "a0", + "isDeleted": false, + "link": null, + "locked": false, + "opacity": 100, + "roughness": 1, + "roundness": { + "type": 3, + }, + "strokeColor": "#1e1e1e", + "strokeStyle": "solid", + "strokeWidth": 2, + "type": "rectangle", + "width": 20, + "x": -10, + "y": 0, + }, + "inserted": { + "isDeleted": true, + }, + }, + }, + "updated": {}, + }, + "id": "id2", + }, +] +`; + +exports[`contextMenu element > shows context menu for element > [end of test] undo stack 2`] = `[]`; diff --git a/packages/excalidraw/tests/__snapshots__/export.test.tsx.snap b/packages/excalidraw/tests/__snapshots__/export.test.tsx.snap index 7f766e1f9..a561ad5e0 100644 --- a/packages/excalidraw/tests/__snapshots__/export.test.tsx.snap +++ b/packages/excalidraw/tests/__snapshots__/export.test.tsx.snap @@ -7,5 +7,5 @@ exports[`export > export svg-embedded scene > svg-embdedded scene export output exports[`export > exporting svg containing transformed images > svg export output 1`] = ` "" + " `; diff --git a/packages/excalidraw/tests/__snapshots__/history.test.tsx.snap b/packages/excalidraw/tests/__snapshots__/history.test.tsx.snap index 680cd82d5..30bfe5c63 100644 --- a/packages/excalidraw/tests/__snapshots__/history.test.tsx.snap +++ b/packages/excalidraw/tests/__snapshots__/history.test.tsx.snap @@ -79,14 +79,14 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl "penMode": false, "pendingImageElementId": null, "previousSelectedElementIds": { - "id172": true, + "id691": true, }, "resizingElement": null, "scrollX": 0, "scrollY": 0, "searchMatches": [], "selectedElementIds": { - "id172": true, + "id691": true, }, "selectedElementsAreBeingDragged": false, "selectedGroupIds": {}, @@ -124,7 +124,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl "frameId": null, "groupIds": [], "height": 100, - "id": "id170", + "id": "id687", "index": "a0", "isDeleted": false, "link": null, @@ -156,7 +156,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl "frameId": null, "groupIds": [], "height": 100, - "id": "id171", + "id": "id688", "index": "a1", "isDeleted": false, "link": null, @@ -187,7 +187,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl "elbowed": false, "endArrowhead": "arrow", "endBinding": { - "elementId": "id175", + "elementId": "id702", "fixedPoint": [ "0.50000", 1, @@ -199,7 +199,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl "frameId": null, "groupIds": [], "height": "102.45605", - "id": "id172", + "id": "id691", "index": "a2", "isDeleted": false, "lastCommittedPoint": null, @@ -240,7 +240,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl "backgroundColor": "transparent", "boundElements": [ { - "id": "id172", + "id": "id691", "type": "arrow", }, ], @@ -249,7 +249,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl "frameId": null, "groupIds": [], "height": 50, - "id": "id175", + "id": "id702", "index": "a3", "isDeleted": false, "link": null, @@ -271,325 +271,324 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl } `; -exports[`history > multiplayer undo/redo > conflicts in arrows and their bindable elements > should rebind bindings when both are updated through the history and the arrow got bound to a different element in the meantime > [end of test] history 1`] = ` -History { - "onHistoryChangedEmitter": Emitter { - "subscribers": [ - [Function], - [Function], - ], - }, - "redoStack": [ - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": {}, - "inserted": {}, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map {}, - "updated": Map { - "id171" => Delta { - "deleted": { - "boundElements": [], - }, - "inserted": { - "boundElements": [ - { - "id": "id172", - "type": "arrow", - }, - ], - }, - }, - "id172" => Delta { - "deleted": { - "endBinding": { - "elementId": "id175", - "fixedPoint": [ - "0.50000", - 1, - ], - "focus": 0, - "gap": 1, - }, - "height": "70.45017", - "points": [ - [ - 0, - 0, - ], - [ - "100.70774", - "70.45017", - ], - ], - "startBinding": { - "elementId": "id170", - "focus": "0.02970", - "gap": 1, - }, - }, - "inserted": { - "endBinding": { - "elementId": "id171", - "focus": "-0.02000", - "gap": 1, - }, - "height": "0.09250", - "points": [ - [ - 0, - 0, - ], - [ - "98.58579", - "0.09250", - ], - ], - "startBinding": { - "elementId": "id170", - "focus": "0.02000", - "gap": 1, - }, - }, - }, - "id175" => Delta { - "deleted": { - "boundElements": [ - { - "id": "id172", - "type": "arrow", - }, - ], - }, - "inserted": { - "boundElements": [], - }, - }, - }, - }, - }, - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": {}, - "inserted": {}, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map {}, - "updated": Map { - "id170" => Delta { - "deleted": { - "boundElements": [], - }, - "inserted": { - "boundElements": [ - { - "id": "id172", - "type": "arrow", - }, - ], - }, - }, - "id172" => Delta { - "deleted": { - "height": "102.45584", - "points": [ - [ - 0, - 0, - ], - [ - "102.79971", - "102.45584", - ], - ], - "startBinding": null, - "y": 0, - }, - "inserted": { - "height": "70.33521", - "points": [ - [ - 0, - 0, - ], - [ - "100.78887", - "70.33521", - ], - ], - "startBinding": { - "elementId": "id170", - "focus": "0.02970", - "gap": 1, - }, - "y": "35.20327", - }, - }, - }, - }, - }, - ], - "undoStack": [ - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": {}, - "inserted": {}, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map { - "id170" => Delta { - "deleted": { - "angle": 0, - "backgroundColor": "transparent", - "boundElements": null, - "customData": undefined, - "fillStyle": "solid", - "frameId": null, - "groupIds": [], - "height": 100, - "index": "a0", - "isDeleted": false, - "link": null, - "locked": false, - "opacity": 100, - "roughness": 1, - "roundness": { - "type": 3, - }, - "strokeColor": "#1e1e1e", - "strokeStyle": "solid", - "strokeWidth": 2, - "type": "rectangle", - "width": 100, - "x": -100, - "y": -50, - }, - "inserted": { - "isDeleted": true, - }, - }, - "id171" => Delta { - "deleted": { - "angle": 0, - "backgroundColor": "transparent", - "boundElements": null, - "customData": undefined, - "fillStyle": "solid", - "frameId": null, - "groupIds": [], - "height": 100, - "index": "a1", - "isDeleted": false, - "link": null, - "locked": false, - "opacity": 100, - "roughness": 1, - "roundness": { - "type": 3, - }, - "strokeColor": "#1e1e1e", - "strokeStyle": "solid", - "strokeWidth": 2, - "type": "rectangle", - "width": 100, - "x": 100, - "y": -50, - }, - "inserted": { - "isDeleted": true, - }, - }, - }, - "updated": Map {}, - }, - }, - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": { - "selectedElementIds": { - "id172": true, - }, - "selectedLinearElementId": "id172", - }, - "inserted": { - "selectedElementIds": {}, - "selectedLinearElementId": null, - }, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map { - "id172" => Delta { - "deleted": { - "angle": 0, - "backgroundColor": "transparent", - "boundElements": null, - "customData": undefined, - "elbowed": false, - "endArrowhead": "arrow", - "endBinding": null, - "fillStyle": "solid", - "frameId": null, - "groupIds": [], - "height": 0, - "index": "a2", - "isDeleted": false, - "lastCommittedPoint": null, - "link": null, - "locked": false, - "opacity": 100, - "points": [ - [ - 0, - 0, - ], - [ - 100, - 0, - ], - ], - "roughness": 1, - "roundness": { - "type": 2, - }, - "startArrowhead": null, - "startBinding": null, - "strokeColor": "#1e1e1e", - "strokeStyle": "solid", - "strokeWidth": 2, - "type": "arrow", - "width": 100, - "x": 0, - "y": 0, - }, - "inserted": { - "isDeleted": true, - }, - }, - }, - "updated": Map {}, - }, - }, - ], -} -`; - exports[`history > multiplayer undo/redo > conflicts in arrows and their bindable elements > should rebind bindings when both are updated through the history and the arrow got bound to a different element in the meantime > [end of test] number of elements 1`] = `4`; exports[`history > multiplayer undo/redo > conflicts in arrows and their bindable elements > should rebind bindings when both are updated through the history and the arrow got bound to a different element in the meantime > [end of test] number of renders 1`] = `21`; +exports[`history > multiplayer undo/redo > conflicts in arrows and their bindable elements > should rebind bindings when both are updated through the history and the arrow got bound to a different element in the meantime > [end of test] redo stack 1`] = ` +[ + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": {}, + "inserted": {}, + }, + }, + "elements": { + "added": {}, + "removed": {}, + "updated": { + "id688": { + "deleted": { + "boundElements": [], + }, + "inserted": { + "boundElements": [ + { + "id": "id691", + "type": "arrow", + }, + ], + }, + }, + "id691": { + "deleted": { + "endBinding": { + "elementId": "id702", + "fixedPoint": [ + "0.50000", + 1, + ], + "focus": 0, + "gap": 1, + }, + "height": "70.45017", + "points": [ + [ + 0, + 0, + ], + [ + "100.70774", + "70.45017", + ], + ], + "startBinding": { + "elementId": "id687", + "focus": "0.02970", + "gap": 1, + }, + }, + "inserted": { + "endBinding": { + "elementId": "id688", + "focus": "-0.02000", + "gap": 1, + }, + "height": "0.09250", + "points": [ + [ + 0, + 0, + ], + [ + "98.58579", + "0.09250", + ], + ], + "startBinding": { + "elementId": "id687", + "focus": "0.02000", + "gap": 1, + }, + }, + }, + "id702": { + "deleted": { + "boundElements": [ + { + "id": "id691", + "type": "arrow", + }, + ], + }, + "inserted": { + "boundElements": [], + }, + }, + }, + }, + "id": "id709", + }, + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": {}, + "inserted": {}, + }, + }, + "elements": { + "added": {}, + "removed": {}, + "updated": { + "id687": { + "deleted": { + "boundElements": [], + }, + "inserted": { + "boundElements": [ + { + "id": "id691", + "type": "arrow", + }, + ], + }, + }, + "id691": { + "deleted": { + "height": "102.45584", + "points": [ + [ + 0, + 0, + ], + [ + "102.79971", + "102.45584", + ], + ], + "startBinding": null, + "y": 0, + }, + "inserted": { + "height": "70.33521", + "points": [ + [ + 0, + 0, + ], + [ + "100.78887", + "70.33521", + ], + ], + "startBinding": { + "elementId": "id687", + "focus": "0.02970", + "gap": 1, + }, + "y": "35.20327", + }, + }, + }, + }, + "id": "id710", + }, +] +`; + +exports[`history > multiplayer undo/redo > conflicts in arrows and their bindable elements > should rebind bindings when both are updated through the history and the arrow got bound to a different element in the meantime > [end of test] undo stack 1`] = ` +[ + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": {}, + "inserted": {}, + }, + }, + "elements": { + "added": {}, + "removed": { + "id687": { + "deleted": { + "angle": 0, + "backgroundColor": "transparent", + "boundElements": null, + "customData": undefined, + "fillStyle": "solid", + "frameId": null, + "groupIds": [], + "height": 100, + "index": "a0", + "isDeleted": false, + "link": null, + "locked": false, + "opacity": 100, + "roughness": 1, + "roundness": { + "type": 3, + }, + "strokeColor": "#1e1e1e", + "strokeStyle": "solid", + "strokeWidth": 2, + "type": "rectangle", + "width": 100, + "x": -100, + "y": -50, + }, + "inserted": { + "isDeleted": true, + }, + }, + "id688": { + "deleted": { + "angle": 0, + "backgroundColor": "transparent", + "boundElements": null, + "customData": undefined, + "fillStyle": "solid", + "frameId": null, + "groupIds": [], + "height": 100, + "index": "a1", + "isDeleted": false, + "link": null, + "locked": false, + "opacity": 100, + "roughness": 1, + "roundness": { + "type": 3, + }, + "strokeColor": "#1e1e1e", + "strokeStyle": "solid", + "strokeWidth": 2, + "type": "rectangle", + "width": 100, + "x": 100, + "y": -50, + }, + "inserted": { + "isDeleted": true, + }, + }, + }, + "updated": {}, + }, + "id": "id690", + }, + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": { + "selectedElementIds": { + "id691": true, + }, + "selectedLinearElementId": "id691", + }, + "inserted": { + "selectedElementIds": {}, + "selectedLinearElementId": null, + }, + }, + }, + "elements": { + "added": {}, + "removed": { + "id691": { + "deleted": { + "angle": 0, + "backgroundColor": "transparent", + "boundElements": null, + "customData": undefined, + "elbowed": false, + "endArrowhead": "arrow", + "endBinding": null, + "fillStyle": "solid", + "frameId": null, + "groupIds": [], + "height": 0, + "index": "a2", + "isDeleted": false, + "lastCommittedPoint": null, + "link": null, + "locked": false, + "opacity": 100, + "points": [ + [ + 0, + 0, + ], + [ + 100, + 0, + ], + ], + "roughness": 1, + "roundness": { + "type": 2, + }, + "startArrowhead": null, + "startBinding": null, + "strokeColor": "#1e1e1e", + "strokeStyle": "solid", + "strokeWidth": 2, + "type": "arrow", + "width": 100, + "x": 0, + "y": 0, + }, + "inserted": { + "isDeleted": true, + }, + }, + }, + "updated": {}, + }, + "id": "id693", + }, +] +`; + exports[`history > multiplayer undo/redo > conflicts in arrows and their bindable elements > should rebind bindings when both are updated through the history and there are no conflicting updates in the meantime > [end of test] appState 1`] = ` { "activeEmbeddable": null, @@ -669,14 +668,14 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl "penMode": false, "pendingImageElementId": null, "previousSelectedElementIds": { - "id167": true, + "id668": true, }, "resizingElement": null, "scrollX": 0, "scrollY": 0, "searchMatches": [], "selectedElementIds": { - "id167": true, + "id668": true, }, "selectedElementsAreBeingDragged": false, "selectedGroupIds": {}, @@ -714,7 +713,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl "frameId": null, "groupIds": [], "height": 100, - "id": "id165", + "id": "id664", "index": "a0", "isDeleted": false, "link": null, @@ -746,7 +745,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl "frameId": null, "groupIds": [], "height": 100, - "id": "id166", + "id": "id665", "index": "a1", "isDeleted": false, "link": null, @@ -781,7 +780,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl "frameId": null, "groupIds": [], "height": 0, - "id": "id167", + "id": "id668", "index": "a2", "isDeleted": false, "lastCommittedPoint": null, @@ -816,288 +815,287 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl } `; -exports[`history > multiplayer undo/redo > conflicts in arrows and their bindable elements > should rebind bindings when both are updated through the history and there are no conflicting updates in the meantime > [end of test] history 1`] = ` -History { - "onHistoryChangedEmitter": Emitter { - "subscribers": [ - [Function], - [Function], - ], - }, - "redoStack": [ - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": {}, - "inserted": {}, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map {}, - "updated": Map { - "id166" => Delta { - "deleted": { - "boundElements": [], - }, - "inserted": { - "boundElements": [ - { - "id": "id167", - "type": "arrow", - }, - ], - }, - }, - "id167" => Delta { - "deleted": { - "endBinding": null, - "points": [ - [ - 0, - 0, - ], - [ - 100, - 0, - ], - ], - }, - "inserted": { - "endBinding": { - "elementId": "id166", - "focus": -0, - "gap": 1, - }, - "points": [ - [ - 0, - 0, - ], - [ - 0, - 0, - ], - ], - }, - }, - }, - }, - }, - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": {}, - "inserted": {}, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map {}, - "updated": Map { - "id165" => Delta { - "deleted": { - "boundElements": [], - }, - "inserted": { - "boundElements": [ - { - "id": "id167", - "type": "arrow", - }, - ], - }, - }, - "id167" => Delta { - "deleted": { - "points": [ - [ - 0, - 0, - ], - [ - 100, - 0, - ], - ], - "startBinding": null, - }, - "inserted": { - "points": [ - [ - 0, - 0, - ], - [ - 100, - 0, - ], - ], - "startBinding": { - "elementId": "id165", - "focus": 0, - "gap": 1, - }, - }, - }, - }, - }, - }, - ], - "undoStack": [ - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": {}, - "inserted": {}, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map { - "id165" => Delta { - "deleted": { - "angle": 0, - "backgroundColor": "transparent", - "boundElements": null, - "customData": undefined, - "fillStyle": "solid", - "frameId": null, - "groupIds": [], - "height": 100, - "index": "a0", - "isDeleted": false, - "link": null, - "locked": false, - "opacity": 100, - "roughness": 1, - "roundness": { - "type": 3, - }, - "strokeColor": "#1e1e1e", - "strokeStyle": "solid", - "strokeWidth": 2, - "type": "rectangle", - "width": 100, - "x": -100, - "y": -50, - }, - "inserted": { - "isDeleted": true, - }, - }, - "id166" => Delta { - "deleted": { - "angle": 0, - "backgroundColor": "transparent", - "boundElements": null, - "customData": undefined, - "fillStyle": "solid", - "frameId": null, - "groupIds": [], - "height": 100, - "index": "a1", - "isDeleted": false, - "link": null, - "locked": false, - "opacity": 100, - "roughness": 1, - "roundness": { - "type": 3, - }, - "strokeColor": "#1e1e1e", - "strokeStyle": "solid", - "strokeWidth": 2, - "type": "rectangle", - "width": 100, - "x": 100, - "y": -50, - }, - "inserted": { - "isDeleted": true, - }, - }, - }, - "updated": Map {}, - }, - }, - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": { - "selectedElementIds": { - "id167": true, - }, - "selectedLinearElementId": "id167", - }, - "inserted": { - "selectedElementIds": {}, - "selectedLinearElementId": null, - }, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map { - "id167" => Delta { - "deleted": { - "angle": 0, - "backgroundColor": "transparent", - "boundElements": null, - "customData": undefined, - "elbowed": false, - "endArrowhead": "arrow", - "endBinding": null, - "fillStyle": "solid", - "frameId": null, - "groupIds": [], - "height": 0, - "index": "a2", - "isDeleted": false, - "lastCommittedPoint": null, - "link": null, - "locked": false, - "opacity": 100, - "points": [ - [ - 0, - 0, - ], - [ - 100, - 0, - ], - ], - "roughness": 1, - "roundness": { - "type": 2, - }, - "startArrowhead": null, - "startBinding": null, - "strokeColor": "#1e1e1e", - "strokeStyle": "solid", - "strokeWidth": 2, - "type": "arrow", - "width": 100, - "x": 0, - "y": 0, - }, - "inserted": { - "isDeleted": true, - }, - }, - }, - "updated": Map {}, - }, - }, - ], -} -`; - exports[`history > multiplayer undo/redo > conflicts in arrows and their bindable elements > should rebind bindings when both are updated through the history and there are no conflicting updates in the meantime > [end of test] number of elements 1`] = `3`; exports[`history > multiplayer undo/redo > conflicts in arrows and their bindable elements > should rebind bindings when both are updated through the history and there are no conflicting updates in the meantime > [end of test] number of renders 1`] = `23`; +exports[`history > multiplayer undo/redo > conflicts in arrows and their bindable elements > should rebind bindings when both are updated through the history and there are no conflicting updates in the meantime > [end of test] redo stack 1`] = ` +[ + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": {}, + "inserted": {}, + }, + }, + "elements": { + "added": {}, + "removed": {}, + "updated": { + "id665": { + "deleted": { + "boundElements": [], + }, + "inserted": { + "boundElements": [ + { + "id": "id668", + "type": "arrow", + }, + ], + }, + }, + "id668": { + "deleted": { + "endBinding": null, + "points": [ + [ + 0, + 0, + ], + [ + 100, + 0, + ], + ], + }, + "inserted": { + "endBinding": { + "elementId": "id665", + "focus": -0, + "gap": 1, + }, + "points": [ + [ + 0, + 0, + ], + [ + 0, + 0, + ], + ], + }, + }, + }, + }, + "id": "id685", + }, + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": {}, + "inserted": {}, + }, + }, + "elements": { + "added": {}, + "removed": {}, + "updated": { + "id664": { + "deleted": { + "boundElements": [], + }, + "inserted": { + "boundElements": [ + { + "id": "id668", + "type": "arrow", + }, + ], + }, + }, + "id668": { + "deleted": { + "points": [ + [ + 0, + 0, + ], + [ + 100, + 0, + ], + ], + "startBinding": null, + }, + "inserted": { + "points": [ + [ + 0, + 0, + ], + [ + 100, + 0, + ], + ], + "startBinding": { + "elementId": "id664", + "focus": 0, + "gap": 1, + }, + }, + }, + }, + }, + "id": "id686", + }, +] +`; + +exports[`history > multiplayer undo/redo > conflicts in arrows and their bindable elements > should rebind bindings when both are updated through the history and there are no conflicting updates in the meantime > [end of test] undo stack 1`] = ` +[ + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": {}, + "inserted": {}, + }, + }, + "elements": { + "added": {}, + "removed": { + "id664": { + "deleted": { + "angle": 0, + "backgroundColor": "transparent", + "boundElements": null, + "customData": undefined, + "fillStyle": "solid", + "frameId": null, + "groupIds": [], + "height": 100, + "index": "a0", + "isDeleted": false, + "link": null, + "locked": false, + "opacity": 100, + "roughness": 1, + "roundness": { + "type": 3, + }, + "strokeColor": "#1e1e1e", + "strokeStyle": "solid", + "strokeWidth": 2, + "type": "rectangle", + "width": 100, + "x": -100, + "y": -50, + }, + "inserted": { + "isDeleted": true, + }, + }, + "id665": { + "deleted": { + "angle": 0, + "backgroundColor": "transparent", + "boundElements": null, + "customData": undefined, + "fillStyle": "solid", + "frameId": null, + "groupIds": [], + "height": 100, + "index": "a1", + "isDeleted": false, + "link": null, + "locked": false, + "opacity": 100, + "roughness": 1, + "roundness": { + "type": 3, + }, + "strokeColor": "#1e1e1e", + "strokeStyle": "solid", + "strokeWidth": 2, + "type": "rectangle", + "width": 100, + "x": 100, + "y": -50, + }, + "inserted": { + "isDeleted": true, + }, + }, + }, + "updated": {}, + }, + "id": "id667", + }, + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": { + "selectedElementIds": { + "id668": true, + }, + "selectedLinearElementId": "id668", + }, + "inserted": { + "selectedElementIds": {}, + "selectedLinearElementId": null, + }, + }, + }, + "elements": { + "added": {}, + "removed": { + "id668": { + "deleted": { + "angle": 0, + "backgroundColor": "transparent", + "boundElements": null, + "customData": undefined, + "elbowed": false, + "endArrowhead": "arrow", + "endBinding": null, + "fillStyle": "solid", + "frameId": null, + "groupIds": [], + "height": 0, + "index": "a2", + "isDeleted": false, + "lastCommittedPoint": null, + "link": null, + "locked": false, + "opacity": 100, + "points": [ + [ + 0, + 0, + ], + [ + 100, + 0, + ], + ], + "roughness": 1, + "roundness": { + "type": 2, + }, + "startArrowhead": null, + "startBinding": null, + "strokeColor": "#1e1e1e", + "strokeStyle": "solid", + "strokeWidth": 2, + "type": "arrow", + "width": 100, + "x": 0, + "y": 0, + }, + "inserted": { + "isDeleted": true, + }, + }, + }, + "updated": {}, + }, + "id": "id670", + }, +] +`; + exports[`history > multiplayer undo/redo > conflicts in arrows and their bindable elements > should rebind remotely added arrow when it's bindable elements are added through the history > [end of test] appState 1`] = ` { "activeEmbeddable": null, @@ -1220,7 +1218,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl "elbowed": false, "endArrowhead": null, "endBinding": { - "elementId": "id177", + "elementId": "id712", "fixedPoint": [ "0.50000", 1, @@ -1232,7 +1230,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl "frameId": null, "groupIds": [], "height": "1.30038", - "id": "id178", + "id": "id715", "index": "Zz", "isDeleted": false, "lastCommittedPoint": null, @@ -1255,7 +1253,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl }, "startArrowhead": null, "startBinding": { - "elementId": "id176", + "elementId": "id711", "fixedPoint": [ 1, "0.50000", @@ -1281,7 +1279,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl "backgroundColor": "transparent", "boundElements": [ { - "id": "id178", + "id": "id715", "type": "arrow", }, ], @@ -1290,7 +1288,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl "frameId": null, "groupIds": [], "height": 100, - "id": "id176", + "id": "id711", "index": "a0", "isDeleted": false, "link": null, @@ -1318,7 +1316,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl "backgroundColor": "transparent", "boundElements": [ { - "id": "id178", + "id": "id715", "type": "arrow", }, ], @@ -1327,7 +1325,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl "frameId": null, "groupIds": [], "height": 100, - "id": "id177", + "id": "id712", "index": "a1", "isDeleted": false, "link": null, @@ -1349,127 +1347,121 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl } `; -exports[`history > multiplayer undo/redo > conflicts in arrows and their bindable elements > should rebind remotely added arrow when it's bindable elements are added through the history > [end of test] history 1`] = ` -History { - "onHistoryChangedEmitter": Emitter { - "subscribers": [ - [Function], - [Function], - ], - }, - "redoStack": [], - "undoStack": [ - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": {}, - "inserted": {}, +exports[`history > multiplayer undo/redo > conflicts in arrows and their bindable elements > should rebind remotely added arrow when it's bindable elements are added through the history > [end of test] number of elements 1`] = `3`; + +exports[`history > multiplayer undo/redo > conflicts in arrows and their bindable elements > should rebind remotely added arrow when it's bindable elements are added through the history > [end of test] number of renders 1`] = `9`; + +exports[`history > multiplayer undo/redo > conflicts in arrows and their bindable elements > should rebind remotely added arrow when it's bindable elements are added through the history > [end of test] redo stack 1`] = `[]`; + +exports[`history > multiplayer undo/redo > conflicts in arrows and their bindable elements > should rebind remotely added arrow when it's bindable elements are added through the history > [end of test] undo stack 1`] = ` +[ + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": {}, + "inserted": {}, + }, + }, + "elements": { + "added": {}, + "removed": { + "id711": { + "deleted": { + "angle": 0, + "backgroundColor": "transparent", + "boundElements": null, + "customData": undefined, + "fillStyle": "solid", + "frameId": null, + "groupIds": [], + "height": 100, + "index": "a0", + "isDeleted": false, + "link": null, + "locked": false, + "opacity": 100, + "roughness": 1, + "roundness": { + "type": 3, + }, + "strokeColor": "#1e1e1e", + "strokeStyle": "solid", + "strokeWidth": 2, + "type": "rectangle", + "width": 100, + "x": -100, + "y": -50, + }, + "inserted": { + "isDeleted": true, + }, + }, + "id712": { + "deleted": { + "angle": 0, + "backgroundColor": "transparent", + "boundElements": null, + "customData": undefined, + "fillStyle": "solid", + "frameId": null, + "groupIds": [], + "height": 100, + "index": "a1", + "isDeleted": false, + "link": null, + "locked": false, + "opacity": 100, + "roughness": 1, + "roundness": { + "type": 3, + }, + "strokeColor": "#1e1e1e", + "strokeStyle": "solid", + "strokeWidth": 2, + "type": "rectangle", + "width": 100, + "x": 100, + "y": -50, + }, + "inserted": { + "isDeleted": true, + }, }, }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map { - "id176" => Delta { - "deleted": { - "angle": 0, - "backgroundColor": "transparent", - "boundElements": null, - "customData": undefined, - "fillStyle": "solid", - "frameId": null, - "groupIds": [], - "height": 100, - "index": "a0", - "isDeleted": false, - "link": null, - "locked": false, - "opacity": 100, - "roughness": 1, - "roundness": { - "type": 3, - }, - "strokeColor": "#1e1e1e", - "strokeStyle": "solid", - "strokeWidth": 2, - "type": "rectangle", - "width": 100, - "x": -100, - "y": -50, + "updated": { + "id715": { + "deleted": { + "endBinding": { + "elementId": "id712", + "fixedPoint": [ + "0.50000", + 1, + ], + "focus": 0, + "gap": 1, }, - "inserted": { - "isDeleted": true, + "startBinding": { + "elementId": "id711", + "fixedPoint": [ + 1, + "0.50000", + ], + "focus": 0, + "gap": 1, }, }, - "id177" => Delta { - "deleted": { - "angle": 0, - "backgroundColor": "transparent", - "boundElements": null, - "customData": undefined, - "fillStyle": "solid", - "frameId": null, - "groupIds": [], - "height": 100, - "index": "a1", - "isDeleted": false, - "link": null, - "locked": false, - "opacity": 100, - "roughness": 1, - "roundness": { - "type": 3, - }, - "strokeColor": "#1e1e1e", - "strokeStyle": "solid", - "strokeWidth": 2, - "type": "rectangle", - "width": 100, - "x": 100, - "y": -50, - }, - "inserted": { - "isDeleted": true, - }, - }, - }, - "updated": Map { - "id178" => Delta { - "deleted": { - "endBinding": { - "elementId": "id177", - "fixedPoint": [ - "0.50000", - 1, - ], - "focus": 0, - "gap": 1, - }, - "startBinding": { - "elementId": "id176", - "fixedPoint": [ - 1, - "0.50000", - ], - "focus": 0, - "gap": 1, - }, - }, - "inserted": { - "endBinding": null, - "startBinding": null, - }, + "inserted": { + "endBinding": null, + "startBinding": null, }, }, }, }, - ], -} + "id": "id719", + }, +] `; -exports[`history > multiplayer undo/redo > conflicts in arrows and their bindable elements > should rebind remotely added arrow when it's bindable elements are added through the history > [end of test] number of elements 1`] = `3`; - -exports[`history > multiplayer undo/redo > conflicts in arrows and their bindable elements > should rebind remotely added arrow when it's bindable elements are added through the history > [end of test] number of renders 1`] = `9`; - exports[`history > multiplayer undo/redo > conflicts in arrows and their bindable elements > should rebind remotely added bindable elements when it's arrow is added through the history > [end of test] appState 1`] = ` { "activeEmbeddable": null, @@ -1592,7 +1584,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl "elbowed": false, "endArrowhead": null, "endBinding": { - "elementId": "id180", + "elementId": "id721", "fixedPoint": [ 1, "0.50000", @@ -1604,7 +1596,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl "frameId": null, "groupIds": [], "height": "1.30038", - "id": "id181", + "id": "id725", "index": "a0", "isDeleted": false, "lastCommittedPoint": null, @@ -1627,7 +1619,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl }, "startArrowhead": null, "startBinding": { - "elementId": "id179", + "elementId": "id720", "fixedPoint": [ "0.50000", 1, @@ -1653,7 +1645,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl "backgroundColor": "transparent", "boundElements": [ { - "id": "id181", + "id": "id725", "type": "arrow", }, ], @@ -1662,7 +1654,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl "frameId": null, "groupIds": [], "height": 100, - "id": "id179", + "id": "id720", "index": "a0V", "isDeleted": false, "link": null, @@ -1690,7 +1682,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl "backgroundColor": "transparent", "boundElements": [ { - "id": "id181", + "id": "id725", "type": "arrow", }, ], @@ -1699,7 +1691,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl "frameId": null, "groupIds": [], "height": 100, - "id": "id180", + "id": "id721", "index": "a1", "isDeleted": false, "link": null, @@ -1721,128 +1713,122 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl } `; -exports[`history > multiplayer undo/redo > conflicts in arrows and their bindable elements > should rebind remotely added bindable elements when it's arrow is added through the history > [end of test] history 1`] = ` -History { - "onHistoryChangedEmitter": Emitter { - "subscribers": [ - [Function], - [Function], - ], - }, - "redoStack": [], - "undoStack": [ - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": {}, - "inserted": {}, +exports[`history > multiplayer undo/redo > conflicts in arrows and their bindable elements > should rebind remotely added bindable elements when it's arrow is added through the history > [end of test] number of elements 1`] = `3`; + +exports[`history > multiplayer undo/redo > conflicts in arrows and their bindable elements > should rebind remotely added bindable elements when it's arrow is added through the history > [end of test] number of renders 1`] = `11`; + +exports[`history > multiplayer undo/redo > conflicts in arrows and their bindable elements > should rebind remotely added bindable elements when it's arrow is added through the history > [end of test] redo stack 1`] = `[]`; + +exports[`history > multiplayer undo/redo > conflicts in arrows and their bindable elements > should rebind remotely added bindable elements when it's arrow is added through the history > [end of test] undo stack 1`] = ` +[ + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": {}, + "inserted": {}, + }, + }, + "elements": { + "added": {}, + "removed": { + "id725": { + "deleted": { + "angle": 0, + "backgroundColor": "transparent", + "boundElements": null, + "customData": undefined, + "elbowed": false, + "endArrowhead": null, + "endBinding": { + "elementId": "id721", + "fixedPoint": [ + 1, + "0.50000", + ], + "focus": 0, + "gap": 1, + }, + "fillStyle": "solid", + "frameId": null, + "groupIds": [], + "height": "11.27227", + "index": "a0", + "isDeleted": false, + "lastCommittedPoint": null, + "link": null, + "locked": false, + "opacity": 100, + "points": [ + [ + 0, + 0, + ], + [ + "98.58579", + "11.27227", + ], + ], + "roughness": 1, + "roundness": { + "type": 2, + }, + "startArrowhead": null, + "startBinding": { + "elementId": "id720", + "fixedPoint": [ + "0.50000", + 1, + ], + "focus": 0, + "gap": 1, + }, + "strokeColor": "#1e1e1e", + "strokeStyle": "solid", + "strokeWidth": 2, + "type": "arrow", + "width": "98.58579", + "x": "0.70711", + "y": 0, + }, + "inserted": { + "isDeleted": true, + }, }, }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map { - "id181" => Delta { - "deleted": { - "angle": 0, - "backgroundColor": "transparent", - "boundElements": null, - "customData": undefined, - "elbowed": false, - "endArrowhead": null, - "endBinding": { - "elementId": "id180", - "fixedPoint": [ - 1, - "0.50000", - ], - "focus": 0, - "gap": 1, + "updated": { + "id720": { + "deleted": { + "boundElements": [ + { + "id": "id725", + "type": "arrow", }, - "fillStyle": "solid", - "frameId": null, - "groupIds": [], - "height": "11.27227", - "index": "a0", - "isDeleted": false, - "lastCommittedPoint": null, - "link": null, - "locked": false, - "opacity": 100, - "points": [ - [ - 0, - 0, - ], - [ - "98.58579", - "11.27227", - ], - ], - "roughness": 1, - "roundness": { - "type": 2, - }, - "startArrowhead": null, - "startBinding": { - "elementId": "id179", - "fixedPoint": [ - "0.50000", - 1, - ], - "focus": 0, - "gap": 1, - }, - "strokeColor": "#1e1e1e", - "strokeStyle": "solid", - "strokeWidth": 2, - "type": "arrow", - "width": "98.58579", - "x": "0.70711", - "y": 0, - }, - "inserted": { - "isDeleted": true, - }, + ], + }, + "inserted": { + "boundElements": [], }, }, - "updated": Map { - "id179" => Delta { - "deleted": { - "boundElements": [ - { - "id": "id181", - "type": "arrow", - }, - ], - }, - "inserted": { - "boundElements": [], - }, + "id721": { + "deleted": { + "boundElements": [ + { + "id": "id725", + "type": "arrow", + }, + ], }, - "id180" => Delta { - "deleted": { - "boundElements": [ - { - "id": "id181", - "type": "arrow", - }, - ], - }, - "inserted": { - "boundElements": [], - }, + "inserted": { + "boundElements": [], }, }, }, }, - ], -} + "id": "id731", + }, +] `; -exports[`history > multiplayer undo/redo > conflicts in arrows and their bindable elements > should rebind remotely added bindable elements when it's arrow is added through the history > [end of test] number of elements 1`] = `3`; - -exports[`history > multiplayer undo/redo > conflicts in arrows and their bindable elements > should rebind remotely added bindable elements when it's arrow is added through the history > [end of test] number of renders 1`] = `11`; - exports[`history > multiplayer undo/redo > conflicts in arrows and their bindable elements > should unbind remotely deleted bindable elements from arrow when the arrow is added through the history > [end of test] appState 1`] = ` { "activeEmbeddable": null, @@ -1966,7 +1952,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl "frameId": null, "groupIds": [], "height": 100, - "id": "id182", + "id": "id732", "index": "a0", "isDeleted": false, "link": null, @@ -1998,7 +1984,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl "frameId": null, "groupIds": [], "height": 100, - "id": "id183", + "id": "id733", "index": "a1", "isDeleted": false, "link": null, @@ -2020,100 +2006,94 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl } `; -exports[`history > multiplayer undo/redo > conflicts in arrows and their bindable elements > should unbind remotely deleted bindable elements from arrow when the arrow is added through the history > [end of test] history 1`] = ` -History { - "onHistoryChangedEmitter": Emitter { - "subscribers": [ - [Function], - [Function], - ], - }, - "redoStack": [], - "undoStack": [ - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": {}, - "inserted": {}, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map { - "id182" => Delta { - "deleted": { - "angle": 0, - "backgroundColor": "transparent", - "boundElements": null, - "customData": undefined, - "fillStyle": "solid", - "frameId": null, - "groupIds": [], - "height": 100, - "index": "a0", - "isDeleted": false, - "link": null, - "locked": false, - "opacity": 100, - "roughness": 1, - "roundness": { - "type": 3, - }, - "strokeColor": "#1e1e1e", - "strokeStyle": "solid", - "strokeWidth": 2, - "type": "rectangle", - "width": 100, - "x": -100, - "y": -50, - }, - "inserted": { - "isDeleted": true, - }, - }, - "id183" => Delta { - "deleted": { - "angle": 0, - "backgroundColor": "transparent", - "boundElements": null, - "customData": undefined, - "fillStyle": "solid", - "frameId": null, - "groupIds": [], - "height": 100, - "index": "a1", - "isDeleted": false, - "link": null, - "locked": false, - "opacity": 100, - "roughness": 1, - "roundness": { - "type": 3, - }, - "strokeColor": "#1e1e1e", - "strokeStyle": "solid", - "strokeWidth": 2, - "type": "rectangle", - "width": 100, - "x": 100, - "y": -50, - }, - "inserted": { - "isDeleted": true, - }, - }, - }, - "updated": Map {}, - }, - }, - ], -} -`; - exports[`history > multiplayer undo/redo > conflicts in arrows and their bindable elements > should unbind remotely deleted bindable elements from arrow when the arrow is added through the history > [end of test] number of elements 1`] = `2`; exports[`history > multiplayer undo/redo > conflicts in arrows and their bindable elements > should unbind remotely deleted bindable elements from arrow when the arrow is added through the history > [end of test] number of renders 1`] = `4`; +exports[`history > multiplayer undo/redo > conflicts in arrows and their bindable elements > should unbind remotely deleted bindable elements from arrow when the arrow is added through the history > [end of test] redo stack 1`] = `[]`; + +exports[`history > multiplayer undo/redo > conflicts in arrows and their bindable elements > should unbind remotely deleted bindable elements from arrow when the arrow is added through the history > [end of test] undo stack 1`] = ` +[ + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": {}, + "inserted": {}, + }, + }, + "elements": { + "added": {}, + "removed": { + "id732": { + "deleted": { + "angle": 0, + "backgroundColor": "transparent", + "boundElements": null, + "customData": undefined, + "fillStyle": "solid", + "frameId": null, + "groupIds": [], + "height": 100, + "index": "a0", + "isDeleted": false, + "link": null, + "locked": false, + "opacity": 100, + "roughness": 1, + "roundness": { + "type": 3, + }, + "strokeColor": "#1e1e1e", + "strokeStyle": "solid", + "strokeWidth": 2, + "type": "rectangle", + "width": 100, + "x": -100, + "y": -50, + }, + "inserted": { + "isDeleted": true, + }, + }, + "id733": { + "deleted": { + "angle": 0, + "backgroundColor": "transparent", + "boundElements": null, + "customData": undefined, + "fillStyle": "solid", + "frameId": null, + "groupIds": [], + "height": 100, + "index": "a1", + "isDeleted": false, + "link": null, + "locked": false, + "opacity": 100, + "roughness": 1, + "roundness": { + "type": 3, + }, + "strokeColor": "#1e1e1e", + "strokeStyle": "solid", + "strokeWidth": 2, + "type": "rectangle", + "width": 100, + "x": 100, + "y": -50, + }, + "inserted": { + "isDeleted": true, + }, + }, + }, + "updated": {}, + }, + "id": "id735", + }, +] +`; + exports[`history > multiplayer undo/redo > conflicts in arrows and their bindable elements > should update bound element points when rectangle was remotely moved and arrow is added back through the history > [end of test] appState 1`] = ` { "activeEmbeddable": null, @@ -2198,7 +2178,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl "scrollY": 0, "searchMatches": [], "selectedElementIds": { - "id186": true, + "id740": true, }, "selectedElementsAreBeingDragged": false, "selectedGroupIds": {}, @@ -2232,7 +2212,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl "backgroundColor": "transparent", "boundElements": [ { - "id": "id186", + "id": "id740", "type": "arrow", }, ], @@ -2241,7 +2221,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl "frameId": null, "groupIds": [], "height": 100, - "id": "id184", + "id": "id736", "index": "a0", "isDeleted": false, "link": null, @@ -2269,7 +2249,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl "backgroundColor": "transparent", "boundElements": [ { - "id": "id186", + "id": "id740", "type": "arrow", }, ], @@ -2278,7 +2258,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl "frameId": null, "groupIds": [], "height": 100, - "id": "id185", + "id": "id737", "index": "a1", "isDeleted": false, "link": null, @@ -2309,7 +2289,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl "elbowed": false, "endArrowhead": "arrow", "endBinding": { - "elementId": "id185", + "elementId": "id737", "focus": -0, "gap": 1, }, @@ -2317,7 +2297,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl "frameId": null, "groupIds": [], "height": "374.05754", - "id": "id186", + "id": "id740", "index": "a2", "isDeleted": false, "lastCommittedPoint": null, @@ -2340,7 +2320,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl }, "startArrowhead": null, "startBinding": { - "elementId": "id184", + "elementId": "id736", "focus": 0, "gap": 1, }, @@ -2356,204 +2336,199 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl } `; -exports[`history > multiplayer undo/redo > conflicts in arrows and their bindable elements > should update bound element points when rectangle was remotely moved and arrow is added back through the history > [end of test] history 1`] = ` -History { - "onHistoryChangedEmitter": Emitter { - "subscribers": [ - [Function], - [Function], - ], - }, - "redoStack": [], - "undoStack": [ - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": {}, - "inserted": {}, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map { - "id184" => Delta { - "deleted": { - "angle": 0, - "backgroundColor": "transparent", - "boundElements": null, - "customData": undefined, - "fillStyle": "solid", - "frameId": null, - "groupIds": [], - "height": 100, - "index": "a0", - "isDeleted": false, - "link": null, - "locked": false, - "opacity": 100, - "roughness": 1, - "roundness": { - "type": 3, - }, - "strokeColor": "#1e1e1e", - "strokeStyle": "solid", - "strokeWidth": 2, - "type": "rectangle", - "width": 100, - "x": -100, - "y": -50, - }, - "inserted": { - "isDeleted": true, - }, - }, - "id185" => Delta { - "deleted": { - "angle": 0, - "backgroundColor": "transparent", - "boundElements": null, - "customData": undefined, - "fillStyle": "solid", - "frameId": null, - "groupIds": [], - "height": 100, - "index": "a1", - "isDeleted": false, - "link": null, - "locked": false, - "opacity": 100, - "roughness": 1, - "roundness": { - "type": 3, - }, - "strokeColor": "#1e1e1e", - "strokeStyle": "solid", - "strokeWidth": 2, - "type": "rectangle", - "width": 100, - "x": 100, - "y": -50, - }, - "inserted": { - "isDeleted": true, - }, - }, - }, - "updated": Map {}, - }, - }, - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": { - "selectedElementIds": { - "id186": true, - }, - "selectedLinearElementId": "id186", - }, - "inserted": { - "selectedElementIds": {}, - "selectedLinearElementId": null, - }, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map { - "id186" => Delta { - "deleted": { - "angle": 0, - "backgroundColor": "transparent", - "boundElements": null, - "customData": undefined, - "elbowed": false, - "endArrowhead": "arrow", - "endBinding": { - "elementId": "id185", - "focus": -0, - "gap": 1, - }, - "fillStyle": "solid", - "frameId": null, - "groupIds": [], - "height": 0, - "index": "a2", - "isDeleted": false, - "lastCommittedPoint": null, - "link": null, - "locked": false, - "opacity": 100, - "points": [ - [ - 0, - 0, - ], - [ - 100, - 0, - ], - ], - "roughness": 1, - "roundness": { - "type": 2, - }, - "startArrowhead": null, - "startBinding": { - "elementId": "id184", - "focus": 0, - "gap": 1, - }, - "strokeColor": "#1e1e1e", - "strokeStyle": "solid", - "strokeWidth": 2, - "type": "arrow", - "width": 100, - "x": 0, - "y": 0, - }, - "inserted": { - "isDeleted": true, - }, - }, - }, - "updated": Map { - "id184" => Delta { - "deleted": { - "boundElements": [ - { - "id": "id186", - "type": "arrow", - }, - ], - }, - "inserted": { - "boundElements": [], - }, - }, - "id185" => Delta { - "deleted": { - "boundElements": [ - { - "id": "id186", - "type": "arrow", - }, - ], - }, - "inserted": { - "boundElements": [], - }, - }, - }, - }, - }, - ], -} -`; - exports[`history > multiplayer undo/redo > conflicts in arrows and their bindable elements > should update bound element points when rectangle was remotely moved and arrow is added back through the history > [end of test] number of elements 1`] = `3`; exports[`history > multiplayer undo/redo > conflicts in arrows and their bindable elements > should update bound element points when rectangle was remotely moved and arrow is added back through the history > [end of test] number of renders 1`] = `9`; +exports[`history > multiplayer undo/redo > conflicts in arrows and their bindable elements > should update bound element points when rectangle was remotely moved and arrow is added back through the history > [end of test] redo stack 1`] = `[]`; + +exports[`history > multiplayer undo/redo > conflicts in arrows and their bindable elements > should update bound element points when rectangle was remotely moved and arrow is added back through the history > [end of test] undo stack 1`] = ` +[ + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": {}, + "inserted": {}, + }, + }, + "elements": { + "added": {}, + "removed": { + "id736": { + "deleted": { + "angle": 0, + "backgroundColor": "transparent", + "boundElements": null, + "customData": undefined, + "fillStyle": "solid", + "frameId": null, + "groupIds": [], + "height": 100, + "index": "a0", + "isDeleted": false, + "link": null, + "locked": false, + "opacity": 100, + "roughness": 1, + "roundness": { + "type": 3, + }, + "strokeColor": "#1e1e1e", + "strokeStyle": "solid", + "strokeWidth": 2, + "type": "rectangle", + "width": 100, + "x": -100, + "y": -50, + }, + "inserted": { + "isDeleted": true, + }, + }, + "id737": { + "deleted": { + "angle": 0, + "backgroundColor": "transparent", + "boundElements": null, + "customData": undefined, + "fillStyle": "solid", + "frameId": null, + "groupIds": [], + "height": 100, + "index": "a1", + "isDeleted": false, + "link": null, + "locked": false, + "opacity": 100, + "roughness": 1, + "roundness": { + "type": 3, + }, + "strokeColor": "#1e1e1e", + "strokeStyle": "solid", + "strokeWidth": 2, + "type": "rectangle", + "width": 100, + "x": 100, + "y": -50, + }, + "inserted": { + "isDeleted": true, + }, + }, + }, + "updated": {}, + }, + "id": "id739", + }, + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": { + "selectedElementIds": { + "id740": true, + }, + "selectedLinearElementId": "id740", + }, + "inserted": { + "selectedElementIds": {}, + "selectedLinearElementId": null, + }, + }, + }, + "elements": { + "added": {}, + "removed": { + "id740": { + "deleted": { + "angle": 0, + "backgroundColor": "transparent", + "boundElements": null, + "customData": undefined, + "elbowed": false, + "endArrowhead": "arrow", + "endBinding": { + "elementId": "id737", + "focus": -0, + "gap": 1, + }, + "fillStyle": "solid", + "frameId": null, + "groupIds": [], + "height": 0, + "index": "a2", + "isDeleted": false, + "lastCommittedPoint": null, + "link": null, + "locked": false, + "opacity": 100, + "points": [ + [ + 0, + 0, + ], + [ + 100, + 0, + ], + ], + "roughness": 1, + "roundness": { + "type": 2, + }, + "startArrowhead": null, + "startBinding": { + "elementId": "id736", + "focus": 0, + "gap": 1, + }, + "strokeColor": "#1e1e1e", + "strokeStyle": "solid", + "strokeWidth": 2, + "type": "arrow", + "width": 100, + "x": 0, + "y": 0, + }, + "inserted": { + "isDeleted": true, + }, + }, + }, + "updated": { + "id736": { + "deleted": { + "boundElements": [ + { + "id": "id740", + "type": "arrow", + }, + ], + }, + "inserted": { + "boundElements": [], + }, + }, + "id737": { + "deleted": { + "boundElements": [ + { + "id": "id740", + "type": "arrow", + }, + ], + }, + "inserted": { + "boundElements": [], + }, + }, + }, + }, + "id": "id744", + }, +] +`; + exports[`history > multiplayer undo/redo > conflicts in bound text elements and their containers > should preserve latest remotely added binding and unbind previous one when the container is added through the history > [end of test] appState 1`] = ` { "activeEmbeddable": null, @@ -2673,7 +2648,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and "backgroundColor": "transparent", "boundElements": [ { - "id": "id153", + "id": "id618", "type": "text", }, ], @@ -2682,7 +2657,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and "frameId": null, "groupIds": [], "height": 100, - "id": "id151", + "id": "id613", "index": "a0", "isDeleted": false, "link": null, @@ -2718,7 +2693,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and "frameId": null, "groupIds": [], "height": 100, - "id": "id152", + "id": "id614", "index": "a1", "isDeleted": false, "lineHeight": "1.25000", @@ -2751,7 +2726,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and "autoResize": true, "backgroundColor": "transparent", "boundElements": null, - "containerId": "id151", + "containerId": "id613", "customData": undefined, "fillStyle": "solid", "fontFamily": 5, @@ -2759,7 +2734,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and "frameId": null, "groupIds": [], "height": 25, - "id": "id153", + "id": "id618", "index": "a2", "isDeleted": false, "lineHeight": "1.25000", @@ -2786,76 +2761,70 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and } `; -exports[`history > multiplayer undo/redo > conflicts in bound text elements and their containers > should preserve latest remotely added binding and unbind previous one when the container is added through the history > [end of test] history 1`] = ` -History { - "onHistoryChangedEmitter": Emitter { - "subscribers": [ - [Function], - [Function], - ], - }, - "redoStack": [ - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": {}, - "inserted": {}, - }, +exports[`history > multiplayer undo/redo > conflicts in bound text elements and their containers > should preserve latest remotely added binding and unbind previous one when the container is added through the history > [end of test] number of elements 1`] = `3`; + +exports[`history > multiplayer undo/redo > conflicts in bound text elements and their containers > should preserve latest remotely added binding and unbind previous one when the container is added through the history > [end of test] number of renders 1`] = `11`; + +exports[`history > multiplayer undo/redo > conflicts in bound text elements and their containers > should preserve latest remotely added binding and unbind previous one when the container is added through the history > [end of test] redo stack 1`] = ` +[ + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": {}, + "inserted": {}, }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map {}, - "updated": Map { - "id151" => Delta { - "deleted": { - "isDeleted": false, - }, - "inserted": { - "angle": 0, - "backgroundColor": "transparent", - "boundElements": null, - "customData": undefined, - "fillStyle": "solid", - "frameId": null, - "groupIds": [], - "height": 100, - "index": "a0", - "isDeleted": false, - "link": null, - "locked": false, - "opacity": 100, - "roughness": 1, - "roundness": { - "type": 3, - }, - "strokeColor": "#1e1e1e", - "strokeStyle": "solid", - "strokeWidth": 2, - "type": "rectangle", - "width": 100, - "x": 10, - "y": 10, - }, + }, + "elements": { + "added": {}, + "removed": {}, + "updated": { + "id613": { + "deleted": { + "isDeleted": false, }, - "id152" => Delta { - "deleted": { - "containerId": null, - }, - "inserted": { - "containerId": null, + "inserted": { + "angle": 0, + "backgroundColor": "transparent", + "boundElements": null, + "customData": undefined, + "fillStyle": "solid", + "frameId": null, + "groupIds": [], + "height": 100, + "index": "a0", + "isDeleted": false, + "link": null, + "locked": false, + "opacity": 100, + "roughness": 1, + "roundness": { + "type": 3, }, + "strokeColor": "#1e1e1e", + "strokeStyle": "solid", + "strokeWidth": 2, + "type": "rectangle", + "width": 100, + "x": 10, + "y": 10, + }, + }, + "id614": { + "deleted": { + "containerId": null, + }, + "inserted": { + "containerId": null, }, }, }, }, - ], - "undoStack": [], -} + "id": "id622", + }, +] `; -exports[`history > multiplayer undo/redo > conflicts in bound text elements and their containers > should preserve latest remotely added binding and unbind previous one when the container is added through the history > [end of test] number of elements 1`] = `3`; - -exports[`history > multiplayer undo/redo > conflicts in bound text elements and their containers > should preserve latest remotely added binding and unbind previous one when the container is added through the history > [end of test] number of renders 1`] = `11`; +exports[`history > multiplayer undo/redo > conflicts in bound text elements and their containers > should preserve latest remotely added binding and unbind previous one when the container is added through the history > [end of test] undo stack 1`] = `[]`; exports[`history > multiplayer undo/redo > conflicts in bound text elements and their containers > should preserve latest remotely added binding and unbind previous one when the text is added through history > [end of test] appState 1`] = ` { @@ -2976,7 +2945,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and "backgroundColor": "transparent", "boundElements": [ { - "id": "id156", + "id": "id628", "type": "text", }, ], @@ -2985,7 +2954,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and "frameId": null, "groupIds": [], "height": 100, - "id": "id154", + "id": "id623", "index": "Zz", "isDeleted": false, "link": null, @@ -3013,7 +2982,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and "autoResize": true, "backgroundColor": "transparent", "boundElements": null, - "containerId": "id154", + "containerId": "id623", "customData": undefined, "fillStyle": "solid", "fontFamily": 5, @@ -3021,7 +2990,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and "frameId": null, "groupIds": [], "height": 100, - "id": "id155", + "id": "id624", "index": "a0", "isDeleted": true, "lineHeight": "1.25000", @@ -3054,7 +3023,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and "autoResize": true, "backgroundColor": "transparent", "boundElements": null, - "containerId": "id154", + "containerId": "id623", "customData": undefined, "fillStyle": "solid", "fontFamily": 5, @@ -3062,7 +3031,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and "frameId": null, "groupIds": [], "height": 25, - "id": "id156", + "id": "id628", "index": "a1", "isDeleted": false, "lineHeight": "1.25000", @@ -3089,61 +3058,55 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and } `; -exports[`history > multiplayer undo/redo > conflicts in bound text elements and their containers > should preserve latest remotely added binding and unbind previous one when the text is added through history > [end of test] history 1`] = ` -History { - "onHistoryChangedEmitter": Emitter { - "subscribers": [ - [Function], - [Function], - ], - }, - "redoStack": [ - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": {}, - "inserted": {}, - }, +exports[`history > multiplayer undo/redo > conflicts in bound text elements and their containers > should preserve latest remotely added binding and unbind previous one when the text is added through history > [end of test] number of elements 1`] = `3`; + +exports[`history > multiplayer undo/redo > conflicts in bound text elements and their containers > should preserve latest remotely added binding and unbind previous one when the text is added through history > [end of test] number of renders 1`] = `11`; + +exports[`history > multiplayer undo/redo > conflicts in bound text elements and their containers > should preserve latest remotely added binding and unbind previous one when the text is added through history > [end of test] redo stack 1`] = ` +[ + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": {}, + "inserted": {}, }, - "elementsChange": ElementsChange { - "added": Map { - "id155" => Delta { - "deleted": { - "containerId": "id154", - "isDeleted": true, - }, - "inserted": { - "containerId": null, - "isDeleted": false, - }, + }, + "elements": { + "added": { + "id624": { + "deleted": { + "containerId": "id623", + "isDeleted": true, + }, + "inserted": { + "containerId": null, + "isDeleted": false, }, }, - "removed": Map {}, - "updated": Map { - "id154" => Delta { - "deleted": { - "boundElements": [], - }, - "inserted": { - "boundElements": [ - { - "id": "id155", - "type": "text", - }, - ], - }, + }, + "removed": {}, + "updated": { + "id623": { + "deleted": { + "boundElements": [], + }, + "inserted": { + "boundElements": [ + { + "id": "id624", + "type": "text", + }, + ], }, }, }, }, - ], - "undoStack": [], -} + "id": "id632", + }, +] `; -exports[`history > multiplayer undo/redo > conflicts in bound text elements and their containers > should preserve latest remotely added binding and unbind previous one when the text is added through history > [end of test] number of elements 1`] = `3`; - -exports[`history > multiplayer undo/redo > conflicts in bound text elements and their containers > should preserve latest remotely added binding and unbind previous one when the text is added through history > [end of test] number of renders 1`] = `11`; +exports[`history > multiplayer undo/redo > conflicts in bound text elements and their containers > should preserve latest remotely added binding and unbind previous one when the text is added through history > [end of test] undo stack 1`] = `[]`; exports[`history > multiplayer undo/redo > conflicts in bound text elements and their containers > should rebind bindings when both are updated through the history and the container got bound to a different text in the meantime > [end of test] appState 1`] = ` { @@ -3264,7 +3227,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and "backgroundColor": "transparent", "boundElements": [ { - "id": "id143", + "id": "id582", "type": "text", }, ], @@ -3273,7 +3236,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and "frameId": null, "groupIds": [], "height": 100, - "id": "id141", + "id": "id577", "index": "a0", "isDeleted": false, "link": null, @@ -3301,7 +3264,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and "autoResize": true, "backgroundColor": "transparent", "boundElements": null, - "containerId": "id141", + "containerId": "id577", "customData": undefined, "fillStyle": "solid", "fontFamily": 5, @@ -3309,7 +3272,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and "frameId": null, "groupIds": [], "height": 25, - "id": "id143", + "id": "id582", "index": "a0V", "isDeleted": false, "lineHeight": "1.25000", @@ -3350,7 +3313,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and "frameId": null, "groupIds": [], "height": 25, - "id": "id142", + "id": "id578", "index": "a1", "isDeleted": false, "lineHeight": "1.25000", @@ -3377,71 +3340,65 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and } `; -exports[`history > multiplayer undo/redo > conflicts in bound text elements and their containers > should rebind bindings when both are updated through the history and the container got bound to a different text in the meantime > [end of test] history 1`] = ` -History { - "onHistoryChangedEmitter": Emitter { - "subscribers": [ - [Function], - [Function], - ], - }, - "redoStack": [ - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": {}, - "inserted": {}, - }, +exports[`history > multiplayer undo/redo > conflicts in bound text elements and their containers > should rebind bindings when both are updated through the history and the container got bound to a different text in the meantime > [end of test] number of elements 1`] = `3`; + +exports[`history > multiplayer undo/redo > conflicts in bound text elements and their containers > should rebind bindings when both are updated through the history and the container got bound to a different text in the meantime > [end of test] number of renders 1`] = `11`; + +exports[`history > multiplayer undo/redo > conflicts in bound text elements and their containers > should rebind bindings when both are updated through the history and the container got bound to a different text in the meantime > [end of test] redo stack 1`] = ` +[ + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": {}, + "inserted": {}, }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map {}, - "updated": Map { - "id141" => Delta { - "deleted": { - "boundElements": [ - { - "id": "id143", - "type": "text", - }, - ], - }, - "inserted": { - "boundElements": [ - { - "id": "id142", - "type": "text", - }, - ], - }, + }, + "elements": { + "added": {}, + "removed": {}, + "updated": { + "id577": { + "deleted": { + "boundElements": [ + { + "id": "id582", + "type": "text", + }, + ], }, - "id142" => Delta { - "deleted": { - "containerId": null, - }, - "inserted": { - "containerId": "id141", - }, + "inserted": { + "boundElements": [ + { + "id": "id578", + "type": "text", + }, + ], }, - "id143" => Delta { - "deleted": { - "containerId": "id141", - }, - "inserted": { - "containerId": null, - }, + }, + "id578": { + "deleted": { + "containerId": null, + }, + "inserted": { + "containerId": "id577", + }, + }, + "id582": { + "deleted": { + "containerId": "id577", + }, + "inserted": { + "containerId": null, }, }, }, }, - ], - "undoStack": [], -} + "id": "id586", + }, +] `; -exports[`history > multiplayer undo/redo > conflicts in bound text elements and their containers > should rebind bindings when both are updated through the history and the container got bound to a different text in the meantime > [end of test] number of elements 1`] = `3`; - -exports[`history > multiplayer undo/redo > conflicts in bound text elements and their containers > should rebind bindings when both are updated through the history and the container got bound to a different text in the meantime > [end of test] number of renders 1`] = `11`; +exports[`history > multiplayer undo/redo > conflicts in bound text elements and their containers > should rebind bindings when both are updated through the history and the container got bound to a different text in the meantime > [end of test] undo stack 1`] = `[]`; exports[`history > multiplayer undo/redo > conflicts in bound text elements and their containers > should rebind bindings when both are updated through the history and the text got bound to a different container in the meantime > [end of test] appState 1`] = ` { @@ -3566,7 +3523,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and "frameId": null, "groupIds": [], "height": 100, - "id": "id144", + "id": "id587", "index": "a0", "isDeleted": false, "link": null, @@ -3594,7 +3551,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and "backgroundColor": "transparent", "boundElements": [ { - "id": "id145", + "id": "id588", "type": "text", }, ], @@ -3603,7 +3560,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and "frameId": null, "groupIds": [], "height": 60, - "id": "id146", + "id": "id592", "index": "a0V", "isDeleted": false, "link": null, @@ -3631,7 +3588,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and "autoResize": true, "backgroundColor": "transparent", "boundElements": null, - "containerId": "id146", + "containerId": "id592", "customData": undefined, "fillStyle": "solid", "fontFamily": 5, @@ -3639,7 +3596,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and "frameId": null, "groupIds": [], "height": 50, - "id": "id145", + "id": "id588", "index": "a1", "isDeleted": false, "lineHeight": "1.25000", @@ -3667,71 +3624,65 @@ pasa", } `; -exports[`history > multiplayer undo/redo > conflicts in bound text elements and their containers > should rebind bindings when both are updated through the history and the text got bound to a different container in the meantime > [end of test] history 1`] = ` -History { - "onHistoryChangedEmitter": Emitter { - "subscribers": [ - [Function], - [Function], - ], - }, - "redoStack": [ - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": {}, - "inserted": {}, - }, +exports[`history > multiplayer undo/redo > conflicts in bound text elements and their containers > should rebind bindings when both are updated through the history and the text got bound to a different container in the meantime > [end of test] number of elements 1`] = `3`; + +exports[`history > multiplayer undo/redo > conflicts in bound text elements and their containers > should rebind bindings when both are updated through the history and the text got bound to a different container in the meantime > [end of test] number of renders 1`] = `11`; + +exports[`history > multiplayer undo/redo > conflicts in bound text elements and their containers > should rebind bindings when both are updated through the history and the text got bound to a different container in the meantime > [end of test] redo stack 1`] = ` +[ + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": {}, + "inserted": {}, }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map {}, - "updated": Map { - "id144" => Delta { - "deleted": { - "boundElements": [], - }, - "inserted": { - "boundElements": [ - { - "id": "id145", - "type": "text", - }, - ], - }, + }, + "elements": { + "added": {}, + "removed": {}, + "updated": { + "id587": { + "deleted": { + "boundElements": [], }, - "id145" => Delta { - "deleted": { - "containerId": "id146", - }, - "inserted": { - "containerId": "id144", - }, + "inserted": { + "boundElements": [ + { + "id": "id588", + "type": "text", + }, + ], }, - "id146" => Delta { - "deleted": { - "boundElements": [ - { - "id": "id145", - "type": "text", - }, - ], - }, - "inserted": { - "boundElements": [], - }, + }, + "id588": { + "deleted": { + "containerId": "id592", + }, + "inserted": { + "containerId": "id587", + }, + }, + "id592": { + "deleted": { + "boundElements": [ + { + "id": "id588", + "type": "text", + }, + ], + }, + "inserted": { + "boundElements": [], }, }, }, }, - ], - "undoStack": [], -} + "id": "id596", + }, +] `; -exports[`history > multiplayer undo/redo > conflicts in bound text elements and their containers > should rebind bindings when both are updated through the history and the text got bound to a different container in the meantime > [end of test] number of elements 1`] = `3`; - -exports[`history > multiplayer undo/redo > conflicts in bound text elements and their containers > should rebind bindings when both are updated through the history and the text got bound to a different container in the meantime > [end of test] number of renders 1`] = `11`; +exports[`history > multiplayer undo/redo > conflicts in bound text elements and their containers > should rebind bindings when both are updated through the history and the text got bound to a different container in the meantime > [end of test] undo stack 1`] = `[]`; exports[`history > multiplayer undo/redo > conflicts in bound text elements and their containers > should rebind bindings when both are updated through the history and there no conflicting updates in the meantime > [end of test] appState 1`] = ` { @@ -3856,7 +3807,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and "frameId": null, "groupIds": [], "height": 100, - "id": "id139", + "id": "id568", "index": "a0", "isDeleted": false, "link": null, @@ -3892,7 +3843,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and "frameId": null, "groupIds": [], "height": 25, - "id": "id140", + "id": "id569", "index": "a1", "isDeleted": false, "lineHeight": "1.25000", @@ -3919,58 +3870,52 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and } `; -exports[`history > multiplayer undo/redo > conflicts in bound text elements and their containers > should rebind bindings when both are updated through the history and there no conflicting updates in the meantime > [end of test] history 1`] = ` -History { - "onHistoryChangedEmitter": Emitter { - "subscribers": [ - [Function], - [Function], - ], - }, - "redoStack": [ - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": {}, - "inserted": {}, - }, +exports[`history > multiplayer undo/redo > conflicts in bound text elements and their containers > should rebind bindings when both are updated through the history and there no conflicting updates in the meantime > [end of test] number of elements 1`] = `2`; + +exports[`history > multiplayer undo/redo > conflicts in bound text elements and their containers > should rebind bindings when both are updated through the history and there no conflicting updates in the meantime > [end of test] number of renders 1`] = `11`; + +exports[`history > multiplayer undo/redo > conflicts in bound text elements and their containers > should rebind bindings when both are updated through the history and there no conflicting updates in the meantime > [end of test] redo stack 1`] = ` +[ + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": {}, + "inserted": {}, }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map {}, - "updated": Map { - "id139" => Delta { - "deleted": { - "boundElements": [], - }, - "inserted": { - "boundElements": [ - { - "id": "id140", - "type": "text", - }, - ], - }, + }, + "elements": { + "added": {}, + "removed": {}, + "updated": { + "id568": { + "deleted": { + "boundElements": [], }, - "id140" => Delta { - "deleted": { - "containerId": null, - }, - "inserted": { - "containerId": "id139", - }, + "inserted": { + "boundElements": [ + { + "id": "id569", + "type": "text", + }, + ], + }, + }, + "id569": { + "deleted": { + "containerId": null, + }, + "inserted": { + "containerId": "id568", }, }, }, }, - ], - "undoStack": [], -} + "id": "id576", + }, +] `; -exports[`history > multiplayer undo/redo > conflicts in bound text elements and their containers > should rebind bindings when both are updated through the history and there no conflicting updates in the meantime > [end of test] number of elements 1`] = `2`; - -exports[`history > multiplayer undo/redo > conflicts in bound text elements and their containers > should rebind bindings when both are updated through the history and there no conflicting updates in the meantime > [end of test] number of renders 1`] = `11`; +exports[`history > multiplayer undo/redo > conflicts in bound text elements and their containers > should rebind bindings when both are updated through the history and there no conflicting updates in the meantime > [end of test] undo stack 1`] = `[]`; exports[`history > multiplayer undo/redo > conflicts in bound text elements and their containers > should rebind remotely added bound text when it's container is added through the history > [end of test] appState 1`] = ` { @@ -4091,7 +4036,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and "backgroundColor": "transparent", "boundElements": [ { - "id": "id148", + "id": "id598", "type": "text", }, ], @@ -4100,7 +4045,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and "frameId": null, "groupIds": [], "height": 100, - "id": "id147", + "id": "id597", "index": "a0", "isDeleted": false, "link": null, @@ -4128,7 +4073,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and "autoResize": true, "backgroundColor": "transparent", "boundElements": null, - "containerId": "id147", + "containerId": "id597", "customData": undefined, "fillStyle": "solid", "fontFamily": 5, @@ -4136,7 +4081,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and "frameId": null, "groupIds": [], "height": 25, - "id": "id148", + "id": "id598", "index": "a1", "isDeleted": false, "lineHeight": "1.25000", @@ -4163,78 +4108,72 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and } `; -exports[`history > multiplayer undo/redo > conflicts in bound text elements and their containers > should rebind remotely added bound text when it's container is added through the history > [end of test] history 1`] = ` -History { - "onHistoryChangedEmitter": Emitter { - "subscribers": [ - [Function], - [Function], - ], - }, - "redoStack": [], - "undoStack": [ - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": {}, - "inserted": {}, - }, +exports[`history > multiplayer undo/redo > conflicts in bound text elements and their containers > should rebind remotely added bound text when it's container is added through the history > [end of test] number of elements 1`] = `2`; + +exports[`history > multiplayer undo/redo > conflicts in bound text elements and their containers > should rebind remotely added bound text when it's container is added through the history > [end of test] number of renders 1`] = `9`; + +exports[`history > multiplayer undo/redo > conflicts in bound text elements and their containers > should rebind remotely added bound text when it's container is added through the history > [end of test] redo stack 1`] = `[]`; + +exports[`history > multiplayer undo/redo > conflicts in bound text elements and their containers > should rebind remotely added bound text when it's container is added through the history > [end of test] undo stack 1`] = ` +[ + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": {}, + "inserted": {}, }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map { - "id147" => Delta { - "deleted": { - "angle": 0, - "backgroundColor": "transparent", - "boundElements": null, - "customData": undefined, - "fillStyle": "solid", - "frameId": null, - "groupIds": [], - "height": 100, - "index": "a0", - "isDeleted": false, - "link": null, - "locked": false, - "opacity": 100, - "roughness": 1, - "roundness": { - "type": 3, - }, - "strokeColor": "#1e1e1e", - "strokeStyle": "solid", - "strokeWidth": 2, - "type": "rectangle", - "width": 100, - "x": 10, - "y": 10, - }, - "inserted": { - "isDeleted": true, + }, + "elements": { + "added": {}, + "removed": { + "id597": { + "deleted": { + "angle": 0, + "backgroundColor": "transparent", + "boundElements": null, + "customData": undefined, + "fillStyle": "solid", + "frameId": null, + "groupIds": [], + "height": 100, + "index": "a0", + "isDeleted": false, + "link": null, + "locked": false, + "opacity": 100, + "roughness": 1, + "roundness": { + "type": 3, }, + "strokeColor": "#1e1e1e", + "strokeStyle": "solid", + "strokeWidth": 2, + "type": "rectangle", + "width": 100, + "x": 10, + "y": 10, + }, + "inserted": { + "isDeleted": true, }, }, - "updated": Map { - "id148" => Delta { - "deleted": { - "containerId": "id147", - }, - "inserted": { - "containerId": null, - }, + }, + "updated": { + "id598": { + "deleted": { + "containerId": "id597", + }, + "inserted": { + "containerId": null, }, }, }, }, - ], -} + "id": "id604", + }, +] `; -exports[`history > multiplayer undo/redo > conflicts in bound text elements and their containers > should rebind remotely added bound text when it's container is added through the history > [end of test] number of elements 1`] = `2`; - -exports[`history > multiplayer undo/redo > conflicts in bound text elements and their containers > should rebind remotely added bound text when it's container is added through the history > [end of test] number of renders 1`] = `9`; - exports[`history > multiplayer undo/redo > conflicts in bound text elements and their containers > should rebind remotely added container when it's bound text is added through the history > [end of test] appState 1`] = ` { "activeEmbeddable": null, @@ -4354,7 +4293,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and "backgroundColor": "transparent", "boundElements": [ { - "id": "id150", + "id": "id606", "type": "text", }, ], @@ -4363,7 +4302,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and "frameId": null, "groupIds": [], "height": 100, - "id": "id149", + "id": "id605", "index": "Zz", "isDeleted": false, "link": null, @@ -4391,7 +4330,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and "autoResize": true, "backgroundColor": "transparent", "boundElements": null, - "containerId": "id149", + "containerId": "id605", "customData": undefined, "fillStyle": "solid", "fontFamily": 5, @@ -4399,7 +4338,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and "frameId": null, "groupIds": [], "height": 25, - "id": "id150", + "id": "id606", "index": "a0", "isDeleted": false, "lineHeight": "1.25000", @@ -4426,92 +4365,86 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and } `; -exports[`history > multiplayer undo/redo > conflicts in bound text elements and their containers > should rebind remotely added container when it's bound text is added through the history > [end of test] history 1`] = ` -History { - "onHistoryChangedEmitter": Emitter { - "subscribers": [ - [Function], - [Function], - ], - }, - "redoStack": [], - "undoStack": [ - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": {}, - "inserted": {}, - }, +exports[`history > multiplayer undo/redo > conflicts in bound text elements and their containers > should rebind remotely added container when it's bound text is added through the history > [end of test] number of elements 1`] = `2`; + +exports[`history > multiplayer undo/redo > conflicts in bound text elements and their containers > should rebind remotely added container when it's bound text is added through the history > [end of test] number of renders 1`] = `9`; + +exports[`history > multiplayer undo/redo > conflicts in bound text elements and their containers > should rebind remotely added container when it's bound text is added through the history > [end of test] redo stack 1`] = `[]`; + +exports[`history > multiplayer undo/redo > conflicts in bound text elements and their containers > should rebind remotely added container when it's bound text is added through the history > [end of test] undo stack 1`] = ` +[ + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": {}, + "inserted": {}, }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map { - "id150" => Delta { - "deleted": { - "angle": 0, - "autoResize": true, - "backgroundColor": "transparent", - "boundElements": null, - "containerId": "id149", - "customData": undefined, - "fillStyle": "solid", - "fontFamily": 5, - "fontSize": 20, - "frameId": null, - "groupIds": [], - "height": 25, - "index": "a0", - "isDeleted": false, - "lineHeight": "1.25000", - "link": null, - "locked": false, - "opacity": 100, - "originalText": "que pasa", - "roughness": 1, - "roundness": { - "type": 3, - }, - "strokeColor": "#1e1e1e", - "strokeStyle": "solid", - "strokeWidth": 2, - "text": "que pasa", - "textAlign": "left", - "type": "text", - "verticalAlign": "top", - "width": 80, - "x": 15, - "y": 15, - }, - "inserted": { - "isDeleted": true, + }, + "elements": { + "added": {}, + "removed": { + "id606": { + "deleted": { + "angle": 0, + "autoResize": true, + "backgroundColor": "transparent", + "boundElements": null, + "containerId": "id605", + "customData": undefined, + "fillStyle": "solid", + "fontFamily": 5, + "fontSize": 20, + "frameId": null, + "groupIds": [], + "height": 25, + "index": "a0", + "isDeleted": false, + "lineHeight": "1.25000", + "link": null, + "locked": false, + "opacity": 100, + "originalText": "que pasa", + "roughness": 1, + "roundness": { + "type": 3, }, + "strokeColor": "#1e1e1e", + "strokeStyle": "solid", + "strokeWidth": 2, + "text": "que pasa", + "textAlign": "left", + "type": "text", + "verticalAlign": "top", + "width": 80, + "x": 15, + "y": 15, + }, + "inserted": { + "isDeleted": true, }, }, - "updated": Map { - "id149" => Delta { - "deleted": { - "boundElements": [ - { - "id": "id150", - "type": "text", - }, - ], - }, - "inserted": { - "boundElements": [], - }, + }, + "updated": { + "id605": { + "deleted": { + "boundElements": [ + { + "id": "id606", + "type": "text", + }, + ], + }, + "inserted": { + "boundElements": [], }, }, }, }, - ], -} + "id": "id612", + }, +] `; -exports[`history > multiplayer undo/redo > conflicts in bound text elements and their containers > should rebind remotely added container when it's bound text is added through the history > [end of test] number of elements 1`] = `2`; - -exports[`history > multiplayer undo/redo > conflicts in bound text elements and their containers > should rebind remotely added container when it's bound text is added through the history > [end of test] number of renders 1`] = `9`; - exports[`history > multiplayer undo/redo > conflicts in bound text elements and their containers > should redraw bound text to match container dimensions when the bound text is updated through the history > [end of test] appState 1`] = ` { "activeEmbeddable": null, @@ -4631,7 +4564,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and "backgroundColor": "transparent", "boundElements": [ { - "id": "id164", + "id": "id658", "type": "text", }, ], @@ -4640,7 +4573,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and "frameId": null, "groupIds": [], "height": 100, - "id": "id163", + "id": "id657", "index": "Zz", "isDeleted": false, "link": null, @@ -4668,7 +4601,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and "autoResize": true, "backgroundColor": "transparent", "boundElements": null, - "containerId": "id163", + "containerId": "id657", "customData": undefined, "fillStyle": "solid", "fontFamily": 5, @@ -4676,7 +4609,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and "frameId": null, "groupIds": [], "height": 25, - "id": "id164", + "id": "id658", "index": "a0", "isDeleted": false, "lineHeight": "1.25000", @@ -4703,49 +4636,43 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and } `; -exports[`history > multiplayer undo/redo > conflicts in bound text elements and their containers > should redraw bound text to match container dimensions when the bound text is updated through the history > [end of test] history 1`] = ` -History { - "onHistoryChangedEmitter": Emitter { - "subscribers": [ - [Function], - [Function], - ], - }, - "redoStack": [ - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": {}, - "inserted": {}, - }, +exports[`history > multiplayer undo/redo > conflicts in bound text elements and their containers > should redraw bound text to match container dimensions when the bound text is updated through the history > [end of test] number of elements 1`] = `2`; + +exports[`history > multiplayer undo/redo > conflicts in bound text elements and their containers > should redraw bound text to match container dimensions when the bound text is updated through the history > [end of test] number of renders 1`] = `9`; + +exports[`history > multiplayer undo/redo > conflicts in bound text elements and their containers > should redraw bound text to match container dimensions when the bound text is updated through the history > [end of test] redo stack 1`] = ` +[ + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": {}, + "inserted": {}, }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map {}, - "updated": Map { - "id164" => Delta { - "deleted": { - "angle": 0, - "x": 15, - "y": 15, - }, - "inserted": { - "angle": 0, - "x": 15, - "y": 15, - }, + }, + "elements": { + "added": {}, + "removed": {}, + "updated": { + "id658": { + "deleted": { + "angle": 0, + "x": 15, + "y": 15, + }, + "inserted": { + "angle": 0, + "x": 15, + "y": 15, }, }, }, }, - ], - "undoStack": [], -} + "id": "id663", + }, +] `; -exports[`history > multiplayer undo/redo > conflicts in bound text elements and their containers > should redraw bound text to match container dimensions when the bound text is updated through the history > [end of test] number of elements 1`] = `2`; - -exports[`history > multiplayer undo/redo > conflicts in bound text elements and their containers > should redraw bound text to match container dimensions when the bound text is updated through the history > [end of test] number of renders 1`] = `9`; +exports[`history > multiplayer undo/redo > conflicts in bound text elements and their containers > should redraw bound text to match container dimensions when the bound text is updated through the history > [end of test] undo stack 1`] = `[]`; exports[`history > multiplayer undo/redo > conflicts in bound text elements and their containers > should redraw remotely added bound text when it's container is updated through the history > [end of test] appState 1`] = ` { @@ -4866,7 +4793,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and "backgroundColor": "transparent", "boundElements": [ { - "id": "id162", + "id": "id650", "type": "text", }, ], @@ -4875,7 +4802,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and "frameId": null, "groupIds": [], "height": 100, - "id": "id161", + "id": "id649", "index": "a0", "isDeleted": false, "link": null, @@ -4903,7 +4830,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and "autoResize": true, "backgroundColor": "transparent", "boundElements": null, - "containerId": "id161", + "containerId": "id649", "customData": undefined, "fillStyle": "solid", "fontFamily": 5, @@ -4911,7 +4838,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and "frameId": null, "groupIds": [], "height": 25, - "id": "id162", + "id": "id650", "index": "a1", "isDeleted": false, "lineHeight": "1.25000", @@ -4938,50 +4865,44 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and } `; -exports[`history > multiplayer undo/redo > conflicts in bound text elements and their containers > should redraw remotely added bound text when it's container is updated through the history > [end of test] history 1`] = ` -History { - "onHistoryChangedEmitter": Emitter { - "subscribers": [ - [Function], - [Function], - ], - }, - "redoStack": [], - "undoStack": [ - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": {}, - "inserted": {}, - }, +exports[`history > multiplayer undo/redo > conflicts in bound text elements and their containers > should redraw remotely added bound text when it's container is updated through the history > [end of test] number of elements 1`] = `2`; + +exports[`history > multiplayer undo/redo > conflicts in bound text elements and their containers > should redraw remotely added bound text when it's container is updated through the history > [end of test] number of renders 1`] = `10`; + +exports[`history > multiplayer undo/redo > conflicts in bound text elements and their containers > should redraw remotely added bound text when it's container is updated through the history > [end of test] redo stack 1`] = `[]`; + +exports[`history > multiplayer undo/redo > conflicts in bound text elements and their containers > should redraw remotely added bound text when it's container is updated through the history > [end of test] undo stack 1`] = ` +[ + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": {}, + "inserted": {}, }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map {}, - "updated": Map { - "id161" => Delta { - "deleted": { - "angle": 90, - "x": 200, - "y": 200, - }, - "inserted": { - "angle": 0, - "x": 10, - "y": 10, - }, + }, + "elements": { + "added": {}, + "removed": {}, + "updated": { + "id649": { + "deleted": { + "angle": 90, + "x": 200, + "y": 200, + }, + "inserted": { + "angle": 0, + "x": 10, + "y": 10, }, }, }, }, - ], -} + "id": "id656", + }, +] `; -exports[`history > multiplayer undo/redo > conflicts in bound text elements and their containers > should redraw remotely added bound text when it's container is updated through the history > [end of test] number of elements 1`] = `2`; - -exports[`history > multiplayer undo/redo > conflicts in bound text elements and their containers > should redraw remotely added bound text when it's container is updated through the history > [end of test] number of renders 1`] = `10`; - exports[`history > multiplayer undo/redo > conflicts in bound text elements and their containers > should unbind remotely deleted bound text from container when the container is added through the history > [end of test] appState 1`] = ` { "activeEmbeddable": null, @@ -5105,7 +5026,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and "frameId": null, "groupIds": [], "height": 100, - "id": "id157", + "id": "id633", "index": "a0", "isDeleted": false, "link": null, @@ -5133,7 +5054,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and "autoResize": true, "backgroundColor": "transparent", "boundElements": null, - "containerId": "id157", + "containerId": "id633", "customData": undefined, "fillStyle": "solid", "fontFamily": 5, @@ -5141,7 +5062,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and "frameId": null, "groupIds": [], "height": 100, - "id": "id158", + "id": "id634", "index": "a1", "isDeleted": true, "lineHeight": "1.25000", @@ -5168,53 +5089,47 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and } `; -exports[`history > multiplayer undo/redo > conflicts in bound text elements and their containers > should unbind remotely deleted bound text from container when the container is added through the history > [end of test] history 1`] = ` -History { - "onHistoryChangedEmitter": Emitter { - "subscribers": [ - [Function], - [Function], - ], - }, - "redoStack": [], - "undoStack": [ - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": {}, - "inserted": {}, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map { - "id157" => Delta { - "deleted": { - "boundElements": [], - "isDeleted": false, - }, - "inserted": { - "boundElements": [ - { - "id": "id158", - "type": "text", - }, - ], - "isDeleted": true, - }, - }, - }, - "updated": Map {}, - }, - }, - ], -} -`; - exports[`history > multiplayer undo/redo > conflicts in bound text elements and their containers > should unbind remotely deleted bound text from container when the container is added through the history > [end of test] number of elements 1`] = `2`; exports[`history > multiplayer undo/redo > conflicts in bound text elements and their containers > should unbind remotely deleted bound text from container when the container is added through the history > [end of test] number of renders 1`] = `9`; +exports[`history > multiplayer undo/redo > conflicts in bound text elements and their containers > should unbind remotely deleted bound text from container when the container is added through the history > [end of test] redo stack 1`] = `[]`; + +exports[`history > multiplayer undo/redo > conflicts in bound text elements and their containers > should unbind remotely deleted bound text from container when the container is added through the history > [end of test] undo stack 1`] = ` +[ + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": {}, + "inserted": {}, + }, + }, + "elements": { + "added": {}, + "removed": { + "id633": { + "deleted": { + "boundElements": [], + "isDeleted": false, + }, + "inserted": { + "boundElements": [ + { + "id": "id634", + "type": "text", + }, + ], + "isDeleted": true, + }, + }, + }, + "updated": {}, + }, + "id": "id640", + }, +] +`; + exports[`history > multiplayer undo/redo > conflicts in bound text elements and their containers > should unbind remotely deleted container from bound text when the text is added through the history > [end of test] appState 1`] = ` { "activeEmbeddable": null, @@ -5334,7 +5249,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and "backgroundColor": "transparent", "boundElements": [ { - "id": "id160", + "id": "id642", "type": "text", }, ], @@ -5343,7 +5258,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and "frameId": null, "groupIds": [], "height": 100, - "id": "id159", + "id": "id641", "index": "Zz", "isDeleted": true, "link": null, @@ -5379,7 +5294,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and "frameId": null, "groupIds": [], "height": 100, - "id": "id160", + "id": "id642", "index": "a0", "isDeleted": false, "lineHeight": "1.25000", @@ -5406,48 +5321,42 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and } `; -exports[`history > multiplayer undo/redo > conflicts in bound text elements and their containers > should unbind remotely deleted container from bound text when the text is added through the history > [end of test] history 1`] = ` -History { - "onHistoryChangedEmitter": Emitter { - "subscribers": [ - [Function], - [Function], - ], - }, - "redoStack": [], - "undoStack": [ - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": {}, - "inserted": {}, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map { - "id160" => Delta { - "deleted": { - "containerId": null, - "isDeleted": false, - }, - "inserted": { - "containerId": "id159", - "isDeleted": true, - }, - }, - }, - "updated": Map {}, - }, - }, - ], -} -`; - exports[`history > multiplayer undo/redo > conflicts in bound text elements and their containers > should unbind remotely deleted container from bound text when the text is added through the history > [end of test] number of elements 1`] = `2`; exports[`history > multiplayer undo/redo > conflicts in bound text elements and their containers > should unbind remotely deleted container from bound text when the text is added through the history > [end of test] number of renders 1`] = `9`; +exports[`history > multiplayer undo/redo > conflicts in bound text elements and their containers > should unbind remotely deleted container from bound text when the text is added through the history > [end of test] redo stack 1`] = `[]`; + +exports[`history > multiplayer undo/redo > conflicts in bound text elements and their containers > should unbind remotely deleted container from bound text when the text is added through the history > [end of test] undo stack 1`] = ` +[ + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": {}, + "inserted": {}, + }, + }, + "elements": { + "added": {}, + "removed": { + "id642": { + "deleted": { + "containerId": null, + "isDeleted": false, + }, + "inserted": { + "containerId": "id641", + "isDeleted": true, + }, + }, + }, + "updated": {}, + }, + "id": "id648", + }, +] +`; + exports[`history > multiplayer undo/redo > conflicts in frames and their children > should not rebind frame child with frame when frame was remotely deleted and frame child is added back through the history > [end of test] appState 1`] = ` { "activeEmbeddable": null, @@ -5571,7 +5480,7 @@ exports[`history > multiplayer undo/redo > conflicts in frames and their childre "frameId": null, "groupIds": [], "height": 100, - "id": "id188", + "id": "id746", "index": "Zz", "isDeleted": false, "link": null, @@ -5603,7 +5512,7 @@ exports[`history > multiplayer undo/redo > conflicts in frames and their childre "frameId": null, "groupIds": [], "height": 500, - "id": "id187", + "id": "id745", "index": "a0", "isDeleted": true, "link": null, @@ -5626,91 +5535,86 @@ exports[`history > multiplayer undo/redo > conflicts in frames and their childre } `; -exports[`history > multiplayer undo/redo > conflicts in frames and their children > should not rebind frame child with frame when frame was remotely deleted and frame child is added back through the history > [end of test] history 1`] = ` -History { - "onHistoryChangedEmitter": Emitter { - "subscribers": [ - [Function], - [Function], - ], - }, - "redoStack": [], - "undoStack": [ - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": {}, - "inserted": {}, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map { - "id188" => Delta { - "deleted": { - "angle": 0, - "backgroundColor": "transparent", - "boundElements": null, - "customData": undefined, - "fillStyle": "solid", - "frameId": null, - "groupIds": [], - "height": 100, - "index": "Zz", - "isDeleted": false, - "link": null, - "locked": false, - "opacity": 100, - "roughness": 1, - "roundness": { - "type": 3, - }, - "strokeColor": "#1e1e1e", - "strokeStyle": "solid", - "strokeWidth": 2, - "type": "rectangle", - "width": 100, - "x": 10, - "y": 10, - }, - "inserted": { - "isDeleted": true, - }, - }, - }, - "updated": Map {}, - }, - }, - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": {}, - "inserted": {}, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map {}, - "updated": Map { - "id188" => Delta { - "deleted": { - "frameId": "id187", - }, - "inserted": { - "frameId": null, - }, - }, - }, - }, - }, - ], -} -`; - exports[`history > multiplayer undo/redo > conflicts in frames and their children > should not rebind frame child with frame when frame was remotely deleted and frame child is added back through the history > [end of test] number of elements 1`] = `2`; exports[`history > multiplayer undo/redo > conflicts in frames and their children > should not rebind frame child with frame when frame was remotely deleted and frame child is added back through the history > [end of test] number of renders 1`] = `13`; +exports[`history > multiplayer undo/redo > conflicts in frames and their children > should not rebind frame child with frame when frame was remotely deleted and frame child is added back through the history > [end of test] redo stack 1`] = `[]`; + +exports[`history > multiplayer undo/redo > conflicts in frames and their children > should not rebind frame child with frame when frame was remotely deleted and frame child is added back through the history > [end of test] undo stack 1`] = ` +[ + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": {}, + "inserted": {}, + }, + }, + "elements": { + "added": {}, + "removed": { + "id746": { + "deleted": { + "angle": 0, + "backgroundColor": "transparent", + "boundElements": null, + "customData": undefined, + "fillStyle": "solid", + "frameId": null, + "groupIds": [], + "height": 100, + "index": "Zz", + "isDeleted": false, + "link": null, + "locked": false, + "opacity": 100, + "roughness": 1, + "roundness": { + "type": 3, + }, + "strokeColor": "#1e1e1e", + "strokeStyle": "solid", + "strokeWidth": 2, + "type": "rectangle", + "width": 100, + "x": 10, + "y": 10, + }, + "inserted": { + "isDeleted": true, + }, + }, + }, + "updated": {}, + }, + "id": "id755", + }, + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": {}, + "inserted": {}, + }, + }, + "elements": { + "added": {}, + "removed": {}, + "updated": { + "id746": { + "deleted": { + "frameId": "id745", + }, + "inserted": { + "frameId": null, + }, + }, + }, + }, + "id": "id756", + }, +] +`; + exports[`history > multiplayer undo/redo > should iterate through the history when editing group contains only remotely deleted elements > [end of test] appState 1`] = ` { "activeEmbeddable": null, @@ -5790,7 +5694,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh "penMode": false, "pendingImageElementId": null, "previousSelectedElementIds": { - "id116": true, + "id469": true, }, "resizingElement": null, "scrollX": 0, @@ -5835,7 +5739,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh "A", ], "height": 100, - "id": "id115", + "id": "id468", "index": "a0", "isDeleted": false, "link": null, @@ -5869,7 +5773,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh "A", ], "height": 100, - "id": "id116", + "id": "id469", "index": "a1", "isDeleted": true, "link": null, @@ -5891,161 +5795,157 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh } `; -exports[`history > multiplayer undo/redo > should iterate through the history when editing group contains only remotely deleted elements > [end of test] history 1`] = ` -History { - "onHistoryChangedEmitter": Emitter { - "subscribers": [ - [Function], - [Function], - ], - }, - "redoStack": [], - "undoStack": [ - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": { - "selectedElementIds": { - "id115": true, - "id116": true, - }, - "selectedGroupIds": { - "A": true, - }, - }, - "inserted": { - "selectedElementIds": {}, - "selectedGroupIds": {}, - }, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map {}, - "updated": Map { - "id115" => Delta { - "deleted": { - "angle": 0, - "backgroundColor": "transparent", - "boundElements": null, - "customData": undefined, - "fillStyle": "solid", - "frameId": null, - "groupIds": [ - "A", - ], - "height": 100, - "index": "a0", - "isDeleted": true, - "link": null, - "locked": false, - "opacity": 100, - "roughness": 1, - "roundness": { - "type": 3, - }, - "strokeColor": "#1e1e1e", - "strokeStyle": "solid", - "strokeWidth": 2, - "type": "rectangle", - "width": 100, - "x": 0, - "y": 0, - }, - "inserted": { - "isDeleted": true, - }, - }, - "id116" => Delta { - "deleted": { - "angle": 0, - "backgroundColor": "transparent", - "boundElements": null, - "customData": undefined, - "fillStyle": "solid", - "frameId": null, - "groupIds": [ - "A", - ], - "height": 100, - "index": "a1", - "isDeleted": true, - "link": null, - "locked": false, - "opacity": 100, - "roughness": 1, - "roundness": { - "type": 3, - }, - "strokeColor": "#1e1e1e", - "strokeStyle": "solid", - "strokeWidth": 2, - "type": "rectangle", - "width": 100, - "x": 100, - "y": 100, - }, - "inserted": { - "isDeleted": true, - }, - }, - }, - }, - }, - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": { - "editingGroupId": "A", - "selectedElementIds": {}, - "selectedGroupIds": {}, - }, - "inserted": { - "editingGroupId": null, - "selectedElementIds": { - "id115": true, - }, - "selectedGroupIds": { - "A": true, - }, - }, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map {}, - "updated": Map {}, - }, - }, - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": { - "editingGroupId": null, - "selectedElementIds": {}, - }, - "inserted": { - "editingGroupId": "A", - "selectedElementIds": { - "id116": true, - }, - }, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map {}, - "updated": Map {}, - }, - }, - ], -} -`; - exports[`history > multiplayer undo/redo > should iterate through the history when editing group contains only remotely deleted elements > [end of test] number of elements 1`] = `2`; exports[`history > multiplayer undo/redo > should iterate through the history when editing group contains only remotely deleted elements > [end of test] number of renders 1`] = `13`; +exports[`history > multiplayer undo/redo > should iterate through the history when editing group contains only remotely deleted elements > [end of test] redo stack 1`] = `[]`; + +exports[`history > multiplayer undo/redo > should iterate through the history when editing group contains only remotely deleted elements > [end of test] undo stack 1`] = ` +[ + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": { + "selectedElementIds": { + "id468": true, + "id469": true, + }, + "selectedGroupIds": { + "A": true, + }, + }, + "inserted": { + "selectedElementIds": {}, + "selectedGroupIds": {}, + }, + }, + }, + "elements": { + "added": {}, + "removed": {}, + "updated": { + "id468": { + "deleted": { + "angle": 0, + "backgroundColor": "transparent", + "boundElements": null, + "customData": undefined, + "fillStyle": "solid", + "frameId": null, + "groupIds": [ + "A", + ], + "height": 100, + "index": "a0", + "isDeleted": true, + "link": null, + "locked": false, + "opacity": 100, + "roughness": 1, + "roundness": { + "type": 3, + }, + "strokeColor": "#1e1e1e", + "strokeStyle": "solid", + "strokeWidth": 2, + "type": "rectangle", + "width": 100, + "x": 0, + "y": 0, + }, + "inserted": { + "isDeleted": true, + }, + }, + "id469": { + "deleted": { + "angle": 0, + "backgroundColor": "transparent", + "boundElements": null, + "customData": undefined, + "fillStyle": "solid", + "frameId": null, + "groupIds": [ + "A", + ], + "height": 100, + "index": "a1", + "isDeleted": true, + "link": null, + "locked": false, + "opacity": 100, + "roughness": 1, + "roundness": { + "type": 3, + }, + "strokeColor": "#1e1e1e", + "strokeStyle": "solid", + "strokeWidth": 2, + "type": "rectangle", + "width": 100, + "x": 100, + "y": 100, + }, + "inserted": { + "isDeleted": true, + }, + }, + }, + }, + "id": "id481", + }, + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": { + "editingGroupId": "A", + "selectedElementIds": {}, + "selectedGroupIds": {}, + }, + "inserted": { + "editingGroupId": null, + "selectedElementIds": { + "id468": true, + }, + "selectedGroupIds": { + "A": true, + }, + }, + }, + }, + "elements": { + "added": {}, + "removed": {}, + "updated": {}, + }, + "id": "id482", + }, + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": { + "editingGroupId": null, + "selectedElementIds": {}, + }, + "inserted": { + "editingGroupId": "A", + "selectedElementIds": { + "id469": true, + }, + }, + }, + }, + "elements": { + "added": {}, + "removed": {}, + "updated": {}, + }, + "id": "id485", + }, +] +`; + exports[`history > multiplayer undo/redo > should iterate through the history when element changes relate only to remotely deleted elements > [end of test] appState 1`] = ` { "activeEmbeddable": null, @@ -6125,7 +6025,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh "penMode": false, "pendingImageElementId": null, "previousSelectedElementIds": { - "id103": true, + "id418": true, }, "resizingElement": null, "scrollX": 0, @@ -6168,7 +6068,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh "frameId": null, "groupIds": [], "height": 10, - "id": "id101", + "id": "id410", "index": "a0", "isDeleted": false, "link": null, @@ -6200,7 +6100,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh "frameId": null, "groupIds": [], "height": 10, - "id": "id102", + "id": "id413", "index": "a1", "isDeleted": true, "link": null, @@ -6232,7 +6132,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh "frameId": null, "groupIds": [], "height": 10, - "id": "id103", + "id": "id418", "index": "a2", "isDeleted": true, "link": null, @@ -6254,227 +6154,225 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh } `; -exports[`history > multiplayer undo/redo > should iterate through the history when element changes relate only to remotely deleted elements > [end of test] history 1`] = ` -History { - "onHistoryChangedEmitter": Emitter { - "subscribers": [ - [Function], - [Function], - ], - }, - "redoStack": [], - "undoStack": [ - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": { - "selectedElementIds": { - "id101": true, - }, - }, - "inserted": { - "selectedElementIds": {}, - }, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map { - "id101" => Delta { - "deleted": { - "angle": 0, - "backgroundColor": "transparent", - "boundElements": null, - "customData": undefined, - "fillStyle": "solid", - "frameId": null, - "groupIds": [], - "height": 10, - "index": "a0", - "isDeleted": false, - "link": null, - "locked": false, - "opacity": 100, - "roughness": 1, - "roundness": { - "type": 3, - }, - "strokeColor": "#1e1e1e", - "strokeStyle": "solid", - "strokeWidth": 2, - "type": "rectangle", - "width": 10, - "x": 10, - "y": 0, - }, - "inserted": { - "isDeleted": true, - }, - }, - }, - "updated": Map {}, - }, - }, - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": { - "selectedElementIds": { - "id102": true, - }, - }, - "inserted": { - "selectedElementIds": { - "id101": true, - }, - }, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map {}, - "updated": Map { - "id102" => Delta { - "deleted": { - "angle": 0, - "backgroundColor": "#ffc9c9", - "boundElements": null, - "customData": undefined, - "fillStyle": "solid", - "frameId": null, - "groupIds": [], - "height": 10, - "index": "a1", - "isDeleted": true, - "link": null, - "locked": false, - "opacity": 100, - "roughness": 1, - "roundness": { - "type": 3, - }, - "strokeColor": "#1e1e1e", - "strokeStyle": "solid", - "strokeWidth": 2, - "type": "rectangle", - "width": 10, - "x": 20, - "y": 0, - }, - "inserted": { - "isDeleted": true, - }, - }, - }, - }, - }, - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": {}, - "inserted": {}, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map {}, - "updated": Map { - "id102" => Delta { - "deleted": { - "backgroundColor": "#ffc9c9", - }, - "inserted": { - "backgroundColor": "transparent", - }, - }, - }, - }, - }, - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": { - "selectedElementIds": { - "id103": true, - }, - }, - "inserted": { - "selectedElementIds": { - "id102": true, - }, - }, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map {}, - "updated": Map { - "id103" => Delta { - "deleted": { - "angle": 0, - "backgroundColor": "#ffc9c9", - "boundElements": null, - "customData": undefined, - "fillStyle": "solid", - "frameId": null, - "groupIds": [], - "height": 10, - "index": "a2", - "isDeleted": true, - "link": null, - "locked": false, - "opacity": 100, - "roughness": 1, - "roundness": { - "type": 3, - }, - "strokeColor": "#1e1e1e", - "strokeStyle": "solid", - "strokeWidth": 2, - "type": "rectangle", - "width": 10, - "x": 50, - "y": 50, - }, - "inserted": { - "isDeleted": true, - }, - }, - }, - }, - }, - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": {}, - "inserted": {}, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map {}, - "updated": Map { - "id103" => Delta { - "deleted": { - "x": 50, - "y": 50, - }, - "inserted": { - "x": 30, - "y": 30, - }, - }, - }, - }, - }, - ], -} -`; - exports[`history > multiplayer undo/redo > should iterate through the history when element changes relate only to remotely deleted elements > [end of test] number of elements 1`] = `3`; exports[`history > multiplayer undo/redo > should iterate through the history when element changes relate only to remotely deleted elements > [end of test] number of renders 1`] = `17`; +exports[`history > multiplayer undo/redo > should iterate through the history when element changes relate only to remotely deleted elements > [end of test] redo stack 1`] = `[]`; + +exports[`history > multiplayer undo/redo > should iterate through the history when element changes relate only to remotely deleted elements > [end of test] undo stack 1`] = ` +[ + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": { + "selectedElementIds": { + "id410": true, + }, + }, + "inserted": { + "selectedElementIds": {}, + }, + }, + }, + "elements": { + "added": {}, + "removed": { + "id410": { + "deleted": { + "angle": 0, + "backgroundColor": "transparent", + "boundElements": null, + "customData": undefined, + "fillStyle": "solid", + "frameId": null, + "groupIds": [], + "height": 10, + "index": "a0", + "isDeleted": false, + "link": null, + "locked": false, + "opacity": 100, + "roughness": 1, + "roundness": { + "type": 3, + }, + "strokeColor": "#1e1e1e", + "strokeStyle": "solid", + "strokeWidth": 2, + "type": "rectangle", + "width": 10, + "x": 10, + "y": 0, + }, + "inserted": { + "isDeleted": true, + }, + }, + }, + "updated": {}, + }, + "id": "id412", + }, + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": { + "selectedElementIds": { + "id413": true, + }, + }, + "inserted": { + "selectedElementIds": { + "id410": true, + }, + }, + }, + }, + "elements": { + "added": {}, + "removed": {}, + "updated": { + "id413": { + "deleted": { + "angle": 0, + "backgroundColor": "#ffc9c9", + "boundElements": null, + "customData": undefined, + "fillStyle": "solid", + "frameId": null, + "groupIds": [], + "height": 10, + "index": "a1", + "isDeleted": true, + "link": null, + "locked": false, + "opacity": 100, + "roughness": 1, + "roundness": { + "type": 3, + }, + "strokeColor": "#1e1e1e", + "strokeStyle": "solid", + "strokeWidth": 2, + "type": "rectangle", + "width": 10, + "x": 20, + "y": 0, + }, + "inserted": { + "isDeleted": true, + }, + }, + }, + }, + "id": "id428", + }, + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": {}, + "inserted": {}, + }, + }, + "elements": { + "added": {}, + "removed": {}, + "updated": { + "id413": { + "deleted": { + "backgroundColor": "#ffc9c9", + }, + "inserted": { + "backgroundColor": "transparent", + }, + }, + }, + }, + "id": "id429", + }, + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": { + "selectedElementIds": { + "id418": true, + }, + }, + "inserted": { + "selectedElementIds": { + "id413": true, + }, + }, + }, + }, + "elements": { + "added": {}, + "removed": {}, + "updated": { + "id418": { + "deleted": { + "angle": 0, + "backgroundColor": "#ffc9c9", + "boundElements": null, + "customData": undefined, + "fillStyle": "solid", + "frameId": null, + "groupIds": [], + "height": 10, + "index": "a2", + "isDeleted": true, + "link": null, + "locked": false, + "opacity": 100, + "roughness": 1, + "roundness": { + "type": 3, + }, + "strokeColor": "#1e1e1e", + "strokeStyle": "solid", + "strokeWidth": 2, + "type": "rectangle", + "width": 10, + "x": 50, + "y": 50, + }, + "inserted": { + "isDeleted": true, + }, + }, + }, + }, + "id": "id430", + }, + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": {}, + "inserted": {}, + }, + }, + "elements": { + "added": {}, + "removed": {}, + "updated": { + "id418": { + "deleted": { + "x": 50, + "y": 50, + }, + "inserted": { + "x": 30, + "y": 30, + }, + }, + }, + }, + "id": "id431", + }, +] +`; + exports[`history > multiplayer undo/redo > should iterate through the history when selected elements relate only to remotely deleted elements > [end of test] appState 1`] = ` { "activeEmbeddable": null, @@ -6554,15 +6452,15 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh "penMode": false, "pendingImageElementId": null, "previousSelectedElementIds": { - "id106": true, + "id433": true, }, "resizingElement": null, "scrollX": 0, "scrollY": 0, "searchMatches": [], "selectedElementIds": { - "id106": true, - "id107": true, + "id433": true, + "id434": true, }, "selectedElementsAreBeingDragged": false, "selectedGroupIds": {}, @@ -6600,7 +6498,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh "frameId": null, "groupIds": [], "height": 100, - "id": "id105", + "id": "id432", "index": "a0", "isDeleted": false, "link": null, @@ -6632,7 +6530,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh "frameId": null, "groupIds": [], "height": 100, - "id": "id106", + "id": "id433", "index": "a1", "isDeleted": false, "link": null, @@ -6664,7 +6562,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh "frameId": null, "groupIds": [], "height": 100, - "id": "id107", + "id": "id434", "index": "a2", "isDeleted": false, "link": null, @@ -6686,177 +6584,173 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh } `; -exports[`history > multiplayer undo/redo > should iterate through the history when selected elements relate only to remotely deleted elements > [end of test] history 1`] = ` -History { - "onHistoryChangedEmitter": Emitter { - "subscribers": [ - [Function], - [Function], - ], - }, - "redoStack": [], - "undoStack": [ - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": { - "selectedElementIds": { - "id105": true, - }, - }, - "inserted": { - "selectedElementIds": {}, - }, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map { - "id105" => Delta { - "deleted": { - "angle": 0, - "backgroundColor": "transparent", - "boundElements": null, - "customData": undefined, - "fillStyle": "solid", - "frameId": null, - "groupIds": [], - "height": 100, - "index": "a0", - "isDeleted": false, - "link": null, - "locked": false, - "opacity": 100, - "roughness": 1, - "roundness": { - "type": 3, - }, - "strokeColor": "#1e1e1e", - "strokeStyle": "solid", - "strokeWidth": 2, - "type": "rectangle", - "width": 100, - "x": 10, - "y": 10, - }, - "inserted": { - "isDeleted": true, - }, - }, - "id106" => Delta { - "deleted": { - "angle": 0, - "backgroundColor": "transparent", - "boundElements": null, - "customData": undefined, - "fillStyle": "solid", - "frameId": null, - "groupIds": [], - "height": 100, - "index": "a1", - "isDeleted": false, - "link": null, - "locked": false, - "opacity": 100, - "roughness": 1, - "roundness": { - "type": 3, - }, - "strokeColor": "#1e1e1e", - "strokeStyle": "solid", - "strokeWidth": 2, - "type": "rectangle", - "width": 100, - "x": 20, - "y": 20, - }, - "inserted": { - "isDeleted": true, - }, - }, - "id107" => Delta { - "deleted": { - "angle": 0, - "backgroundColor": "transparent", - "boundElements": null, - "customData": undefined, - "fillStyle": "solid", - "frameId": null, - "groupIds": [], - "height": 100, - "index": "a2", - "isDeleted": false, - "link": null, - "locked": false, - "opacity": 100, - "roughness": 1, - "roundness": { - "type": 3, - }, - "strokeColor": "#1e1e1e", - "strokeStyle": "solid", - "strokeWidth": 2, - "type": "rectangle", - "width": 100, - "x": 30, - "y": 30, - }, - "inserted": { - "isDeleted": true, - }, - }, - }, - "updated": Map {}, - }, - }, - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": { - "selectedElementIds": { - "id106": true, - }, - }, - "inserted": { - "selectedElementIds": { - "id105": true, - }, - }, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map {}, - "updated": Map {}, - }, - }, - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": { - "selectedElementIds": { - "id107": true, - }, - }, - "inserted": { - "selectedElementIds": {}, - }, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map {}, - "updated": Map {}, - }, - }, - ], -} -`; - exports[`history > multiplayer undo/redo > should iterate through the history when selected elements relate only to remotely deleted elements > [end of test] number of elements 1`] = `3`; exports[`history > multiplayer undo/redo > should iterate through the history when selected elements relate only to remotely deleted elements > [end of test] number of renders 1`] = `15`; +exports[`history > multiplayer undo/redo > should iterate through the history when selected elements relate only to remotely deleted elements > [end of test] redo stack 1`] = `[]`; + +exports[`history > multiplayer undo/redo > should iterate through the history when selected elements relate only to remotely deleted elements > [end of test] undo stack 1`] = ` +[ + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": { + "selectedElementIds": { + "id432": true, + }, + }, + "inserted": { + "selectedElementIds": {}, + }, + }, + }, + "elements": { + "added": {}, + "removed": { + "id432": { + "deleted": { + "angle": 0, + "backgroundColor": "transparent", + "boundElements": null, + "customData": undefined, + "fillStyle": "solid", + "frameId": null, + "groupIds": [], + "height": 100, + "index": "a0", + "isDeleted": false, + "link": null, + "locked": false, + "opacity": 100, + "roughness": 1, + "roundness": { + "type": 3, + }, + "strokeColor": "#1e1e1e", + "strokeStyle": "solid", + "strokeWidth": 2, + "type": "rectangle", + "width": 100, + "x": 10, + "y": 10, + }, + "inserted": { + "isDeleted": true, + }, + }, + "id433": { + "deleted": { + "angle": 0, + "backgroundColor": "transparent", + "boundElements": null, + "customData": undefined, + "fillStyle": "solid", + "frameId": null, + "groupIds": [], + "height": 100, + "index": "a1", + "isDeleted": false, + "link": null, + "locked": false, + "opacity": 100, + "roughness": 1, + "roundness": { + "type": 3, + }, + "strokeColor": "#1e1e1e", + "strokeStyle": "solid", + "strokeWidth": 2, + "type": "rectangle", + "width": 100, + "x": 20, + "y": 20, + }, + "inserted": { + "isDeleted": true, + }, + }, + "id434": { + "deleted": { + "angle": 0, + "backgroundColor": "transparent", + "boundElements": null, + "customData": undefined, + "fillStyle": "solid", + "frameId": null, + "groupIds": [], + "height": 100, + "index": "a2", + "isDeleted": false, + "link": null, + "locked": false, + "opacity": 100, + "roughness": 1, + "roundness": { + "type": 3, + }, + "strokeColor": "#1e1e1e", + "strokeStyle": "solid", + "strokeWidth": 2, + "type": "rectangle", + "width": 100, + "x": 30, + "y": 30, + }, + "inserted": { + "isDeleted": true, + }, + }, + }, + "updated": {}, + }, + "id": "id437", + }, + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": { + "selectedElementIds": { + "id433": true, + }, + }, + "inserted": { + "selectedElementIds": { + "id432": true, + }, + }, + }, + }, + "elements": { + "added": {}, + "removed": {}, + "updated": {}, + }, + "id": "id450", + }, + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": { + "selectedElementIds": { + "id434": true, + }, + }, + "inserted": { + "selectedElementIds": {}, + }, + }, + }, + "elements": { + "added": {}, + "removed": {}, + "updated": {}, + }, + "id": "id451", + }, +] +`; + exports[`history > multiplayer undo/redo > should iterate through the history when selected groups contain only remotely deleted elements > [end of test] appState 1`] = ` { "activeEmbeddable": null, @@ -6944,10 +6838,10 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh "scrollY": 0, "searchMatches": [], "selectedElementIds": { - "id111": true, - "id112": true, - "id113": true, - "id114": true, + "id452": true, + "id453": true, + "id454": true, + "id455": true, }, "selectedElementsAreBeingDragged": false, "selectedGroupIds": { @@ -6990,7 +6884,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh "A", ], "height": 100, - "id": "id111", + "id": "id452", "index": "a0", "isDeleted": false, "link": null, @@ -7024,7 +6918,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh "A", ], "height": 100, - "id": "id112", + "id": "id453", "index": "a1", "isDeleted": false, "link": null, @@ -7058,7 +6952,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh "B", ], "height": 100, - "id": "id113", + "id": "id454", "index": "a2", "isDeleted": false, "link": null, @@ -7092,7 +6986,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh "B", ], "height": 100, - "id": "id114", + "id": "id455", "index": "a3", "isDeleted": false, "link": null, @@ -7114,72 +7008,67 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh } `; -exports[`history > multiplayer undo/redo > should iterate through the history when selected groups contain only remotely deleted elements > [end of test] history 1`] = ` -History { - "onHistoryChangedEmitter": Emitter { - "subscribers": [ - [Function], - [Function], - ], - }, - "redoStack": [], - "undoStack": [ - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": { - "selectedElementIds": { - "id111": true, - "id112": true, - }, - "selectedGroupIds": { - "A": true, - }, - }, - "inserted": { - "selectedElementIds": {}, - "selectedGroupIds": {}, - }, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map {}, - "updated": Map {}, - }, - }, - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": { - "selectedElementIds": { - "id113": true, - "id114": true, - }, - "selectedGroupIds": { - "B": true, - }, - }, - "inserted": { - "selectedElementIds": {}, - "selectedGroupIds": {}, - }, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map {}, - "updated": Map {}, - }, - }, - ], -} -`; - exports[`history > multiplayer undo/redo > should iterate through the history when selected groups contain only remotely deleted elements > [end of test] number of elements 1`] = `4`; exports[`history > multiplayer undo/redo > should iterate through the history when selected groups contain only remotely deleted elements > [end of test] number of renders 1`] = `15`; +exports[`history > multiplayer undo/redo > should iterate through the history when selected groups contain only remotely deleted elements > [end of test] redo stack 1`] = `[]`; + +exports[`history > multiplayer undo/redo > should iterate through the history when selected groups contain only remotely deleted elements > [end of test] undo stack 1`] = ` +[ + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": { + "selectedElementIds": { + "id452": true, + "id453": true, + }, + "selectedGroupIds": { + "A": true, + }, + }, + "inserted": { + "selectedElementIds": {}, + "selectedGroupIds": {}, + }, + }, + }, + "elements": { + "added": {}, + "removed": {}, + "updated": {}, + }, + "id": "id466", + }, + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": { + "selectedElementIds": { + "id454": true, + "id455": true, + }, + "selectedGroupIds": { + "B": true, + }, + }, + "inserted": { + "selectedElementIds": {}, + "selectedGroupIds": {}, + }, + }, + }, + "elements": { + "added": {}, + "removed": {}, + "updated": {}, + }, + "id": "id467", + }, +] +`; + exports[`history > multiplayer undo/redo > should iterate through the history when selected or editing linear element was remotely deleted > [end of test] appState 1`] = ` { "activeEmbeddable": null, @@ -7264,7 +7153,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh "scrollY": 0, "searchMatches": [], "selectedElementIds": { - "id119": true, + "id486": true, }, "selectedElementsAreBeingDragged": false, "selectedGroupIds": {}, @@ -7305,7 +7194,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh "frameId": null, "groupIds": [], "height": 10, - "id": "id119", + "id": "id486", "index": "a0", "isDeleted": true, "lastCommittedPoint": [ @@ -7343,145 +7232,142 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh } `; -exports[`history > multiplayer undo/redo > should iterate through the history when selected or editing linear element was remotely deleted > [end of test] history 1`] = ` -History { - "onHistoryChangedEmitter": Emitter { - "subscribers": [ - [Function], - [Function], - ], - }, - "redoStack": [], - "undoStack": [ - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": { - "selectedElementIds": { - "id119": true, - }, - }, - "inserted": { - "selectedElementIds": {}, - }, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map { - "id119" => Delta { - "deleted": { - "angle": 0, - "backgroundColor": "transparent", - "boundElements": null, - "customData": undefined, - "elbowed": false, - "endArrowhead": "arrow", - "endBinding": null, - "fillStyle": "solid", - "frameId": null, - "groupIds": [], - "height": 10, - "index": "a0", - "isDeleted": false, - "lastCommittedPoint": [ - 10, - 10, - ], - "link": null, - "locked": false, - "opacity": 100, - "points": [ - [ - 0, - 0, - ], - [ - 10, - 10, - ], - ], - "roughness": 1, - "roundness": { - "type": 2, - }, - "startArrowhead": null, - "startBinding": null, - "strokeColor": "#1e1e1e", - "strokeStyle": "solid", - "strokeWidth": 2, - "type": "arrow", - "width": 10, - "x": 0, - "y": 0, - }, - "inserted": { - "isDeleted": true, - }, - }, - }, - "updated": Map {}, - }, - }, - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": { - "selectedLinearElementId": "id119", - }, - "inserted": { - "selectedLinearElementId": null, - }, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map {}, - "updated": Map {}, - }, - }, - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": { - "editingLinearElementId": "id119", - }, - "inserted": { - "editingLinearElementId": null, - }, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map {}, - "updated": Map {}, - }, - }, - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": { - "editingLinearElementId": null, - }, - "inserted": { - "editingLinearElementId": "id119", - }, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map {}, - "updated": Map {}, - }, - }, - ], -} -`; - exports[`history > multiplayer undo/redo > should iterate through the history when selected or editing linear element was remotely deleted > [end of test] number of elements 1`] = `1`; exports[`history > multiplayer undo/redo > should iterate through the history when selected or editing linear element was remotely deleted > [end of test] number of renders 1`] = `9`; +exports[`history > multiplayer undo/redo > should iterate through the history when selected or editing linear element was remotely deleted > [end of test] redo stack 1`] = `[]`; + +exports[`history > multiplayer undo/redo > should iterate through the history when selected or editing linear element was remotely deleted > [end of test] undo stack 1`] = ` +[ + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": { + "selectedElementIds": { + "id486": true, + }, + }, + "inserted": { + "selectedElementIds": {}, + }, + }, + }, + "elements": { + "added": {}, + "removed": { + "id486": { + "deleted": { + "angle": 0, + "backgroundColor": "transparent", + "boundElements": null, + "customData": undefined, + "elbowed": false, + "endArrowhead": "arrow", + "endBinding": null, + "fillStyle": "solid", + "frameId": null, + "groupIds": [], + "height": 10, + "index": "a0", + "isDeleted": false, + "lastCommittedPoint": [ + 10, + 10, + ], + "link": null, + "locked": false, + "opacity": 100, + "points": [ + [ + 0, + 0, + ], + [ + 10, + 10, + ], + ], + "roughness": 1, + "roundness": { + "type": 2, + }, + "startArrowhead": null, + "startBinding": null, + "strokeColor": "#1e1e1e", + "strokeStyle": "solid", + "strokeWidth": 2, + "type": "arrow", + "width": 10, + "x": 0, + "y": 0, + }, + "inserted": { + "isDeleted": true, + }, + }, + }, + "updated": {}, + }, + "id": "id488", + }, + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": { + "selectedLinearElementId": "id486", + }, + "inserted": { + "selectedLinearElementId": null, + }, + }, + }, + "elements": { + "added": {}, + "removed": {}, + "updated": {}, + }, + "id": "id498", + }, + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": { + "editingLinearElementId": "id486", + }, + "inserted": { + "editingLinearElementId": null, + }, + }, + }, + "elements": { + "added": {}, + "removed": {}, + "updated": {}, + }, + "id": "id499", + }, + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": { + "editingLinearElementId": null, + }, + "inserted": { + "editingLinearElementId": "id486", + }, + }, + }, + "elements": { + "added": {}, + "removed": {}, + "updated": {}, + }, + "id": "id500", + }, +] +`; + exports[`history > multiplayer undo/redo > should iterate through the history when when element change relates to remotely deleted element > [end of test] appState 1`] = ` { "activeEmbeddable": null, @@ -7602,7 +7488,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh "frameId": null, "groupIds": [], "height": 10, - "id": "id100", + "id": "id401", "index": "a0", "isDeleted": true, "link": null, @@ -7624,97 +7510,92 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh } `; -exports[`history > multiplayer undo/redo > should iterate through the history when when element change relates to remotely deleted element > [end of test] history 1`] = ` -History { - "onHistoryChangedEmitter": Emitter { - "subscribers": [ - [Function], - [Function], - ], - }, - "redoStack": [], - "undoStack": [ - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": { - "selectedElementIds": { - "id100": true, - }, - }, - "inserted": { - "selectedElementIds": {}, - }, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map {}, - "updated": Map { - "id100" => Delta { - "deleted": { - "angle": 0, - "backgroundColor": "#ffec99", - "boundElements": null, - "customData": undefined, - "fillStyle": "solid", - "frameId": null, - "groupIds": [], - "height": 10, - "index": "a0", - "isDeleted": true, - "link": null, - "locked": false, - "opacity": 100, - "roughness": 1, - "roundness": { - "type": 3, - }, - "strokeColor": "#1e1e1e", - "strokeStyle": "solid", - "strokeWidth": 2, - "type": "rectangle", - "width": 10, - "x": 10, - "y": 0, - }, - "inserted": { - "isDeleted": true, - }, - }, - }, - }, - }, - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": {}, - "inserted": {}, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map {}, - "updated": Map { - "id100" => Delta { - "deleted": { - "backgroundColor": "#ffec99", - }, - "inserted": { - "backgroundColor": "transparent", - }, - }, - }, - }, - }, - ], -} -`; - exports[`history > multiplayer undo/redo > should iterate through the history when when element change relates to remotely deleted element > [end of test] number of elements 1`] = `1`; exports[`history > multiplayer undo/redo > should iterate through the history when when element change relates to remotely deleted element > [end of test] number of renders 1`] = `9`; +exports[`history > multiplayer undo/redo > should iterate through the history when when element change relates to remotely deleted element > [end of test] redo stack 1`] = `[]`; + +exports[`history > multiplayer undo/redo > should iterate through the history when when element change relates to remotely deleted element > [end of test] undo stack 1`] = ` +[ + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": { + "selectedElementIds": { + "id401": true, + }, + }, + "inserted": { + "selectedElementIds": {}, + }, + }, + }, + "elements": { + "added": {}, + "removed": {}, + "updated": { + "id401": { + "deleted": { + "angle": 0, + "backgroundColor": "#ffec99", + "boundElements": null, + "customData": undefined, + "fillStyle": "solid", + "frameId": null, + "groupIds": [], + "height": 10, + "index": "a0", + "isDeleted": true, + "link": null, + "locked": false, + "opacity": 100, + "roughness": 1, + "roundness": { + "type": 3, + }, + "strokeColor": "#1e1e1e", + "strokeStyle": "solid", + "strokeWidth": 2, + "type": "rectangle", + "width": 10, + "x": 10, + "y": 0, + }, + "inserted": { + "isDeleted": true, + }, + }, + }, + }, + "id": "id408", + }, + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": {}, + "inserted": {}, + }, + }, + "elements": { + "added": {}, + "removed": {}, + "updated": { + "id401": { + "deleted": { + "backgroundColor": "#ffec99", + }, + "inserted": { + "backgroundColor": "transparent", + }, + }, + }, + }, + "id": "id409", + }, +] +`; + exports[`history > multiplayer undo/redo > should iterate through the history when z-index changes do not produce visible change and we synced all indices > [end of test] appState 1`] = ` { "activeEmbeddable": null, @@ -7835,7 +7716,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh "frameId": null, "groupIds": [], "height": 100, - "id": "id125", + "id": "id514", "index": "a1", "isDeleted": true, "link": null, @@ -7867,7 +7748,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh "frameId": null, "groupIds": [], "height": 100, - "id": "id126", + "id": "id515", "index": "a3V", "isDeleted": true, "link": null, @@ -7899,7 +7780,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh "frameId": null, "groupIds": [], "height": 100, - "id": "id124", + "id": "id513", "index": "a4", "isDeleted": true, "link": null, @@ -7921,159 +7802,154 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh } `; -exports[`history > multiplayer undo/redo > should iterate through the history when z-index changes do not produce visible change and we synced all indices > [end of test] history 1`] = ` -History { - "onHistoryChangedEmitter": Emitter { - "subscribers": [ - [Function], - [Function], - ], - }, - "redoStack": [ - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": {}, - "inserted": {}, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map {}, - "updated": Map { - "id125" => Delta { - "deleted": { - "index": "a1", - }, - "inserted": { - "index": "a3", - }, - }, - }, - }, - }, - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": { - "selectedElementIds": {}, - }, - "inserted": { - "selectedElementIds": { - "id125": true, - }, - }, - }, - }, - "elementsChange": ElementsChange { - "added": Map { - "id124" => Delta { - "deleted": { - "isDeleted": true, - }, - "inserted": { - "angle": 0, - "backgroundColor": "transparent", - "boundElements": null, - "customData": undefined, - "fillStyle": "solid", - "frameId": null, - "groupIds": [], - "height": 100, - "index": "a4", - "isDeleted": false, - "link": null, - "locked": false, - "opacity": 100, - "roughness": 1, - "roundness": { - "type": 3, - }, - "strokeColor": "#1e1e1e", - "strokeStyle": "solid", - "strokeWidth": 2, - "type": "rectangle", - "width": 100, - "x": 10, - "y": 10, - }, - }, - "id125" => Delta { - "deleted": { - "isDeleted": true, - }, - "inserted": { - "angle": 0, - "backgroundColor": "transparent", - "boundElements": null, - "customData": undefined, - "fillStyle": "solid", - "frameId": null, - "groupIds": [], - "height": 100, - "index": "a3", - "isDeleted": false, - "link": null, - "locked": false, - "opacity": 100, - "roughness": 1, - "roundness": { - "type": 3, - }, - "strokeColor": "#1e1e1e", - "strokeStyle": "solid", - "strokeWidth": 2, - "type": "rectangle", - "width": 100, - "x": 20, - "y": 20, - }, - }, - "id126" => Delta { - "deleted": { - "isDeleted": true, - }, - "inserted": { - "angle": 0, - "backgroundColor": "transparent", - "boundElements": null, - "customData": undefined, - "fillStyle": "solid", - "frameId": null, - "groupIds": [], - "height": 100, - "index": "a3V", - "isDeleted": false, - "link": null, - "locked": false, - "opacity": 100, - "roughness": 1, - "roundness": { - "type": 3, - }, - "strokeColor": "#1e1e1e", - "strokeStyle": "solid", - "strokeWidth": 2, - "type": "rectangle", - "width": 100, - "x": 30, - "y": 30, - }, - }, - }, - "removed": Map {}, - "updated": Map {}, - }, - }, - ], - "undoStack": [], -} -`; - exports[`history > multiplayer undo/redo > should iterate through the history when z-index changes do not produce visible change and we synced all indices > [end of test] number of elements 1`] = `3`; exports[`history > multiplayer undo/redo > should iterate through the history when z-index changes do not produce visible change and we synced all indices > [end of test] number of renders 1`] = `11`; +exports[`history > multiplayer undo/redo > should iterate through the history when z-index changes do not produce visible change and we synced all indices > [end of test] redo stack 1`] = ` +[ + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": {}, + "inserted": {}, + }, + }, + "elements": { + "added": {}, + "removed": {}, + "updated": { + "id514": { + "deleted": { + "index": "a1", + }, + "inserted": { + "index": "a3", + }, + }, + }, + }, + "id": "id523", + }, + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": { + "selectedElementIds": {}, + }, + "inserted": { + "selectedElementIds": { + "id514": true, + }, + }, + }, + }, + "elements": { + "added": { + "id513": { + "deleted": { + "isDeleted": true, + }, + "inserted": { + "angle": 0, + "backgroundColor": "transparent", + "boundElements": null, + "customData": undefined, + "fillStyle": "solid", + "frameId": null, + "groupIds": [], + "height": 100, + "index": "a4", + "isDeleted": false, + "link": null, + "locked": false, + "opacity": 100, + "roughness": 1, + "roundness": { + "type": 3, + }, + "strokeColor": "#1e1e1e", + "strokeStyle": "solid", + "strokeWidth": 2, + "type": "rectangle", + "width": 100, + "x": 10, + "y": 10, + }, + }, + "id514": { + "deleted": { + "isDeleted": true, + }, + "inserted": { + "angle": 0, + "backgroundColor": "transparent", + "boundElements": null, + "customData": undefined, + "fillStyle": "solid", + "frameId": null, + "groupIds": [], + "height": 100, + "index": "a3", + "isDeleted": false, + "link": null, + "locked": false, + "opacity": 100, + "roughness": 1, + "roundness": { + "type": 3, + }, + "strokeColor": "#1e1e1e", + "strokeStyle": "solid", + "strokeWidth": 2, + "type": "rectangle", + "width": 100, + "x": 20, + "y": 20, + }, + }, + "id515": { + "deleted": { + "isDeleted": true, + }, + "inserted": { + "angle": 0, + "backgroundColor": "transparent", + "boundElements": null, + "customData": undefined, + "fillStyle": "solid", + "frameId": null, + "groupIds": [], + "height": 100, + "index": "a3V", + "isDeleted": false, + "link": null, + "locked": false, + "opacity": 100, + "roughness": 1, + "roundness": { + "type": 3, + }, + "strokeColor": "#1e1e1e", + "strokeStyle": "solid", + "strokeWidth": 2, + "type": "rectangle", + "width": 100, + "x": 30, + "y": 30, + }, + }, + }, + "removed": {}, + "updated": {}, + }, + "id": "id524", + }, +] +`; + +exports[`history > multiplayer undo/redo > should iterate through the history when z-index changes do not produce visible change and we synced all indices > [end of test] undo stack 1`] = `[]`; + exports[`history > multiplayer undo/redo > should iterate through the history when z-index changes do not produce visible change and we synced changed indices > [end of test] appState 1`] = ` { "activeEmbeddable": null, @@ -8194,7 +8070,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh "frameId": null, "groupIds": [], "height": 100, - "id": "id120", + "id": "id501", "index": "Zx", "isDeleted": true, "link": null, @@ -8226,7 +8102,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh "frameId": null, "groupIds": [], "height": 100, - "id": "id122", + "id": "id503", "index": "Zy", "isDeleted": true, "link": null, @@ -8258,7 +8134,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh "frameId": null, "groupIds": [], "height": 100, - "id": "id121", + "id": "id502", "index": "a1", "isDeleted": true, "link": null, @@ -8280,159 +8156,154 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh } `; -exports[`history > multiplayer undo/redo > should iterate through the history when z-index changes do not produce visible change and we synced changed indices > [end of test] history 1`] = ` -History { - "onHistoryChangedEmitter": Emitter { - "subscribers": [ - [Function], - [Function], - ], - }, - "redoStack": [ - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": {}, - "inserted": {}, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map {}, - "updated": Map { - "id121" => Delta { - "deleted": { - "index": "a1", - }, - "inserted": { - "index": "Zz", - }, - }, - }, - }, - }, - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": { - "selectedElementIds": {}, - }, - "inserted": { - "selectedElementIds": { - "id121": true, - }, - }, - }, - }, - "elementsChange": ElementsChange { - "added": Map { - "id120" => Delta { - "deleted": { - "isDeleted": true, - }, - "inserted": { - "angle": 0, - "backgroundColor": "transparent", - "boundElements": null, - "customData": undefined, - "fillStyle": "solid", - "frameId": null, - "groupIds": [], - "height": 100, - "index": "Zx", - "isDeleted": false, - "link": null, - "locked": false, - "opacity": 100, - "roughness": 1, - "roundness": { - "type": 3, - }, - "strokeColor": "#1e1e1e", - "strokeStyle": "solid", - "strokeWidth": 2, - "type": "rectangle", - "width": 100, - "x": 10, - "y": 10, - }, - }, - "id121" => Delta { - "deleted": { - "isDeleted": true, - }, - "inserted": { - "angle": 0, - "backgroundColor": "transparent", - "boundElements": null, - "customData": undefined, - "fillStyle": "solid", - "frameId": null, - "groupIds": [], - "height": 100, - "index": "Zz", - "isDeleted": false, - "link": null, - "locked": false, - "opacity": 100, - "roughness": 1, - "roundness": { - "type": 3, - }, - "strokeColor": "#1e1e1e", - "strokeStyle": "solid", - "strokeWidth": 2, - "type": "rectangle", - "width": 100, - "x": 20, - "y": 20, - }, - }, - "id122" => Delta { - "deleted": { - "isDeleted": true, - }, - "inserted": { - "angle": 0, - "backgroundColor": "transparent", - "boundElements": null, - "customData": undefined, - "fillStyle": "solid", - "frameId": null, - "groupIds": [], - "height": 100, - "index": "Zy", - "isDeleted": false, - "link": null, - "locked": false, - "opacity": 100, - "roughness": 1, - "roundness": { - "type": 3, - }, - "strokeColor": "#1e1e1e", - "strokeStyle": "solid", - "strokeWidth": 2, - "type": "rectangle", - "width": 100, - "x": 30, - "y": 30, - }, - }, - }, - "removed": Map {}, - "updated": Map {}, - }, - }, - ], - "undoStack": [], -} -`; - exports[`history > multiplayer undo/redo > should iterate through the history when z-index changes do not produce visible change and we synced changed indices > [end of test] number of elements 1`] = `3`; exports[`history > multiplayer undo/redo > should iterate through the history when z-index changes do not produce visible change and we synced changed indices > [end of test] number of renders 1`] = `11`; +exports[`history > multiplayer undo/redo > should iterate through the history when z-index changes do not produce visible change and we synced changed indices > [end of test] redo stack 1`] = ` +[ + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": {}, + "inserted": {}, + }, + }, + "elements": { + "added": {}, + "removed": {}, + "updated": { + "id502": { + "deleted": { + "index": "a1", + }, + "inserted": { + "index": "Zz", + }, + }, + }, + }, + "id": "id511", + }, + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": { + "selectedElementIds": {}, + }, + "inserted": { + "selectedElementIds": { + "id502": true, + }, + }, + }, + }, + "elements": { + "added": { + "id501": { + "deleted": { + "isDeleted": true, + }, + "inserted": { + "angle": 0, + "backgroundColor": "transparent", + "boundElements": null, + "customData": undefined, + "fillStyle": "solid", + "frameId": null, + "groupIds": [], + "height": 100, + "index": "Zx", + "isDeleted": false, + "link": null, + "locked": false, + "opacity": 100, + "roughness": 1, + "roundness": { + "type": 3, + }, + "strokeColor": "#1e1e1e", + "strokeStyle": "solid", + "strokeWidth": 2, + "type": "rectangle", + "width": 100, + "x": 10, + "y": 10, + }, + }, + "id502": { + "deleted": { + "isDeleted": true, + }, + "inserted": { + "angle": 0, + "backgroundColor": "transparent", + "boundElements": null, + "customData": undefined, + "fillStyle": "solid", + "frameId": null, + "groupIds": [], + "height": 100, + "index": "Zz", + "isDeleted": false, + "link": null, + "locked": false, + "opacity": 100, + "roughness": 1, + "roundness": { + "type": 3, + }, + "strokeColor": "#1e1e1e", + "strokeStyle": "solid", + "strokeWidth": 2, + "type": "rectangle", + "width": 100, + "x": 20, + "y": 20, + }, + }, + "id503": { + "deleted": { + "isDeleted": true, + }, + "inserted": { + "angle": 0, + "backgroundColor": "transparent", + "boundElements": null, + "customData": undefined, + "fillStyle": "solid", + "frameId": null, + "groupIds": [], + "height": 100, + "index": "Zy", + "isDeleted": false, + "link": null, + "locked": false, + "opacity": 100, + "roughness": 1, + "roundness": { + "type": 3, + }, + "strokeColor": "#1e1e1e", + "strokeStyle": "solid", + "strokeWidth": 2, + "type": "rectangle", + "width": 100, + "x": 30, + "y": 30, + }, + }, + }, + "removed": {}, + "updated": {}, + }, + "id": "id512", + }, +] +`; + +exports[`history > multiplayer undo/redo > should iterate through the history when z-index changes do not produce visible change and we synced changed indices > [end of test] undo stack 1`] = `[]`; + exports[`history > multiplayer undo/redo > should not let remote changes to interfere with in progress dragging > [end of test] appState 1`] = ` { "activeEmbeddable": null, @@ -8512,16 +8383,16 @@ exports[`history > multiplayer undo/redo > should not let remote changes to inte "penMode": false, "pendingImageElementId": null, "previousSelectedElementIds": { - "id133": true, - "id134": true, + "id542": true, + "id545": true, }, "resizingElement": null, "scrollX": 0, "scrollY": 0, "searchMatches": [], "selectedElementIds": { - "id133": true, - "id134": true, + "id542": true, + "id545": true, }, "selectedElementsAreBeingDragged": false, "selectedGroupIds": {}, @@ -8559,7 +8430,7 @@ exports[`history > multiplayer undo/redo > should not let remote changes to inte "frameId": null, "groupIds": [], "height": 10, - "id": "id133", + "id": "id542", "index": "a0", "isDeleted": false, "link": null, @@ -8591,7 +8462,7 @@ exports[`history > multiplayer undo/redo > should not let remote changes to inte "frameId": null, "groupIds": [], "height": 10, - "id": "id134", + "id": "id545", "index": "a1", "isDeleted": false, "link": null, @@ -8623,7 +8494,7 @@ exports[`history > multiplayer undo/redo > should not let remote changes to inte "frameId": null, "groupIds": [], "height": 100, - "id": "id138", + "id": "id555", "index": "a2", "isDeleted": false, "link": null, @@ -8645,202 +8516,200 @@ exports[`history > multiplayer undo/redo > should not let remote changes to inte } `; -exports[`history > multiplayer undo/redo > should not let remote changes to interfere with in progress dragging > [end of test] history 1`] = ` -History { - "onHistoryChangedEmitter": Emitter { - "subscribers": [ - [Function], - [Function], - ], - }, - "redoStack": [], - "undoStack": [ - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": { - "selectedElementIds": { - "id133": true, - }, - }, - "inserted": { - "selectedElementIds": {}, - }, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map { - "id133" => Delta { - "deleted": { - "angle": 0, - "backgroundColor": "transparent", - "boundElements": null, - "customData": undefined, - "fillStyle": "solid", - "frameId": null, - "groupIds": [], - "height": 10, - "index": "a0", - "isDeleted": false, - "link": null, - "locked": false, - "opacity": 100, - "roughness": 1, - "roundness": { - "type": 3, - }, - "strokeColor": "#1e1e1e", - "strokeStyle": "solid", - "strokeWidth": 2, - "type": "rectangle", - "width": 10, - "x": 10, - "y": 10, - }, - "inserted": { - "isDeleted": true, - }, - }, - }, - "updated": Map {}, - }, - }, - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": { - "selectedElementIds": { - "id134": true, - }, - }, - "inserted": { - "selectedElementIds": { - "id133": true, - }, - }, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map { - "id134" => Delta { - "deleted": { - "angle": 0, - "backgroundColor": "transparent", - "boundElements": null, - "customData": undefined, - "fillStyle": "solid", - "frameId": null, - "groupIds": [], - "height": 10, - "index": "a1", - "isDeleted": false, - "link": null, - "locked": false, - "opacity": 100, - "roughness": 1, - "roundness": { - "type": 3, - }, - "strokeColor": "#1e1e1e", - "strokeStyle": "solid", - "strokeWidth": 2, - "type": "rectangle", - "width": 10, - "x": 30, - "y": 30, - }, - "inserted": { - "isDeleted": true, - }, - }, - }, - "updated": Map {}, - }, - }, - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": { - "selectedElementIds": { - "id133": true, - }, - }, - "inserted": { - "selectedElementIds": { - "id134": true, - }, - }, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map {}, - "updated": Map {}, - }, - }, - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": { - "selectedElementIds": { - "id134": true, - }, - }, - "inserted": { - "selectedElementIds": {}, - }, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map {}, - "updated": Map {}, - }, - }, - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": {}, - "inserted": {}, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map {}, - "updated": Map { - "id133" => Delta { - "deleted": { - "x": 90, - "y": 90, - }, - "inserted": { - "x": 10, - "y": 10, - }, - }, - "id134" => Delta { - "deleted": { - "x": 110, - "y": 110, - }, - "inserted": { - "x": 30, - "y": 30, - }, - }, - }, - }, - }, - ], -} -`; - exports[`history > multiplayer undo/redo > should not let remote changes to interfere with in progress dragging > [end of test] number of elements 1`] = `3`; exports[`history > multiplayer undo/redo > should not let remote changes to interfere with in progress dragging > [end of test] number of renders 1`] = `25`; +exports[`history > multiplayer undo/redo > should not let remote changes to interfere with in progress dragging > [end of test] redo stack 1`] = `[]`; + +exports[`history > multiplayer undo/redo > should not let remote changes to interfere with in progress dragging > [end of test] undo stack 1`] = ` +[ + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": { + "selectedElementIds": { + "id542": true, + }, + }, + "inserted": { + "selectedElementIds": {}, + }, + }, + }, + "elements": { + "added": {}, + "removed": { + "id542": { + "deleted": { + "angle": 0, + "backgroundColor": "transparent", + "boundElements": null, + "customData": undefined, + "fillStyle": "solid", + "frameId": null, + "groupIds": [], + "height": 10, + "index": "a0", + "isDeleted": false, + "link": null, + "locked": false, + "opacity": 100, + "roughness": 1, + "roundness": { + "type": 3, + }, + "strokeColor": "#1e1e1e", + "strokeStyle": "solid", + "strokeWidth": 2, + "type": "rectangle", + "width": 10, + "x": 10, + "y": 10, + }, + "inserted": { + "isDeleted": true, + }, + }, + }, + "updated": {}, + }, + "id": "id563", + }, + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": { + "selectedElementIds": { + "id545": true, + }, + }, + "inserted": { + "selectedElementIds": { + "id542": true, + }, + }, + }, + }, + "elements": { + "added": {}, + "removed": { + "id545": { + "deleted": { + "angle": 0, + "backgroundColor": "transparent", + "boundElements": null, + "customData": undefined, + "fillStyle": "solid", + "frameId": null, + "groupIds": [], + "height": 10, + "index": "a1", + "isDeleted": false, + "link": null, + "locked": false, + "opacity": 100, + "roughness": 1, + "roundness": { + "type": 3, + }, + "strokeColor": "#1e1e1e", + "strokeStyle": "solid", + "strokeWidth": 2, + "type": "rectangle", + "width": 10, + "x": 30, + "y": 30, + }, + "inserted": { + "isDeleted": true, + }, + }, + }, + "updated": {}, + }, + "id": "id564", + }, + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": { + "selectedElementIds": { + "id542": true, + }, + }, + "inserted": { + "selectedElementIds": { + "id545": true, + }, + }, + }, + }, + "elements": { + "added": {}, + "removed": {}, + "updated": {}, + }, + "id": "id565", + }, + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": { + "selectedElementIds": { + "id545": true, + }, + }, + "inserted": { + "selectedElementIds": {}, + }, + }, + }, + "elements": { + "added": {}, + "removed": {}, + "updated": {}, + }, + "id": "id566", + }, + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": {}, + "inserted": {}, + }, + }, + "elements": { + "added": {}, + "removed": {}, + "updated": { + "id542": { + "deleted": { + "x": 90, + "y": 90, + }, + "inserted": { + "x": 10, + "y": 10, + }, + }, + "id545": { + "deleted": { + "x": 110, + "y": 110, + }, + "inserted": { + "x": 30, + "y": 30, + }, + }, + }, + }, + "id": "id567", + }, +] +`; + exports[`history > multiplayer undo/redo > should not let remote changes to interfere with in progress freedraw > [end of test] appState 1`] = ` { "activeEmbeddable": null, @@ -8961,7 +8830,7 @@ exports[`history > multiplayer undo/redo > should not let remote changes to inte "frameId": null, "groupIds": [], "height": 50, - "id": "id128", + "id": "id525", "index": "a0", "isDeleted": false, "lastCommittedPoint": [ @@ -9020,7 +8889,7 @@ exports[`history > multiplayer undo/redo > should not let remote changes to inte "frameId": null, "groupIds": [], "height": 100, - "id": "id129", + "id": "id526", "index": "a1", "isDeleted": false, "link": null, @@ -9042,96 +8911,90 @@ exports[`history > multiplayer undo/redo > should not let remote changes to inte } `; -exports[`history > multiplayer undo/redo > should not let remote changes to interfere with in progress freedraw > [end of test] history 1`] = ` -History { - "onHistoryChangedEmitter": Emitter { - "subscribers": [ - [Function], - [Function], - ], - }, - "redoStack": [], - "undoStack": [ - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": {}, - "inserted": {}, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map { - "id128" => Delta { - "deleted": { - "angle": 0, - "backgroundColor": "transparent", - "boundElements": null, - "customData": undefined, - "fillStyle": "solid", - "frameId": null, - "groupIds": [], - "height": 50, - "index": "a0", - "isDeleted": false, - "lastCommittedPoint": [ - 50, - 50, - ], - "link": null, - "locked": false, - "opacity": 100, - "points": [ - [ - 0, - 0, - ], - [ - 20, - 20, - ], - [ - 50, - 50, - ], - [ - 50, - 50, - ], - ], - "pressures": [ - 0, - 0, - 0, - 0, - ], - "roughness": 1, - "roundness": null, - "simulatePressure": false, - "strokeColor": "#1e1e1e", - "strokeStyle": "solid", - "strokeWidth": 2, - "type": "freedraw", - "width": 50, - "x": 10, - "y": 10, - }, - "inserted": { - "isDeleted": true, - }, - }, - }, - "updated": Map {}, - }, - }, - ], -} -`; - exports[`history > multiplayer undo/redo > should not let remote changes to interfere with in progress freedraw > [end of test] number of elements 1`] = `2`; exports[`history > multiplayer undo/redo > should not let remote changes to interfere with in progress freedraw > [end of test] number of renders 1`] = `8`; +exports[`history > multiplayer undo/redo > should not let remote changes to interfere with in progress freedraw > [end of test] redo stack 1`] = `[]`; + +exports[`history > multiplayer undo/redo > should not let remote changes to interfere with in progress freedraw > [end of test] undo stack 1`] = ` +[ + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": {}, + "inserted": {}, + }, + }, + "elements": { + "added": {}, + "removed": { + "id525": { + "deleted": { + "angle": 0, + "backgroundColor": "transparent", + "boundElements": null, + "customData": undefined, + "fillStyle": "solid", + "frameId": null, + "groupIds": [], + "height": 50, + "index": "a0", + "isDeleted": false, + "lastCommittedPoint": [ + 50, + 50, + ], + "link": null, + "locked": false, + "opacity": 100, + "points": [ + [ + 0, + 0, + ], + [ + 20, + 20, + ], + [ + 50, + 50, + ], + [ + 50, + 50, + ], + ], + "pressures": [ + 0, + 0, + 0, + 0, + ], + "roughness": 1, + "roundness": null, + "simulatePressure": false, + "strokeColor": "#1e1e1e", + "strokeStyle": "solid", + "strokeWidth": 2, + "type": "freedraw", + "width": 50, + "x": 10, + "y": 10, + }, + "inserted": { + "isDeleted": true, + }, + }, + }, + "updated": {}, + }, + "id": "id530", + }, +] +`; + exports[`history > multiplayer undo/redo > should not let remote changes to interfere with in progress resizing > [end of test] appState 1`] = ` { "activeEmbeddable": null, @@ -9216,7 +9079,7 @@ exports[`history > multiplayer undo/redo > should not let remote changes to inte "scrollY": 0, "searchMatches": [], "selectedElementIds": { - "id130": true, + "id531": true, }, "selectedElementsAreBeingDragged": false, "selectedGroupIds": {}, @@ -9254,7 +9117,7 @@ exports[`history > multiplayer undo/redo > should not let remote changes to inte "frameId": null, "groupIds": [], "height": 90, - "id": "id130", + "id": "id531", "index": "a0", "isDeleted": false, "link": null, @@ -9286,7 +9149,7 @@ exports[`history > multiplayer undo/redo > should not let remote changes to inte "frameId": null, "groupIds": [], "height": 100, - "id": "id132", + "id": "id535", "index": "a1", "isDeleted": false, "link": null, @@ -9308,99 +9171,94 @@ exports[`history > multiplayer undo/redo > should not let remote changes to inte } `; -exports[`history > multiplayer undo/redo > should not let remote changes to interfere with in progress resizing > [end of test] history 1`] = ` -History { - "onHistoryChangedEmitter": Emitter { - "subscribers": [ - [Function], - [Function], - ], - }, - "redoStack": [], - "undoStack": [ - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": { - "selectedElementIds": { - "id130": true, - }, - }, - "inserted": { - "selectedElementIds": {}, - }, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map { - "id130" => Delta { - "deleted": { - "angle": 0, - "backgroundColor": "transparent", - "boundElements": null, - "customData": undefined, - "fillStyle": "solid", - "frameId": null, - "groupIds": [], - "height": 10, - "index": "a0", - "isDeleted": false, - "link": null, - "locked": false, - "opacity": 100, - "roughness": 1, - "roundness": { - "type": 3, - }, - "strokeColor": "#1e1e1e", - "strokeStyle": "solid", - "strokeWidth": 2, - "type": "rectangle", - "width": 10, - "x": 10, - "y": 10, - }, - "inserted": { - "isDeleted": true, - }, - }, - }, - "updated": Map {}, - }, - }, - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": {}, - "inserted": {}, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map {}, - "updated": Map { - "id130" => Delta { - "deleted": { - "height": 90, - "width": 90, - }, - "inserted": { - "height": 10, - "width": 10, - }, - }, - }, - }, - }, - ], -} -`; - exports[`history > multiplayer undo/redo > should not let remote changes to interfere with in progress resizing > [end of test] number of elements 1`] = `2`; exports[`history > multiplayer undo/redo > should not let remote changes to interfere with in progress resizing > [end of test] number of renders 1`] = `13`; +exports[`history > multiplayer undo/redo > should not let remote changes to interfere with in progress resizing > [end of test] redo stack 1`] = `[]`; + +exports[`history > multiplayer undo/redo > should not let remote changes to interfere with in progress resizing > [end of test] undo stack 1`] = ` +[ + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": { + "selectedElementIds": { + "id531": true, + }, + }, + "inserted": { + "selectedElementIds": {}, + }, + }, + }, + "elements": { + "added": {}, + "removed": { + "id531": { + "deleted": { + "angle": 0, + "backgroundColor": "transparent", + "boundElements": null, + "customData": undefined, + "fillStyle": "solid", + "frameId": null, + "groupIds": [], + "height": 10, + "index": "a0", + "isDeleted": false, + "link": null, + "locked": false, + "opacity": 100, + "roughness": 1, + "roundness": { + "type": 3, + }, + "strokeColor": "#1e1e1e", + "strokeStyle": "solid", + "strokeWidth": 2, + "type": "rectangle", + "width": 10, + "x": 10, + "y": 10, + }, + "inserted": { + "isDeleted": true, + }, + }, + }, + "updated": {}, + }, + "id": "id540", + }, + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": {}, + "inserted": {}, + }, + }, + "elements": { + "added": {}, + "removed": {}, + "updated": { + "id531": { + "deleted": { + "height": 90, + "width": 90, + }, + "inserted": { + "height": 10, + "width": 10, + }, + }, + }, + }, + "id": "id541", + }, +] +`; + exports[`history > multiplayer undo/redo > should not override remote changes on different elements > [end of test] appState 1`] = ` { "activeEmbeddable": null, @@ -9485,7 +9343,7 @@ exports[`history > multiplayer undo/redo > should not override remote changes on "scrollY": 0, "searchMatches": [], "selectedElementIds": { - "id87": true, + "id333": true, }, "selectedElementsAreBeingDragged": false, "selectedGroupIds": {}, @@ -9523,7 +9381,7 @@ exports[`history > multiplayer undo/redo > should not override remote changes on "frameId": null, "groupIds": [], "height": 10, - "id": "id87", + "id": "id333", "index": "a0", "isDeleted": false, "link": null, @@ -9555,7 +9413,7 @@ exports[`history > multiplayer undo/redo > should not override remote changes on "frameId": null, "groupIds": [], "height": 100, - "id": "id88", + "id": "id338", "index": "a1", "isDeleted": false, "link": null, @@ -9577,98 +9435,95 @@ exports[`history > multiplayer undo/redo > should not override remote changes on } `; -exports[`history > multiplayer undo/redo > should not override remote changes on different elements > [end of test] history 1`] = ` -History { - "onHistoryChangedEmitter": Emitter { - "subscribers": [ - [Function], - [Function], - ], - }, - "redoStack": [ - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": {}, - "inserted": {}, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map {}, - "updated": Map { - "id87" => Delta { - "deleted": { - "backgroundColor": "transparent", - }, - "inserted": { - "backgroundColor": "#ffc9c9", - }, - }, - }, - }, - }, - ], - "undoStack": [ - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": { - "selectedElementIds": { - "id87": true, - }, - }, - "inserted": { - "selectedElementIds": {}, - }, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map { - "id87" => Delta { - "deleted": { - "angle": 0, - "backgroundColor": "transparent", - "boundElements": null, - "customData": undefined, - "fillStyle": "solid", - "frameId": null, - "groupIds": [], - "height": 10, - "index": "a0", - "isDeleted": false, - "link": null, - "locked": false, - "opacity": 100, - "roughness": 1, - "roundness": { - "type": 3, - }, - "strokeColor": "#1e1e1e", - "strokeStyle": "solid", - "strokeWidth": 2, - "type": "rectangle", - "width": 10, - "x": 10, - "y": 0, - }, - "inserted": { - "isDeleted": true, - }, - }, - }, - "updated": Map {}, - }, - }, - ], -} -`; - exports[`history > multiplayer undo/redo > should not override remote changes on different elements > [end of test] number of elements 1`] = `2`; exports[`history > multiplayer undo/redo > should not override remote changes on different elements > [end of test] number of renders 1`] = `10`; +exports[`history > multiplayer undo/redo > should not override remote changes on different elements > [end of test] redo stack 1`] = ` +[ + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": {}, + "inserted": {}, + }, + }, + "elements": { + "added": {}, + "removed": {}, + "updated": { + "id333": { + "deleted": { + "backgroundColor": "transparent", + }, + "inserted": { + "backgroundColor": "#ffc9c9", + }, + }, + }, + }, + "id": "id341", + }, +] +`; + +exports[`history > multiplayer undo/redo > should not override remote changes on different elements > [end of test] undo stack 1`] = ` +[ + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": { + "selectedElementIds": { + "id333": true, + }, + }, + "inserted": { + "selectedElementIds": {}, + }, + }, + }, + "elements": { + "added": {}, + "removed": { + "id333": { + "deleted": { + "angle": 0, + "backgroundColor": "transparent", + "boundElements": null, + "customData": undefined, + "fillStyle": "solid", + "frameId": null, + "groupIds": [], + "height": 10, + "index": "a0", + "isDeleted": false, + "link": null, + "locked": false, + "opacity": 100, + "roughness": 1, + "roundness": { + "type": 3, + }, + "strokeColor": "#1e1e1e", + "strokeStyle": "solid", + "strokeWidth": 2, + "type": "rectangle", + "width": 10, + "x": 10, + "y": 0, + }, + "inserted": { + "isDeleted": true, + }, + }, + }, + "updated": {}, + }, + "id": "id335", + }, +] +`; + exports[`history > multiplayer undo/redo > should not override remote changes on different properties > [end of test] appState 1`] = ` { "activeEmbeddable": null, @@ -9753,7 +9608,7 @@ exports[`history > multiplayer undo/redo > should not override remote changes on "scrollY": 0, "searchMatches": [], "selectedElementIds": { - "id89": true, + "id342": true, }, "selectedElementsAreBeingDragged": false, "selectedGroupIds": {}, @@ -9791,7 +9646,7 @@ exports[`history > multiplayer undo/redo > should not override remote changes on "frameId": null, "groupIds": [], "height": 10, - "id": "id89", + "id": "id342", "index": "a0", "isDeleted": false, "link": null, @@ -9813,97 +9668,92 @@ exports[`history > multiplayer undo/redo > should not override remote changes on } `; -exports[`history > multiplayer undo/redo > should not override remote changes on different properties > [end of test] history 1`] = ` -History { - "onHistoryChangedEmitter": Emitter { - "subscribers": [ - [Function], - [Function], - ], - }, - "redoStack": [], - "undoStack": [ - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": { - "selectedElementIds": { - "id89": true, - }, - }, - "inserted": { - "selectedElementIds": {}, - }, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map { - "id89" => Delta { - "deleted": { - "angle": 0, - "backgroundColor": "transparent", - "boundElements": null, - "customData": undefined, - "fillStyle": "solid", - "frameId": null, - "groupIds": [], - "height": 10, - "index": "a0", - "isDeleted": false, - "link": null, - "locked": false, - "opacity": 100, - "roughness": 1, - "roundness": { - "type": 3, - }, - "strokeColor": "#1e1e1e", - "strokeStyle": "solid", - "strokeWidth": 2, - "type": "rectangle", - "width": 10, - "x": 10, - "y": 0, - }, - "inserted": { - "isDeleted": true, - }, - }, - }, - "updated": Map {}, - }, - }, - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": {}, - "inserted": {}, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map {}, - "updated": Map { - "id89" => Delta { - "deleted": { - "backgroundColor": "#ffc9c9", - }, - "inserted": { - "backgroundColor": "transparent", - }, - }, - }, - }, - }, - ], -} -`; - exports[`history > multiplayer undo/redo > should not override remote changes on different properties > [end of test] number of elements 1`] = `1`; exports[`history > multiplayer undo/redo > should not override remote changes on different properties > [end of test] number of renders 1`] = `9`; +exports[`history > multiplayer undo/redo > should not override remote changes on different properties > [end of test] redo stack 1`] = `[]`; + +exports[`history > multiplayer undo/redo > should not override remote changes on different properties > [end of test] undo stack 1`] = ` +[ + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": { + "selectedElementIds": { + "id342": true, + }, + }, + "inserted": { + "selectedElementIds": {}, + }, + }, + }, + "elements": { + "added": {}, + "removed": { + "id342": { + "deleted": { + "angle": 0, + "backgroundColor": "transparent", + "boundElements": null, + "customData": undefined, + "fillStyle": "solid", + "frameId": null, + "groupIds": [], + "height": 10, + "index": "a0", + "isDeleted": false, + "link": null, + "locked": false, + "opacity": 100, + "roughness": 1, + "roundness": { + "type": 3, + }, + "strokeColor": "#1e1e1e", + "strokeStyle": "solid", + "strokeWidth": 2, + "type": "rectangle", + "width": 10, + "x": 10, + "y": 0, + }, + "inserted": { + "isDeleted": true, + }, + }, + }, + "updated": {}, + }, + "id": "id344", + }, + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": {}, + "inserted": {}, + }, + }, + "elements": { + "added": {}, + "removed": {}, + "updated": { + "id342": { + "deleted": { + "backgroundColor": "#ffc9c9", + }, + "inserted": { + "backgroundColor": "transparent", + }, + }, + }, + }, + "id": "id348", + }, +] +`; + exports[`history > multiplayer undo/redo > should override remotely added groups on undo, but restore them on redo > [end of test] appState 1`] = ` { "activeEmbeddable": null, @@ -10030,7 +9880,7 @@ exports[`history > multiplayer undo/redo > should override remotely added groups "B", ], "height": 100, - "id": "id94", + "id": "id371", "index": "a0", "isDeleted": false, "link": null, @@ -10065,7 +9915,7 @@ exports[`history > multiplayer undo/redo > should override remotely added groups "B", ], "height": 100, - "id": "id95", + "id": "id372", "index": "a1", "isDeleted": false, "link": null, @@ -10099,7 +9949,7 @@ exports[`history > multiplayer undo/redo > should override remotely added groups "B", ], "height": 100, - "id": "id96", + "id": "id375", "index": "a2", "isDeleted": false, "link": null, @@ -10133,7 +9983,7 @@ exports[`history > multiplayer undo/redo > should override remotely added groups "B", ], "height": 100, - "id": "id97", + "id": "id376", "index": "a3", "isDeleted": false, "link": null, @@ -10155,60 +10005,54 @@ exports[`history > multiplayer undo/redo > should override remotely added groups } `; -exports[`history > multiplayer undo/redo > should override remotely added groups on undo, but restore them on redo > [end of test] history 1`] = ` -History { - "onHistoryChangedEmitter": Emitter { - "subscribers": [ - [Function], - [Function], - ], - }, - "redoStack": [], - "undoStack": [ - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": {}, - "inserted": {}, - }, +exports[`history > multiplayer undo/redo > should override remotely added groups on undo, but restore them on redo > [end of test] number of elements 1`] = `4`; + +exports[`history > multiplayer undo/redo > should override remotely added groups on undo, but restore them on redo > [end of test] number of renders 1`] = `8`; + +exports[`history > multiplayer undo/redo > should override remotely added groups on undo, but restore them on redo > [end of test] redo stack 1`] = `[]`; + +exports[`history > multiplayer undo/redo > should override remotely added groups on undo, but restore them on redo > [end of test] undo stack 1`] = ` +[ + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": {}, + "inserted": {}, }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map {}, - "updated": Map { - "id94" => Delta { - "deleted": { - "groupIds": [ - "A", - "B", - ], - }, - "inserted": { - "groupIds": [], - }, + }, + "elements": { + "added": {}, + "removed": {}, + "updated": { + "id371": { + "deleted": { + "groupIds": [ + "A", + "B", + ], }, - "id95" => Delta { - "deleted": { - "groupIds": [ - "A", - "B", - ], - }, - "inserted": { - "groupIds": [], - }, + "inserted": { + "groupIds": [], + }, + }, + "id372": { + "deleted": { + "groupIds": [ + "A", + "B", + ], + }, + "inserted": { + "groupIds": [], }, }, }, }, - ], -} + "id": "id378", + }, +] `; -exports[`history > multiplayer undo/redo > should override remotely added groups on undo, but restore them on redo > [end of test] number of elements 1`] = `4`; - -exports[`history > multiplayer undo/redo > should override remotely added groups on undo, but restore them on redo > [end of test] number of renders 1`] = `8`; - exports[`history > multiplayer undo/redo > should override remotely added points on undo, but restore them on redo > [end of test] appState 1`] = ` { "activeEmbeddable": null, @@ -10293,7 +10137,7 @@ exports[`history > multiplayer undo/redo > should override remotely added points "scrollY": 0, "searchMatches": [], "selectedElementIds": { - "id98": true, + "id379": true, }, "selectedElementsAreBeingDragged": false, "selectedGroupIds": {}, @@ -10334,7 +10178,7 @@ exports[`history > multiplayer undo/redo > should override remotely added points "frameId": null, "groupIds": [], "height": 30, - "id": "id98", + "id": "id379", "index": "a0", "isDeleted": false, "lastCommittedPoint": [ @@ -10384,175 +10228,171 @@ exports[`history > multiplayer undo/redo > should override remotely added points } `; -exports[`history > multiplayer undo/redo > should override remotely added points on undo, but restore them on redo > [end of test] history 1`] = ` -History { - "onHistoryChangedEmitter": Emitter { - "subscribers": [ - [Function], - [Function], - ], - }, - "redoStack": [], - "undoStack": [ - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": { - "selectedElementIds": { - "id98": true, - }, - }, - "inserted": { - "selectedElementIds": {}, - }, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map { - "id98" => Delta { - "deleted": { - "angle": 0, - "backgroundColor": "transparent", - "boundElements": null, - "customData": undefined, - "elbowed": false, - "endArrowhead": "arrow", - "endBinding": null, - "fillStyle": "solid", - "frameId": null, - "groupIds": [], - "height": 10, - "index": "a0", - "isDeleted": false, - "lastCommittedPoint": [ - 10, - 10, - ], - "link": null, - "locked": false, - "opacity": 100, - "points": [ - [ - 0, - 0, - ], - [ - 10, - 10, - ], - ], - "roughness": 1, - "roundness": { - "type": 2, - }, - "startArrowhead": null, - "startBinding": null, - "strokeColor": "#1e1e1e", - "strokeStyle": "solid", - "strokeWidth": 2, - "type": "arrow", - "width": 10, - "x": 0, - "y": 0, - }, - "inserted": { - "isDeleted": true, - }, - }, - }, - "updated": Map {}, - }, - }, - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": {}, - "inserted": {}, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map {}, - "updated": Map { - "id98" => Delta { - "deleted": { - "height": 30, - "lastCommittedPoint": [ - 30, - 30, - ], - "points": [ - [ - 0, - 0, - ], - [ - 5, - 5, - ], - [ - 10, - 10, - ], - [ - 15, - 15, - ], - [ - 20, - 20, - ], - ], - "width": 30, - }, - "inserted": { - "height": 10, - "lastCommittedPoint": [ - 10, - 10, - ], - "points": [ - [ - 0, - 0, - ], - [ - 10, - 10, - ], - ], - "width": 10, - }, - }, - }, - }, - }, - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": { - "selectedLinearElementId": "id98", - }, - "inserted": { - "selectedLinearElementId": null, - }, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map {}, - "updated": Map {}, - }, - }, - ], -} -`; - exports[`history > multiplayer undo/redo > should override remotely added points on undo, but restore them on redo > [end of test] number of elements 1`] = `1`; exports[`history > multiplayer undo/redo > should override remotely added points on undo, but restore them on redo > [end of test] number of renders 1`] = `14`; +exports[`history > multiplayer undo/redo > should override remotely added points on undo, but restore them on redo > [end of test] redo stack 1`] = `[]`; + +exports[`history > multiplayer undo/redo > should override remotely added points on undo, but restore them on redo > [end of test] undo stack 1`] = ` +[ + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": { + "selectedElementIds": { + "id379": true, + }, + }, + "inserted": { + "selectedElementIds": {}, + }, + }, + }, + "elements": { + "added": {}, + "removed": { + "id379": { + "deleted": { + "angle": 0, + "backgroundColor": "transparent", + "boundElements": null, + "customData": undefined, + "elbowed": false, + "endArrowhead": "arrow", + "endBinding": null, + "fillStyle": "solid", + "frameId": null, + "groupIds": [], + "height": 10, + "index": "a0", + "isDeleted": false, + "lastCommittedPoint": [ + 10, + 10, + ], + "link": null, + "locked": false, + "opacity": 100, + "points": [ + [ + 0, + 0, + ], + [ + 10, + 10, + ], + ], + "roughness": 1, + "roundness": { + "type": 2, + }, + "startArrowhead": null, + "startBinding": null, + "strokeColor": "#1e1e1e", + "strokeStyle": "solid", + "strokeWidth": 2, + "type": "arrow", + "width": 10, + "x": 0, + "y": 0, + }, + "inserted": { + "isDeleted": true, + }, + }, + }, + "updated": {}, + }, + "id": "id389", + }, + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": {}, + "inserted": {}, + }, + }, + "elements": { + "added": {}, + "removed": {}, + "updated": { + "id379": { + "deleted": { + "height": 30, + "lastCommittedPoint": [ + 30, + 30, + ], + "points": [ + [ + 0, + 0, + ], + [ + 5, + 5, + ], + [ + 10, + 10, + ], + [ + 15, + 15, + ], + [ + 20, + 20, + ], + ], + "width": 30, + }, + "inserted": { + "height": 10, + "lastCommittedPoint": [ + 10, + 10, + ], + "points": [ + [ + 0, + 0, + ], + [ + 10, + 10, + ], + ], + "width": 10, + }, + }, + }, + }, + "id": "id390", + }, + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": { + "selectedLinearElementId": "id379", + }, + "inserted": { + "selectedLinearElementId": null, + }, + }, + }, + "elements": { + "added": {}, + "removed": {}, + "updated": {}, + }, + "id": "id391", + }, +] +`; + exports[`history > multiplayer undo/redo > should redistribute deltas when element gets removed locally but is restored remotely > [end of test] appState 1`] = ` { "activeEmbeddable": null, @@ -10673,7 +10513,7 @@ exports[`history > multiplayer undo/redo > should redistribute deltas when eleme "frameId": null, "groupIds": [], "height": 10, - "id": "id99", + "id": "id392", "index": "a0", "isDeleted": false, "link": null, @@ -10695,103 +10535,98 @@ exports[`history > multiplayer undo/redo > should redistribute deltas when eleme } `; -exports[`history > multiplayer undo/redo > should redistribute deltas when element gets removed locally but is restored remotely > [end of test] history 1`] = ` -History { - "onHistoryChangedEmitter": Emitter { - "subscribers": [ - [Function], - [Function], - ], - }, - "redoStack": [], - "undoStack": [ - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": { - "selectedElementIds": { - "id99": true, - }, - }, - "inserted": { - "selectedElementIds": {}, - }, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map { - "id99" => Delta { - "deleted": { - "angle": 0, - "backgroundColor": "#ffec99", - "boundElements": null, - "customData": undefined, - "fillStyle": "solid", - "frameId": null, - "groupIds": [], - "height": 10, - "index": "a0", - "isDeleted": false, - "link": null, - "locked": false, - "opacity": 100, - "roughness": 1, - "roundness": { - "type": 3, - }, - "strokeColor": "#1e1e1e", - "strokeStyle": "solid", - "strokeWidth": 2, - "type": "rectangle", - "width": 10, - "x": 10, - "y": 0, - }, - "inserted": { - "isDeleted": true, - }, - }, - }, - "updated": Map {}, - }, - }, - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": { - "selectedElementIds": {}, - }, - "inserted": { - "selectedElementIds": { - "id99": true, - }, - }, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map {}, - "updated": Map { - "id99" => Delta { - "deleted": { - "isDeleted": false, - }, - "inserted": { - "isDeleted": false, - }, - }, - }, - }, - }, - ], -} -`; - exports[`history > multiplayer undo/redo > should redistribute deltas when element gets removed locally but is restored remotely > [end of test] number of elements 1`] = `1`; exports[`history > multiplayer undo/redo > should redistribute deltas when element gets removed locally but is restored remotely > [end of test] number of renders 1`] = `11`; +exports[`history > multiplayer undo/redo > should redistribute deltas when element gets removed locally but is restored remotely > [end of test] redo stack 1`] = `[]`; + +exports[`history > multiplayer undo/redo > should redistribute deltas when element gets removed locally but is restored remotely > [end of test] undo stack 1`] = ` +[ + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": { + "selectedElementIds": { + "id392": true, + }, + }, + "inserted": { + "selectedElementIds": {}, + }, + }, + }, + "elements": { + "added": {}, + "removed": { + "id392": { + "deleted": { + "angle": 0, + "backgroundColor": "#ffec99", + "boundElements": null, + "customData": undefined, + "fillStyle": "solid", + "frameId": null, + "groupIds": [], + "height": 10, + "index": "a0", + "isDeleted": false, + "link": null, + "locked": false, + "opacity": 100, + "roughness": 1, + "roundness": { + "type": 3, + }, + "strokeColor": "#1e1e1e", + "strokeStyle": "solid", + "strokeWidth": 2, + "type": "rectangle", + "width": 10, + "x": 10, + "y": 0, + }, + "inserted": { + "isDeleted": true, + }, + }, + }, + "updated": {}, + }, + "id": "id399", + }, + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": { + "selectedElementIds": {}, + }, + "inserted": { + "selectedElementIds": { + "id392": true, + }, + }, + }, + }, + "elements": { + "added": {}, + "removed": {}, + "updated": { + "id392": { + "deleted": { + "isDeleted": false, + }, + "inserted": { + "isDeleted": false, + }, + }, + }, + }, + "id": "id400", + }, +] +`; + exports[`history > multiplayer undo/redo > should redraw arrows on undo > [end of test] appState 1`] = ` { "activeEmbeddable": null, @@ -11040,211 +10875,206 @@ exports[`history > multiplayer undo/redo > should redraw arrows on undo > [end o } `; -exports[`history > multiplayer undo/redo > should redraw arrows on undo > [end of test] history 1`] = ` -History { - "onHistoryChangedEmitter": Emitter { - "subscribers": [ - [Function], - [Function], - ], - }, - "redoStack": [ - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": {}, - "inserted": {}, - }, - }, - "elementsChange": ElementsChange { - "added": Map { - "6Rm4g567UQM4WjLwej2Vc" => Delta { - "deleted": { - "isDeleted": true, - }, - "inserted": { - "angle": 0, - "backgroundColor": "transparent", - "boundElements": null, - "customData": undefined, - "elbowed": true, - "endArrowhead": null, - "endBinding": { - "elementId": "u2JGnnmoJ0VATV4vCNJE5", - "fixedPoint": [ - "0.49919", - "-0.03875", - ], - "focus": "-0.00161", - "gap": "3.53708", - }, - "endIsSpecial": false, - "fillStyle": "solid", - "fixedSegments": [], - "frameId": null, - "groupIds": [], - "height": "236.10000", - "index": "a2", - "isDeleted": false, - "lastCommittedPoint": null, - "link": null, - "locked": false, - "opacity": 100, - "points": [ - [ - 0, - 0, - ], - [ - "178.90000", - 0, - ], - [ - "178.90000", - "236.10000", - ], - ], - "roughness": 1, - "roundness": { - "type": 2, - }, - "startArrowhead": null, - "startBinding": { - "elementId": "KPrBI4g_v9qUB1XxYLgSz", - "fixedPoint": [ - "1.03185", - "0.49921", - ], - "focus": "-0.00159", - "gap": 5, - }, - "startIsSpecial": false, - "strokeColor": "#1e1e1e", - "strokeStyle": "solid", - "strokeWidth": 2, - "type": "arrow", - "width": "178.90000", - "x": 1035, - "y": "274.90000", - }, - }, - }, - "removed": Map {}, - "updated": Map { - "KPrBI4g_v9qUB1XxYLgSz" => Delta { - "deleted": { - "boundElements": [], - }, - "inserted": { - "boundElements": [ - { - "id": "6Rm4g567UQM4WjLwej2Vc", - "type": "arrow", - }, - ], - }, - }, - "u2JGnnmoJ0VATV4vCNJE5" => Delta { - "deleted": { - "boundElements": [], - }, - "inserted": { - "boundElements": [ - { - "id": "6Rm4g567UQM4WjLwej2Vc", - "type": "arrow", - }, - ], - }, - }, - }, - }, - }, - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": {}, - "inserted": {}, - }, - }, - "elementsChange": ElementsChange { - "added": Map { - "KPrBI4g_v9qUB1XxYLgSz" => Delta { - "deleted": { - "isDeleted": true, - }, - "inserted": { - "angle": 0, - "backgroundColor": "transparent", - "boundElements": null, - "customData": undefined, - "fillStyle": "solid", - "frameId": null, - "groupIds": [], - "height": 126, - "index": "a0", - "isDeleted": false, - "link": null, - "locked": false, - "opacity": 100, - "roughness": 1, - "roundness": { - "type": 3, - }, - "strokeColor": "#1e1e1e", - "strokeStyle": "solid", - "strokeWidth": 2, - "type": "rectangle", - "width": 157, - "x": 600, - "y": 0, - }, - }, - "u2JGnnmoJ0VATV4vCNJE5" => Delta { - "deleted": { - "isDeleted": true, - }, - "inserted": { - "angle": 0, - "backgroundColor": "transparent", - "boundElements": null, - "customData": undefined, - "fillStyle": "solid", - "frameId": null, - "groupIds": [], - "height": 129, - "index": "a1", - "isDeleted": false, - "link": null, - "locked": false, - "opacity": 100, - "roughness": 1, - "roundness": { - "type": 3, - }, - "strokeColor": "#1e1e1e", - "strokeStyle": "solid", - "strokeWidth": 2, - "type": "diamond", - "width": 124, - "x": 1152, - "y": 516, - }, - }, - }, - "removed": Map {}, - "updated": Map {}, - }, - }, - ], - "undoStack": [], -} -`; - exports[`history > multiplayer undo/redo > should redraw arrows on undo > [end of test] number of elements 1`] = `3`; exports[`history > multiplayer undo/redo > should redraw arrows on undo > [end of test] number of renders 1`] = `8`; +exports[`history > multiplayer undo/redo > should redraw arrows on undo > [end of test] redo stack 1`] = ` +[ + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": {}, + "inserted": {}, + }, + }, + "elements": { + "added": { + "6Rm4g567UQM4WjLwej2Vc": { + "deleted": { + "isDeleted": true, + }, + "inserted": { + "angle": 0, + "backgroundColor": "transparent", + "boundElements": null, + "customData": undefined, + "elbowed": true, + "endArrowhead": null, + "endBinding": { + "elementId": "u2JGnnmoJ0VATV4vCNJE5", + "fixedPoint": [ + "0.49919", + "-0.03875", + ], + "focus": "-0.00161", + "gap": "3.53708", + }, + "endIsSpecial": false, + "fillStyle": "solid", + "fixedSegments": [], + "frameId": null, + "groupIds": [], + "height": "236.10000", + "index": "a2", + "isDeleted": false, + "lastCommittedPoint": null, + "link": null, + "locked": false, + "opacity": 100, + "points": [ + [ + 0, + 0, + ], + [ + "178.90000", + 0, + ], + [ + "178.90000", + "236.10000", + ], + ], + "roughness": 1, + "roundness": { + "type": 2, + }, + "startArrowhead": null, + "startBinding": { + "elementId": "KPrBI4g_v9qUB1XxYLgSz", + "fixedPoint": [ + "1.03185", + "0.49921", + ], + "focus": "-0.00159", + "gap": 5, + }, + "startIsSpecial": false, + "strokeColor": "#1e1e1e", + "strokeStyle": "solid", + "strokeWidth": 2, + "type": "arrow", + "width": "178.90000", + "x": 1035, + "y": "274.90000", + }, + }, + }, + "removed": {}, + "updated": { + "KPrBI4g_v9qUB1XxYLgSz": { + "deleted": { + "boundElements": [], + }, + "inserted": { + "boundElements": [ + { + "id": "6Rm4g567UQM4WjLwej2Vc", + "type": "arrow", + }, + ], + }, + }, + "u2JGnnmoJ0VATV4vCNJE5": { + "deleted": { + "boundElements": [], + }, + "inserted": { + "boundElements": [ + { + "id": "6Rm4g567UQM4WjLwej2Vc", + "type": "arrow", + }, + ], + }, + }, + }, + }, + "id": "id369", + }, + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": {}, + "inserted": {}, + }, + }, + "elements": { + "added": { + "KPrBI4g_v9qUB1XxYLgSz": { + "deleted": { + "isDeleted": true, + }, + "inserted": { + "angle": 0, + "backgroundColor": "transparent", + "boundElements": null, + "customData": undefined, + "fillStyle": "solid", + "frameId": null, + "groupIds": [], + "height": 126, + "index": "a0", + "isDeleted": false, + "link": null, + "locked": false, + "opacity": 100, + "roughness": 1, + "roundness": { + "type": 3, + }, + "strokeColor": "#1e1e1e", + "strokeStyle": "solid", + "strokeWidth": 2, + "type": "rectangle", + "width": 157, + "x": 600, + "y": 0, + }, + }, + "u2JGnnmoJ0VATV4vCNJE5": { + "deleted": { + "isDeleted": true, + }, + "inserted": { + "angle": 0, + "backgroundColor": "transparent", + "boundElements": null, + "customData": undefined, + "fillStyle": "solid", + "frameId": null, + "groupIds": [], + "height": 129, + "index": "a1", + "isDeleted": false, + "link": null, + "locked": false, + "opacity": 100, + "roughness": 1, + "roundness": { + "type": 3, + }, + "strokeColor": "#1e1e1e", + "strokeStyle": "solid", + "strokeWidth": 2, + "type": "diamond", + "width": 124, + "x": 1152, + "y": 516, + }, + }, + }, + "removed": {}, + "updated": {}, + }, + "id": "id370", + }, +] +`; + +exports[`history > multiplayer undo/redo > should redraw arrows on undo > [end of test] undo stack 1`] = `[]`; + exports[`history > multiplayer undo/redo > should update history entries after remote changes on the same properties > [end of test] appState 1`] = ` { "activeEmbeddable": null, @@ -11329,7 +11159,7 @@ exports[`history > multiplayer undo/redo > should update history entries after r "scrollY": 0, "searchMatches": [], "selectedElementIds": { - "id90": true, + "id349": true, }, "selectedElementsAreBeingDragged": false, "selectedGroupIds": {}, @@ -11367,7 +11197,7 @@ exports[`history > multiplayer undo/redo > should update history entries after r "frameId": null, "groupIds": [], "height": 10, - "id": "id90", + "id": "id349", "index": "a0", "isDeleted": false, "link": null, @@ -11389,120 +11219,118 @@ exports[`history > multiplayer undo/redo > should update history entries after r } `; -exports[`history > multiplayer undo/redo > should update history entries after remote changes on the same properties > [end of test] history 1`] = ` -History { - "onHistoryChangedEmitter": Emitter { - "subscribers": [ - [Function], - [Function], - ], - }, - "redoStack": [ - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": {}, - "inserted": {}, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map {}, - "updated": Map { - "id90" => Delta { - "deleted": { - "backgroundColor": "#d0bfff", - }, - "inserted": { - "backgroundColor": "#ffec99", - }, - }, - }, - }, - }, - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": {}, - "inserted": {}, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map {}, - "updated": Map { - "id90" => Delta { - "deleted": { - "backgroundColor": "transparent", - }, - "inserted": { - "backgroundColor": "#d0bfff", - }, - }, - }, - }, - }, - ], - "undoStack": [ - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": { - "selectedElementIds": { - "id90": true, - }, - }, - "inserted": { - "selectedElementIds": {}, - }, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map { - "id90" => Delta { - "deleted": { - "angle": 0, - "backgroundColor": "transparent", - "boundElements": null, - "customData": undefined, - "fillStyle": "solid", - "frameId": null, - "groupIds": [], - "height": 10, - "index": "a0", - "isDeleted": false, - "link": null, - "locked": false, - "opacity": 100, - "roughness": 1, - "roundness": { - "type": 3, - }, - "strokeColor": "#1e1e1e", - "strokeStyle": "solid", - "strokeWidth": 2, - "type": "rectangle", - "width": 10, - "x": 10, - "y": 0, - }, - "inserted": { - "isDeleted": true, - }, - }, - }, - "updated": Map {}, - }, - }, - ], -} -`; - exports[`history > multiplayer undo/redo > should update history entries after remote changes on the same properties > [end of test] number of elements 1`] = `1`; exports[`history > multiplayer undo/redo > should update history entries after remote changes on the same properties > [end of test] number of renders 1`] = `15`; +exports[`history > multiplayer undo/redo > should update history entries after remote changes on the same properties > [end of test] redo stack 1`] = ` +[ + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": {}, + "inserted": {}, + }, + }, + "elements": { + "added": {}, + "removed": {}, + "updated": { + "id349": { + "deleted": { + "backgroundColor": "#d0bfff", + }, + "inserted": { + "backgroundColor": "#ffec99", + }, + }, + }, + }, + "id": "id360", + }, + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": {}, + "inserted": {}, + }, + }, + "elements": { + "added": {}, + "removed": {}, + "updated": { + "id349": { + "deleted": { + "backgroundColor": "transparent", + }, + "inserted": { + "backgroundColor": "#d0bfff", + }, + }, + }, + }, + "id": "id361", + }, +] +`; + +exports[`history > multiplayer undo/redo > should update history entries after remote changes on the same properties > [end of test] undo stack 1`] = ` +[ + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": { + "selectedElementIds": { + "id349": true, + }, + }, + "inserted": { + "selectedElementIds": {}, + }, + }, + }, + "elements": { + "added": {}, + "removed": { + "id349": { + "deleted": { + "angle": 0, + "backgroundColor": "transparent", + "boundElements": null, + "customData": undefined, + "fillStyle": "solid", + "frameId": null, + "groupIds": [], + "height": 10, + "index": "a0", + "isDeleted": false, + "link": null, + "locked": false, + "opacity": 100, + "roughness": 1, + "roundness": { + "type": 3, + }, + "strokeColor": "#1e1e1e", + "strokeStyle": "solid", + "strokeWidth": 2, + "type": "rectangle", + "width": 10, + "x": 10, + "y": 0, + }, + "inserted": { + "isDeleted": true, + }, + }, + }, + "updated": {}, + }, + "id": "id351", + }, +] +`; + exports[`history > singleplayer undo/redo > remounting undo/redo buttons should initialize undo/redo state correctly > [end of test] appState 1`] = ` { "activeEmbeddable": null, @@ -11655,7 +11483,7 @@ exports[`history > singleplayer undo/redo > remounting undo/redo buttons should "frameId": null, "groupIds": [], "height": 10, - "id": "id86", + "id": "id329", "index": "a1", "isDeleted": true, "link": null, @@ -11677,75 +11505,69 @@ exports[`history > singleplayer undo/redo > remounting undo/redo buttons should } `; -exports[`history > singleplayer undo/redo > remounting undo/redo buttons should initialize undo/redo state correctly > [end of test] history 1`] = ` -History { - "onHistoryChangedEmitter": Emitter { - "subscribers": [ - [Function], - [Function], - ], - }, - "redoStack": [ - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": { - "selectedElementIds": {}, - }, - "inserted": { - "selectedElementIds": { - "id86": true, - }, - }, - }, - }, - "elementsChange": ElementsChange { - "added": Map { - "id86" => Delta { - "deleted": { - "isDeleted": true, - }, - "inserted": { - "angle": 0, - "backgroundColor": "transparent", - "boundElements": null, - "customData": undefined, - "fillStyle": "solid", - "frameId": null, - "groupIds": [], - "height": 10, - "index": "a1", - "isDeleted": false, - "link": null, - "locked": false, - "opacity": 100, - "roughness": 1, - "roundness": { - "type": 3, - }, - "strokeColor": "#1e1e1e", - "strokeStyle": "solid", - "strokeWidth": 2, - "type": "rectangle", - "width": 10, - "x": 0, - "y": 0, - }, - }, - }, - "removed": Map {}, - "updated": Map {}, - }, - }, - ], - "undoStack": [], -} -`; - exports[`history > singleplayer undo/redo > remounting undo/redo buttons should initialize undo/redo state correctly > [end of test] number of elements 1`] = `2`; exports[`history > singleplayer undo/redo > remounting undo/redo buttons should initialize undo/redo state correctly > [end of test] number of renders 1`] = `11`; +exports[`history > singleplayer undo/redo > remounting undo/redo buttons should initialize undo/redo state correctly > [end of test] redo stack 1`] = ` +[ + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": { + "selectedElementIds": {}, + }, + "inserted": { + "selectedElementIds": { + "id329": true, + }, + }, + }, + }, + "elements": { + "added": { + "id329": { + "deleted": { + "isDeleted": true, + }, + "inserted": { + "angle": 0, + "backgroundColor": "transparent", + "boundElements": null, + "customData": undefined, + "fillStyle": "solid", + "frameId": null, + "groupIds": [], + "height": 10, + "index": "a1", + "isDeleted": false, + "link": null, + "locked": false, + "opacity": 100, + "roughness": 1, + "roundness": { + "type": 3, + }, + "strokeColor": "#1e1e1e", + "strokeStyle": "solid", + "strokeWidth": 2, + "type": "rectangle", + "width": 10, + "x": 0, + "y": 0, + }, + }, + }, + "removed": {}, + "updated": {}, + }, + "id": "id332", + }, +] +`; + +exports[`history > singleplayer undo/redo > remounting undo/redo buttons should initialize undo/redo state correctly > [end of test] undo stack 1`] = `[]`; + exports[`history > singleplayer undo/redo > should clear the redo stack on elements change > [end of test] appState 1`] = ` { "activeEmbeddable": null, @@ -11830,7 +11652,7 @@ exports[`history > singleplayer undo/redo > should clear the redo stack on eleme "scrollY": 0, "searchMatches": [], "selectedElementIds": { - "id14": true, + "id50": true, }, "selectedElementsAreBeingDragged": false, "selectedGroupIds": {}, @@ -11868,7 +11690,7 @@ exports[`history > singleplayer undo/redo > should clear the redo stack on eleme "frameId": null, "groupIds": [], "height": 10, - "id": "id13", + "id": "id46", "index": "a0", "isDeleted": true, "link": null, @@ -11900,7 +11722,7 @@ exports[`history > singleplayer undo/redo > should clear the redo stack on eleme "frameId": null, "groupIds": [], "height": 10, - "id": "id14", + "id": "id50", "index": "a1", "isDeleted": false, "link": null, @@ -11922,75 +11744,69 @@ exports[`history > singleplayer undo/redo > should clear the redo stack on eleme } `; -exports[`history > singleplayer undo/redo > should clear the redo stack on elements change > [end of test] history 1`] = ` -History { - "onHistoryChangedEmitter": Emitter { - "subscribers": [ - [Function], - [Function], - ], - }, - "redoStack": [], - "undoStack": [ - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": { - "selectedElementIds": { - "id14": true, - }, - }, - "inserted": { - "selectedElementIds": {}, - }, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map { - "id14" => Delta { - "deleted": { - "angle": 0, - "backgroundColor": "transparent", - "boundElements": null, - "customData": undefined, - "fillStyle": "solid", - "frameId": null, - "groupIds": [], - "height": 10, - "index": "a1", - "isDeleted": false, - "link": null, - "locked": false, - "opacity": 100, - "roughness": 1, - "roundness": { - "type": 3, - }, - "strokeColor": "#1e1e1e", - "strokeStyle": "solid", - "strokeWidth": 2, - "type": "rectangle", - "width": 10, - "x": 20, - "y": 0, - }, - "inserted": { - "isDeleted": true, - }, - }, - }, - "updated": Map {}, - }, - }, - ], -} -`; - exports[`history > singleplayer undo/redo > should clear the redo stack on elements change > [end of test] number of elements 1`] = `2`; exports[`history > singleplayer undo/redo > should clear the redo stack on elements change > [end of test] number of renders 1`] = `8`; +exports[`history > singleplayer undo/redo > should clear the redo stack on elements change > [end of test] redo stack 1`] = `[]`; + +exports[`history > singleplayer undo/redo > should clear the redo stack on elements change > [end of test] undo stack 1`] = ` +[ + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": { + "selectedElementIds": { + "id50": true, + }, + }, + "inserted": { + "selectedElementIds": {}, + }, + }, + }, + "elements": { + "added": {}, + "removed": { + "id50": { + "deleted": { + "angle": 0, + "backgroundColor": "transparent", + "boundElements": null, + "customData": undefined, + "fillStyle": "solid", + "frameId": null, + "groupIds": [], + "height": 10, + "index": "a1", + "isDeleted": false, + "link": null, + "locked": false, + "opacity": 100, + "roughness": 1, + "roundness": { + "type": 3, + }, + "strokeColor": "#1e1e1e", + "strokeStyle": "solid", + "strokeWidth": 2, + "type": "rectangle", + "width": 10, + "x": 20, + "y": 0, + }, + "inserted": { + "isDeleted": true, + }, + }, + }, + "updated": {}, + }, + "id": "id52", + }, +] +`; + exports[`history > singleplayer undo/redo > should create entry when selecting freedraw > [end of test] appState 1`] = ` { "activeEmbeddable": null, @@ -12111,7 +11927,7 @@ exports[`history > singleplayer undo/redo > should create entry when selecting f "frameId": null, "groupIds": [], "height": 10, - "id": "id32", + "id": "id148", "index": "a0", "isDeleted": false, "link": null, @@ -12143,7 +11959,7 @@ exports[`history > singleplayer undo/redo > should create entry when selecting f "frameId": null, "groupIds": [], "height": 10, - "id": "id33", + "id": "id153", "index": "a1", "isDeleted": true, "lastCommittedPoint": [ @@ -12197,7 +12013,7 @@ exports[`history > singleplayer undo/redo > should create entry when selecting f "frameId": null, "groupIds": [], "height": 10, - "id": "id34", + "id": "id157", "index": "a2", "isDeleted": false, "lastCommittedPoint": [ @@ -12241,161 +12057,157 @@ exports[`history > singleplayer undo/redo > should create entry when selecting f } `; -exports[`history > singleplayer undo/redo > should create entry when selecting freedraw > [end of test] history 1`] = ` -History { - "onHistoryChangedEmitter": Emitter { - "subscribers": [ - [Function], - [Function], - ], - }, - "redoStack": [], - "undoStack": [ - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": { - "selectedElementIds": { - "id32": true, - }, - }, - "inserted": { - "selectedElementIds": {}, - }, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map { - "id32" => Delta { - "deleted": { - "angle": 0, - "backgroundColor": "transparent", - "boundElements": null, - "customData": undefined, - "fillStyle": "solid", - "frameId": null, - "groupIds": [], - "height": 10, - "index": "a0", - "isDeleted": false, - "link": null, - "locked": false, - "opacity": 100, - "roughness": 1, - "roundness": { - "type": 3, - }, - "strokeColor": "#1e1e1e", - "strokeStyle": "solid", - "strokeWidth": 2, - "type": "rectangle", - "width": 10, - "x": -10, - "y": -10, - }, - "inserted": { - "isDeleted": true, - }, - }, - }, - "updated": Map {}, - }, - }, - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": { - "selectedElementIds": {}, - }, - "inserted": { - "selectedElementIds": { - "id32": true, - }, - }, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map {}, - "updated": Map {}, - }, - }, - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": {}, - "inserted": {}, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map { - "id34" => Delta { - "deleted": { - "angle": 0, - "backgroundColor": "transparent", - "boundElements": null, - "customData": undefined, - "fillStyle": "solid", - "frameId": null, - "groupIds": [], - "height": 10, - "index": "a2", - "isDeleted": false, - "lastCommittedPoint": [ - 50, - 10, - ], - "link": null, - "locked": false, - "opacity": 100, - "points": [ - [ - 0, - 0, - ], - [ - 50, - 10, - ], - [ - 50, - 10, - ], - ], - "pressures": [ - 0, - 0, - 0, - ], - "roughness": 1, - "roundness": null, - "simulatePressure": false, - "strokeColor": "#e03131", - "strokeStyle": "solid", - "strokeWidth": 2, - "type": "freedraw", - "width": 50, - "x": 130, - "y": -30, - }, - "inserted": { - "isDeleted": true, - }, - }, - }, - "updated": Map {}, - }, - }, - ], -} -`; - exports[`history > singleplayer undo/redo > should create entry when selecting freedraw > [end of test] number of elements 1`] = `3`; exports[`history > singleplayer undo/redo > should create entry when selecting freedraw > [end of test] number of renders 1`] = `12`; +exports[`history > singleplayer undo/redo > should create entry when selecting freedraw > [end of test] redo stack 1`] = `[]`; + +exports[`history > singleplayer undo/redo > should create entry when selecting freedraw > [end of test] undo stack 1`] = ` +[ + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": { + "selectedElementIds": { + "id148": true, + }, + }, + "inserted": { + "selectedElementIds": {}, + }, + }, + }, + "elements": { + "added": {}, + "removed": { + "id148": { + "deleted": { + "angle": 0, + "backgroundColor": "transparent", + "boundElements": null, + "customData": undefined, + "fillStyle": "solid", + "frameId": null, + "groupIds": [], + "height": 10, + "index": "a0", + "isDeleted": false, + "link": null, + "locked": false, + "opacity": 100, + "roughness": 1, + "roundness": { + "type": 3, + }, + "strokeColor": "#1e1e1e", + "strokeStyle": "solid", + "strokeWidth": 2, + "type": "rectangle", + "width": 10, + "x": -10, + "y": -10, + }, + "inserted": { + "isDeleted": true, + }, + }, + }, + "updated": {}, + }, + "id": "id150", + }, + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": { + "selectedElementIds": {}, + }, + "inserted": { + "selectedElementIds": { + "id148": true, + }, + }, + }, + }, + "elements": { + "added": {}, + "removed": {}, + "updated": {}, + }, + "id": "id152", + }, + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": {}, + "inserted": {}, + }, + }, + "elements": { + "added": {}, + "removed": { + "id157": { + "deleted": { + "angle": 0, + "backgroundColor": "transparent", + "boundElements": null, + "customData": undefined, + "fillStyle": "solid", + "frameId": null, + "groupIds": [], + "height": 10, + "index": "a2", + "isDeleted": false, + "lastCommittedPoint": [ + 50, + 10, + ], + "link": null, + "locked": false, + "opacity": 100, + "points": [ + [ + 0, + 0, + ], + [ + 50, + 10, + ], + [ + 50, + 10, + ], + ], + "pressures": [ + 0, + 0, + 0, + ], + "roughness": 1, + "roundness": null, + "simulatePressure": false, + "strokeColor": "#e03131", + "strokeStyle": "solid", + "strokeWidth": 2, + "type": "freedraw", + "width": 50, + "x": 130, + "y": -30, + }, + "inserted": { + "isDeleted": true, + }, + }, + }, + "updated": {}, + }, + "id": "id159", + }, +] +`; + exports[`history > singleplayer undo/redo > should create new history entry on scene import via drag&drop > [end of test] appState 1`] = ` { "activeEmbeddable": null, @@ -12572,81 +12384,75 @@ exports[`history > singleplayer undo/redo > should create new history entry on s } `; -exports[`history > singleplayer undo/redo > should create new history entry on scene import via drag&drop > [end of test] history 1`] = ` -History { - "onHistoryChangedEmitter": Emitter { - "subscribers": [ - [Function], - [Function], - ], - }, - "redoStack": [], - "undoStack": [ - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": { - "viewBackgroundColor": "#000", - }, - "inserted": { - "viewBackgroundColor": "#FFF", - }, - }, - }, - "elementsChange": ElementsChange { - "added": Map { - "A" => Delta { - "deleted": { - "isDeleted": true, - }, - "inserted": { - "isDeleted": false, - }, - }, - }, - "removed": Map { - "B" => Delta { - "deleted": { - "angle": 0, - "backgroundColor": "transparent", - "boundElements": [], - "fillStyle": "solid", - "frameId": null, - "groupIds": [], - "height": 100, - "index": "a1", - "isDeleted": false, - "link": null, - "locked": false, - "opacity": 100, - "roughness": 1, - "roundness": { - "type": 3, - }, - "strokeColor": "#1e1e1e", - "strokeStyle": "solid", - "strokeWidth": 2, - "type": "rectangle", - "width": 100, - "x": 0, - "y": 0, - }, - "inserted": { - "isDeleted": true, - }, - }, - }, - "updated": Map {}, - }, - }, - ], -} -`; - exports[`history > singleplayer undo/redo > should create new history entry on scene import via drag&drop > [end of test] number of elements 1`] = `2`; exports[`history > singleplayer undo/redo > should create new history entry on scene import via drag&drop > [end of test] number of renders 1`] = `6`; +exports[`history > singleplayer undo/redo > should create new history entry on scene import via drag&drop > [end of test] redo stack 1`] = `[]`; + +exports[`history > singleplayer undo/redo > should create new history entry on scene import via drag&drop > [end of test] undo stack 1`] = ` +[ + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": { + "viewBackgroundColor": "#000", + }, + "inserted": { + "viewBackgroundColor": "#FFF", + }, + }, + }, + "elements": { + "added": { + "A": { + "deleted": { + "isDeleted": true, + }, + "inserted": { + "isDeleted": false, + }, + }, + }, + "removed": { + "B": { + "deleted": { + "angle": 0, + "backgroundColor": "transparent", + "boundElements": [], + "fillStyle": "solid", + "frameId": null, + "groupIds": [], + "height": 100, + "index": "a1", + "isDeleted": false, + "link": null, + "locked": false, + "opacity": 100, + "roughness": 1, + "roundness": { + "type": 3, + }, + "strokeColor": "#1e1e1e", + "strokeStyle": "solid", + "strokeWidth": 2, + "type": "rectangle", + "width": 100, + "x": 0, + "y": 0, + }, + "inserted": { + "isDeleted": true, + }, + }, + }, + "updated": {}, + }, + "id": "id80", + }, +] +`; + exports[`history > singleplayer undo/redo > should disable undo/redo buttons when stacks empty > [end of test] appState 1`] = ` { "activeEmbeddable": null, @@ -12731,7 +12537,7 @@ exports[`history > singleplayer undo/redo > should disable undo/redo buttons whe "scrollY": 0, "searchMatches": [], "selectedElementIds": { - "id84": true, + "id323": true, }, "selectedElementsAreBeingDragged": false, "selectedGroupIds": {}, @@ -12801,7 +12607,7 @@ exports[`history > singleplayer undo/redo > should disable undo/redo buttons whe "frameId": null, "groupIds": [], "height": 10, - "id": "id84", + "id": "id323", "index": "a1", "isDeleted": false, "link": null, @@ -12823,75 +12629,69 @@ exports[`history > singleplayer undo/redo > should disable undo/redo buttons whe } `; -exports[`history > singleplayer undo/redo > should disable undo/redo buttons when stacks empty > [end of test] history 1`] = ` -History { - "onHistoryChangedEmitter": Emitter { - "subscribers": [ - [Function], - [Function], - ], - }, - "redoStack": [], - "undoStack": [ - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": { - "selectedElementIds": { - "id84": true, - }, - }, - "inserted": { - "selectedElementIds": {}, - }, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map { - "id84" => Delta { - "deleted": { - "angle": 0, - "backgroundColor": "transparent", - "boundElements": null, - "customData": undefined, - "fillStyle": "solid", - "frameId": null, - "groupIds": [], - "height": 10, - "index": "a1", - "isDeleted": false, - "link": null, - "locked": false, - "opacity": 100, - "roughness": 1, - "roundness": { - "type": 3, - }, - "strokeColor": "#1e1e1e", - "strokeStyle": "solid", - "strokeWidth": 2, - "type": "rectangle", - "width": 10, - "x": 0, - "y": 0, - }, - "inserted": { - "isDeleted": true, - }, - }, - }, - "updated": Map {}, - }, - }, - ], -} -`; - exports[`history > singleplayer undo/redo > should disable undo/redo buttons when stacks empty > [end of test] number of elements 1`] = `2`; exports[`history > singleplayer undo/redo > should disable undo/redo buttons when stacks empty > [end of test] number of renders 1`] = `7`; +exports[`history > singleplayer undo/redo > should disable undo/redo buttons when stacks empty > [end of test] redo stack 1`] = `[]`; + +exports[`history > singleplayer undo/redo > should disable undo/redo buttons when stacks empty > [end of test] undo stack 1`] = ` +[ + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": { + "selectedElementIds": { + "id323": true, + }, + }, + "inserted": { + "selectedElementIds": {}, + }, + }, + }, + "elements": { + "added": {}, + "removed": { + "id323": { + "deleted": { + "angle": 0, + "backgroundColor": "transparent", + "boundElements": null, + "customData": undefined, + "fillStyle": "solid", + "frameId": null, + "groupIds": [], + "height": 10, + "index": "a1", + "isDeleted": false, + "link": null, + "locked": false, + "opacity": 100, + "roughness": 1, + "roundness": { + "type": 3, + }, + "strokeColor": "#1e1e1e", + "strokeStyle": "solid", + "strokeWidth": 2, + "type": "rectangle", + "width": 10, + "x": 0, + "y": 0, + }, + "inserted": { + "isDeleted": true, + }, + }, + }, + "updated": {}, + }, + "id": "id327", + }, +] +`; + exports[`history > singleplayer undo/redo > should end up with no history entry after initializing scene > [end of test] appState 1`] = ` { "activeEmbeddable": null, @@ -12976,7 +12776,7 @@ exports[`history > singleplayer undo/redo > should end up with no history entry "scrollY": 0, "searchMatches": [], "selectedElementIds": { - "id19": true, + "id70": true, }, "selectedElementsAreBeingDragged": false, "selectedGroupIds": {}, @@ -13046,7 +12846,7 @@ exports[`history > singleplayer undo/redo > should end up with no history entry "frameId": null, "groupIds": [], "height": 10, - "id": "id19", + "id": "id70", "index": "a1", "isDeleted": false, "link": null, @@ -13068,74 +12868,68 @@ exports[`history > singleplayer undo/redo > should end up with no history entry } `; -exports[`history > singleplayer undo/redo > should end up with no history entry after initializing scene > [end of test] history 1`] = ` -History { - "onHistoryChangedEmitter": Emitter { - "subscribers": [ - [Function], - [Function], - ], - }, - "redoStack": [], - "undoStack": [ - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": { - "selectedElementIds": { - "id19": true, - }, - }, - "inserted": { - "selectedElementIds": {}, - }, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map { - "id19" => Delta { - "deleted": { - "angle": 0, - "backgroundColor": "transparent", - "boundElements": null, - "customData": undefined, - "fillStyle": "solid", - "frameId": null, - "groupIds": [], - "height": 10, - "index": "a1", - "isDeleted": false, - "link": null, - "locked": false, - "opacity": 100, - "roughness": 1, - "roundness": { - "type": 3, - }, - "strokeColor": "#1e1e1e", - "strokeStyle": "solid", - "strokeWidth": 2, - "type": "rectangle", - "width": 10, - "x": 0, - "y": 0, - }, - "inserted": { - "isDeleted": true, - }, - }, - }, - "updated": Map {}, - }, - }, - ], -} -`; - exports[`history > singleplayer undo/redo > should end up with no history entry after initializing scene > [end of test] number of elements 1`] = `2`; -exports[`history > singleplayer undo/redo > should end up with no history entry after initializing scene > [end of test] number of renders 1`] = `7`; +exports[`history > singleplayer undo/redo > should end up with no history entry after initializing scene > [end of test] number of renders 1`] = `9`; + +exports[`history > singleplayer undo/redo > should end up with no history entry after initializing scene > [end of test] redo stack 1`] = `[]`; + +exports[`history > singleplayer undo/redo > should end up with no history entry after initializing scene > [end of test] undo stack 1`] = ` +[ + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": { + "selectedElementIds": { + "id70": true, + }, + }, + "inserted": { + "selectedElementIds": {}, + }, + }, + }, + "elements": { + "added": {}, + "removed": { + "id70": { + "deleted": { + "angle": 0, + "backgroundColor": "transparent", + "boundElements": null, + "customData": undefined, + "fillStyle": "solid", + "frameId": null, + "groupIds": [], + "height": 10, + "index": "a1", + "isDeleted": false, + "link": null, + "locked": false, + "opacity": 100, + "roughness": 1, + "roundness": { + "type": 3, + }, + "strokeColor": "#1e1e1e", + "strokeStyle": "solid", + "strokeWidth": 2, + "type": "rectangle", + "width": 10, + "x": 0, + "y": 0, + }, + "inserted": { + "isDeleted": true, + }, + }, + }, + "updated": {}, + }, + "id": "id74", + }, +] +`; exports[`history > singleplayer undo/redo > should iterate through the history when selection changes do not produce visible change > [end of test] appState 1`] = ` { @@ -13216,7 +13010,7 @@ exports[`history > singleplayer undo/redo > should iterate through the history w "penMode": false, "pendingImageElementId": null, "previousSelectedElementIds": { - "id15": true, + "id53": true, }, "resizingElement": null, "scrollX": 0, @@ -13259,7 +13053,7 @@ exports[`history > singleplayer undo/redo > should iterate through the history w "frameId": null, "groupIds": [], "height": 10, - "id": "id15", + "id": "id53", "index": "a0", "isDeleted": true, "link": null, @@ -13281,113 +13075,109 @@ exports[`history > singleplayer undo/redo > should iterate through the history w } `; -exports[`history > singleplayer undo/redo > should iterate through the history when selection changes do not produce visible change > [end of test] history 1`] = ` -History { - "onHistoryChangedEmitter": Emitter { - "subscribers": [ - [Function], - [Function], - ], - }, - "redoStack": [ - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": { - "selectedElementIds": { - "id15": true, - }, - }, - "inserted": { - "selectedElementIds": {}, - }, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map {}, - "updated": Map {}, - }, - }, - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": { - "selectedElementIds": { - "id15": true, - }, - }, - "inserted": { - "selectedElementIds": {}, - }, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map {}, - "updated": Map {}, - }, - }, - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": { - "selectedElementIds": {}, - }, - "inserted": { - "selectedElementIds": { - "id15": true, - }, - }, - }, - }, - "elementsChange": ElementsChange { - "added": Map { - "id15" => Delta { - "deleted": { - "isDeleted": true, - }, - "inserted": { - "angle": 0, - "backgroundColor": "transparent", - "boundElements": null, - "customData": undefined, - "fillStyle": "solid", - "frameId": null, - "groupIds": [], - "height": 10, - "index": "a0", - "isDeleted": false, - "link": null, - "locked": false, - "opacity": 100, - "roughness": 1, - "roundness": { - "type": 3, - }, - "strokeColor": "#1e1e1e", - "strokeStyle": "solid", - "strokeWidth": 2, - "type": "rectangle", - "width": 10, - "x": 10, - "y": 0, - }, - }, - }, - "removed": Map {}, - "updated": Map {}, - }, - }, - ], - "undoStack": [], -} -`; - exports[`history > singleplayer undo/redo > should iterate through the history when selection changes do not produce visible change > [end of test] number of elements 1`] = `1`; exports[`history > singleplayer undo/redo > should iterate through the history when selection changes do not produce visible change > [end of test] number of renders 1`] = `13`; +exports[`history > singleplayer undo/redo > should iterate through the history when selection changes do not produce visible change > [end of test] redo stack 1`] = ` +[ + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": { + "selectedElementIds": { + "id53": true, + }, + }, + "inserted": { + "selectedElementIds": {}, + }, + }, + }, + "elements": { + "added": {}, + "removed": {}, + "updated": {}, + }, + "id": "id66", + }, + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": { + "selectedElementIds": { + "id53": true, + }, + }, + "inserted": { + "selectedElementIds": {}, + }, + }, + }, + "elements": { + "added": {}, + "removed": {}, + "updated": {}, + }, + "id": "id67", + }, + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": { + "selectedElementIds": {}, + }, + "inserted": { + "selectedElementIds": { + "id53": true, + }, + }, + }, + }, + "elements": { + "added": { + "id53": { + "deleted": { + "isDeleted": true, + }, + "inserted": { + "angle": 0, + "backgroundColor": "transparent", + "boundElements": null, + "customData": undefined, + "fillStyle": "solid", + "frameId": null, + "groupIds": [], + "height": 10, + "index": "a0", + "isDeleted": false, + "link": null, + "locked": false, + "opacity": 100, + "roughness": 1, + "roundness": { + "type": 3, + }, + "strokeColor": "#1e1e1e", + "strokeStyle": "solid", + "strokeWidth": 2, + "type": "rectangle", + "width": 10, + "x": 10, + "y": 0, + }, + }, + }, + "removed": {}, + "updated": {}, + }, + "id": "id68", + }, +] +`; + +exports[`history > singleplayer undo/redo > should iterate through the history when selection changes do not produce visible change > [end of test] undo stack 1`] = `[]`; + exports[`history > singleplayer undo/redo > should not clear the redo stack on standalone appstate change > [end of test] appState 1`] = ` { "activeEmbeddable": null, @@ -13472,7 +13262,7 @@ exports[`history > singleplayer undo/redo > should not clear the redo stack on s "scrollY": 0, "searchMatches": [], "selectedElementIds": { - "id8": true, + "id18": true, }, "selectedElementsAreBeingDragged": false, "selectedGroupIds": {}, @@ -13510,7 +13300,7 @@ exports[`history > singleplayer undo/redo > should not clear the redo stack on s "frameId": null, "groupIds": [], "height": 10, - "id": "id7", + "id": "id15", "index": "a0", "isDeleted": false, "link": null, @@ -13542,7 +13332,7 @@ exports[`history > singleplayer undo/redo > should not clear the redo stack on s "frameId": null, "groupIds": [], "height": 10, - "id": "id8", + "id": "id18", "index": "a1", "isDeleted": false, "link": null, @@ -13564,166 +13354,163 @@ exports[`history > singleplayer undo/redo > should not clear the redo stack on s } `; -exports[`history > singleplayer undo/redo > should not clear the redo stack on standalone appstate change > [end of test] history 1`] = ` -History { - "onHistoryChangedEmitter": Emitter { - "subscribers": [ - [Function], - [Function], - ], - }, - "redoStack": [], - "undoStack": [ - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": { - "selectedElementIds": { - "id7": true, - }, - }, - "inserted": { - "selectedElementIds": {}, - }, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map { - "id7" => Delta { - "deleted": { - "angle": 0, - "backgroundColor": "transparent", - "boundElements": null, - "customData": undefined, - "fillStyle": "solid", - "frameId": null, - "groupIds": [], - "height": 10, - "index": "a0", - "isDeleted": false, - "link": null, - "locked": false, - "opacity": 100, - "roughness": 1, - "roundness": { - "type": 3, - }, - "strokeColor": "#1e1e1e", - "strokeStyle": "solid", - "strokeWidth": 2, - "type": "rectangle", - "width": 10, - "x": 10, - "y": 0, - }, - "inserted": { - "isDeleted": true, - }, - }, - }, - "updated": Map {}, - }, - }, - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": { - "selectedElementIds": {}, - }, - "inserted": { - "selectedElementIds": { - "id7": true, - }, - }, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map {}, - "updated": Map {}, - }, - }, - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": { - "selectedElementIds": { - "id7": true, - }, - }, - "inserted": { - "selectedElementIds": {}, - }, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map {}, - "updated": Map {}, - }, - }, - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": { - "selectedElementIds": { - "id8": true, - }, - }, - "inserted": { - "selectedElementIds": { - "id7": true, - }, - }, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map { - "id8" => Delta { - "deleted": { - "angle": 0, - "backgroundColor": "transparent", - "boundElements": null, - "customData": undefined, - "fillStyle": "solid", - "frameId": null, - "groupIds": [], - "height": 10, - "index": "a1", - "isDeleted": false, - "link": null, - "locked": false, - "opacity": 100, - "roughness": 1, - "roundness": { - "type": 3, - }, - "strokeColor": "#1e1e1e", - "strokeStyle": "solid", - "strokeWidth": 2, - "type": "rectangle", - "width": 10, - "x": 20, - "y": 0, - }, - "inserted": { - "isDeleted": true, - }, - }, - }, - "updated": Map {}, - }, - }, - ], -} -`; - exports[`history > singleplayer undo/redo > should not clear the redo stack on standalone appstate change > [end of test] number of elements 1`] = `2`; exports[`history > singleplayer undo/redo > should not clear the redo stack on standalone appstate change > [end of test] number of renders 1`] = `12`; +exports[`history > singleplayer undo/redo > should not clear the redo stack on standalone appstate change > [end of test] redo stack 1`] = `[]`; + +exports[`history > singleplayer undo/redo > should not clear the redo stack on standalone appstate change > [end of test] undo stack 1`] = ` +[ + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": { + "selectedElementIds": { + "id15": true, + }, + }, + "inserted": { + "selectedElementIds": {}, + }, + }, + }, + "elements": { + "added": {}, + "removed": { + "id15": { + "deleted": { + "angle": 0, + "backgroundColor": "transparent", + "boundElements": null, + "customData": undefined, + "fillStyle": "solid", + "frameId": null, + "groupIds": [], + "height": 10, + "index": "a0", + "isDeleted": false, + "link": null, + "locked": false, + "opacity": 100, + "roughness": 1, + "roundness": { + "type": 3, + }, + "strokeColor": "#1e1e1e", + "strokeStyle": "solid", + "strokeWidth": 2, + "type": "rectangle", + "width": 10, + "x": 10, + "y": 0, + }, + "inserted": { + "isDeleted": true, + }, + }, + }, + "updated": {}, + }, + "id": "id17", + }, + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": { + "selectedElementIds": {}, + }, + "inserted": { + "selectedElementIds": { + "id15": true, + }, + }, + }, + }, + "elements": { + "added": {}, + "removed": {}, + "updated": {}, + }, + "id": "id24", + }, + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": { + "selectedElementIds": { + "id15": true, + }, + }, + "inserted": { + "selectedElementIds": {}, + }, + }, + }, + "elements": { + "added": {}, + "removed": {}, + "updated": {}, + }, + "id": "id27", + }, + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": { + "selectedElementIds": { + "id18": true, + }, + }, + "inserted": { + "selectedElementIds": { + "id15": true, + }, + }, + }, + }, + "elements": { + "added": {}, + "removed": { + "id18": { + "deleted": { + "angle": 0, + "backgroundColor": "transparent", + "boundElements": null, + "customData": undefined, + "fillStyle": "solid", + "frameId": null, + "groupIds": [], + "height": 10, + "index": "a1", + "isDeleted": false, + "link": null, + "locked": false, + "opacity": 100, + "roughness": 1, + "roundness": { + "type": 3, + }, + "strokeColor": "#1e1e1e", + "strokeStyle": "solid", + "strokeWidth": 2, + "type": "rectangle", + "width": 10, + "x": 20, + "y": 0, + }, + "inserted": { + "isDeleted": true, + }, + }, + }, + "updated": {}, + }, + "id": "id28", + }, +] +`; + exports[`history > singleplayer undo/redo > should not collapse when applying corrupted history entry > [end of test] appState 1`] = ` { "activeEmbeddable": null, @@ -13869,37 +13656,31 @@ exports[`history > singleplayer undo/redo > should not collapse when applying co } `; -exports[`history > singleplayer undo/redo > should not collapse when applying corrupted history entry > [end of test] history 1`] = ` -History { - "onHistoryChangedEmitter": Emitter { - "subscribers": [ - [Function], - [Function], - ], - }, - "redoStack": [], - "undoStack": [ - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": {}, - "inserted": {}, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map {}, - "updated": Map {}, - }, - }, - ], -} -`; - exports[`history > singleplayer undo/redo > should not collapse when applying corrupted history entry > [end of test] number of elements 1`] = `1`; exports[`history > singleplayer undo/redo > should not collapse when applying corrupted history entry > [end of test] number of renders 1`] = `4`; +exports[`history > singleplayer undo/redo > should not collapse when applying corrupted history entry > [end of test] redo stack 1`] = `[]`; + +exports[`history > singleplayer undo/redo > should not collapse when applying corrupted history entry > [end of test] undo stack 1`] = ` +[ + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": {}, + "inserted": {}, + }, + }, + "elements": { + "added": {}, + "removed": {}, + "updated": {}, + }, + "id": "id3", + }, +] +`; + exports[`history > singleplayer undo/redo > should not end up with history entry when there are no appstate changes > [end of test] appState 1`] = ` { "activeEmbeddable": null, @@ -13984,8 +13765,8 @@ exports[`history > singleplayer undo/redo > should not end up with history entry "scrollY": 0, "searchMatches": [], "selectedElementIds": { - "id1": true, - "id2": true, + "id4": true, + "id5": true, }, "selectedElementsAreBeingDragged": false, "selectedGroupIds": { @@ -14027,7 +13808,7 @@ exports[`history > singleplayer undo/redo > should not end up with history entry "A", ], "height": 100, - "id": "id1", + "id": "id4", "index": "a0", "isDeleted": false, "link": null, @@ -14061,7 +13842,7 @@ exports[`history > singleplayer undo/redo > should not end up with history entry "A", ], "height": 100, - "id": "id2", + "id": "id5", "index": "a1", "isDeleted": false, "link": null, @@ -14083,115 +13864,109 @@ exports[`history > singleplayer undo/redo > should not end up with history entry } `; -exports[`history > singleplayer undo/redo > should not end up with history entry when there are no appstate changes > [end of test] history 1`] = ` -History { - "onHistoryChangedEmitter": Emitter { - "subscribers": [ - [Function], - [Function], - ], - }, - "redoStack": [], - "undoStack": [ - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": { - "selectedElementIds": { - "id1": true, - "id2": true, - }, - "selectedGroupIds": { - "A": true, - }, - }, - "inserted": { - "selectedElementIds": {}, - "selectedGroupIds": {}, - }, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map { - "id1" => Delta { - "deleted": { - "angle": 0, - "backgroundColor": "transparent", - "boundElements": null, - "customData": undefined, - "fillStyle": "solid", - "frameId": null, - "groupIds": [ - "A", - ], - "height": 100, - "index": "a0", - "isDeleted": false, - "link": null, - "locked": false, - "opacity": 100, - "roughness": 1, - "roundness": { - "type": 3, - }, - "strokeColor": "#1e1e1e", - "strokeStyle": "solid", - "strokeWidth": 2, - "type": "rectangle", - "width": 100, - "x": 0, - "y": 0, - }, - "inserted": { - "isDeleted": true, - }, - }, - "id2" => Delta { - "deleted": { - "angle": 0, - "backgroundColor": "transparent", - "boundElements": null, - "customData": undefined, - "fillStyle": "solid", - "frameId": null, - "groupIds": [ - "A", - ], - "height": 100, - "index": "a1", - "isDeleted": false, - "link": null, - "locked": false, - "opacity": 100, - "roughness": 1, - "roundness": { - "type": 3, - }, - "strokeColor": "#1e1e1e", - "strokeStyle": "solid", - "strokeWidth": 2, - "type": "rectangle", - "width": 100, - "x": 0, - "y": 0, - }, - "inserted": { - "isDeleted": true, - }, - }, - }, - "updated": Map {}, - }, - }, - ], -} -`; - exports[`history > singleplayer undo/redo > should not end up with history entry when there are no appstate changes > [end of test] number of elements 1`] = `2`; exports[`history > singleplayer undo/redo > should not end up with history entry when there are no appstate changes > [end of test] number of renders 1`] = `7`; +exports[`history > singleplayer undo/redo > should not end up with history entry when there are no appstate changes > [end of test] redo stack 1`] = `[]`; + +exports[`history > singleplayer undo/redo > should not end up with history entry when there are no appstate changes > [end of test] undo stack 1`] = ` +[ + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": { + "selectedElementIds": { + "id4": true, + "id5": true, + }, + "selectedGroupIds": { + "A": true, + }, + }, + "inserted": { + "selectedElementIds": {}, + "selectedGroupIds": {}, + }, + }, + }, + "elements": { + "added": {}, + "removed": { + "id4": { + "deleted": { + "angle": 0, + "backgroundColor": "transparent", + "boundElements": null, + "customData": undefined, + "fillStyle": "solid", + "frameId": null, + "groupIds": [ + "A", + ], + "height": 100, + "index": "a0", + "isDeleted": false, + "link": null, + "locked": false, + "opacity": 100, + "roughness": 1, + "roundness": { + "type": 3, + }, + "strokeColor": "#1e1e1e", + "strokeStyle": "solid", + "strokeWidth": 2, + "type": "rectangle", + "width": 100, + "x": 0, + "y": 0, + }, + "inserted": { + "isDeleted": true, + }, + }, + "id5": { + "deleted": { + "angle": 0, + "backgroundColor": "transparent", + "boundElements": null, + "customData": undefined, + "fillStyle": "solid", + "frameId": null, + "groupIds": [ + "A", + ], + "height": 100, + "index": "a1", + "isDeleted": false, + "link": null, + "locked": false, + "opacity": 100, + "roughness": 1, + "roundness": { + "type": 3, + }, + "strokeColor": "#1e1e1e", + "strokeStyle": "solid", + "strokeWidth": 2, + "type": "rectangle", + "width": 100, + "x": 0, + "y": 0, + }, + "inserted": { + "isDeleted": true, + }, + }, + }, + "updated": {}, + }, + "id": "id8", + }, +] +`; + exports[`history > singleplayer undo/redo > should not end up with history entry when there are no elements changes > [end of test] appState 1`] = ` { "activeEmbeddable": null, @@ -14315,7 +14090,7 @@ exports[`history > singleplayer undo/redo > should not end up with history entry "frameId": null, "groupIds": [], "height": 100, - "id": "id5", + "id": "id10", "index": "a0", "isDeleted": false, "link": null, @@ -14347,7 +14122,7 @@ exports[`history > singleplayer undo/redo > should not end up with history entry "frameId": null, "groupIds": [], "height": 100, - "id": "id6", + "id": "id11", "index": "a1", "isDeleted": false, "link": null, @@ -14369,100 +14144,94 @@ exports[`history > singleplayer undo/redo > should not end up with history entry } `; -exports[`history > singleplayer undo/redo > should not end up with history entry when there are no elements changes > [end of test] history 1`] = ` -History { - "onHistoryChangedEmitter": Emitter { - "subscribers": [ - [Function], - [Function], - ], - }, - "redoStack": [], - "undoStack": [ - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": {}, - "inserted": {}, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map { - "id5" => Delta { - "deleted": { - "angle": 0, - "backgroundColor": "transparent", - "boundElements": null, - "customData": undefined, - "fillStyle": "solid", - "frameId": null, - "groupIds": [], - "height": 100, - "index": "a0", - "isDeleted": false, - "link": null, - "locked": false, - "opacity": 100, - "roughness": 1, - "roundness": { - "type": 3, - }, - "strokeColor": "#1e1e1e", - "strokeStyle": "solid", - "strokeWidth": 2, - "type": "rectangle", - "width": 100, - "x": 0, - "y": 0, - }, - "inserted": { - "isDeleted": true, - }, - }, - "id6" => Delta { - "deleted": { - "angle": 0, - "backgroundColor": "transparent", - "boundElements": null, - "customData": undefined, - "fillStyle": "solid", - "frameId": null, - "groupIds": [], - "height": 100, - "index": "a1", - "isDeleted": false, - "link": null, - "locked": false, - "opacity": 100, - "roughness": 1, - "roundness": { - "type": 3, - }, - "strokeColor": "#1e1e1e", - "strokeStyle": "solid", - "strokeWidth": 2, - "type": "rectangle", - "width": 100, - "x": 0, - "y": 0, - }, - "inserted": { - "isDeleted": true, - }, - }, - }, - "updated": Map {}, - }, - }, - ], -} -`; - exports[`history > singleplayer undo/redo > should not end up with history entry when there are no elements changes > [end of test] number of elements 1`] = `2`; exports[`history > singleplayer undo/redo > should not end up with history entry when there are no elements changes > [end of test] number of renders 1`] = `5`; +exports[`history > singleplayer undo/redo > should not end up with history entry when there are no elements changes > [end of test] redo stack 1`] = `[]`; + +exports[`history > singleplayer undo/redo > should not end up with history entry when there are no elements changes > [end of test] undo stack 1`] = ` +[ + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": {}, + "inserted": {}, + }, + }, + "elements": { + "added": {}, + "removed": { + "id10": { + "deleted": { + "angle": 0, + "backgroundColor": "transparent", + "boundElements": null, + "customData": undefined, + "fillStyle": "solid", + "frameId": null, + "groupIds": [], + "height": 100, + "index": "a0", + "isDeleted": false, + "link": null, + "locked": false, + "opacity": 100, + "roughness": 1, + "roundness": { + "type": 3, + }, + "strokeColor": "#1e1e1e", + "strokeStyle": "solid", + "strokeWidth": 2, + "type": "rectangle", + "width": 100, + "x": 0, + "y": 0, + }, + "inserted": { + "isDeleted": true, + }, + }, + "id11": { + "deleted": { + "angle": 0, + "backgroundColor": "transparent", + "boundElements": null, + "customData": undefined, + "fillStyle": "solid", + "frameId": null, + "groupIds": [], + "height": 100, + "index": "a1", + "isDeleted": false, + "link": null, + "locked": false, + "opacity": 100, + "roughness": 1, + "roundness": { + "type": 3, + }, + "strokeColor": "#1e1e1e", + "strokeStyle": "solid", + "strokeWidth": 2, + "type": "rectangle", + "width": 100, + "x": 0, + "y": 0, + }, + "inserted": { + "isDeleted": true, + }, + }, + }, + "updated": {}, + }, + "id": "id13", + }, +] +`; + exports[`history > singleplayer undo/redo > should not override appstate changes when redo stack is not cleared > [end of test] appState 1`] = ` { "activeEmbeddable": null, @@ -14542,14 +14311,14 @@ exports[`history > singleplayer undo/redo > should not override appstate changes "penMode": false, "pendingImageElementId": null, "previousSelectedElementIds": { - "id11": true, + "id29": true, }, "resizingElement": null, "scrollX": 0, "scrollY": 0, "searchMatches": [], "selectedElementIds": { - "id11": true, + "id29": true, }, "selectedElementsAreBeingDragged": false, "selectedGroupIds": {}, @@ -14587,7 +14356,7 @@ exports[`history > singleplayer undo/redo > should not override appstate changes "frameId": null, "groupIds": [], "height": 10, - "id": "id11", + "id": "id29", "index": "a0", "isDeleted": false, "link": null, @@ -14609,139 +14378,138 @@ exports[`history > singleplayer undo/redo > should not override appstate changes } `; -exports[`history > singleplayer undo/redo > should not override appstate changes when redo stack is not cleared > [end of test] history 1`] = ` -History { - "onHistoryChangedEmitter": Emitter { - "subscribers": [ - [Function], - [Function], - ], - }, - "redoStack": [ - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": {}, - "inserted": {}, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map {}, - "updated": Map { - "id11" => Delta { - "deleted": { - "backgroundColor": "#ffc9c9", - }, - "inserted": { - "backgroundColor": "#a5d8ff", - }, - }, - }, - }, - }, - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": {}, - "inserted": {}, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map {}, - "updated": Map { - "id11" => Delta { - "deleted": { - "backgroundColor": "transparent", - }, - "inserted": { - "backgroundColor": "#ffc9c9", - }, - }, - }, - }, - }, - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": { - "selectedElementIds": { - "id11": true, - }, - }, - "inserted": { - "selectedElementIds": {}, - }, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map {}, - "updated": Map {}, - }, - }, - ], - "undoStack": [ - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": { - "selectedElementIds": { - "id11": true, - }, - }, - "inserted": { - "selectedElementIds": {}, - }, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map { - "id11" => Delta { - "deleted": { - "angle": 0, - "backgroundColor": "transparent", - "boundElements": null, - "customData": undefined, - "fillStyle": "solid", - "frameId": null, - "groupIds": [], - "height": 10, - "index": "a0", - "isDeleted": false, - "link": null, - "locked": false, - "opacity": 100, - "roughness": 1, - "roundness": { - "type": 3, - }, - "strokeColor": "#1e1e1e", - "strokeStyle": "solid", - "strokeWidth": 2, - "type": "rectangle", - "width": 10, - "x": 10, - "y": 0, - }, - "inserted": { - "isDeleted": true, - }, - }, - }, - "updated": Map {}, - }, - }, - ], -} -`; - exports[`history > singleplayer undo/redo > should not override appstate changes when redo stack is not cleared > [end of test] number of elements 1`] = `1`; exports[`history > singleplayer undo/redo > should not override appstate changes when redo stack is not cleared > [end of test] number of renders 1`] = `15`; +exports[`history > singleplayer undo/redo > should not override appstate changes when redo stack is not cleared > [end of test] redo stack 1`] = ` +[ + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": {}, + "inserted": {}, + }, + }, + "elements": { + "added": {}, + "removed": {}, + "updated": { + "id29": { + "deleted": { + "backgroundColor": "#ffc9c9", + }, + "inserted": { + "backgroundColor": "#a5d8ff", + }, + }, + }, + }, + "id": "id43", + }, + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": {}, + "inserted": {}, + }, + }, + "elements": { + "added": {}, + "removed": {}, + "updated": { + "id29": { + "deleted": { + "backgroundColor": "transparent", + }, + "inserted": { + "backgroundColor": "#ffc9c9", + }, + }, + }, + }, + "id": "id44", + }, + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": { + "selectedElementIds": { + "id29": true, + }, + }, + "inserted": { + "selectedElementIds": {}, + }, + }, + }, + "elements": { + "added": {}, + "removed": {}, + "updated": {}, + }, + "id": "id45", + }, +] +`; + +exports[`history > singleplayer undo/redo > should not override appstate changes when redo stack is not cleared > [end of test] undo stack 1`] = ` +[ + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": { + "selectedElementIds": { + "id29": true, + }, + }, + "inserted": { + "selectedElementIds": {}, + }, + }, + }, + "elements": { + "added": {}, + "removed": { + "id29": { + "deleted": { + "angle": 0, + "backgroundColor": "transparent", + "boundElements": null, + "customData": undefined, + "fillStyle": "solid", + "frameId": null, + "groupIds": [], + "height": 10, + "index": "a0", + "isDeleted": false, + "link": null, + "locked": false, + "opacity": 100, + "roughness": 1, + "roundness": { + "type": 3, + }, + "strokeColor": "#1e1e1e", + "strokeStyle": "solid", + "strokeWidth": 2, + "type": "rectangle", + "width": 10, + "x": 10, + "y": 0, + }, + "inserted": { + "isDeleted": true, + }, + }, + }, + "updated": {}, + }, + "id": "id31", + }, +] +`; + exports[`history > singleplayer undo/redo > should support appstate name or viewBackgroundColor change > [end of test] appState 1`] = ` { "activeEmbeddable": null, @@ -14855,58 +14623,53 @@ exports[`history > singleplayer undo/redo > should support appstate name or view } `; -exports[`history > singleplayer undo/redo > should support appstate name or viewBackgroundColor change > [end of test] history 1`] = ` -History { - "onHistoryChangedEmitter": Emitter { - "subscribers": [ - [Function], - [Function], - ], - }, - "redoStack": [], - "undoStack": [ - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": { - "name": "New name", - }, - "inserted": { - "name": "Old name", - }, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map {}, - "updated": Map {}, - }, - }, - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": { - "viewBackgroundColor": "#000", - }, - "inserted": { - "viewBackgroundColor": "#FFF", - }, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map {}, - "updated": Map {}, - }, - }, - ], -} -`; - exports[`history > singleplayer undo/redo > should support appstate name or viewBackgroundColor change > [end of test] number of elements 1`] = `0`; exports[`history > singleplayer undo/redo > should support appstate name or viewBackgroundColor change > [end of test] number of renders 1`] = `8`; +exports[`history > singleplayer undo/redo > should support appstate name or viewBackgroundColor change > [end of test] redo stack 1`] = `[]`; + +exports[`history > singleplayer undo/redo > should support appstate name or viewBackgroundColor change > [end of test] undo stack 1`] = ` +[ + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": { + "name": "New name", + }, + "inserted": { + "name": "Old name", + }, + }, + }, + "elements": { + "added": {}, + "removed": {}, + "updated": {}, + }, + "id": "id88", + }, + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": { + "viewBackgroundColor": "#000", + }, + "inserted": { + "viewBackgroundColor": "#FFF", + }, + }, + }, + "elements": { + "added": {}, + "removed": {}, + "updated": {}, + }, + "id": "id89", + }, +] +`; + exports[`history > singleplayer undo/redo > should support bidirectional bindings > should unbind arrow from non deleted bindable elements on deletion and rebind on undo > [end of test] appState 1`] = ` { "activeEmbeddable": null, @@ -14986,14 +14749,14 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding "penMode": false, "pendingImageElementId": null, "previousSelectedElementIds": { - "id56": true, + "id230": true, }, "resizingElement": null, "scrollX": 0, "scrollY": 0, "searchMatches": [], "selectedElementIds": { - "id61": true, + "id243": true, }, "selectedElementsAreBeingDragged": false, "selectedGroupIds": {}, @@ -15027,11 +14790,11 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding "backgroundColor": "transparent", "boundElements": [ { - "id": "id57", + "id": "id231", "type": "text", }, { - "id": "id61", + "id": "id243", "type": "arrow", }, ], @@ -15040,7 +14803,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding "frameId": null, "groupIds": [], "height": 100, - "id": "id56", + "id": "id230", "index": "a0", "isDeleted": false, "link": null, @@ -15068,7 +14831,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding "autoResize": true, "backgroundColor": "transparent", "boundElements": null, - "containerId": "id56", + "containerId": "id230", "customData": undefined, "fillStyle": "solid", "fontFamily": 5, @@ -15076,7 +14839,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding "frameId": null, "groupIds": [], "height": 25, - "id": "id57", + "id": "id231", "index": "a1", "isDeleted": false, "lineHeight": "1.25000", @@ -15109,7 +14872,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding "backgroundColor": "transparent", "boundElements": [ { - "id": "id61", + "id": "id243", "type": "arrow", }, ], @@ -15118,7 +14881,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding "frameId": null, "groupIds": [], "height": 100, - "id": "id58", + "id": "id232", "index": "a2", "isDeleted": false, "link": null, @@ -15149,7 +14912,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding "elbowed": false, "endArrowhead": "arrow", "endBinding": { - "elementId": "id58", + "elementId": "id232", "focus": -0, "gap": 1, }, @@ -15157,7 +14920,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding "frameId": null, "groupIds": [], "height": 0, - "id": "id61", + "id": "id243", "index": "a3", "isDeleted": false, "lastCommittedPoint": null, @@ -15180,7 +14943,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding }, "startArrowhead": null, "startBinding": { - "elementId": "id56", + "elementId": "id230", "focus": 0, "gap": 1, }, @@ -15196,415 +14959,416 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding } `; -exports[`history > singleplayer undo/redo > should support bidirectional bindings > should unbind arrow from non deleted bindable elements on deletion and rebind on undo > [end of test] history 1`] = ` -History { - "onHistoryChangedEmitter": Emitter { - "subscribers": [ - [Function], - [Function], - ], - }, - "redoStack": [ - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": { - "selectedElementIds": { - "id61": true, - }, - "selectedLinearElementId": "id61", - }, - "inserted": { - "selectedElementIds": {}, - "selectedLinearElementId": null, - }, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map { - "id61" => Delta { - "deleted": { - "isDeleted": false, - "points": [ - [ - 0, - 0, - ], - [ - 100, - 0, - ], - ], - }, - "inserted": { - "isDeleted": true, - "points": [ - [ - 0, - 0, - ], - [ - 100, - 0, - ], - ], - }, - }, - }, - "updated": Map { - "id56" => Delta { - "deleted": { - "boundElements": [ - { - "id": "id61", - "type": "arrow", - }, - ], - }, - "inserted": { - "boundElements": [], - }, - }, - "id58" => Delta { - "deleted": { - "boundElements": [ - { - "id": "id61", - "type": "arrow", - }, - ], - }, - "inserted": { - "boundElements": [], - }, - }, - }, - }, - }, - ], - "undoStack": [ - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": {}, - "inserted": {}, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map { - "id56" => Delta { - "deleted": { - "angle": 0, - "backgroundColor": "transparent", - "boundElements": null, - "customData": undefined, - "fillStyle": "solid", - "frameId": null, - "groupIds": [], - "height": 100, - "index": "a0", - "isDeleted": false, - "link": null, - "locked": false, - "opacity": 100, - "roughness": 1, - "roundness": { - "type": 3, - }, - "strokeColor": "#1e1e1e", - "strokeStyle": "solid", - "strokeWidth": 2, - "type": "rectangle", - "width": 100, - "x": -100, - "y": -50, - }, - "inserted": { - "isDeleted": true, - }, - }, - "id57" => Delta { - "deleted": { - "angle": 0, - "autoResize": true, - "backgroundColor": "transparent", - "boundElements": null, - "containerId": null, - "customData": undefined, - "fillStyle": "solid", - "fontFamily": 5, - "fontSize": 20, - "frameId": null, - "groupIds": [], - "height": 100, - "index": "a1", - "isDeleted": false, - "lineHeight": "1.25000", - "link": null, - "locked": false, - "opacity": 100, - "originalText": "ola", - "roughness": 1, - "roundness": { - "type": 3, - }, - "strokeColor": "#1e1e1e", - "strokeStyle": "solid", - "strokeWidth": 2, - "text": "ola", - "textAlign": "left", - "type": "text", - "verticalAlign": "top", - "width": 100, - "x": -200, - "y": -200, - }, - "inserted": { - "isDeleted": true, - }, - }, - "id58" => Delta { - "deleted": { - "angle": 0, - "backgroundColor": "transparent", - "boundElements": null, - "customData": undefined, - "fillStyle": "solid", - "frameId": null, - "groupIds": [], - "height": 100, - "index": "a2", - "isDeleted": false, - "link": null, - "locked": false, - "opacity": 100, - "roughness": 1, - "roundness": { - "type": 3, - }, - "strokeColor": "#1e1e1e", - "strokeStyle": "solid", - "strokeWidth": 2, - "type": "rectangle", - "width": 100, - "x": 100, - "y": -50, - }, - "inserted": { - "isDeleted": true, - }, - }, - }, - "updated": Map {}, - }, - }, - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": { - "selectedElementIds": { - "id56": true, - }, - }, - "inserted": { - "selectedElementIds": {}, - }, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map {}, - "updated": Map {}, - }, - }, - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": { - "selectedElementIds": { - "id57": true, - }, - }, - "inserted": { - "selectedElementIds": {}, - }, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map {}, - "updated": Map {}, - }, - }, - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": { - "selectedElementIds": {}, - }, - "inserted": { - "selectedElementIds": { - "id57": true, - }, - }, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map {}, - "updated": Map { - "id56" => Delta { - "deleted": { - "boundElements": [ - { - "id": "id57", - "type": "text", - }, - ], - }, - "inserted": { - "boundElements": [], - }, - }, - "id57" => Delta { - "deleted": { - "containerId": "id56", - "height": 25, - "textAlign": "center", - "verticalAlign": "middle", - "width": 30, - "x": -65, - "y": "-12.50000", - }, - "inserted": { - "containerId": null, - "height": 100, - "textAlign": "left", - "verticalAlign": "top", - "width": 100, - "x": -200, - "y": -200, - }, - }, - }, - }, - }, - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": { - "selectedElementIds": { - "id61": true, - }, - "selectedLinearElementId": "id61", - }, - "inserted": { - "selectedElementIds": { - "id56": true, - }, - "selectedLinearElementId": null, - }, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map { - "id61" => Delta { - "deleted": { - "angle": 0, - "backgroundColor": "transparent", - "boundElements": null, - "customData": undefined, - "elbowed": false, - "endArrowhead": "arrow", - "endBinding": { - "elementId": "id58", - "focus": -0, - "gap": 1, - }, - "fillStyle": "solid", - "frameId": null, - "groupIds": [], - "height": 0, - "index": "a3", - "isDeleted": false, - "lastCommittedPoint": null, - "link": null, - "locked": false, - "opacity": 100, - "points": [ - [ - 0, - 0, - ], - [ - 100, - 0, - ], - ], - "roughness": 1, - "roundness": { - "type": 2, - }, - "startArrowhead": null, - "startBinding": { - "elementId": "id56", - "focus": 0, - "gap": 1, - }, - "strokeColor": "#1e1e1e", - "strokeStyle": "solid", - "strokeWidth": 2, - "type": "arrow", - "width": 100, - "x": 0, - "y": 0, - }, - "inserted": { - "isDeleted": true, - }, - }, - }, - "updated": Map { - "id56" => Delta { - "deleted": { - "boundElements": [ - { - "id": "id61", - "type": "arrow", - }, - ], - }, - "inserted": { - "boundElements": [], - }, - }, - "id58" => Delta { - "deleted": { - "boundElements": [ - { - "id": "id61", - "type": "arrow", - }, - ], - }, - "inserted": { - "boundElements": [], - }, - }, - }, - }, - }, - ], -} -`; - exports[`history > singleplayer undo/redo > should support bidirectional bindings > should unbind arrow from non deleted bindable elements on deletion and rebind on undo > [end of test] number of elements 1`] = `4`; exports[`history > singleplayer undo/redo > should support bidirectional bindings > should unbind arrow from non deleted bindable elements on deletion and rebind on undo > [end of test] number of renders 1`] = `12`; +exports[`history > singleplayer undo/redo > should support bidirectional bindings > should unbind arrow from non deleted bindable elements on deletion and rebind on undo > [end of test] redo stack 1`] = ` +[ + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": { + "selectedElementIds": { + "id243": true, + }, + "selectedLinearElementId": "id243", + }, + "inserted": { + "selectedElementIds": {}, + "selectedLinearElementId": null, + }, + }, + }, + "elements": { + "added": {}, + "removed": { + "id243": { + "deleted": { + "isDeleted": false, + "points": [ + [ + 0, + 0, + ], + [ + 100, + 0, + ], + ], + }, + "inserted": { + "isDeleted": true, + "points": [ + [ + 0, + 0, + ], + [ + 100, + 0, + ], + ], + }, + }, + }, + "updated": { + "id230": { + "deleted": { + "boundElements": [ + { + "id": "id243", + "type": "arrow", + }, + ], + }, + "inserted": { + "boundElements": [], + }, + }, + "id232": { + "deleted": { + "boundElements": [ + { + "id": "id243", + "type": "arrow", + }, + ], + }, + "inserted": { + "boundElements": [], + }, + }, + }, + }, + "id": "id248", + }, +] +`; + +exports[`history > singleplayer undo/redo > should support bidirectional bindings > should unbind arrow from non deleted bindable elements on deletion and rebind on undo > [end of test] undo stack 1`] = ` +[ + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": {}, + "inserted": {}, + }, + }, + "elements": { + "added": {}, + "removed": { + "id230": { + "deleted": { + "angle": 0, + "backgroundColor": "transparent", + "boundElements": null, + "customData": undefined, + "fillStyle": "solid", + "frameId": null, + "groupIds": [], + "height": 100, + "index": "a0", + "isDeleted": false, + "link": null, + "locked": false, + "opacity": 100, + "roughness": 1, + "roundness": { + "type": 3, + }, + "strokeColor": "#1e1e1e", + "strokeStyle": "solid", + "strokeWidth": 2, + "type": "rectangle", + "width": 100, + "x": -100, + "y": -50, + }, + "inserted": { + "isDeleted": true, + }, + }, + "id231": { + "deleted": { + "angle": 0, + "autoResize": true, + "backgroundColor": "transparent", + "boundElements": null, + "containerId": null, + "customData": undefined, + "fillStyle": "solid", + "fontFamily": 5, + "fontSize": 20, + "frameId": null, + "groupIds": [], + "height": 100, + "index": "a1", + "isDeleted": false, + "lineHeight": "1.25000", + "link": null, + "locked": false, + "opacity": 100, + "originalText": "ola", + "roughness": 1, + "roundness": { + "type": 3, + }, + "strokeColor": "#1e1e1e", + "strokeStyle": "solid", + "strokeWidth": 2, + "text": "ola", + "textAlign": "left", + "type": "text", + "verticalAlign": "top", + "width": 100, + "x": -200, + "y": -200, + }, + "inserted": { + "isDeleted": true, + }, + }, + "id232": { + "deleted": { + "angle": 0, + "backgroundColor": "transparent", + "boundElements": null, + "customData": undefined, + "fillStyle": "solid", + "frameId": null, + "groupIds": [], + "height": 100, + "index": "a2", + "isDeleted": false, + "link": null, + "locked": false, + "opacity": 100, + "roughness": 1, + "roundness": { + "type": 3, + }, + "strokeColor": "#1e1e1e", + "strokeStyle": "solid", + "strokeWidth": 2, + "type": "rectangle", + "width": 100, + "x": 100, + "y": -50, + }, + "inserted": { + "isDeleted": true, + }, + }, + }, + "updated": {}, + }, + "id": "id234", + }, + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": { + "selectedElementIds": { + "id230": true, + }, + }, + "inserted": { + "selectedElementIds": {}, + }, + }, + }, + "elements": { + "added": {}, + "removed": {}, + "updated": {}, + }, + "id": "id237", + }, + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": { + "selectedElementIds": { + "id231": true, + }, + }, + "inserted": { + "selectedElementIds": {}, + }, + }, + }, + "elements": { + "added": {}, + "removed": {}, + "updated": {}, + }, + "id": "id240", + }, + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": { + "selectedElementIds": {}, + }, + "inserted": { + "selectedElementIds": { + "id231": true, + }, + }, + }, + }, + "elements": { + "added": {}, + "removed": {}, + "updated": { + "id230": { + "deleted": { + "boundElements": [ + { + "id": "id231", + "type": "text", + }, + ], + }, + "inserted": { + "boundElements": [], + }, + }, + "id231": { + "deleted": { + "containerId": "id230", + "height": 25, + "textAlign": "center", + "verticalAlign": "middle", + "width": 30, + "x": -65, + "y": "-12.50000", + }, + "inserted": { + "containerId": null, + "height": 100, + "textAlign": "left", + "verticalAlign": "top", + "width": 100, + "x": -200, + "y": -200, + }, + }, + }, + }, + "id": "id242", + }, + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": { + "selectedElementIds": { + "id243": true, + }, + "selectedLinearElementId": "id243", + }, + "inserted": { + "selectedElementIds": { + "id230": true, + }, + "selectedLinearElementId": null, + }, + }, + }, + "elements": { + "added": {}, + "removed": { + "id243": { + "deleted": { + "angle": 0, + "backgroundColor": "transparent", + "boundElements": null, + "customData": undefined, + "elbowed": false, + "endArrowhead": "arrow", + "endBinding": { + "elementId": "id232", + "focus": -0, + "gap": 1, + }, + "fillStyle": "solid", + "frameId": null, + "groupIds": [], + "height": 0, + "index": "a3", + "isDeleted": false, + "lastCommittedPoint": null, + "link": null, + "locked": false, + "opacity": 100, + "points": [ + [ + 0, + 0, + ], + [ + 100, + 0, + ], + ], + "roughness": 1, + "roundness": { + "type": 2, + }, + "startArrowhead": null, + "startBinding": { + "elementId": "id230", + "focus": 0, + "gap": 1, + }, + "strokeColor": "#1e1e1e", + "strokeStyle": "solid", + "strokeWidth": 2, + "type": "arrow", + "width": 100, + "x": 0, + "y": 0, + }, + "inserted": { + "isDeleted": true, + }, + }, + }, + "updated": { + "id230": { + "deleted": { + "boundElements": [ + { + "id": "id243", + "type": "arrow", + }, + ], + }, + "inserted": { + "boundElements": [], + }, + }, + "id232": { + "deleted": { + "boundElements": [ + { + "id": "id243", + "type": "arrow", + }, + ], + }, + "inserted": { + "boundElements": [], + }, + }, + }, + }, + "id": "id245", + }, +] +`; + exports[`history > singleplayer undo/redo > should support bidirectional bindings > should unbind arrow from non deleted bindable elements on undo and rebind on redo > [end of test] appState 1`] = ` { "activeEmbeddable": null, @@ -15684,14 +15448,14 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding "penMode": false, "pendingImageElementId": null, "previousSelectedElementIds": { - "id50": true, + "id212": true, }, "resizingElement": null, "scrollX": 0, "scrollY": 0, "searchMatches": [], "selectedElementIds": { - "id55": true, + "id225": true, }, "selectedElementsAreBeingDragged": false, "selectedGroupIds": {}, @@ -15725,11 +15489,11 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding "backgroundColor": "transparent", "boundElements": [ { - "id": "id51", + "id": "id213", "type": "text", }, { - "id": "id55", + "id": "id225", "type": "arrow", }, ], @@ -15738,7 +15502,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding "frameId": null, "groupIds": [], "height": 100, - "id": "id50", + "id": "id212", "index": "a0", "isDeleted": false, "link": null, @@ -15766,7 +15530,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding "autoResize": true, "backgroundColor": "transparent", "boundElements": null, - "containerId": "id50", + "containerId": "id212", "customData": undefined, "fillStyle": "solid", "fontFamily": 5, @@ -15774,7 +15538,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding "frameId": null, "groupIds": [], "height": 25, - "id": "id51", + "id": "id213", "index": "a1", "isDeleted": false, "lineHeight": "1.25000", @@ -15807,7 +15571,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding "backgroundColor": "transparent", "boundElements": [ { - "id": "id55", + "id": "id225", "type": "arrow", }, ], @@ -15816,7 +15580,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding "frameId": null, "groupIds": [], "height": 100, - "id": "id52", + "id": "id214", "index": "a2", "isDeleted": false, "link": null, @@ -15847,7 +15611,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding "elbowed": false, "endArrowhead": "arrow", "endBinding": { - "elementId": "id52", + "elementId": "id214", "focus": -0, "gap": 1, }, @@ -15855,7 +15619,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding "frameId": null, "groupIds": [], "height": 0, - "id": "id55", + "id": "id225", "index": "a3", "isDeleted": false, "lastCommittedPoint": null, @@ -15878,7 +15642,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding }, "startArrowhead": null, "startBinding": { - "elementId": "id50", + "elementId": "id212", "focus": 0, "gap": 1, }, @@ -15894,337 +15658,335 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding } `; -exports[`history > singleplayer undo/redo > should support bidirectional bindings > should unbind arrow from non deleted bindable elements on undo and rebind on redo > [end of test] history 1`] = ` -History { - "onHistoryChangedEmitter": Emitter { - "subscribers": [ - [Function], - [Function], - ], - }, - "redoStack": [], - "undoStack": [ - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": {}, - "inserted": {}, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map { - "id50" => Delta { - "deleted": { - "angle": 0, - "backgroundColor": "transparent", - "boundElements": null, - "customData": undefined, - "fillStyle": "solid", - "frameId": null, - "groupIds": [], - "height": 100, - "index": "a0", - "isDeleted": false, - "link": null, - "locked": false, - "opacity": 100, - "roughness": 1, - "roundness": { - "type": 3, - }, - "strokeColor": "#1e1e1e", - "strokeStyle": "solid", - "strokeWidth": 2, - "type": "rectangle", - "width": 100, - "x": -100, - "y": -50, - }, - "inserted": { - "isDeleted": true, - }, - }, - "id51" => Delta { - "deleted": { - "angle": 0, - "autoResize": true, - "backgroundColor": "transparent", - "boundElements": null, - "containerId": null, - "customData": undefined, - "fillStyle": "solid", - "fontFamily": 5, - "fontSize": 20, - "frameId": null, - "groupIds": [], - "height": 100, - "index": "a1", - "isDeleted": false, - "lineHeight": "1.25000", - "link": null, - "locked": false, - "opacity": 100, - "originalText": "ola", - "roughness": 1, - "roundness": { - "type": 3, - }, - "strokeColor": "#1e1e1e", - "strokeStyle": "solid", - "strokeWidth": 2, - "text": "ola", - "textAlign": "left", - "type": "text", - "verticalAlign": "top", - "width": 100, - "x": -200, - "y": -200, - }, - "inserted": { - "isDeleted": true, - }, - }, - "id52" => Delta { - "deleted": { - "angle": 0, - "backgroundColor": "transparent", - "boundElements": null, - "customData": undefined, - "fillStyle": "solid", - "frameId": null, - "groupIds": [], - "height": 100, - "index": "a2", - "isDeleted": false, - "link": null, - "locked": false, - "opacity": 100, - "roughness": 1, - "roundness": { - "type": 3, - }, - "strokeColor": "#1e1e1e", - "strokeStyle": "solid", - "strokeWidth": 2, - "type": "rectangle", - "width": 100, - "x": 100, - "y": -50, - }, - "inserted": { - "isDeleted": true, - }, - }, - }, - "updated": Map {}, - }, - }, - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": { - "selectedElementIds": { - "id50": true, - }, - }, - "inserted": { - "selectedElementIds": {}, - }, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map {}, - "updated": Map {}, - }, - }, - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": { - "selectedElementIds": { - "id51": true, - }, - }, - "inserted": { - "selectedElementIds": {}, - }, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map {}, - "updated": Map {}, - }, - }, - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": { - "selectedElementIds": {}, - }, - "inserted": { - "selectedElementIds": { - "id51": true, - }, - }, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map {}, - "updated": Map { - "id50" => Delta { - "deleted": { - "boundElements": [ - { - "id": "id51", - "type": "text", - }, - ], - }, - "inserted": { - "boundElements": [], - }, - }, - "id51" => Delta { - "deleted": { - "containerId": "id50", - "height": 25, - "textAlign": "center", - "verticalAlign": "middle", - "width": 30, - "x": -65, - "y": "-12.50000", - }, - "inserted": { - "containerId": null, - "height": 100, - "textAlign": "left", - "verticalAlign": "top", - "width": 100, - "x": -200, - "y": -200, - }, - }, - }, - }, - }, - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": { - "selectedElementIds": { - "id55": true, - }, - "selectedLinearElementId": "id55", - }, - "inserted": { - "selectedElementIds": { - "id50": true, - }, - "selectedLinearElementId": null, - }, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map { - "id55" => Delta { - "deleted": { - "angle": 0, - "backgroundColor": "transparent", - "boundElements": null, - "customData": undefined, - "elbowed": false, - "endArrowhead": "arrow", - "endBinding": { - "elementId": "id52", - "focus": -0, - "gap": 1, - }, - "fillStyle": "solid", - "frameId": null, - "groupIds": [], - "height": 0, - "index": "a3", - "isDeleted": false, - "lastCommittedPoint": null, - "link": null, - "locked": false, - "opacity": 100, - "points": [ - [ - 0, - 0, - ], - [ - 100, - 0, - ], - ], - "roughness": 1, - "roundness": { - "type": 2, - }, - "startArrowhead": null, - "startBinding": { - "elementId": "id50", - "focus": 0, - "gap": 1, - }, - "strokeColor": "#1e1e1e", - "strokeStyle": "solid", - "strokeWidth": 2, - "type": "arrow", - "width": 100, - "x": 0, - "y": 0, - }, - "inserted": { - "isDeleted": true, - }, - }, - }, - "updated": Map { - "id50" => Delta { - "deleted": { - "boundElements": [ - { - "id": "id55", - "type": "arrow", - }, - ], - }, - "inserted": { - "boundElements": [], - }, - }, - "id52" => Delta { - "deleted": { - "boundElements": [ - { - "id": "id55", - "type": "arrow", - }, - ], - }, - "inserted": { - "boundElements": [], - }, - }, - }, - }, - }, - ], -} -`; - exports[`history > singleplayer undo/redo > should support bidirectional bindings > should unbind arrow from non deleted bindable elements on undo and rebind on redo > [end of test] number of elements 1`] = `4`; exports[`history > singleplayer undo/redo > should support bidirectional bindings > should unbind arrow from non deleted bindable elements on undo and rebind on redo > [end of test] number of renders 1`] = `12`; +exports[`history > singleplayer undo/redo > should support bidirectional bindings > should unbind arrow from non deleted bindable elements on undo and rebind on redo > [end of test] redo stack 1`] = `[]`; + +exports[`history > singleplayer undo/redo > should support bidirectional bindings > should unbind arrow from non deleted bindable elements on undo and rebind on redo > [end of test] undo stack 1`] = ` +[ + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": {}, + "inserted": {}, + }, + }, + "elements": { + "added": {}, + "removed": { + "id212": { + "deleted": { + "angle": 0, + "backgroundColor": "transparent", + "boundElements": null, + "customData": undefined, + "fillStyle": "solid", + "frameId": null, + "groupIds": [], + "height": 100, + "index": "a0", + "isDeleted": false, + "link": null, + "locked": false, + "opacity": 100, + "roughness": 1, + "roundness": { + "type": 3, + }, + "strokeColor": "#1e1e1e", + "strokeStyle": "solid", + "strokeWidth": 2, + "type": "rectangle", + "width": 100, + "x": -100, + "y": -50, + }, + "inserted": { + "isDeleted": true, + }, + }, + "id213": { + "deleted": { + "angle": 0, + "autoResize": true, + "backgroundColor": "transparent", + "boundElements": null, + "containerId": null, + "customData": undefined, + "fillStyle": "solid", + "fontFamily": 5, + "fontSize": 20, + "frameId": null, + "groupIds": [], + "height": 100, + "index": "a1", + "isDeleted": false, + "lineHeight": "1.25000", + "link": null, + "locked": false, + "opacity": 100, + "originalText": "ola", + "roughness": 1, + "roundness": { + "type": 3, + }, + "strokeColor": "#1e1e1e", + "strokeStyle": "solid", + "strokeWidth": 2, + "text": "ola", + "textAlign": "left", + "type": "text", + "verticalAlign": "top", + "width": 100, + "x": -200, + "y": -200, + }, + "inserted": { + "isDeleted": true, + }, + }, + "id214": { + "deleted": { + "angle": 0, + "backgroundColor": "transparent", + "boundElements": null, + "customData": undefined, + "fillStyle": "solid", + "frameId": null, + "groupIds": [], + "height": 100, + "index": "a2", + "isDeleted": false, + "link": null, + "locked": false, + "opacity": 100, + "roughness": 1, + "roundness": { + "type": 3, + }, + "strokeColor": "#1e1e1e", + "strokeStyle": "solid", + "strokeWidth": 2, + "type": "rectangle", + "width": 100, + "x": 100, + "y": -50, + }, + "inserted": { + "isDeleted": true, + }, + }, + }, + "updated": {}, + }, + "id": "id216", + }, + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": { + "selectedElementIds": { + "id212": true, + }, + }, + "inserted": { + "selectedElementIds": {}, + }, + }, + }, + "elements": { + "added": {}, + "removed": {}, + "updated": {}, + }, + "id": "id219", + }, + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": { + "selectedElementIds": { + "id213": true, + }, + }, + "inserted": { + "selectedElementIds": {}, + }, + }, + }, + "elements": { + "added": {}, + "removed": {}, + "updated": {}, + }, + "id": "id222", + }, + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": { + "selectedElementIds": {}, + }, + "inserted": { + "selectedElementIds": { + "id213": true, + }, + }, + }, + }, + "elements": { + "added": {}, + "removed": {}, + "updated": { + "id212": { + "deleted": { + "boundElements": [ + { + "id": "id213", + "type": "text", + }, + ], + }, + "inserted": { + "boundElements": [], + }, + }, + "id213": { + "deleted": { + "containerId": "id212", + "height": 25, + "textAlign": "center", + "verticalAlign": "middle", + "width": 30, + "x": -65, + "y": "-12.50000", + }, + "inserted": { + "containerId": null, + "height": 100, + "textAlign": "left", + "verticalAlign": "top", + "width": 100, + "x": -200, + "y": -200, + }, + }, + }, + }, + "id": "id224", + }, + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": { + "selectedElementIds": { + "id225": true, + }, + "selectedLinearElementId": "id225", + }, + "inserted": { + "selectedElementIds": { + "id212": true, + }, + "selectedLinearElementId": null, + }, + }, + }, + "elements": { + "added": {}, + "removed": { + "id225": { + "deleted": { + "angle": 0, + "backgroundColor": "transparent", + "boundElements": null, + "customData": undefined, + "elbowed": false, + "endArrowhead": "arrow", + "endBinding": { + "elementId": "id214", + "focus": -0, + "gap": 1, + }, + "fillStyle": "solid", + "frameId": null, + "groupIds": [], + "height": 0, + "index": "a3", + "isDeleted": false, + "lastCommittedPoint": null, + "link": null, + "locked": false, + "opacity": 100, + "points": [ + [ + 0, + 0, + ], + [ + 100, + 0, + ], + ], + "roughness": 1, + "roundness": { + "type": 2, + }, + "startArrowhead": null, + "startBinding": { + "elementId": "id212", + "focus": 0, + "gap": 1, + }, + "strokeColor": "#1e1e1e", + "strokeStyle": "solid", + "strokeWidth": 2, + "type": "arrow", + "width": 100, + "x": 0, + "y": 0, + }, + "inserted": { + "isDeleted": true, + }, + }, + }, + "updated": { + "id212": { + "deleted": { + "boundElements": [ + { + "id": "id225", + "type": "arrow", + }, + ], + }, + "inserted": { + "boundElements": [], + }, + }, + "id214": { + "deleted": { + "boundElements": [ + { + "id": "id225", + "type": "arrow", + }, + ], + }, + "inserted": { + "boundElements": [], + }, + }, + }, + }, + "id": "id229", + }, +] +`; + exports[`history > singleplayer undo/redo > should support bidirectional bindings > should unbind everything from non deleted elements when iterating through the whole undo stack and vice versa rebind everything on redo > [end of test] appState 1`] = ` { "activeEmbeddable": null, @@ -16304,14 +16066,14 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding "penMode": false, "pendingImageElementId": null, "previousSelectedElementIds": { - "id62": true, + "id249": true, }, "resizingElement": null, "scrollX": 0, "scrollY": 0, "searchMatches": [], "selectedElementIds": { - "id67": true, + "id262": true, }, "selectedElementsAreBeingDragged": false, "selectedGroupIds": {}, @@ -16345,11 +16107,11 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding "backgroundColor": "transparent", "boundElements": [ { - "id": "id63", + "id": "id250", "type": "text", }, { - "id": "id67", + "id": "id262", "type": "arrow", }, ], @@ -16358,7 +16120,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding "frameId": null, "groupIds": [], "height": 100, - "id": "id62", + "id": "id249", "index": "a0", "isDeleted": false, "link": null, @@ -16386,7 +16148,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding "autoResize": true, "backgroundColor": "transparent", "boundElements": null, - "containerId": "id62", + "containerId": "id249", "customData": undefined, "fillStyle": "solid", "fontFamily": 5, @@ -16394,7 +16156,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding "frameId": null, "groupIds": [], "height": 25, - "id": "id63", + "id": "id250", "index": "a1", "isDeleted": false, "lineHeight": "1.25000", @@ -16427,7 +16189,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding "backgroundColor": "transparent", "boundElements": [ { - "id": "id67", + "id": "id262", "type": "arrow", }, ], @@ -16436,7 +16198,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding "frameId": null, "groupIds": [], "height": 100, - "id": "id64", + "id": "id251", "index": "a2", "isDeleted": false, "link": null, @@ -16467,7 +16229,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding "elbowed": false, "endArrowhead": "arrow", "endBinding": { - "elementId": "id64", + "elementId": "id251", "focus": -0, "gap": 1, }, @@ -16475,7 +16237,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding "frameId": null, "groupIds": [], "height": 0, - "id": "id67", + "id": "id262", "index": "a3", "isDeleted": false, "lastCommittedPoint": null, @@ -16498,7 +16260,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding }, "startArrowhead": null, "startBinding": { - "elementId": "id62", + "elementId": "id249", "focus": 0, "gap": 1, }, @@ -16514,337 +16276,335 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding } `; -exports[`history > singleplayer undo/redo > should support bidirectional bindings > should unbind everything from non deleted elements when iterating through the whole undo stack and vice versa rebind everything on redo > [end of test] history 1`] = ` -History { - "onHistoryChangedEmitter": Emitter { - "subscribers": [ - [Function], - [Function], - ], - }, - "redoStack": [], - "undoStack": [ - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": {}, - "inserted": {}, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map { - "id62" => Delta { - "deleted": { - "angle": 0, - "backgroundColor": "transparent", - "boundElements": null, - "customData": undefined, - "fillStyle": "solid", - "frameId": null, - "groupIds": [], - "height": 100, - "index": "a0", - "isDeleted": false, - "link": null, - "locked": false, - "opacity": 100, - "roughness": 1, - "roundness": { - "type": 3, - }, - "strokeColor": "#1e1e1e", - "strokeStyle": "solid", - "strokeWidth": 2, - "type": "rectangle", - "width": 100, - "x": -100, - "y": -50, - }, - "inserted": { - "isDeleted": true, - }, - }, - "id63" => Delta { - "deleted": { - "angle": 0, - "autoResize": true, - "backgroundColor": "transparent", - "boundElements": null, - "containerId": null, - "customData": undefined, - "fillStyle": "solid", - "fontFamily": 5, - "fontSize": 20, - "frameId": null, - "groupIds": [], - "height": 100, - "index": "a1", - "isDeleted": false, - "lineHeight": "1.25000", - "link": null, - "locked": false, - "opacity": 100, - "originalText": "ola", - "roughness": 1, - "roundness": { - "type": 3, - }, - "strokeColor": "#1e1e1e", - "strokeStyle": "solid", - "strokeWidth": 2, - "text": "ola", - "textAlign": "left", - "type": "text", - "verticalAlign": "top", - "width": 100, - "x": -200, - "y": -200, - }, - "inserted": { - "isDeleted": true, - }, - }, - "id64" => Delta { - "deleted": { - "angle": 0, - "backgroundColor": "transparent", - "boundElements": null, - "customData": undefined, - "fillStyle": "solid", - "frameId": null, - "groupIds": [], - "height": 100, - "index": "a2", - "isDeleted": false, - "link": null, - "locked": false, - "opacity": 100, - "roughness": 1, - "roundness": { - "type": 3, - }, - "strokeColor": "#1e1e1e", - "strokeStyle": "solid", - "strokeWidth": 2, - "type": "rectangle", - "width": 100, - "x": 100, - "y": -50, - }, - "inserted": { - "isDeleted": true, - }, - }, - }, - "updated": Map {}, - }, - }, - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": { - "selectedElementIds": { - "id62": true, - }, - }, - "inserted": { - "selectedElementIds": {}, - }, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map {}, - "updated": Map {}, - }, - }, - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": { - "selectedElementIds": { - "id63": true, - }, - }, - "inserted": { - "selectedElementIds": {}, - }, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map {}, - "updated": Map {}, - }, - }, - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": { - "selectedElementIds": {}, - }, - "inserted": { - "selectedElementIds": { - "id63": true, - }, - }, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map {}, - "updated": Map { - "id62" => Delta { - "deleted": { - "boundElements": [ - { - "id": "id63", - "type": "text", - }, - ], - }, - "inserted": { - "boundElements": [], - }, - }, - "id63" => Delta { - "deleted": { - "containerId": "id62", - "height": 25, - "textAlign": "center", - "verticalAlign": "middle", - "width": 30, - "x": -65, - "y": "-12.50000", - }, - "inserted": { - "containerId": null, - "height": 100, - "textAlign": "left", - "verticalAlign": "top", - "width": 100, - "x": -200, - "y": -200, - }, - }, - }, - }, - }, - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": { - "selectedElementIds": { - "id67": true, - }, - "selectedLinearElementId": "id67", - }, - "inserted": { - "selectedElementIds": { - "id62": true, - }, - "selectedLinearElementId": null, - }, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map { - "id67" => Delta { - "deleted": { - "angle": 0, - "backgroundColor": "transparent", - "boundElements": null, - "customData": undefined, - "elbowed": false, - "endArrowhead": "arrow", - "endBinding": { - "elementId": "id64", - "focus": -0, - "gap": 1, - }, - "fillStyle": "solid", - "frameId": null, - "groupIds": [], - "height": 0, - "index": "a3", - "isDeleted": false, - "lastCommittedPoint": null, - "link": null, - "locked": false, - "opacity": 100, - "points": [ - [ - 0, - 0, - ], - [ - 100, - 0, - ], - ], - "roughness": 1, - "roundness": { - "type": 2, - }, - "startArrowhead": null, - "startBinding": { - "elementId": "id62", - "focus": 0, - "gap": 1, - }, - "strokeColor": "#1e1e1e", - "strokeStyle": "solid", - "strokeWidth": 2, - "type": "arrow", - "width": 100, - "x": 0, - "y": 0, - }, - "inserted": { - "isDeleted": true, - }, - }, - }, - "updated": Map { - "id62" => Delta { - "deleted": { - "boundElements": [ - { - "id": "id67", - "type": "arrow", - }, - ], - }, - "inserted": { - "boundElements": [], - }, - }, - "id64" => Delta { - "deleted": { - "boundElements": [ - { - "id": "id67", - "type": "arrow", - }, - ], - }, - "inserted": { - "boundElements": [], - }, - }, - }, - }, - }, - ], -} -`; - exports[`history > singleplayer undo/redo > should support bidirectional bindings > should unbind everything from non deleted elements when iterating through the whole undo stack and vice versa rebind everything on redo > [end of test] number of elements 1`] = `4`; exports[`history > singleplayer undo/redo > should support bidirectional bindings > should unbind everything from non deleted elements when iterating through the whole undo stack and vice versa rebind everything on redo > [end of test] number of renders 1`] = `20`; +exports[`history > singleplayer undo/redo > should support bidirectional bindings > should unbind everything from non deleted elements when iterating through the whole undo stack and vice versa rebind everything on redo > [end of test] redo stack 1`] = `[]`; + +exports[`history > singleplayer undo/redo > should support bidirectional bindings > should unbind everything from non deleted elements when iterating through the whole undo stack and vice versa rebind everything on redo > [end of test] undo stack 1`] = ` +[ + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": {}, + "inserted": {}, + }, + }, + "elements": { + "added": {}, + "removed": { + "id249": { + "deleted": { + "angle": 0, + "backgroundColor": "transparent", + "boundElements": null, + "customData": undefined, + "fillStyle": "solid", + "frameId": null, + "groupIds": [], + "height": 100, + "index": "a0", + "isDeleted": false, + "link": null, + "locked": false, + "opacity": 100, + "roughness": 1, + "roundness": { + "type": 3, + }, + "strokeColor": "#1e1e1e", + "strokeStyle": "solid", + "strokeWidth": 2, + "type": "rectangle", + "width": 100, + "x": -100, + "y": -50, + }, + "inserted": { + "isDeleted": true, + }, + }, + "id250": { + "deleted": { + "angle": 0, + "autoResize": true, + "backgroundColor": "transparent", + "boundElements": null, + "containerId": null, + "customData": undefined, + "fillStyle": "solid", + "fontFamily": 5, + "fontSize": 20, + "frameId": null, + "groupIds": [], + "height": 100, + "index": "a1", + "isDeleted": false, + "lineHeight": "1.25000", + "link": null, + "locked": false, + "opacity": 100, + "originalText": "ola", + "roughness": 1, + "roundness": { + "type": 3, + }, + "strokeColor": "#1e1e1e", + "strokeStyle": "solid", + "strokeWidth": 2, + "text": "ola", + "textAlign": "left", + "type": "text", + "verticalAlign": "top", + "width": 100, + "x": -200, + "y": -200, + }, + "inserted": { + "isDeleted": true, + }, + }, + "id251": { + "deleted": { + "angle": 0, + "backgroundColor": "transparent", + "boundElements": null, + "customData": undefined, + "fillStyle": "solid", + "frameId": null, + "groupIds": [], + "height": 100, + "index": "a2", + "isDeleted": false, + "link": null, + "locked": false, + "opacity": 100, + "roughness": 1, + "roundness": { + "type": 3, + }, + "strokeColor": "#1e1e1e", + "strokeStyle": "solid", + "strokeWidth": 2, + "type": "rectangle", + "width": 100, + "x": 100, + "y": -50, + }, + "inserted": { + "isDeleted": true, + }, + }, + }, + "updated": {}, + }, + "id": "id270", + }, + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": { + "selectedElementIds": { + "id249": true, + }, + }, + "inserted": { + "selectedElementIds": {}, + }, + }, + }, + "elements": { + "added": {}, + "removed": {}, + "updated": {}, + }, + "id": "id271", + }, + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": { + "selectedElementIds": { + "id250": true, + }, + }, + "inserted": { + "selectedElementIds": {}, + }, + }, + }, + "elements": { + "added": {}, + "removed": {}, + "updated": {}, + }, + "id": "id272", + }, + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": { + "selectedElementIds": {}, + }, + "inserted": { + "selectedElementIds": { + "id250": true, + }, + }, + }, + }, + "elements": { + "added": {}, + "removed": {}, + "updated": { + "id249": { + "deleted": { + "boundElements": [ + { + "id": "id250", + "type": "text", + }, + ], + }, + "inserted": { + "boundElements": [], + }, + }, + "id250": { + "deleted": { + "containerId": "id249", + "height": 25, + "textAlign": "center", + "verticalAlign": "middle", + "width": 30, + "x": -65, + "y": "-12.50000", + }, + "inserted": { + "containerId": null, + "height": 100, + "textAlign": "left", + "verticalAlign": "top", + "width": 100, + "x": -200, + "y": -200, + }, + }, + }, + }, + "id": "id273", + }, + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": { + "selectedElementIds": { + "id262": true, + }, + "selectedLinearElementId": "id262", + }, + "inserted": { + "selectedElementIds": { + "id249": true, + }, + "selectedLinearElementId": null, + }, + }, + }, + "elements": { + "added": {}, + "removed": { + "id262": { + "deleted": { + "angle": 0, + "backgroundColor": "transparent", + "boundElements": null, + "customData": undefined, + "elbowed": false, + "endArrowhead": "arrow", + "endBinding": { + "elementId": "id251", + "focus": -0, + "gap": 1, + }, + "fillStyle": "solid", + "frameId": null, + "groupIds": [], + "height": 0, + "index": "a3", + "isDeleted": false, + "lastCommittedPoint": null, + "link": null, + "locked": false, + "opacity": 100, + "points": [ + [ + 0, + 0, + ], + [ + 100, + 0, + ], + ], + "roughness": 1, + "roundness": { + "type": 2, + }, + "startArrowhead": null, + "startBinding": { + "elementId": "id249", + "focus": 0, + "gap": 1, + }, + "strokeColor": "#1e1e1e", + "strokeStyle": "solid", + "strokeWidth": 2, + "type": "arrow", + "width": 100, + "x": 0, + "y": 0, + }, + "inserted": { + "isDeleted": true, + }, + }, + }, + "updated": { + "id249": { + "deleted": { + "boundElements": [ + { + "id": "id262", + "type": "arrow", + }, + ], + }, + "inserted": { + "boundElements": [], + }, + }, + "id251": { + "deleted": { + "boundElements": [ + { + "id": "id262", + "type": "arrow", + }, + ], + }, + "inserted": { + "boundElements": [], + }, + }, + }, + }, + "id": "id274", + }, +] +`; + exports[`history > singleplayer undo/redo > should support bidirectional bindings > should unbind rectangle from arrow on deletion and rebind on undo > [end of test] appState 1`] = ` { "activeEmbeddable": null, @@ -16929,7 +16689,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding "scrollY": 0, "searchMatches": [], "selectedElementIds": { - "id68": true, + "id275": true, }, "selectedElementsAreBeingDragged": false, "selectedGroupIds": {}, @@ -16963,11 +16723,11 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding "backgroundColor": "transparent", "boundElements": [ { - "id": "id73", + "id": "id288", "type": "arrow", }, { - "id": "id69", + "id": "id276", "type": "text", }, ], @@ -16976,7 +16736,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding "frameId": null, "groupIds": [], "height": 100, - "id": "id68", + "id": "id275", "index": "a0", "isDeleted": false, "link": null, @@ -17004,7 +16764,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding "autoResize": true, "backgroundColor": "transparent", "boundElements": null, - "containerId": "id68", + "containerId": "id275", "customData": undefined, "fillStyle": "solid", "fontFamily": 5, @@ -17012,7 +16772,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding "frameId": null, "groupIds": [], "height": 25, - "id": "id69", + "id": "id276", "index": "a1", "isDeleted": false, "lineHeight": "1.25000", @@ -17045,7 +16805,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding "backgroundColor": "transparent", "boundElements": [ { - "id": "id73", + "id": "id288", "type": "arrow", }, ], @@ -17054,7 +16814,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding "frameId": null, "groupIds": [], "height": 100, - "id": "id70", + "id": "id277", "index": "a2", "isDeleted": false, "link": null, @@ -17085,7 +16845,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding "elbowed": false, "endArrowhead": "arrow", "endBinding": { - "elementId": "id70", + "elementId": "id277", "focus": -0, "gap": 1, }, @@ -17093,7 +16853,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding "frameId": null, "groupIds": [], "height": 0, - "id": "id73", + "id": "id288", "index": "a3", "isDeleted": false, "lastCommittedPoint": null, @@ -17116,7 +16876,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding }, "startArrowhead": null, "startBinding": { - "elementId": "id68", + "elementId": "id275", "focus": 0, "gap": 1, }, @@ -17132,430 +16892,432 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding } `; -exports[`history > singleplayer undo/redo > should support bidirectional bindings > should unbind rectangle from arrow on deletion and rebind on undo > [end of test] history 1`] = ` -History { - "onHistoryChangedEmitter": Emitter { - "subscribers": [ - [Function], - [Function], - ], - }, - "redoStack": [ - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": { - "selectedElementIds": { - "id68": true, - }, - }, - "inserted": { - "selectedElementIds": {}, - }, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map { - "id68" => Delta { - "deleted": { - "isDeleted": false, - }, - "inserted": { - "isDeleted": true, - }, - }, - "id69" => Delta { - "deleted": { - "isDeleted": false, - }, - "inserted": { - "isDeleted": true, - }, - }, - }, - "updated": Map { - "id73" => Delta { - "deleted": { - "points": [ - [ - 0, - 0, - ], - [ - 100, - 0, - ], - ], - "startBinding": { - "elementId": "id68", - "focus": 0, - "gap": 1, - }, - }, - "inserted": { - "points": [ - [ - 0, - 0, - ], - [ - 100, - 0, - ], - ], - "startBinding": null, - }, - }, - }, - }, - }, - ], - "undoStack": [ - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": {}, - "inserted": {}, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map { - "id68" => Delta { - "deleted": { - "angle": 0, - "backgroundColor": "transparent", - "boundElements": null, - "customData": undefined, - "fillStyle": "solid", - "frameId": null, - "groupIds": [], - "height": 100, - "index": "a0", - "isDeleted": false, - "link": null, - "locked": false, - "opacity": 100, - "roughness": 1, - "roundness": { - "type": 3, - }, - "strokeColor": "#1e1e1e", - "strokeStyle": "solid", - "strokeWidth": 2, - "type": "rectangle", - "width": 100, - "x": -100, - "y": -50, - }, - "inserted": { - "isDeleted": true, - }, - }, - "id69" => Delta { - "deleted": { - "angle": 0, - "autoResize": true, - "backgroundColor": "transparent", - "boundElements": null, - "containerId": null, - "customData": undefined, - "fillStyle": "solid", - "fontFamily": 5, - "fontSize": 20, - "frameId": null, - "groupIds": [], - "height": 100, - "index": "a1", - "isDeleted": false, - "lineHeight": "1.25000", - "link": null, - "locked": false, - "opacity": 100, - "originalText": "ola", - "roughness": 1, - "roundness": { - "type": 3, - }, - "strokeColor": "#1e1e1e", - "strokeStyle": "solid", - "strokeWidth": 2, - "text": "ola", - "textAlign": "left", - "type": "text", - "verticalAlign": "top", - "width": 100, - "x": -200, - "y": -200, - }, - "inserted": { - "isDeleted": true, - }, - }, - "id70" => Delta { - "deleted": { - "angle": 0, - "backgroundColor": "transparent", - "boundElements": null, - "customData": undefined, - "fillStyle": "solid", - "frameId": null, - "groupIds": [], - "height": 100, - "index": "a2", - "isDeleted": false, - "link": null, - "locked": false, - "opacity": 100, - "roughness": 1, - "roundness": { - "type": 3, - }, - "strokeColor": "#1e1e1e", - "strokeStyle": "solid", - "strokeWidth": 2, - "type": "rectangle", - "width": 100, - "x": 100, - "y": -50, - }, - "inserted": { - "isDeleted": true, - }, - }, - }, - "updated": Map {}, - }, - }, - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": { - "selectedElementIds": { - "id68": true, - }, - }, - "inserted": { - "selectedElementIds": {}, - }, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map {}, - "updated": Map {}, - }, - }, - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": { - "selectedElementIds": { - "id69": true, - }, - }, - "inserted": { - "selectedElementIds": {}, - }, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map {}, - "updated": Map {}, - }, - }, - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": { - "selectedElementIds": {}, - }, - "inserted": { - "selectedElementIds": { - "id69": true, - }, - }, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map {}, - "updated": Map { - "id68" => Delta { - "deleted": { - "boundElements": [ - { - "id": "id69", - "type": "text", - }, - ], - }, - "inserted": { - "boundElements": [], - }, - }, - "id69" => Delta { - "deleted": { - "containerId": "id68", - "height": 25, - "textAlign": "center", - "verticalAlign": "middle", - "width": 30, - "x": -65, - "y": "-12.50000", - }, - "inserted": { - "containerId": null, - "height": 100, - "textAlign": "left", - "verticalAlign": "top", - "width": 100, - "x": -200, - "y": -200, - }, - }, - }, - }, - }, - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": { - "selectedElementIds": { - "id73": true, - }, - "selectedLinearElementId": "id73", - }, - "inserted": { - "selectedElementIds": { - "id68": true, - }, - "selectedLinearElementId": null, - }, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map { - "id73" => Delta { - "deleted": { - "angle": 0, - "backgroundColor": "transparent", - "boundElements": null, - "customData": undefined, - "elbowed": false, - "endArrowhead": "arrow", - "endBinding": { - "elementId": "id70", - "focus": -0, - "gap": 1, - }, - "fillStyle": "solid", - "frameId": null, - "groupIds": [], - "height": 0, - "index": "a3", - "isDeleted": false, - "lastCommittedPoint": null, - "link": null, - "locked": false, - "opacity": 100, - "points": [ - [ - 0, - 0, - ], - [ - 100, - 0, - ], - ], - "roughness": 1, - "roundness": { - "type": 2, - }, - "startArrowhead": null, - "startBinding": { - "elementId": "id68", - "focus": 0, - "gap": 1, - }, - "strokeColor": "#1e1e1e", - "strokeStyle": "solid", - "strokeWidth": 2, - "type": "arrow", - "width": 100, - "x": 0, - "y": 0, - }, - "inserted": { - "isDeleted": true, - }, - }, - }, - "updated": Map { - "id68" => Delta { - "deleted": { - "boundElements": [ - { - "id": "id73", - "type": "arrow", - }, - ], - }, - "inserted": { - "boundElements": [], - }, - }, - "id70" => Delta { - "deleted": { - "boundElements": [ - { - "id": "id73", - "type": "arrow", - }, - ], - }, - "inserted": { - "boundElements": [], - }, - }, - }, - }, - }, - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": { - "selectedElementIds": { - "id68": true, - }, - "selectedLinearElementId": null, - }, - "inserted": { - "selectedElementIds": { - "id73": true, - }, - "selectedLinearElementId": "id73", - }, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map {}, - "updated": Map {}, - }, - }, - ], -} -`; - exports[`history > singleplayer undo/redo > should support bidirectional bindings > should unbind rectangle from arrow on deletion and rebind on undo > [end of test] number of elements 1`] = `4`; exports[`history > singleplayer undo/redo > should support bidirectional bindings > should unbind rectangle from arrow on deletion and rebind on undo > [end of test] number of renders 1`] = `14`; +exports[`history > singleplayer undo/redo > should support bidirectional bindings > should unbind rectangle from arrow on deletion and rebind on undo > [end of test] redo stack 1`] = ` +[ + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": { + "selectedElementIds": { + "id275": true, + }, + }, + "inserted": { + "selectedElementIds": {}, + }, + }, + }, + "elements": { + "added": {}, + "removed": { + "id275": { + "deleted": { + "isDeleted": false, + }, + "inserted": { + "isDeleted": true, + }, + }, + "id276": { + "deleted": { + "isDeleted": false, + }, + "inserted": { + "isDeleted": true, + }, + }, + }, + "updated": { + "id288": { + "deleted": { + "points": [ + [ + 0, + 0, + ], + [ + 100, + 0, + ], + ], + "startBinding": { + "elementId": "id275", + "focus": 0, + "gap": 1, + }, + }, + "inserted": { + "points": [ + [ + 0, + 0, + ], + [ + 100, + 0, + ], + ], + "startBinding": null, + }, + }, + }, + }, + "id": "id296", + }, +] +`; + +exports[`history > singleplayer undo/redo > should support bidirectional bindings > should unbind rectangle from arrow on deletion and rebind on undo > [end of test] undo stack 1`] = ` +[ + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": {}, + "inserted": {}, + }, + }, + "elements": { + "added": {}, + "removed": { + "id275": { + "deleted": { + "angle": 0, + "backgroundColor": "transparent", + "boundElements": null, + "customData": undefined, + "fillStyle": "solid", + "frameId": null, + "groupIds": [], + "height": 100, + "index": "a0", + "isDeleted": false, + "link": null, + "locked": false, + "opacity": 100, + "roughness": 1, + "roundness": { + "type": 3, + }, + "strokeColor": "#1e1e1e", + "strokeStyle": "solid", + "strokeWidth": 2, + "type": "rectangle", + "width": 100, + "x": -100, + "y": -50, + }, + "inserted": { + "isDeleted": true, + }, + }, + "id276": { + "deleted": { + "angle": 0, + "autoResize": true, + "backgroundColor": "transparent", + "boundElements": null, + "containerId": null, + "customData": undefined, + "fillStyle": "solid", + "fontFamily": 5, + "fontSize": 20, + "frameId": null, + "groupIds": [], + "height": 100, + "index": "a1", + "isDeleted": false, + "lineHeight": "1.25000", + "link": null, + "locked": false, + "opacity": 100, + "originalText": "ola", + "roughness": 1, + "roundness": { + "type": 3, + }, + "strokeColor": "#1e1e1e", + "strokeStyle": "solid", + "strokeWidth": 2, + "text": "ola", + "textAlign": "left", + "type": "text", + "verticalAlign": "top", + "width": 100, + "x": -200, + "y": -200, + }, + "inserted": { + "isDeleted": true, + }, + }, + "id277": { + "deleted": { + "angle": 0, + "backgroundColor": "transparent", + "boundElements": null, + "customData": undefined, + "fillStyle": "solid", + "frameId": null, + "groupIds": [], + "height": 100, + "index": "a2", + "isDeleted": false, + "link": null, + "locked": false, + "opacity": 100, + "roughness": 1, + "roundness": { + "type": 3, + }, + "strokeColor": "#1e1e1e", + "strokeStyle": "solid", + "strokeWidth": 2, + "type": "rectangle", + "width": 100, + "x": 100, + "y": -50, + }, + "inserted": { + "isDeleted": true, + }, + }, + }, + "updated": {}, + }, + "id": "id279", + }, + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": { + "selectedElementIds": { + "id275": true, + }, + }, + "inserted": { + "selectedElementIds": {}, + }, + }, + }, + "elements": { + "added": {}, + "removed": {}, + "updated": {}, + }, + "id": "id282", + }, + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": { + "selectedElementIds": { + "id276": true, + }, + }, + "inserted": { + "selectedElementIds": {}, + }, + }, + }, + "elements": { + "added": {}, + "removed": {}, + "updated": {}, + }, + "id": "id285", + }, + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": { + "selectedElementIds": {}, + }, + "inserted": { + "selectedElementIds": { + "id276": true, + }, + }, + }, + }, + "elements": { + "added": {}, + "removed": {}, + "updated": { + "id275": { + "deleted": { + "boundElements": [ + { + "id": "id276", + "type": "text", + }, + ], + }, + "inserted": { + "boundElements": [], + }, + }, + "id276": { + "deleted": { + "containerId": "id275", + "height": 25, + "textAlign": "center", + "verticalAlign": "middle", + "width": 30, + "x": -65, + "y": "-12.50000", + }, + "inserted": { + "containerId": null, + "height": 100, + "textAlign": "left", + "verticalAlign": "top", + "width": 100, + "x": -200, + "y": -200, + }, + }, + }, + }, + "id": "id287", + }, + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": { + "selectedElementIds": { + "id288": true, + }, + "selectedLinearElementId": "id288", + }, + "inserted": { + "selectedElementIds": { + "id275": true, + }, + "selectedLinearElementId": null, + }, + }, + }, + "elements": { + "added": {}, + "removed": { + "id288": { + "deleted": { + "angle": 0, + "backgroundColor": "transparent", + "boundElements": null, + "customData": undefined, + "elbowed": false, + "endArrowhead": "arrow", + "endBinding": { + "elementId": "id277", + "focus": -0, + "gap": 1, + }, + "fillStyle": "solid", + "frameId": null, + "groupIds": [], + "height": 0, + "index": "a3", + "isDeleted": false, + "lastCommittedPoint": null, + "link": null, + "locked": false, + "opacity": 100, + "points": [ + [ + 0, + 0, + ], + [ + 100, + 0, + ], + ], + "roughness": 1, + "roundness": { + "type": 2, + }, + "startArrowhead": null, + "startBinding": { + "elementId": "id275", + "focus": 0, + "gap": 1, + }, + "strokeColor": "#1e1e1e", + "strokeStyle": "solid", + "strokeWidth": 2, + "type": "arrow", + "width": 100, + "x": 0, + "y": 0, + }, + "inserted": { + "isDeleted": true, + }, + }, + }, + "updated": { + "id275": { + "deleted": { + "boundElements": [ + { + "id": "id288", + "type": "arrow", + }, + ], + }, + "inserted": { + "boundElements": [], + }, + }, + "id277": { + "deleted": { + "boundElements": [ + { + "id": "id288", + "type": "arrow", + }, + ], + }, + "inserted": { + "boundElements": [], + }, + }, + }, + }, + "id": "id290", + }, + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": { + "selectedElementIds": { + "id275": true, + }, + "selectedLinearElementId": null, + }, + "inserted": { + "selectedElementIds": { + "id288": true, + }, + "selectedLinearElementId": "id288", + }, + }, + }, + "elements": { + "added": {}, + "removed": {}, + "updated": {}, + }, + "id": "id293", + }, +] +`; + exports[`history > singleplayer undo/redo > should support bidirectional bindings > should unbind rectangles from arrow on deletion and rebind on undo > [end of test] appState 1`] = ` { "activeEmbeddable": null, @@ -17635,15 +17397,15 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding "penMode": false, "pendingImageElementId": null, "previousSelectedElementIds": { - "id75": true, + "id297": true, }, "resizingElement": null, "scrollX": 0, "scrollY": 0, "searchMatches": [], "selectedElementIds": { - "id75": true, - "id77": true, + "id297": true, + "id299": true, }, "selectedElementsAreBeingDragged": false, "selectedGroupIds": {}, @@ -17677,11 +17439,11 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding "backgroundColor": "transparent", "boundElements": [ { - "id": "id80", + "id": "id310", "type": "arrow", }, { - "id": "id76", + "id": "id298", "type": "text", }, ], @@ -17690,7 +17452,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding "frameId": null, "groupIds": [], "height": 100, - "id": "id75", + "id": "id297", "index": "a0", "isDeleted": false, "link": null, @@ -17718,7 +17480,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding "autoResize": true, "backgroundColor": "transparent", "boundElements": null, - "containerId": "id75", + "containerId": "id297", "customData": undefined, "fillStyle": "solid", "fontFamily": 5, @@ -17726,7 +17488,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding "frameId": null, "groupIds": [], "height": 25, - "id": "id76", + "id": "id298", "index": "a1", "isDeleted": false, "lineHeight": "1.25000", @@ -17759,7 +17521,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding "backgroundColor": "transparent", "boundElements": [ { - "id": "id80", + "id": "id310", "type": "arrow", }, ], @@ -17768,7 +17530,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding "frameId": null, "groupIds": [], "height": 100, - "id": "id77", + "id": "id299", "index": "a2", "isDeleted": false, "link": null, @@ -17799,7 +17561,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding "elbowed": false, "endArrowhead": "arrow", "endBinding": { - "elementId": "id77", + "elementId": "id299", "focus": -0, "gap": 1, }, @@ -17807,7 +17569,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding "frameId": null, "groupIds": [], "height": 0, - "id": "id80", + "id": "id310", "index": "a3", "isDeleted": false, "lastCommittedPoint": null, @@ -17830,7 +17592,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding }, "startArrowhead": null, "startBinding": { - "elementId": "id75", + "elementId": "id297", "focus": 0, "gap": 1, }, @@ -17846,464 +17608,467 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding } `; -exports[`history > singleplayer undo/redo > should support bidirectional bindings > should unbind rectangles from arrow on deletion and rebind on undo > [end of test] history 1`] = ` -History { - "onHistoryChangedEmitter": Emitter { - "subscribers": [ - [Function], - [Function], - ], - }, - "redoStack": [ - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": { - "selectedElementIds": { - "id75": true, - "id77": true, - }, - }, - "inserted": { - "selectedElementIds": {}, - }, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map { - "id75" => Delta { - "deleted": { - "isDeleted": false, - }, - "inserted": { - "isDeleted": true, - }, - }, - "id76" => Delta { - "deleted": { - "isDeleted": false, - }, - "inserted": { - "isDeleted": true, - }, - }, - "id77" => Delta { - "deleted": { - "isDeleted": false, - }, - "inserted": { - "isDeleted": true, - }, - }, - }, - "updated": Map { - "id80" => Delta { - "deleted": { - "endBinding": { - "elementId": "id77", - "focus": -0, - "gap": 1, - }, - "points": [ - [ - 0, - 0, - ], - [ - 100, - 0, - ], - ], - "startBinding": { - "elementId": "id75", - "focus": 0, - "gap": 1, - }, - }, - "inserted": { - "endBinding": null, - "points": [ - [ - 0, - 0, - ], - [ - 100, - 0, - ], - ], - "startBinding": null, - }, - }, - }, - }, - }, - ], - "undoStack": [ - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": {}, - "inserted": {}, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map { - "id75" => Delta { - "deleted": { - "angle": 0, - "backgroundColor": "transparent", - "boundElements": null, - "customData": undefined, - "fillStyle": "solid", - "frameId": null, - "groupIds": [], - "height": 100, - "index": "a0", - "isDeleted": false, - "link": null, - "locked": false, - "opacity": 100, - "roughness": 1, - "roundness": { - "type": 3, - }, - "strokeColor": "#1e1e1e", - "strokeStyle": "solid", - "strokeWidth": 2, - "type": "rectangle", - "width": 100, - "x": -100, - "y": -50, - }, - "inserted": { - "isDeleted": true, - }, - }, - "id76" => Delta { - "deleted": { - "angle": 0, - "autoResize": true, - "backgroundColor": "transparent", - "boundElements": null, - "containerId": null, - "customData": undefined, - "fillStyle": "solid", - "fontFamily": 5, - "fontSize": 20, - "frameId": null, - "groupIds": [], - "height": 100, - "index": "a1", - "isDeleted": false, - "lineHeight": "1.25000", - "link": null, - "locked": false, - "opacity": 100, - "originalText": "ola", - "roughness": 1, - "roundness": { - "type": 3, - }, - "strokeColor": "#1e1e1e", - "strokeStyle": "solid", - "strokeWidth": 2, - "text": "ola", - "textAlign": "left", - "type": "text", - "verticalAlign": "top", - "width": 100, - "x": -200, - "y": -200, - }, - "inserted": { - "isDeleted": true, - }, - }, - "id77" => Delta { - "deleted": { - "angle": 0, - "backgroundColor": "transparent", - "boundElements": null, - "customData": undefined, - "fillStyle": "solid", - "frameId": null, - "groupIds": [], - "height": 100, - "index": "a2", - "isDeleted": false, - "link": null, - "locked": false, - "opacity": 100, - "roughness": 1, - "roundness": { - "type": 3, - }, - "strokeColor": "#1e1e1e", - "strokeStyle": "solid", - "strokeWidth": 2, - "type": "rectangle", - "width": 100, - "x": 100, - "y": -50, - }, - "inserted": { - "isDeleted": true, - }, - }, - }, - "updated": Map {}, - }, - }, - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": { - "selectedElementIds": { - "id75": true, - }, - }, - "inserted": { - "selectedElementIds": {}, - }, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map {}, - "updated": Map {}, - }, - }, - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": { - "selectedElementIds": { - "id76": true, - }, - }, - "inserted": { - "selectedElementIds": {}, - }, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map {}, - "updated": Map {}, - }, - }, - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": { - "selectedElementIds": {}, - }, - "inserted": { - "selectedElementIds": { - "id76": true, - }, - }, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map {}, - "updated": Map { - "id75" => Delta { - "deleted": { - "boundElements": [ - { - "id": "id76", - "type": "text", - }, - ], - }, - "inserted": { - "boundElements": [], - }, - }, - "id76" => Delta { - "deleted": { - "containerId": "id75", - "height": 25, - "textAlign": "center", - "verticalAlign": "middle", - "width": 30, - "x": -65, - "y": "-12.50000", - }, - "inserted": { - "containerId": null, - "height": 100, - "textAlign": "left", - "verticalAlign": "top", - "width": 100, - "x": -200, - "y": -200, - }, - }, - }, - }, - }, - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": { - "selectedElementIds": { - "id80": true, - }, - "selectedLinearElementId": "id80", - }, - "inserted": { - "selectedElementIds": { - "id75": true, - }, - "selectedLinearElementId": null, - }, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map { - "id80" => Delta { - "deleted": { - "angle": 0, - "backgroundColor": "transparent", - "boundElements": null, - "customData": undefined, - "elbowed": false, - "endArrowhead": "arrow", - "endBinding": { - "elementId": "id77", - "focus": -0, - "gap": 1, - }, - "fillStyle": "solid", - "frameId": null, - "groupIds": [], - "height": 0, - "index": "a3", - "isDeleted": false, - "lastCommittedPoint": null, - "link": null, - "locked": false, - "opacity": 100, - "points": [ - [ - 0, - 0, - ], - [ - 100, - 0, - ], - ], - "roughness": 1, - "roundness": { - "type": 2, - }, - "startArrowhead": null, - "startBinding": { - "elementId": "id75", - "focus": 0, - "gap": 1, - }, - "strokeColor": "#1e1e1e", - "strokeStyle": "solid", - "strokeWidth": 2, - "type": "arrow", - "width": 100, - "x": 0, - "y": 0, - }, - "inserted": { - "isDeleted": true, - }, - }, - }, - "updated": Map { - "id75" => Delta { - "deleted": { - "boundElements": [ - { - "id": "id80", - "type": "arrow", - }, - ], - }, - "inserted": { - "boundElements": [], - }, - }, - "id77" => Delta { - "deleted": { - "boundElements": [ - { - "id": "id80", - "type": "arrow", - }, - ], - }, - "inserted": { - "boundElements": [], - }, - }, - }, - }, - }, - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": { - "selectedElementIds": { - "id75": true, - }, - "selectedLinearElementId": null, - }, - "inserted": { - "selectedElementIds": { - "id80": true, - }, - "selectedLinearElementId": "id80", - }, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map {}, - "updated": Map {}, - }, - }, - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": { - "selectedElementIds": { - "id77": true, - }, - }, - "inserted": { - "selectedElementIds": {}, - }, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map {}, - "updated": Map {}, - }, - }, - ], -} -`; - exports[`history > singleplayer undo/redo > should support bidirectional bindings > should unbind rectangles from arrow on deletion and rebind on undo > [end of test] number of elements 1`] = `4`; exports[`history > singleplayer undo/redo > should support bidirectional bindings > should unbind rectangles from arrow on deletion and rebind on undo > [end of test] number of renders 1`] = `15`; +exports[`history > singleplayer undo/redo > should support bidirectional bindings > should unbind rectangles from arrow on deletion and rebind on undo > [end of test] redo stack 1`] = ` +[ + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": { + "selectedElementIds": { + "id297": true, + "id299": true, + }, + }, + "inserted": { + "selectedElementIds": {}, + }, + }, + }, + "elements": { + "added": {}, + "removed": { + "id297": { + "deleted": { + "isDeleted": false, + }, + "inserted": { + "isDeleted": true, + }, + }, + "id298": { + "deleted": { + "isDeleted": false, + }, + "inserted": { + "isDeleted": true, + }, + }, + "id299": { + "deleted": { + "isDeleted": false, + }, + "inserted": { + "isDeleted": true, + }, + }, + }, + "updated": { + "id310": { + "deleted": { + "endBinding": { + "elementId": "id299", + "focus": -0, + "gap": 1, + }, + "points": [ + [ + 0, + 0, + ], + [ + 100, + 0, + ], + ], + "startBinding": { + "elementId": "id297", + "focus": 0, + "gap": 1, + }, + }, + "inserted": { + "endBinding": null, + "points": [ + [ + 0, + 0, + ], + [ + 100, + 0, + ], + ], + "startBinding": null, + }, + }, + }, + }, + "id": "id321", + }, +] +`; + +exports[`history > singleplayer undo/redo > should support bidirectional bindings > should unbind rectangles from arrow on deletion and rebind on undo > [end of test] undo stack 1`] = ` +[ + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": {}, + "inserted": {}, + }, + }, + "elements": { + "added": {}, + "removed": { + "id297": { + "deleted": { + "angle": 0, + "backgroundColor": "transparent", + "boundElements": null, + "customData": undefined, + "fillStyle": "solid", + "frameId": null, + "groupIds": [], + "height": 100, + "index": "a0", + "isDeleted": false, + "link": null, + "locked": false, + "opacity": 100, + "roughness": 1, + "roundness": { + "type": 3, + }, + "strokeColor": "#1e1e1e", + "strokeStyle": "solid", + "strokeWidth": 2, + "type": "rectangle", + "width": 100, + "x": -100, + "y": -50, + }, + "inserted": { + "isDeleted": true, + }, + }, + "id298": { + "deleted": { + "angle": 0, + "autoResize": true, + "backgroundColor": "transparent", + "boundElements": null, + "containerId": null, + "customData": undefined, + "fillStyle": "solid", + "fontFamily": 5, + "fontSize": 20, + "frameId": null, + "groupIds": [], + "height": 100, + "index": "a1", + "isDeleted": false, + "lineHeight": "1.25000", + "link": null, + "locked": false, + "opacity": 100, + "originalText": "ola", + "roughness": 1, + "roundness": { + "type": 3, + }, + "strokeColor": "#1e1e1e", + "strokeStyle": "solid", + "strokeWidth": 2, + "text": "ola", + "textAlign": "left", + "type": "text", + "verticalAlign": "top", + "width": 100, + "x": -200, + "y": -200, + }, + "inserted": { + "isDeleted": true, + }, + }, + "id299": { + "deleted": { + "angle": 0, + "backgroundColor": "transparent", + "boundElements": null, + "customData": undefined, + "fillStyle": "solid", + "frameId": null, + "groupIds": [], + "height": 100, + "index": "a2", + "isDeleted": false, + "link": null, + "locked": false, + "opacity": 100, + "roughness": 1, + "roundness": { + "type": 3, + }, + "strokeColor": "#1e1e1e", + "strokeStyle": "solid", + "strokeWidth": 2, + "type": "rectangle", + "width": 100, + "x": 100, + "y": -50, + }, + "inserted": { + "isDeleted": true, + }, + }, + }, + "updated": {}, + }, + "id": "id301", + }, + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": { + "selectedElementIds": { + "id297": true, + }, + }, + "inserted": { + "selectedElementIds": {}, + }, + }, + }, + "elements": { + "added": {}, + "removed": {}, + "updated": {}, + }, + "id": "id304", + }, + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": { + "selectedElementIds": { + "id298": true, + }, + }, + "inserted": { + "selectedElementIds": {}, + }, + }, + }, + "elements": { + "added": {}, + "removed": {}, + "updated": {}, + }, + "id": "id307", + }, + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": { + "selectedElementIds": {}, + }, + "inserted": { + "selectedElementIds": { + "id298": true, + }, + }, + }, + }, + "elements": { + "added": {}, + "removed": {}, + "updated": { + "id297": { + "deleted": { + "boundElements": [ + { + "id": "id298", + "type": "text", + }, + ], + }, + "inserted": { + "boundElements": [], + }, + }, + "id298": { + "deleted": { + "containerId": "id297", + "height": 25, + "textAlign": "center", + "verticalAlign": "middle", + "width": 30, + "x": -65, + "y": "-12.50000", + }, + "inserted": { + "containerId": null, + "height": 100, + "textAlign": "left", + "verticalAlign": "top", + "width": 100, + "x": -200, + "y": -200, + }, + }, + }, + }, + "id": "id309", + }, + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": { + "selectedElementIds": { + "id310": true, + }, + "selectedLinearElementId": "id310", + }, + "inserted": { + "selectedElementIds": { + "id297": true, + }, + "selectedLinearElementId": null, + }, + }, + }, + "elements": { + "added": {}, + "removed": { + "id310": { + "deleted": { + "angle": 0, + "backgroundColor": "transparent", + "boundElements": null, + "customData": undefined, + "elbowed": false, + "endArrowhead": "arrow", + "endBinding": { + "elementId": "id299", + "focus": -0, + "gap": 1, + }, + "fillStyle": "solid", + "frameId": null, + "groupIds": [], + "height": 0, + "index": "a3", + "isDeleted": false, + "lastCommittedPoint": null, + "link": null, + "locked": false, + "opacity": 100, + "points": [ + [ + 0, + 0, + ], + [ + 100, + 0, + ], + ], + "roughness": 1, + "roundness": { + "type": 2, + }, + "startArrowhead": null, + "startBinding": { + "elementId": "id297", + "focus": 0, + "gap": 1, + }, + "strokeColor": "#1e1e1e", + "strokeStyle": "solid", + "strokeWidth": 2, + "type": "arrow", + "width": 100, + "x": 0, + "y": 0, + }, + "inserted": { + "isDeleted": true, + }, + }, + }, + "updated": { + "id297": { + "deleted": { + "boundElements": [ + { + "id": "id310", + "type": "arrow", + }, + ], + }, + "inserted": { + "boundElements": [], + }, + }, + "id299": { + "deleted": { + "boundElements": [ + { + "id": "id310", + "type": "arrow", + }, + ], + }, + "inserted": { + "boundElements": [], + }, + }, + }, + }, + "id": "id312", + }, + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": { + "selectedElementIds": { + "id297": true, + }, + "selectedLinearElementId": null, + }, + "inserted": { + "selectedElementIds": { + "id310": true, + }, + "selectedLinearElementId": "id310", + }, + }, + }, + "elements": { + "added": {}, + "removed": {}, + "updated": {}, + }, + "id": "id315", + }, + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": { + "selectedElementIds": { + "id299": true, + }, + }, + "inserted": { + "selectedElementIds": {}, + }, + }, + }, + "elements": { + "added": {}, + "removed": {}, + "updated": {}, + }, + "id": "id318", + }, +] +`; + exports[`history > singleplayer undo/redo > should support changes in elements' order > [end of test] appState 1`] = ` { "activeEmbeddable": null, @@ -18383,15 +18148,15 @@ exports[`history > singleplayer undo/redo > should support changes in elements' "penMode": false, "pendingImageElementId": null, "previousSelectedElementIds": { - "id45": true, + "id189": true, }, "resizingElement": null, "scrollX": 0, "scrollY": 0, "searchMatches": [], "selectedElementIds": { - "id45": true, - "id47": true, + "id189": true, + "id195": true, }, "selectedElementsAreBeingDragged": false, "selectedGroupIds": {}, @@ -18429,7 +18194,7 @@ exports[`history > singleplayer undo/redo > should support changes in elements' "frameId": null, "groupIds": [], "height": 10, - "id": "id46", + "id": "id192", "index": "a1", "isDeleted": false, "link": null, @@ -18461,7 +18226,7 @@ exports[`history > singleplayer undo/redo > should support changes in elements' "frameId": null, "groupIds": [], "height": 10, - "id": "id45", + "id": "id189", "index": "a2", "isDeleted": false, "link": null, @@ -18493,7 +18258,7 @@ exports[`history > singleplayer undo/redo > should support changes in elements' "frameId": null, "groupIds": [], "height": 10, - "id": "id47", + "id": "id195", "index": "a3", "isDeleted": false, "link": null, @@ -18515,273 +18280,273 @@ exports[`history > singleplayer undo/redo > should support changes in elements' } `; -exports[`history > singleplayer undo/redo > should support changes in elements' order > [end of test] history 1`] = ` -History { - "onHistoryChangedEmitter": Emitter { - "subscribers": [ - [Function], - [Function], - ], - }, - "redoStack": [], - "undoStack": [ - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": { - "selectedElementIds": { - "id45": true, - }, - }, - "inserted": { - "selectedElementIds": {}, - }, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map { - "id45" => Delta { - "deleted": { - "angle": 0, - "backgroundColor": "transparent", - "boundElements": null, - "customData": undefined, - "fillStyle": "solid", - "frameId": null, - "groupIds": [], - "height": 10, - "index": "a0", - "isDeleted": false, - "link": null, - "locked": false, - "opacity": 100, - "roughness": 1, - "roundness": { - "type": 3, - }, - "strokeColor": "#1e1e1e", - "strokeStyle": "solid", - "strokeWidth": 2, - "type": "rectangle", - "width": 10, - "x": 10, - "y": 0, - }, - "inserted": { - "isDeleted": true, - }, - }, - }, - "updated": Map {}, - }, - }, - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": { - "selectedElementIds": { - "id46": true, - }, - }, - "inserted": { - "selectedElementIds": { - "id45": true, - }, - }, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map { - "id46" => Delta { - "deleted": { - "angle": 0, - "backgroundColor": "transparent", - "boundElements": null, - "customData": undefined, - "fillStyle": "solid", - "frameId": null, - "groupIds": [], - "height": 10, - "index": "a1", - "isDeleted": false, - "link": null, - "locked": false, - "opacity": 100, - "roughness": 1, - "roundness": { - "type": 3, - }, - "strokeColor": "#1e1e1e", - "strokeStyle": "solid", - "strokeWidth": 2, - "type": "rectangle", - "width": 10, - "x": 20, - "y": 20, - }, - "inserted": { - "isDeleted": true, - }, - }, - }, - "updated": Map {}, - }, - }, - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": { - "selectedElementIds": { - "id47": true, - }, - }, - "inserted": { - "selectedElementIds": { - "id46": true, - }, - }, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map { - "id47" => Delta { - "deleted": { - "angle": 0, - "backgroundColor": "transparent", - "boundElements": null, - "customData": undefined, - "fillStyle": "solid", - "frameId": null, - "groupIds": [], - "height": 10, - "index": "a2", - "isDeleted": false, - "link": null, - "locked": false, - "opacity": 100, - "roughness": 1, - "roundness": { - "type": 3, - }, - "strokeColor": "#1e1e1e", - "strokeStyle": "solid", - "strokeWidth": 2, - "type": "rectangle", - "width": 10, - "x": 40, - "y": 40, - }, - "inserted": { - "isDeleted": true, - }, - }, - }, - "updated": Map {}, - }, - }, - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": {}, - "inserted": {}, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map {}, - "updated": Map { - "id47" => Delta { - "deleted": { - "index": "a0V", - }, - "inserted": { - "index": "a2", - }, - }, - }, - }, - }, - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": { - "selectedElementIds": { - "id45": true, - }, - }, - "inserted": { - "selectedElementIds": { - "id47": true, - }, - }, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map {}, - "updated": Map {}, - }, - }, - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": { - "selectedElementIds": { - "id47": true, - }, - }, - "inserted": { - "selectedElementIds": {}, - }, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map {}, - "updated": Map {}, - }, - }, - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": {}, - "inserted": {}, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map {}, - "updated": Map { - "id45" => Delta { - "deleted": { - "index": "a2", - }, - "inserted": { - "index": "Zz", - }, - }, - "id47" => Delta { - "deleted": { - "index": "a3", - }, - "inserted": { - "index": "a0", - }, - }, - }, - }, - }, - ], -} -`; - exports[`history > singleplayer undo/redo > should support changes in elements' order > [end of test] number of elements 1`] = `3`; exports[`history > singleplayer undo/redo > should support changes in elements' order > [end of test] number of renders 1`] = `20`; +exports[`history > singleplayer undo/redo > should support changes in elements' order > [end of test] redo stack 1`] = `[]`; + +exports[`history > singleplayer undo/redo > should support changes in elements' order > [end of test] undo stack 1`] = ` +[ + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": { + "selectedElementIds": { + "id189": true, + }, + }, + "inserted": { + "selectedElementIds": {}, + }, + }, + }, + "elements": { + "added": {}, + "removed": { + "id189": { + "deleted": { + "angle": 0, + "backgroundColor": "transparent", + "boundElements": null, + "customData": undefined, + "fillStyle": "solid", + "frameId": null, + "groupIds": [], + "height": 10, + "index": "a0", + "isDeleted": false, + "link": null, + "locked": false, + "opacity": 100, + "roughness": 1, + "roundness": { + "type": 3, + }, + "strokeColor": "#1e1e1e", + "strokeStyle": "solid", + "strokeWidth": 2, + "type": "rectangle", + "width": 10, + "x": 10, + "y": 0, + }, + "inserted": { + "isDeleted": true, + }, + }, + }, + "updated": {}, + }, + "id": "id191", + }, + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": { + "selectedElementIds": { + "id192": true, + }, + }, + "inserted": { + "selectedElementIds": { + "id189": true, + }, + }, + }, + }, + "elements": { + "added": {}, + "removed": { + "id192": { + "deleted": { + "angle": 0, + "backgroundColor": "transparent", + "boundElements": null, + "customData": undefined, + "fillStyle": "solid", + "frameId": null, + "groupIds": [], + "height": 10, + "index": "a1", + "isDeleted": false, + "link": null, + "locked": false, + "opacity": 100, + "roughness": 1, + "roundness": { + "type": 3, + }, + "strokeColor": "#1e1e1e", + "strokeStyle": "solid", + "strokeWidth": 2, + "type": "rectangle", + "width": 10, + "x": 20, + "y": 20, + }, + "inserted": { + "isDeleted": true, + }, + }, + }, + "updated": {}, + }, + "id": "id194", + }, + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": { + "selectedElementIds": { + "id195": true, + }, + }, + "inserted": { + "selectedElementIds": { + "id192": true, + }, + }, + }, + }, + "elements": { + "added": {}, + "removed": { + "id195": { + "deleted": { + "angle": 0, + "backgroundColor": "transparent", + "boundElements": null, + "customData": undefined, + "fillStyle": "solid", + "frameId": null, + "groupIds": [], + "height": 10, + "index": "a2", + "isDeleted": false, + "link": null, + "locked": false, + "opacity": 100, + "roughness": 1, + "roundness": { + "type": 3, + }, + "strokeColor": "#1e1e1e", + "strokeStyle": "solid", + "strokeWidth": 2, + "type": "rectangle", + "width": 10, + "x": 40, + "y": 40, + }, + "inserted": { + "isDeleted": true, + }, + }, + }, + "updated": {}, + }, + "id": "id197", + }, + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": {}, + "inserted": {}, + }, + }, + "elements": { + "added": {}, + "removed": {}, + "updated": { + "id195": { + "deleted": { + "index": "a0V", + }, + "inserted": { + "index": "a2", + }, + }, + }, + }, + "id": "id201", + }, + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": { + "selectedElementIds": { + "id189": true, + }, + }, + "inserted": { + "selectedElementIds": { + "id195": true, + }, + }, + }, + }, + "elements": { + "added": {}, + "removed": {}, + "updated": {}, + }, + "id": "id204", + }, + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": { + "selectedElementIds": { + "id195": true, + }, + }, + "inserted": { + "selectedElementIds": {}, + }, + }, + }, + "elements": { + "added": {}, + "removed": {}, + "updated": {}, + }, + "id": "id207", + }, + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": {}, + "inserted": {}, + }, + }, + "elements": { + "added": {}, + "removed": {}, + "updated": { + "id189": { + "deleted": { + "index": "a2", + }, + "inserted": { + "index": "Zz", + }, + }, + "id195": { + "deleted": { + "index": "a3", + }, + "inserted": { + "index": "a0", + }, + }, + }, + }, + "id": "id211", + }, +] +`; + exports[`history > singleplayer undo/redo > should support duplication of groups, appstate group selection and editing group > [end of test] appState 1`] = ` { "activeEmbeddable": null, @@ -18861,19 +18626,19 @@ exports[`history > singleplayer undo/redo > should support duplication of groups "penMode": false, "pendingImageElementId": null, "previousSelectedElementIds": { - "id36": true, + "id161": true, }, "resizingElement": null, "scrollX": 0, "scrollY": 0, "searchMatches": [], "selectedElementIds": { - "id42": true, - "id44": true, + "id184": true, + "id186": true, }, "selectedElementsAreBeingDragged": false, "selectedGroupIds": { - "id43": true, + "id185": true, }, "selectionElement": null, "shouldCacheIgnoreZoom": false, @@ -18911,7 +18676,7 @@ exports[`history > singleplayer undo/redo > should support duplication of groups "A", ], "height": 100, - "id": "id35", + "id": "id160", "index": "a0", "isDeleted": false, "link": null, @@ -18945,7 +18710,7 @@ exports[`history > singleplayer undo/redo > should support duplication of groups "A", ], "height": 100, - "id": "id36", + "id": "id161", "index": "a1", "isDeleted": false, "link": null, @@ -18976,10 +18741,10 @@ exports[`history > singleplayer undo/redo > should support duplication of groups "fillStyle": "solid", "frameId": null, "groupIds": [ - "id43", + "id185", ], "height": 100, - "id": "id42", + "id": "id184", "index": "a1G", "isDeleted": false, "link": null, @@ -19010,10 +18775,10 @@ exports[`history > singleplayer undo/redo > should support duplication of groups "fillStyle": "solid", "frameId": null, "groupIds": [ - "id43", + "id185", ], "height": 100, - "id": "id44", + "id": "id186", "index": "a1V", "isDeleted": false, "link": null, @@ -19044,10 +18809,10 @@ exports[`history > singleplayer undo/redo > should support duplication of groups "fillStyle": "solid", "frameId": null, "groupIds": [ - "id40", + "id177", ], "height": 100, - "id": "id39", + "id": "id176", "index": "a2", "isDeleted": true, "link": null, @@ -19078,10 +18843,10 @@ exports[`history > singleplayer undo/redo > should support duplication of groups "fillStyle": "solid", "frameId": null, "groupIds": [ - "id40", + "id177", ], "height": 100, - "id": "id41", + "id": "id178", "index": "a3", "isDeleted": true, "link": null, @@ -19103,211 +18868,206 @@ exports[`history > singleplayer undo/redo > should support duplication of groups } `; -exports[`history > singleplayer undo/redo > should support duplication of groups, appstate group selection and editing group > [end of test] history 1`] = ` -History { - "onHistoryChangedEmitter": Emitter { - "subscribers": [ - [Function], - [Function], - ], - }, - "redoStack": [], - "undoStack": [ - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": { - "selectedElementIds": { - "id35": true, - "id36": true, - }, - "selectedGroupIds": { - "A": true, - }, - }, - "inserted": { - "selectedElementIds": {}, - "selectedGroupIds": {}, - }, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map { - "id35" => Delta { - "deleted": { - "angle": 0, - "backgroundColor": "transparent", - "boundElements": null, - "customData": undefined, - "fillStyle": "solid", - "frameId": null, - "groupIds": [ - "A", - ], - "height": 100, - "index": "a0", - "isDeleted": false, - "link": null, - "locked": false, - "opacity": 100, - "roughness": 1, - "roundness": { - "type": 3, - }, - "strokeColor": "#1e1e1e", - "strokeStyle": "solid", - "strokeWidth": 2, - "type": "rectangle", - "width": 100, - "x": 0, - "y": 0, - }, - "inserted": { - "isDeleted": true, - }, - }, - "id36" => Delta { - "deleted": { - "angle": 0, - "backgroundColor": "transparent", - "boundElements": null, - "customData": undefined, - "fillStyle": "solid", - "frameId": null, - "groupIds": [ - "A", - ], - "height": 100, - "index": "a1", - "isDeleted": false, - "link": null, - "locked": false, - "opacity": 100, - "roughness": 1, - "roundness": { - "type": 3, - }, - "strokeColor": "#1e1e1e", - "strokeStyle": "solid", - "strokeWidth": 2, - "type": "rectangle", - "width": 100, - "x": 100, - "y": 100, - }, - "inserted": { - "isDeleted": true, - }, - }, - }, - "updated": Map {}, - }, - }, - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": { - "selectedElementIds": { - "id42": true, - "id44": true, - }, - "selectedGroupIds": { - "id43": true, - }, - }, - "inserted": { - "selectedElementIds": { - "id35": true, - "id36": true, - }, - "selectedGroupIds": { - "A": true, - }, - }, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map { - "id42" => Delta { - "deleted": { - "angle": 0, - "backgroundColor": "transparent", - "boundElements": null, - "customData": undefined, - "fillStyle": "solid", - "frameId": null, - "groupIds": [ - "id43", - ], - "height": 100, - "index": "a1G", - "isDeleted": false, - "link": null, - "locked": false, - "opacity": 100, - "roughness": 1, - "roundness": { - "type": 3, - }, - "strokeColor": "#1e1e1e", - "strokeStyle": "solid", - "strokeWidth": 2, - "type": "rectangle", - "width": 100, - "x": 10, - "y": 10, - }, - "inserted": { - "isDeleted": true, - }, - }, - "id44" => Delta { - "deleted": { - "angle": 0, - "backgroundColor": "transparent", - "boundElements": null, - "customData": undefined, - "fillStyle": "solid", - "frameId": null, - "groupIds": [ - "id43", - ], - "height": 100, - "index": "a1V", - "isDeleted": false, - "link": null, - "locked": false, - "opacity": 100, - "roughness": 1, - "roundness": { - "type": 3, - }, - "strokeColor": "#1e1e1e", - "strokeStyle": "solid", - "strokeWidth": 2, - "type": "rectangle", - "width": 100, - "x": 110, - "y": 110, - }, - "inserted": { - "isDeleted": true, - }, - }, - }, - "updated": Map {}, - }, - }, - ], -} -`; - exports[`history > singleplayer undo/redo > should support duplication of groups, appstate group selection and editing group > [end of test] number of elements 1`] = `6`; exports[`history > singleplayer undo/redo > should support duplication of groups, appstate group selection and editing group > [end of test] number of renders 1`] = `18`; +exports[`history > singleplayer undo/redo > should support duplication of groups, appstate group selection and editing group > [end of test] redo stack 1`] = `[]`; + +exports[`history > singleplayer undo/redo > should support duplication of groups, appstate group selection and editing group > [end of test] undo stack 1`] = ` +[ + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": { + "selectedElementIds": { + "id160": true, + "id161": true, + }, + "selectedGroupIds": { + "A": true, + }, + }, + "inserted": { + "selectedElementIds": {}, + "selectedGroupIds": {}, + }, + }, + }, + "elements": { + "added": {}, + "removed": { + "id160": { + "deleted": { + "angle": 0, + "backgroundColor": "transparent", + "boundElements": null, + "customData": undefined, + "fillStyle": "solid", + "frameId": null, + "groupIds": [ + "A", + ], + "height": 100, + "index": "a0", + "isDeleted": false, + "link": null, + "locked": false, + "opacity": 100, + "roughness": 1, + "roundness": { + "type": 3, + }, + "strokeColor": "#1e1e1e", + "strokeStyle": "solid", + "strokeWidth": 2, + "type": "rectangle", + "width": 100, + "x": 0, + "y": 0, + }, + "inserted": { + "isDeleted": true, + }, + }, + "id161": { + "deleted": { + "angle": 0, + "backgroundColor": "transparent", + "boundElements": null, + "customData": undefined, + "fillStyle": "solid", + "frameId": null, + "groupIds": [ + "A", + ], + "height": 100, + "index": "a1", + "isDeleted": false, + "link": null, + "locked": false, + "opacity": 100, + "roughness": 1, + "roundness": { + "type": 3, + }, + "strokeColor": "#1e1e1e", + "strokeStyle": "solid", + "strokeWidth": 2, + "type": "rectangle", + "width": 100, + "x": 100, + "y": 100, + }, + "inserted": { + "isDeleted": true, + }, + }, + }, + "updated": {}, + }, + "id": "id164", + }, + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": { + "selectedElementIds": { + "id184": true, + "id186": true, + }, + "selectedGroupIds": { + "id185": true, + }, + }, + "inserted": { + "selectedElementIds": { + "id160": true, + "id161": true, + }, + "selectedGroupIds": { + "A": true, + }, + }, + }, + }, + "elements": { + "added": {}, + "removed": { + "id184": { + "deleted": { + "angle": 0, + "backgroundColor": "transparent", + "boundElements": null, + "customData": undefined, + "fillStyle": "solid", + "frameId": null, + "groupIds": [ + "id185", + ], + "height": 100, + "index": "a1G", + "isDeleted": false, + "link": null, + "locked": false, + "opacity": 100, + "roughness": 1, + "roundness": { + "type": 3, + }, + "strokeColor": "#1e1e1e", + "strokeStyle": "solid", + "strokeWidth": 2, + "type": "rectangle", + "width": 100, + "x": 10, + "y": 10, + }, + "inserted": { + "isDeleted": true, + }, + }, + "id186": { + "deleted": { + "angle": 0, + "backgroundColor": "transparent", + "boundElements": null, + "customData": undefined, + "fillStyle": "solid", + "frameId": null, + "groupIds": [ + "id185", + ], + "height": 100, + "index": "a1V", + "isDeleted": false, + "link": null, + "locked": false, + "opacity": 100, + "roughness": 1, + "roundness": { + "type": 3, + }, + "strokeColor": "#1e1e1e", + "strokeStyle": "solid", + "strokeWidth": 2, + "type": "rectangle", + "width": 100, + "x": 110, + "y": 110, + }, + "inserted": { + "isDeleted": true, + }, + }, + }, + "updated": {}, + }, + "id": "id188", + }, +] +`; + exports[`history > singleplayer undo/redo > should support element creation, deletion and appstate element selection change > [end of test] appState 1`] = ` { "activeEmbeddable": null, @@ -19387,7 +19147,7 @@ exports[`history > singleplayer undo/redo > should support element creation, del "penMode": false, "pendingImageElementId": null, "previousSelectedElementIds": { - "id23": true, + "id93": true, }, "resizingElement": null, "scrollX": 0, @@ -19430,7 +19190,7 @@ exports[`history > singleplayer undo/redo > should support element creation, del "frameId": null, "groupIds": [], "height": 10, - "id": "id22", + "id": "id90", "index": "a0", "isDeleted": false, "link": null, @@ -19462,7 +19222,7 @@ exports[`history > singleplayer undo/redo > should support element creation, del "frameId": null, "groupIds": [], "height": 10, - "id": "id23", + "id": "id93", "index": "a1", "isDeleted": true, "link": null, @@ -19494,7 +19254,7 @@ exports[`history > singleplayer undo/redo > should support element creation, del "frameId": null, "groupIds": [], "height": 10, - "id": "id24", + "id": "id96", "index": "a2", "isDeleted": true, "link": null, @@ -19516,257 +19276,256 @@ exports[`history > singleplayer undo/redo > should support element creation, del } `; -exports[`history > singleplayer undo/redo > should support element creation, deletion and appstate element selection change > [end of test] history 1`] = ` -History { - "onHistoryChangedEmitter": Emitter { - "subscribers": [ - [Function], - [Function], - ], - }, - "redoStack": [], - "undoStack": [ - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": { - "selectedElementIds": { - "id22": true, - }, - }, - "inserted": { - "selectedElementIds": {}, - }, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map { - "id22" => Delta { - "deleted": { - "angle": 0, - "backgroundColor": "transparent", - "boundElements": null, - "customData": undefined, - "fillStyle": "solid", - "frameId": null, - "groupIds": [], - "height": 10, - "index": "a0", - "isDeleted": false, - "link": null, - "locked": false, - "opacity": 100, - "roughness": 1, - "roundness": { - "type": 3, - }, - "strokeColor": "#1e1e1e", - "strokeStyle": "solid", - "strokeWidth": 2, - "type": "rectangle", - "width": 10, - "x": 10, - "y": 0, - }, - "inserted": { - "isDeleted": true, - }, - }, - }, - "updated": Map {}, - }, - }, - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": { - "selectedElementIds": { - "id23": true, - }, - }, - "inserted": { - "selectedElementIds": { - "id22": true, - }, - }, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map { - "id23" => Delta { - "deleted": { - "angle": 0, - "backgroundColor": "transparent", - "boundElements": null, - "customData": undefined, - "fillStyle": "solid", - "frameId": null, - "groupIds": [], - "height": 10, - "index": "a1", - "isDeleted": false, - "link": null, - "locked": false, - "opacity": 100, - "roughness": 1, - "roundness": { - "type": 3, - }, - "strokeColor": "#1e1e1e", - "strokeStyle": "solid", - "strokeWidth": 2, - "type": "rectangle", - "width": 10, - "x": 20, - "y": 20, - }, - "inserted": { - "isDeleted": true, - }, - }, - }, - "updated": Map {}, - }, - }, - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": { - "selectedElementIds": { - "id24": true, - }, - }, - "inserted": { - "selectedElementIds": { - "id23": true, - }, - }, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map { - "id24" => Delta { - "deleted": { - "angle": 0, - "backgroundColor": "transparent", - "boundElements": null, - "customData": undefined, - "fillStyle": "solid", - "frameId": null, - "groupIds": [], - "height": 10, - "index": "a2", - "isDeleted": false, - "link": null, - "locked": false, - "opacity": 100, - "roughness": 1, - "roundness": { - "type": 3, - }, - "strokeColor": "#1e1e1e", - "strokeStyle": "solid", - "strokeWidth": 2, - "type": "rectangle", - "width": 10, - "x": 40, - "y": 40, - }, - "inserted": { - "isDeleted": true, - }, - }, - }, - "updated": Map {}, - }, - }, - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": { - "selectedElementIds": { - "id23": true, - }, - }, - "inserted": { - "selectedElementIds": { - "id24": true, - }, - }, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map {}, - "updated": Map {}, - }, - }, - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": { - "selectedElementIds": { - "id24": true, - }, - }, - "inserted": { - "selectedElementIds": {}, - }, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map {}, - "updated": Map {}, - }, - }, - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": { - "selectedElementIds": {}, - }, - "inserted": { - "selectedElementIds": { - "id23": true, - "id24": true, - }, - }, - }, - }, - "elementsChange": ElementsChange { - "added": Map { - "id23" => Delta { - "deleted": { - "isDeleted": true, - }, - "inserted": { - "isDeleted": false, - }, - }, - "id24" => Delta { - "deleted": { - "isDeleted": true, - }, - "inserted": { - "isDeleted": false, - }, - }, - }, - "removed": Map {}, - "updated": Map {}, - }, - }, - ], -} -`; - exports[`history > singleplayer undo/redo > should support element creation, deletion and appstate element selection change > [end of test] number of elements 1`] = `3`; -exports[`history > singleplayer undo/redo > should support element creation, deletion and appstate element selection change > [end of test] number of renders 1`] = `27`; +exports[`history > singleplayer undo/redo > should support element creation, deletion and appstate element selection change > [end of test] number of renders 1`] = `29`; + +exports[`history > singleplayer undo/redo > should support element creation, deletion and appstate element selection change > [end of test] redo stack 1`] = `[]`; + +exports[`history > singleplayer undo/redo > should support element creation, deletion and appstate element selection change > [end of test] undo stack 1`] = ` +[ + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": { + "selectedElementIds": { + "id90": true, + }, + }, + "inserted": { + "selectedElementIds": {}, + }, + }, + }, + "elements": { + "added": {}, + "removed": { + "id90": { + "deleted": { + "angle": 0, + "backgroundColor": "transparent", + "boundElements": null, + "customData": undefined, + "fillStyle": "solid", + "frameId": null, + "groupIds": [], + "height": 10, + "index": "a0", + "isDeleted": false, + "link": null, + "locked": false, + "opacity": 100, + "roughness": 1, + "roundness": { + "type": 3, + }, + "strokeColor": "#1e1e1e", + "strokeStyle": "solid", + "strokeWidth": 2, + "type": "rectangle", + "width": 10, + "x": 10, + "y": 0, + }, + "inserted": { + "isDeleted": true, + }, + }, + }, + "updated": {}, + }, + "id": "id113", + }, + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": { + "selectedElementIds": { + "id93": true, + }, + }, + "inserted": { + "selectedElementIds": { + "id90": true, + }, + }, + }, + }, + "elements": { + "added": {}, + "removed": { + "id93": { + "deleted": { + "angle": 0, + "backgroundColor": "transparent", + "boundElements": null, + "customData": undefined, + "fillStyle": "solid", + "frameId": null, + "groupIds": [], + "height": 10, + "index": "a1", + "isDeleted": false, + "link": null, + "locked": false, + "opacity": 100, + "roughness": 1, + "roundness": { + "type": 3, + }, + "strokeColor": "#1e1e1e", + "strokeStyle": "solid", + "strokeWidth": 2, + "type": "rectangle", + "width": 10, + "x": 20, + "y": 20, + }, + "inserted": { + "isDeleted": true, + }, + }, + }, + "updated": {}, + }, + "id": "id114", + }, + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": { + "selectedElementIds": { + "id96": true, + }, + }, + "inserted": { + "selectedElementIds": { + "id93": true, + }, + }, + }, + }, + "elements": { + "added": {}, + "removed": { + "id96": { + "deleted": { + "angle": 0, + "backgroundColor": "transparent", + "boundElements": null, + "customData": undefined, + "fillStyle": "solid", + "frameId": null, + "groupIds": [], + "height": 10, + "index": "a2", + "isDeleted": false, + "link": null, + "locked": false, + "opacity": 100, + "roughness": 1, + "roundness": { + "type": 3, + }, + "strokeColor": "#1e1e1e", + "strokeStyle": "solid", + "strokeWidth": 2, + "type": "rectangle", + "width": 10, + "x": 40, + "y": 40, + }, + "inserted": { + "isDeleted": true, + }, + }, + }, + "updated": {}, + }, + "id": "id115", + }, + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": { + "selectedElementIds": { + "id93": true, + }, + }, + "inserted": { + "selectedElementIds": { + "id96": true, + }, + }, + }, + }, + "elements": { + "added": {}, + "removed": {}, + "updated": {}, + }, + "id": "id116", + }, + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": { + "selectedElementIds": { + "id96": true, + }, + }, + "inserted": { + "selectedElementIds": {}, + }, + }, + }, + "elements": { + "added": {}, + "removed": {}, + "updated": {}, + }, + "id": "id117", + }, + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": { + "selectedElementIds": {}, + }, + "inserted": { + "selectedElementIds": { + "id93": true, + "id96": true, + }, + }, + }, + }, + "elements": { + "added": { + "id93": { + "deleted": { + "isDeleted": true, + }, + "inserted": { + "isDeleted": false, + }, + }, + "id96": { + "deleted": { + "isDeleted": true, + }, + "inserted": { + "isDeleted": false, + }, + }, + }, + "removed": {}, + "updated": {}, + }, + "id": "id118", + }, +] +`; exports[`history > singleplayer undo/redo > should support linear element creation and points manipulation through the editor > [end of test] appState 1`] = ` { @@ -19847,14 +19606,14 @@ exports[`history > singleplayer undo/redo > should support linear element creati "penMode": false, "pendingImageElementId": null, "previousSelectedElementIds": { - "id27": true, + "id119": true, }, "resizingElement": null, "scrollX": 0, "scrollY": 0, "searchMatches": [], "selectedElementIds": { - "id27": true, + "id119": true, }, "selectedElementsAreBeingDragged": false, "selectedGroupIds": {}, @@ -19895,7 +19654,7 @@ exports[`history > singleplayer undo/redo > should support linear element creati "frameId": null, "groupIds": [], "height": 20, - "id": "id27", + "id": "id119", "index": "a0", "isDeleted": false, "lastCommittedPoint": [ @@ -19937,245 +19696,244 @@ exports[`history > singleplayer undo/redo > should support linear element creati } `; -exports[`history > singleplayer undo/redo > should support linear element creation and points manipulation through the editor > [end of test] history 1`] = ` -History { - "onHistoryChangedEmitter": Emitter { - "subscribers": [ - [Function], - [Function], - ], - }, - "redoStack": [], - "undoStack": [ - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": { - "selectedElementIds": { - "id27": true, - }, - }, - "inserted": { - "selectedElementIds": {}, - }, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map { - "id27" => Delta { - "deleted": { - "angle": 0, - "backgroundColor": "transparent", - "boundElements": null, - "customData": undefined, - "elbowed": false, - "endArrowhead": "arrow", - "endBinding": null, - "fillStyle": "solid", - "frameId": null, - "groupIds": [], - "height": 10, - "index": "a0", - "isDeleted": false, - "lastCommittedPoint": [ - 10, - 10, - ], - "link": null, - "locked": false, - "opacity": 100, - "points": [ - [ - 0, - 0, - ], - [ - 10, - 10, - ], - ], - "roughness": 1, - "roundness": { - "type": 2, - }, - "startArrowhead": null, - "startBinding": null, - "strokeColor": "#1e1e1e", - "strokeStyle": "solid", - "strokeWidth": 2, - "type": "arrow", - "width": 10, - "x": 0, - "y": 0, - }, - "inserted": { - "isDeleted": true, - }, - }, - }, - "updated": Map {}, - }, - }, - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": {}, - "inserted": {}, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map {}, - "updated": Map { - "id27" => Delta { - "deleted": { - "lastCommittedPoint": [ - 20, - 0, - ], - "points": [ - [ - 0, - 0, - ], - [ - 10, - 10, - ], - [ - 20, - 0, - ], - ], - "width": 20, - }, - "inserted": { - "lastCommittedPoint": [ - 10, - 10, - ], - "points": [ - [ - 0, - 0, - ], - [ - 10, - 10, - ], - ], - "width": 10, - }, - }, - }, - }, - }, - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": { - "selectedLinearElementId": "id27", - }, - "inserted": { - "selectedLinearElementId": null, - }, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map {}, - "updated": Map {}, - }, - }, - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": { - "editingLinearElementId": "id27", - }, - "inserted": { - "editingLinearElementId": null, - }, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map {}, - "updated": Map {}, - }, - }, - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": {}, - "inserted": {}, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map {}, - "updated": Map { - "id27" => Delta { - "deleted": { - "height": 20, - "points": [ - [ - 0, - 0, - ], - [ - 10, - 10, - ], - [ - 20, - 20, - ], - ], - }, - "inserted": { - "height": 10, - "points": [ - [ - 0, - 0, - ], - [ - 10, - 10, - ], - [ - 20, - 0, - ], - ], - }, - }, - }, - }, - }, - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": { - "editingLinearElementId": null, - }, - "inserted": { - "editingLinearElementId": "id27", - }, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map {}, - "updated": Map {}, - }, - }, - ], -} -`; - exports[`history > singleplayer undo/redo > should support linear element creation and points manipulation through the editor > [end of test] number of elements 1`] = `1`; exports[`history > singleplayer undo/redo > should support linear element creation and points manipulation through the editor > [end of test] number of renders 1`] = `20`; + +exports[`history > singleplayer undo/redo > should support linear element creation and points manipulation through the editor > [end of test] redo stack 1`] = `[]`; + +exports[`history > singleplayer undo/redo > should support linear element creation and points manipulation through the editor > [end of test] undo stack 1`] = ` +[ + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": { + "selectedElementIds": { + "id119": true, + }, + }, + "inserted": { + "selectedElementIds": {}, + }, + }, + }, + "elements": { + "added": {}, + "removed": { + "id119": { + "deleted": { + "angle": 0, + "backgroundColor": "transparent", + "boundElements": null, + "customData": undefined, + "elbowed": false, + "endArrowhead": "arrow", + "endBinding": null, + "fillStyle": "solid", + "frameId": null, + "groupIds": [], + "height": 10, + "index": "a0", + "isDeleted": false, + "lastCommittedPoint": [ + 10, + 10, + ], + "link": null, + "locked": false, + "opacity": 100, + "points": [ + [ + 0, + 0, + ], + [ + 10, + 10, + ], + ], + "roughness": 1, + "roundness": { + "type": 2, + }, + "startArrowhead": null, + "startBinding": null, + "strokeColor": "#1e1e1e", + "strokeStyle": "solid", + "strokeWidth": 2, + "type": "arrow", + "width": 10, + "x": 0, + "y": 0, + }, + "inserted": { + "isDeleted": true, + }, + }, + }, + "updated": {}, + }, + "id": "id142", + }, + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": {}, + "inserted": {}, + }, + }, + "elements": { + "added": {}, + "removed": {}, + "updated": { + "id119": { + "deleted": { + "lastCommittedPoint": [ + 20, + 0, + ], + "points": [ + [ + 0, + 0, + ], + [ + 10, + 10, + ], + [ + 20, + 0, + ], + ], + "width": 20, + }, + "inserted": { + "lastCommittedPoint": [ + 10, + 10, + ], + "points": [ + [ + 0, + 0, + ], + [ + 10, + 10, + ], + ], + "width": 10, + }, + }, + }, + }, + "id": "id143", + }, + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": { + "selectedLinearElementId": "id119", + }, + "inserted": { + "selectedLinearElementId": null, + }, + }, + }, + "elements": { + "added": {}, + "removed": {}, + "updated": {}, + }, + "id": "id144", + }, + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": { + "editingLinearElementId": "id119", + }, + "inserted": { + "editingLinearElementId": null, + }, + }, + }, + "elements": { + "added": {}, + "removed": {}, + "updated": {}, + }, + "id": "id145", + }, + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": {}, + "inserted": {}, + }, + }, + "elements": { + "added": {}, + "removed": {}, + "updated": { + "id119": { + "deleted": { + "height": 20, + "points": [ + [ + 0, + 0, + ], + [ + 10, + 10, + ], + [ + 20, + 20, + ], + ], + }, + "inserted": { + "height": 10, + "points": [ + [ + 0, + 0, + ], + [ + 10, + 10, + ], + [ + 20, + 0, + ], + ], + }, + }, + }, + }, + "id": "id146", + }, + { + "appState": AppStateDelta { + "delta": Delta { + "deleted": { + "editingLinearElementId": null, + }, + "inserted": { + "editingLinearElementId": "id119", + }, + }, + }, + "elements": { + "added": {}, + "removed": {}, + "updated": {}, + }, + "id": "id147", + }, +] +`; diff --git a/packages/excalidraw/tests/__snapshots__/move.test.tsx.snap b/packages/excalidraw/tests/__snapshots__/move.test.tsx.snap index 5078a31a0..0ee4ea46a 100644 --- a/packages/excalidraw/tests/__snapshots__/move.test.tsx.snap +++ b/packages/excalidraw/tests/__snapshots__/move.test.tsx.snap @@ -44,7 +44,7 @@ exports[`duplicate element on move when ALT is clicked > rectangle 6`] = ` "frameId": null, "groupIds": [], "height": 50, - "id": "id2", + "id": "id4", "index": "a1", "isDeleted": false, "link": null, @@ -108,7 +108,7 @@ exports[`move element > rectangles with binding arrow 5`] = ` "backgroundColor": "transparent", "boundElements": [ { - "id": "id2", + "id": "id6", "type": "arrow", }, ], @@ -147,7 +147,7 @@ exports[`move element > rectangles with binding arrow 6`] = ` "backgroundColor": "transparent", "boundElements": [ { - "id": "id2", + "id": "id6", "type": "arrow", }, ], @@ -156,7 +156,7 @@ exports[`move element > rectangles with binding arrow 6`] = ` "frameId": null, "groupIds": [], "height": 300, - "id": "id1", + "id": "id3", "index": "a1", "isDeleted": false, "link": null, @@ -189,7 +189,7 @@ exports[`move element > rectangles with binding arrow 7`] = ` "elbowed": false, "endArrowhead": "arrow", "endBinding": { - "elementId": "id1", + "elementId": "id3", "focus": "-0.46667", "gap": 10, }, @@ -197,7 +197,7 @@ exports[`move element > rectangles with binding arrow 7`] = ` "frameId": null, "groupIds": [], "height": "87.29887", - "id": "id2", + "id": "id6", "index": "a2", "isDeleted": false, "lastCommittedPoint": null, diff --git a/packages/excalidraw/tests/contextmenu.test.tsx b/packages/excalidraw/tests/contextmenu.test.tsx index ef36e3d52..75de2717f 100644 --- a/packages/excalidraw/tests/contextmenu.test.tsx +++ b/packages/excalidraw/tests/contextmenu.test.tsx @@ -23,6 +23,7 @@ import { waitFor, togglePopover, unmountComponent, + checkpointHistory, } from "./test-utils"; import type { ShortcutName } from "../actions/shortcuts"; @@ -33,11 +34,12 @@ const checkpoint = (name: string) => { `[${name}] number of renders`, ); expect(h.state).toMatchSnapshot(`[${name}] appState`); - expect(h.history).toMatchSnapshot(`[${name}] history`); expect(h.elements.length).toMatchSnapshot(`[${name}] number of elements`); h.elements.forEach((element, i) => expect(element).toMatchSnapshot(`[${name}] element ${i}`), ); + + checkpointHistory(h.history, name); }; const mouse = new Pointer("mouse"); diff --git a/packages/excalidraw/tests/history.test.tsx b/packages/excalidraw/tests/history.test.tsx index 8dd65c7a5..f8e01f469 100644 --- a/packages/excalidraw/tests/history.test.tsx +++ b/packages/excalidraw/tests/history.test.tsx @@ -23,6 +23,10 @@ import { import "@excalidraw/utils/test-utils"; +import { ElementsDelta, AppStateDelta } from "@excalidraw/element/delta"; + +import { CaptureUpdateAction, StoreDelta } from "@excalidraw/element/store"; + import type { LocalPoint, Radians } from "@excalidraw/math"; import type { @@ -46,11 +50,8 @@ import { import { createUndoAction, createRedoAction } from "../actions/actionHistory"; import { actionToggleViewMode } from "../actions/actionToggleViewMode"; import { getDefaultAppState } from "../appState"; -import { HistoryEntry } from "../history"; import { Excalidraw } from "../index"; import * as StaticScene from "../renderer/staticScene"; -import { Snapshot, CaptureUpdateAction } from "../store"; -import { AppStateChange, ElementsChange } from "../change"; import { API } from "./helpers/api"; import { Keyboard, Pointer, UI } from "./helpers/ui"; @@ -61,6 +62,7 @@ import { render, togglePopover, getCloneByOrigId, + checkpointHistory, } from "./test-utils"; import type { AppState } from "../types"; @@ -82,13 +84,15 @@ const checkpoint = (name: string) => { ...strippedAppState } = h.state; expect(strippedAppState).toMatchSnapshot(`[${name}] appState`); - expect(h.history).toMatchSnapshot(`[${name}] history`); expect(h.elements.length).toMatchSnapshot(`[${name}] number of elements`); + h.elements .map(({ seed, versionNonce, ...strippedElement }) => strippedElement) .forEach((element, i) => expect(element).toMatchSnapshot(`[${name}] element ${i}`), ); + + checkpointHistory(h.history, name); }; const renderStaticScene = vi.spyOn(StaticScene, "renderStaticScene"); @@ -116,12 +120,12 @@ describe("history", () => { API.setElements([rect]); - const corrupedEntry = HistoryEntry.create( - AppStateChange.empty(), - ElementsChange.empty(), + const corrupedEntry = StoreDelta.create( + ElementsDelta.empty(), + AppStateDelta.empty(), ); - vi.spyOn(corrupedEntry, "applyTo").mockImplementation(() => { + vi.spyOn(corrupedEntry.elements, "applyTo").mockImplementation(() => { throw new Error("Oh no, I am corrupted!"); }); @@ -136,7 +140,6 @@ describe("history", () => { h.history.undo( arrayToMap(h.elements) as SceneElementsMap, appState, - Snapshot.empty(), ) as any, ); } catch (e) { @@ -157,7 +160,6 @@ describe("history", () => { h.history.redo( arrayToMap(h.elements) as SceneElementsMap, appState, - Snapshot.empty(), ) as any, ); } catch (e) { @@ -454,8 +456,8 @@ describe("history", () => { expect(h.history.isUndoStackEmpty).toBeTruthy(); }); - const undoAction = createUndoAction(h.history, h.store); - const redoAction = createRedoAction(h.history, h.store); + const undoAction = createUndoAction(h.history); + const redoAction = createRedoAction(h.history); // noop API.executeAction(undoAction); expect(h.elements).toEqual([ @@ -531,8 +533,8 @@ describe("history", () => { expect.objectContaining({ id: "B", isDeleted: false }), ]); - const undoAction = createUndoAction(h.history, h.store); - const redoAction = createRedoAction(h.history, h.store); + const undoAction = createUndoAction(h.history); + const redoAction = createRedoAction(h.history); API.executeAction(undoAction); expect(API.getSnapshot()).toEqual([ @@ -1713,8 +1715,8 @@ describe("history", () => { />, ); - const undoAction = createUndoAction(h.history, h.store); - const redoAction = createRedoAction(h.history, h.store); + const undoAction = createUndoAction(h.history); + const redoAction = createRedoAction(h.history); await waitFor(() => { expect(h.elements).toEqual([expect.objectContaining({ id: "A" })]); @@ -1763,7 +1765,7 @@ describe("history", () => { />, ); - const undoAction = createUndoAction(h.history, h.store); + const undoAction = createUndoAction(h.history); await waitFor(() => { expect(h.elements).toEqual([expect.objectContaining({ id: "A" })]); diff --git a/packages/excalidraw/tests/linearElementEditor.test.tsx b/packages/excalidraw/tests/linearElementEditor.test.tsx index 2e32e8821..dad797334 100644 --- a/packages/excalidraw/tests/linearElementEditor.test.tsx +++ b/packages/excalidraw/tests/linearElementEditor.test.tsx @@ -1384,19 +1384,30 @@ describe("Test Linear Elements", () => { const [origStartX, origStartY] = [line.x, line.y]; act(() => { - LinearElementEditor.movePoints(line, h.app.scene, [ - { - index: 0, - point: pointFrom(line.points[0][0] + 10, line.points[0][1] + 10), - }, - { - index: line.points.length - 1, - point: pointFrom( - line.points[line.points.length - 1][0] - 10, - line.points[line.points.length - 1][1] - 10, - ), - }, - ]); + LinearElementEditor.movePoints( + line, + h.app.scene, + new Map([ + [ + 0, + { + point: pointFrom( + line.points[0][0] + 10, + line.points[0][1] + 10, + ), + }, + ], + [ + line.points.length - 1, + { + point: pointFrom( + line.points[line.points.length - 1][0] - 10, + line.points[line.points.length - 1][1] - 10, + ), + }, + ], + ]), + ); }); expect(line.x).toBe(origStartX + 10); expect(line.y).toBe(origStartY + 10); diff --git a/packages/excalidraw/tests/regressionTests.test.tsx b/packages/excalidraw/tests/regressionTests.test.tsx index 68765024e..9deefb8c7 100644 --- a/packages/excalidraw/tests/regressionTests.test.tsx +++ b/packages/excalidraw/tests/regressionTests.test.tsx @@ -14,6 +14,7 @@ import { API } from "./helpers/api"; import { Keyboard, Pointer, UI } from "./helpers/ui"; import { assertSelectedElements, + checkpointHistory, fireEvent, render, screen, @@ -39,11 +40,12 @@ const checkpoint = (name: string) => { `[${name}] number of renders`, ); expect(h.state).toMatchSnapshot(`[${name}] appState`); - expect(h.history).toMatchSnapshot(`[${name}] history`); expect(h.elements.length).toMatchSnapshot(`[${name}] number of elements`); h.elements.forEach((element, i) => expect(element).toMatchSnapshot(`[${name}] element ${i}`), ); + + checkpointHistory(h.history, name); }; beforeEach(async () => { unmountComponent(); diff --git a/packages/excalidraw/tests/test-utils.ts b/packages/excalidraw/tests/test-utils.ts index 894d748dd..ea0f66e44 100644 --- a/packages/excalidraw/tests/test-utils.ts +++ b/packages/excalidraw/tests/test-utils.ts @@ -22,6 +22,8 @@ import { STORAGE_KEYS } from "../../../excalidraw-app/app_constants"; import { Pointer, UI } from "./helpers/ui"; import * as toolQueries from "./queries/toolQueries"; +import type { History } from "../history"; + import type { RenderResult, RenderOptions } from "@testing-library/react"; import type { ImportedDataState } from "../data/types"; @@ -432,3 +434,45 @@ export const assertElements = >( expect(h.state.selectedElementIds).toEqual(selectedElementIds); }; + +const stripSeed = (deltas: Record) => + Object.entries(deltas).reduce((acc, curr) => { + const { inserted, deleted, ...rest } = curr[1]; + + delete inserted.seed; + delete deleted.seed; + + acc[curr[0]] = { + inserted, + deleted, + ...rest, + }; + + return acc; + }, {} as Record); + +export const checkpointHistory = (history: History, name: string) => { + expect( + history.undoStack.map((x) => ({ + ...x, + elements: { + ...x.elements, + added: stripSeed(x.elements.added), + removed: stripSeed(x.elements.removed), + updated: stripSeed(x.elements.updated), + }, + })), + ).toMatchSnapshot(`[${name}] undo stack`); + + expect( + history.redoStack.map((x) => ({ + ...x, + elements: { + ...x.elements, + added: stripSeed(x.elements.added), + removed: stripSeed(x.elements.removed), + updated: stripSeed(x.elements.updated), + }, + })), + ).toMatchSnapshot(`[${name}] redo stack`); +}; diff --git a/packages/excalidraw/types.ts b/packages/excalidraw/types.ts index bd6e21fdc..146906d50 100644 --- a/packages/excalidraw/types.ts +++ b/packages/excalidraw/types.ts @@ -43,6 +43,12 @@ import type { MakeBrand, } from "@excalidraw/common/utility-types"; +import type { + CaptureUpdateActionType, + DurableIncrement, + EphemeralIncrement, +} from "@excalidraw/element/store"; + import type { Action } from "./actions/types"; import type { Spreadsheet } from "./charts"; import type { ClipboardData } from "./clipboard"; @@ -51,7 +57,6 @@ import type Library from "./data/library"; import type { FileSystemHandle } from "./data/filesystem"; import type { ContextMenuItems } from "./components/ContextMenu"; import type { SnapLine } from "./snapping"; -import type { CaptureUpdateActionType } from "./store"; import type { ImportedDataState } from "./data/types"; import type { Language } from "./i18n"; @@ -518,6 +523,7 @@ export interface ExcalidrawProps { appState: AppState, files: BinaryFiles, ) => void; + onIncrement?: (event: DurableIncrement | EphemeralIncrement) => void; initialData?: | (() => MaybePromise) | MaybePromise; @@ -821,6 +827,9 @@ export interface ExcalidrawImperativeAPI { files: BinaryFiles, ) => void, ) => UnsubscribeCallback; + onIncrement: ( + callback: (event: DurableIncrement | EphemeralIncrement) => void, + ) => UnsubscribeCallback; onPointerDown: ( callback: ( activeTool: AppState["activeTool"], diff --git a/packages/math/package.json b/packages/math/package.json index f8b411891..e9f5fd8da 100644 --- a/packages/math/package.json +++ b/packages/math/package.json @@ -54,7 +54,7 @@ "bugs": "https://github.com/excalidraw/excalidraw/issues", "repository": "https://github.com/excalidraw/excalidraw", "scripts": { - "gen:types": "rm -rf types && tsc", - "build:esm": "rm -rf dist && node ../../scripts/buildBase.js && yarn gen:types" + "gen:types": "rimraf types && tsc", + "build:esm": "rimraf dist && node ../../scripts/buildBase.js && yarn gen:types" } } diff --git a/packages/math/src/curve.ts b/packages/math/src/curve.ts index a79fb43a1..ec2d1afcd 100644 --- a/packages/math/src/curve.ts +++ b/packages/math/src/curve.ts @@ -2,6 +2,7 @@ import type { Bounds } from "@excalidraw/element/bounds"; import { isPoint, pointDistance, pointFrom } from "./point"; import { rectangle, rectangleIntersectLineSegment } from "./rectangle"; +import { vector } from "./vector"; import type { Curve, GlobalPoint, LineSegment, LocalPoint } from "./types"; @@ -82,7 +83,7 @@ function solve( return [t0, s0]; } -const bezierEquation = ( +export const bezierEquation = ( c: Curve, t: number, ) => @@ -274,6 +275,26 @@ export function isCurve

( ); } +export function curveTangent( + [p0, p1, p2, p3]: Curve, + t: number, +) { + return vector( + -3 * (1 - t) * (1 - t) * p0[0] + + 3 * (1 - t) * (1 - t) * p1[0] - + 6 * t * (1 - t) * p1[0] - + 3 * t * t * p2[0] + + 6 * t * (1 - t) * p2[0] + + 3 * t * t * p3[0], + -3 * (1 - t) * (1 - t) * p0[1] + + 3 * (1 - t) * (1 - t) * p1[1] - + 6 * t * (1 - t) * p1[1] - + 3 * t * t * p2[1] + + 6 * t * (1 - t) * p2[1] + + 3 * t * t * p3[1], + ); +} + function curveBounds( c: Curve, ): Bounds { diff --git a/packages/math/src/vector.ts b/packages/math/src/vector.ts index 246722067..12682fcd9 100644 --- a/packages/math/src/vector.ts +++ b/packages/math/src/vector.ts @@ -143,3 +143,8 @@ export const vectorNormalize = (v: Vector): Vector => { return vector(v[0] / m, v[1] / m); }; + +/** + * Calculate the right-hand normal of the vector. + */ +export const vectorNormal = (v: Vector): Vector => vector(v[1], -v[0]); diff --git a/packages/utils/package.json b/packages/utils/package.json index ca3eee23e..2dc54c59c 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -69,7 +69,7 @@ "bugs": "https://github.com/excalidraw/excalidraw/issues", "repository": "https://github.com/excalidraw/excalidraw", "scripts": { - "gen:types": "rm -rf types && tsc", - "build:esm": "rm -rf dist && node ../../scripts/buildUtils.js && yarn gen:types" + "gen:types": "rimraf types && tsc", + "build:esm": "rimraf dist && node ../../scripts/buildUtils.js && yarn gen:types" } } diff --git a/yarn.lock b/yarn.lock index 366a3f99f..21374749a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5945,7 +5945,7 @@ glob-to-regexp@^0.4.1: resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz#c75297087c851b9a578bd217dd59a92f59fe546e" integrity sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw== -glob@^10.4.1: +glob@^10.3.7, glob@^10.4.1: version "10.4.5" resolved "https://registry.yarnpkg.com/glob/-/glob-10.4.5.tgz#f4d9f0b90ffdbab09c9d77f5f29b4262517b0956" integrity sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg== @@ -8314,6 +8314,13 @@ rimraf@3.0.2, rimraf@^3.0.2: dependencies: glob "^7.1.3" +rimraf@^5.0.0: + version "5.0.10" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-5.0.10.tgz#23b9843d3dc92db71f96e1a2ce92e39fd2a8221c" + integrity sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ== + dependencies: + glob "^10.3.7" + robust-predicates@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/robust-predicates/-/robust-predicates-3.0.2.tgz#d5b28528c4824d20fc48df1928d41d9efa1ad771" @@ -8770,8 +8777,16 @@ string-natural-compare@^3.0.1: resolved "https://registry.yarnpkg.com/string-natural-compare/-/string-natural-compare-3.0.1.tgz#7a42d58474454963759e8e8b7ae63d71c1e7fdf4" integrity sha512-n3sPwynL1nwKi3WJ6AIsClwBMa0zTi54fn2oLU6ndfTSIO05xaznjSf15PcBZU6FNWbmN5Q6cxT4V5hGvB4taw== -"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: - name string-width-cjs +"string-width-cjs@npm:string-width@^4.2.0": + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -8873,7 +8888,14 @@ stringify-object@^3.3.0: is-obj "^1.0.1" is-regexp "^1.0.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@6.0.1, strip-ansi@^6.0.0, strip-ansi@^6.0.1, strip-ansi@^7.0.1: +"strip-ansi-cjs@npm:strip-ansi@^6.0.1": + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +strip-ansi@6.0.1, strip-ansi@^6.0.0, strip-ansi@^6.0.1, strip-ansi@^7.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -10006,8 +10028,7 @@ workbox-window@7.3.0, workbox-window@^7.3.0: "@types/trusted-types" "^2.0.2" workbox-core "7.3.0" -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: - name wrap-ansi-cjs +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== @@ -10025,6 +10046,15 @@ wrap-ansi@^6.2.0: string-width "^4.1.0" strip-ansi "^6.0.0" +wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + wrap-ansi@^8.1.0: version "8.1.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214"