feat: make appState.selectedElementIds more stable (#6745)

This commit is contained in:
David Luzar 2023-07-08 23:33:34 +02:00 committed by GitHub
parent 3ddcc48e4c
commit 49e4289878
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 503 additions and 295 deletions

View File

@ -31,6 +31,7 @@ import {
} from "../element/types"; } from "../element/types";
import { getSelectedElements } from "../scene"; import { getSelectedElements } from "../scene";
import { AppState } from "../types"; import { AppState } from "../types";
import { Mutable } from "../utility-types";
import { getFontString } from "../utils"; import { getFontString } from "../utils";
import { register } from "./register"; import { register } from "./register";
@ -211,7 +212,7 @@ export const actionWrapTextInContainer = register({
appState, appState,
); );
let updatedElements: readonly ExcalidrawElement[] = elements.slice(); let updatedElements: readonly ExcalidrawElement[] = elements.slice();
const containerIds: AppState["selectedElementIds"] = {}; const containerIds: Mutable<AppState["selectedElementIds"]> = {};
for (const textElement of selectedElements) { for (const textElement of selectedElements) {
if (isTextElement(textElement)) { if (isTextElement(textElement)) {

View File

@ -274,6 +274,7 @@ const duplicateElements = (
), ),
}, },
getNonDeletedElements(finalElements), getNonDeletedElements(finalElements),
appState,
), ),
}; };
}; };

View File

@ -125,13 +125,6 @@ export const actionFinalize = register({
{ x, y }, { x, y },
); );
} }
if (
!appState.activeTool.locked &&
appState.activeTool.type !== "freedraw"
) {
appState.selectedElementIds[multiPointElement.id] = true;
}
} }
if ( if (

View File

@ -218,6 +218,7 @@ export const actionUngroup = register({
const updateAppState = selectGroupsForSelectedElements( const updateAppState = selectGroupsForSelectedElements(
{ ...appState, selectedGroupIds: {} }, { ...appState, selectedGroupIds: {} },
getNonDeletedElements(nextElements), getNonDeletedElements(nextElements),
appState,
); );
frames.forEach((frame) => { frames.forEach((frame) => {
@ -232,9 +233,18 @@ export const actionUngroup = register({
}); });
// remove binded text elements from selection // remove binded text elements from selection
boundTextElementIds.forEach( updateAppState.selectedElementIds = Object.entries(
(id) => (updateAppState.selectedElementIds[id] = false), updateAppState.selectedElementIds,
).reduce(
(acc: { [key: ExcalidrawElement["id"]]: true }, [id, selected]) => {
if (selected && !boundTextElementIds.includes(id)) {
acc[id] = true;
}
return acc;
},
{},
); );
return { return {
appState: updateAppState, appState: updateAppState,
elements: nextElements, elements: nextElements,

View File

@ -41,6 +41,7 @@ export const actionSelectAll = register({
selectedElementIds, selectedElementIds,
}, },
getNonDeletedElements(elements), getNonDeletedElements(elements),
appState,
), ),
commitToHistory: true, commitToHistory: true,
}; };

View File

@ -315,7 +315,10 @@ import {
updateFrameMembershipOfSelectedElements, updateFrameMembershipOfSelectedElements,
isElementInFrame, isElementInFrame,
} from "../frame"; } from "../frame";
import { excludeElementsInFramesFromSelection } from "../scene/selection"; import {
excludeElementsInFramesFromSelection,
makeNextSelectedElementIds,
} from "../scene/selection";
import { actionPaste } from "../actions/actionClipboard"; import { actionPaste } from "../actions/actionClipboard";
import { import {
actionRemoveAllElementsFromFrame, actionRemoveAllElementsFromFrame,
@ -1353,6 +1356,7 @@ class App extends React.Component<AppProps, AppState> {
this.scene.destroy(); this.scene.destroy();
this.library.destroy(); this.library.destroy();
clearTimeout(touchTimeout); clearTimeout(touchTimeout);
isSomeElementSelected.clearCache();
touchTimeout = 0; touchTimeout = 0;
} }
@ -1825,7 +1829,7 @@ class App extends React.Component<AppProps, AppState> {
if (event.touches.length === 2) { if (event.touches.length === 2) {
this.setState({ this.setState({
selectedElementIds: {}, selectedElementIds: makeNextSelectedElementIds({}, this.state),
}); });
} }
}; };
@ -1835,7 +1839,10 @@ class App extends React.Component<AppProps, AppState> {
if (event.touches.length > 0) { if (event.touches.length > 0) {
this.setState({ this.setState({
previousSelectedElementIds: {}, previousSelectedElementIds: {},
selectedElementIds: this.state.previousSelectedElementIds, selectedElementIds: makeNextSelectedElementIds(
this.state.previousSelectedElementIds,
this.state,
),
}); });
} else { } else {
gesture.pointers.clear(); gesture.pointers.clear();
@ -1895,7 +1902,14 @@ class App extends React.Component<AppProps, AppState> {
const imageElement = this.createImageElement({ sceneX, sceneY }); const imageElement = this.createImageElement({ sceneX, sceneY });
this.insertImageElement(imageElement, file); this.insertImageElement(imageElement, file);
this.initializeImageDimensions(imageElement); this.initializeImageDimensions(imageElement);
this.setState({ selectedElementIds: { [imageElement.id]: true } }); this.setState({
selectedElementIds: makeNextSelectedElementIds(
{
[imageElement.id]: true,
},
this.state,
),
});
return; return;
} }
@ -2032,6 +2046,7 @@ class App extends React.Component<AppProps, AppState> {
selectedGroupIds: {}, selectedGroupIds: {},
}, },
this.scene.getNonDeletedElements(), this.scene.getNonDeletedElements(),
this.state,
), ),
() => { () => {
if (opts.files) { if (opts.files) {
@ -2130,8 +2145,9 @@ class App extends React.Component<AppProps, AppState> {
} }
this.setState({ this.setState({
selectedElementIds: Object.fromEntries( selectedElementIds: makeNextSelectedElementIds(
textElements.map((el) => [el.id, true]), Object.fromEntries(textElements.map((el) => [el.id, true])),
this.state,
), ),
}); });
@ -2749,7 +2765,7 @@ class App extends React.Component<AppProps, AppState> {
} else { } else {
setCursorForShape(this.canvas, this.state); setCursorForShape(this.canvas, this.state);
this.setState({ this.setState({
selectedElementIds: {}, selectedElementIds: makeNextSelectedElementIds({}, this.state),
selectedGroupIds: {}, selectedGroupIds: {},
editingGroupId: null, editingGroupId: null,
}); });
@ -2794,7 +2810,7 @@ class App extends React.Component<AppProps, AppState> {
if (nextActiveTool.type !== "selection") { if (nextActiveTool.type !== "selection") {
this.setState({ this.setState({
activeTool: nextActiveTool, activeTool: nextActiveTool,
selectedElementIds: {}, selectedElementIds: makeNextSelectedElementIds({}, this.state),
selectedGroupIds: {}, selectedGroupIds: {},
editingGroupId: null, editingGroupId: null,
}); });
@ -2831,7 +2847,7 @@ class App extends React.Component<AppProps, AppState> {
// elements by mistake while zooming // elements by mistake while zooming
if (this.isTouchScreenMultiTouchGesture()) { if (this.isTouchScreenMultiTouchGesture()) {
this.setState({ this.setState({
selectedElementIds: {}, selectedElementIds: makeNextSelectedElementIds({}, this.state),
}); });
} }
gesture.initialScale = this.state.zoom.value; gesture.initialScale = this.state.zoom.value;
@ -2876,7 +2892,10 @@ class App extends React.Component<AppProps, AppState> {
if (this.isTouchScreenMultiTouchGesture()) { if (this.isTouchScreenMultiTouchGesture()) {
this.setState({ this.setState({
previousSelectedElementIds: {}, previousSelectedElementIds: {},
selectedElementIds: this.state.previousSelectedElementIds, selectedElementIds: makeNextSelectedElementIds(
this.state.previousSelectedElementIds,
this.state,
),
}); });
} }
gesture.initialScale = null; gesture.initialScale = null;
@ -2941,10 +2960,13 @@ class App extends React.Component<AppProps, AppState> {
? element.containerId ? element.containerId
: element.id; : element.id;
this.setState((prevState) => ({ this.setState((prevState) => ({
selectedElementIds: { selectedElementIds: makeNextSelectedElementIds(
...prevState.selectedElementIds, {
[elementIdToSelect]: true, ...prevState.selectedElementIds,
}, [elementIdToSelect]: true,
},
prevState,
),
})); }));
} }
if (isDeleted) { if (isDeleted) {
@ -2980,7 +3002,7 @@ class App extends React.Component<AppProps, AppState> {
private deselectElements() { private deselectElements() {
this.setState({ this.setState({
selectedElementIds: {}, selectedElementIds: makeNextSelectedElementIds({}, this.state),
selectedGroupIds: {}, selectedGroupIds: {},
editingGroupId: null, editingGroupId: null,
}); });
@ -3291,6 +3313,7 @@ class App extends React.Component<AppProps, AppState> {
selectedGroupIds: {}, selectedGroupIds: {},
}, },
this.scene.getNonDeletedElements(), this.scene.getNonDeletedElements(),
prevState,
), ),
); );
return; return;
@ -3998,12 +4021,15 @@ class App extends React.Component<AppProps, AppState> {
editingElement: null, editingElement: null,
startBoundElement: null, startBoundElement: null,
suggestedBindings: [], suggestedBindings: [],
selectedElementIds: Object.keys(this.state.selectedElementIds) selectedElementIds: makeNextSelectedElementIds(
.filter((key) => key !== element.id) Object.keys(this.state.selectedElementIds)
.reduce((obj: { [id: string]: boolean }, key) => { .filter((key) => key !== element.id)
obj[key] = this.state.selectedElementIds[key]; .reduce((obj: { [id: string]: true }, key) => {
return obj; obj[key] = this.state.selectedElementIds[key];
}, {}), return obj;
}, {}),
this.state,
),
}, },
}); });
return; return;
@ -4472,7 +4498,7 @@ class App extends React.Component<AppProps, AppState> {
private clearSelectionIfNotUsingSelection = (): void => { private clearSelectionIfNotUsingSelection = (): void => {
if (this.state.activeTool.type !== "selection") { if (this.state.activeTool.type !== "selection") {
this.setState({ this.setState({
selectedElementIds: {}, selectedElementIds: makeNextSelectedElementIds({}, this.state),
selectedGroupIds: {}, selectedGroupIds: {},
editingGroupId: null, editingGroupId: null,
}); });
@ -4604,9 +4630,12 @@ class App extends React.Component<AppProps, AppState> {
if (this.state.editingLinearElement) { if (this.state.editingLinearElement) {
this.setState({ this.setState({
selectedElementIds: { selectedElementIds: makeNextSelectedElementIds(
[this.state.editingLinearElement.elementId]: true, {
}, [this.state.editingLinearElement.elementId]: true,
},
this.state,
),
}); });
// If we click on something // If we click on something
} else if (hitElement != null) { } else if (hitElement != null) {
@ -4634,7 +4663,7 @@ class App extends React.Component<AppProps, AppState> {
!isElementInGroup(hitElement, this.state.editingGroupId) !isElementInGroup(hitElement, this.state.editingGroupId)
) { ) {
this.setState({ this.setState({
selectedElementIds: {}, selectedElementIds: makeNextSelectedElementIds({}, this.state),
selectedGroupIds: {}, selectedGroupIds: {},
editingGroupId: null, editingGroupId: null,
}); });
@ -4650,7 +4679,7 @@ class App extends React.Component<AppProps, AppState> {
!pointerDownState.hit.hasHitCommonBoundingBoxOfSelectedElements !pointerDownState.hit.hasHitCommonBoundingBoxOfSelectedElements
) { ) {
this.setState((prevState) => { this.setState((prevState) => {
const nextSelectedElementIds = { const nextSelectedElementIds: { [id: string]: true } = {
...prevState.selectedElementIds, ...prevState.selectedElementIds,
[hitElement.id]: true, [hitElement.id]: true,
}; };
@ -4668,13 +4697,13 @@ class App extends React.Component<AppProps, AppState> {
previouslySelectedElements, previouslySelectedElements,
hitElement.id, hitElement.id,
).forEach((element) => { ).forEach((element) => {
nextSelectedElementIds[element.id] = false; delete nextSelectedElementIds[element.id];
}); });
} else if (hitElement.frameId) { } else if (hitElement.frameId) {
// if hitElement is in a frame and its frame has been selected // if hitElement is in a frame and its frame has been selected
// disable selection for the given element // disable selection for the given element
if (nextSelectedElementIds[hitElement.frameId]) { if (nextSelectedElementIds[hitElement.frameId]) {
nextSelectedElementIds[hitElement.id] = false; delete nextSelectedElementIds[hitElement.id];
} }
} else { } else {
// hitElement is neither a frame nor an element in a frame // hitElement is neither a frame nor an element in a frame
@ -4704,7 +4733,7 @@ class App extends React.Component<AppProps, AppState> {
framesInGroups.has(element.frameId) framesInGroups.has(element.frameId)
) { ) {
// deselect element and groups containing the element // deselect element and groups containing the element
nextSelectedElementIds[element.id] = false; delete nextSelectedElementIds[element.id];
element.groupIds element.groupIds
.flatMap((gid) => .flatMap((gid) =>
getElementsInGroup( getElementsInGroup(
@ -4712,10 +4741,9 @@ class App extends React.Component<AppProps, AppState> {
gid, gid,
), ),
) )
.forEach( .forEach((element) => {
(element) => delete nextSelectedElementIds[element.id];
(nextSelectedElementIds[element.id] = false), });
);
} }
}); });
} }
@ -4728,6 +4756,7 @@ class App extends React.Component<AppProps, AppState> {
showHyperlinkPopup: hitElement.link ? "info" : false, showHyperlinkPopup: hitElement.link ? "info" : false,
}, },
this.scene.getNonDeletedElements(), this.scene.getNonDeletedElements(),
prevState,
); );
}); });
pointerDownState.hit.wasAddedToSelection = true; pointerDownState.hit.wasAddedToSelection = true;
@ -4844,12 +4873,18 @@ class App extends React.Component<AppProps, AppState> {
frameId: topLayerFrame ? topLayerFrame.id : null, frameId: topLayerFrame ? topLayerFrame.id : null,
}); });
this.setState((prevState) => ({ this.setState((prevState) => {
selectedElementIds: { const nextSelectedElementIds = {
...prevState.selectedElementIds, ...prevState.selectedElementIds,
[element.id]: false, };
}, delete nextSelectedElementIds[element.id];
})); return {
selectedElementIds: makeNextSelectedElementIds(
nextSelectedElementIds,
prevState,
),
};
});
const pressures = element.simulatePressure const pressures = element.simulatePressure
? element.pressures ? element.pressures
@ -4945,10 +4980,13 @@ class App extends React.Component<AppProps, AppState> {
} }
this.setState((prevState) => ({ this.setState((prevState) => ({
selectedElementIds: { selectedElementIds: makeNextSelectedElementIds(
...prevState.selectedElementIds, {
[multiElement.id]: true, ...prevState.selectedElementIds,
}, [multiElement.id]: true,
},
prevState,
),
})); }));
// clicking outside commit zone → update reference for last committed // clicking outside commit zone → update reference for last committed
// point // point
@ -4999,12 +5037,18 @@ class App extends React.Component<AppProps, AppState> {
locked: false, locked: false,
frameId: topLayerFrame ? topLayerFrame.id : null, frameId: topLayerFrame ? topLayerFrame.id : null,
}); });
this.setState((prevState) => ({ this.setState((prevState) => {
selectedElementIds: { const nextSelectedElementIds = {
...prevState.selectedElementIds, ...prevState.selectedElementIds,
[element.id]: false, };
}, delete nextSelectedElementIds[element.id];
})); return {
selectedElementIds: makeNextSelectedElementIds(
nextSelectedElementIds,
prevState,
),
};
});
mutateElement(element, { mutateElement(element, {
points: [...element.points, [0, 0]], points: [...element.points, [0, 0]],
}); });
@ -5378,15 +5422,16 @@ class App extends React.Component<AppProps, AppState> {
const oldIdToDuplicatedId = new Map(); const oldIdToDuplicatedId = new Map();
const hitElement = pointerDownState.hit.element; const hitElement = pointerDownState.hit.element;
const elements = this.scene.getElementsIncludingDeleted(); const elements = this.scene.getElementsIncludingDeleted();
const selectedElementIds: Array<ExcalidrawElement["id"]> = const selectedElementIds = new Set(
getSelectedElements(elements, this.state, { getSelectedElements(elements, this.state, {
includeBoundTextElement: true, includeBoundTextElement: true,
includeElementsInFrames: true, includeElementsInFrames: true,
}).map((element) => element.id); }).map((element) => element.id),
);
for (const element of elements) { for (const element of elements) {
if ( if (
selectedElementIds.includes(element.id) || selectedElementIds.has(element.id) ||
// case: the state.selectedElementIds might not have been // case: the state.selectedElementIds might not have been
// updated yet by the time this mousemove event is fired // updated yet by the time this mousemove event is fired
(element.id === hitElement?.id && (element.id === hitElement?.id &&
@ -5524,14 +5569,9 @@ class App extends React.Component<AppProps, AppState> {
}, },
}, },
this.scene.getNonDeletedElements(), this.scene.getNonDeletedElements(),
prevState,
), ),
); );
} else {
this.setState({
selectedElementIds: {},
selectedGroupIds: {},
editingGroupId: null,
});
} }
} }
// box-select line editor points // box-select line editor points
@ -5547,28 +5587,29 @@ class App extends React.Component<AppProps, AppState> {
elements, elements,
draggingElement, draggingElement,
); );
this.setState((prevState) => this.setState((prevState) => {
selectGroupsForSelectedElements( const nextSelectedElementIds = elementsWithinSelection.reduce(
(acc: Record<ExcalidrawElement["id"], true>, element) => {
acc[element.id] = true;
return acc;
},
{},
);
if (pointerDownState.hit.element) {
// if using ctrl/cmd, select the hitElement only if we
// haven't box-selected anything else
if (!elementsWithinSelection.length) {
nextSelectedElementIds[pointerDownState.hit.element.id] = true;
} else {
delete nextSelectedElementIds[pointerDownState.hit.element.id];
}
}
return selectGroupsForSelectedElements(
{ {
...prevState, ...prevState,
selectedElementIds: { selectedElementIds: nextSelectedElementIds,
...prevState.selectedElementIds,
...elementsWithinSelection.reduce(
(acc: Record<ExcalidrawElement["id"], true>, element) => {
acc[element.id] = true;
return acc;
},
{},
),
...(pointerDownState.hit.element
? {
// if using ctrl/cmd, select the hitElement only if we
// haven't box-selected anything else
[pointerDownState.hit.element.id]:
!elementsWithinSelection.length,
}
: null),
},
showHyperlinkPopup: showHyperlinkPopup:
elementsWithinSelection.length === 1 && elementsWithinSelection.length === 1 &&
elementsWithinSelection[0].link elementsWithinSelection[0].link
@ -5585,8 +5626,9 @@ class App extends React.Component<AppProps, AppState> {
: null, : null,
}, },
this.scene.getNonDeletedElements(), this.scene.getNonDeletedElements(),
), prevState,
); );
});
} }
} }
}); });
@ -5780,7 +5822,12 @@ class App extends React.Component<AppProps, AppState> {
try { try {
this.initializeImageDimensions(imageElement); this.initializeImageDimensions(imageElement);
this.setState( this.setState(
{ selectedElementIds: { [imageElement.id]: true } }, {
selectedElementIds: makeNextSelectedElementIds(
{ [imageElement.id]: true },
this.state,
),
},
() => { () => {
this.actionManager.executeAction(actionFinalize); this.actionManager.executeAction(actionFinalize);
}, },
@ -5844,10 +5891,13 @@ class App extends React.Component<AppProps, AppState> {
activeTool: updateActiveTool(this.state, { activeTool: updateActiveTool(this.state, {
type: "selection", type: "selection",
}), }),
selectedElementIds: { selectedElementIds: makeNextSelectedElementIds(
...prevState.selectedElementIds, {
[draggingElement.id]: true, ...prevState.selectedElementIds,
}, [draggingElement.id]: true,
},
prevState,
),
selectedLinearElement: new LinearElementEditor( selectedLinearElement: new LinearElementEditor(
draggingElement, draggingElement,
this.scene, this.scene,
@ -6141,31 +6191,37 @@ class App extends React.Component<AppProps, AppState> {
if (childEvent.shiftKey && !this.state.editingLinearElement) { if (childEvent.shiftKey && !this.state.editingLinearElement) {
if (this.state.selectedElementIds[hitElement.id]) { if (this.state.selectedElementIds[hitElement.id]) {
if (isSelectedViaGroup(this.state, hitElement)) { if (isSelectedViaGroup(this.state, hitElement)) {
// We want to unselect all groups hitElement is part of this.setState((_prevState) => {
// as well as all elements that are part of the groups const nextSelectedElementIds = {
// hitElement is part of ..._prevState.selectedElementIds,
const idsOfSelectedElementsThatAreInGroups = hitElement.groupIds };
.flatMap((groupId) =>
getElementsInGroup(
this.scene.getNonDeletedElements(),
groupId,
),
)
.map((element) => ({ [element.id]: false }))
.reduce((prevId, acc) => ({ ...prevId, ...acc }), {});
this.setState((_prevState) => ({ // We want to unselect all groups hitElement is part of
selectedGroupIds: { // as well as all elements that are part of the groups
..._prevState.selectedElementIds, // hitElement is part of
...hitElement.groupIds for (const groupedElement of hitElement.groupIds.flatMap(
.map((gId) => ({ [gId]: false })) (groupId) =>
.reduce((prev, acc) => ({ ...prev, ...acc }), {}), getElementsInGroup(
}, this.scene.getNonDeletedElements(),
selectedElementIds: { groupId,
..._prevState.selectedElementIds, ),
...idsOfSelectedElementsThatAreInGroups, )) {
}, delete nextSelectedElementIds[groupedElement.id];
})); }
return {
selectedGroupIds: {
..._prevState.selectedElementIds,
...hitElement.groupIds
.map((gId) => ({ [gId]: false }))
.reduce((prev, acc) => ({ ...prev, ...acc }), {}),
},
selectedElementIds: makeNextSelectedElementIds(
nextSelectedElementIds,
_prevState,
),
};
});
// if not gragging a linear element point (outside editor) // if not gragging a linear element point (outside editor)
} else if (!this.state.selectedLinearElement?.isDragging) { } else if (!this.state.selectedLinearElement?.isDragging) {
// remove element from selection while // remove element from selection while
@ -6174,8 +6230,8 @@ class App extends React.Component<AppProps, AppState> {
this.setState((prevState) => { this.setState((prevState) => {
const newSelectedElementIds = { const newSelectedElementIds = {
...prevState.selectedElementIds, ...prevState.selectedElementIds,
[hitElement!.id]: false,
}; };
delete newSelectedElementIds[hitElement!.id];
const newSelectedElements = getSelectedElements( const newSelectedElements = getSelectedElements(
this.scene.getNonDeletedElements(), this.scene.getNonDeletedElements(),
{ ...prevState, selectedElementIds: newSelectedElementIds }, { ...prevState, selectedElementIds: newSelectedElementIds },
@ -6196,6 +6252,7 @@ class App extends React.Component<AppProps, AppState> {
: prevState.selectedLinearElement, : prevState.selectedLinearElement,
}, },
this.scene.getNonDeletedElements(), this.scene.getNonDeletedElements(),
prevState,
); );
}); });
} }
@ -6206,21 +6263,23 @@ class App extends React.Component<AppProps, AppState> {
// when hitElement is part of a selected frame, deselect the frame // when hitElement is part of a selected frame, deselect the frame
// to avoid frame and containing elements selected simultaneously // to avoid frame and containing elements selected simultaneously
this.setState((prevState) => { this.setState((prevState) => {
const nextSelectedElementIds = { const nextSelectedElementIds: {
[id: string]: true;
} = {
...prevState.selectedElementIds, ...prevState.selectedElementIds,
[hitElement.id]: true, [hitElement.id]: true,
// deselect the frame
[hitElement.frameId!]: false,
}; };
// deselect the frame
delete nextSelectedElementIds[hitElement.frameId!];
// deselect groups containing the frame // deselect groups containing the frame
(this.scene.getElement(hitElement.frameId!)?.groupIds ?? []) (this.scene.getElement(hitElement.frameId!)?.groupIds ?? [])
.flatMap((gid) => .flatMap((gid) =>
getElementsInGroup(this.scene.getNonDeletedElements(), gid), getElementsInGroup(this.scene.getNonDeletedElements(), gid),
) )
.forEach( .forEach((element) => {
(element) => (nextSelectedElementIds[element.id] = false), delete nextSelectedElementIds[element.id];
); });
return selectGroupsForSelectedElements( return selectGroupsForSelectedElements(
{ {
@ -6229,15 +6288,19 @@ class App extends React.Component<AppProps, AppState> {
showHyperlinkPopup: hitElement.link ? "info" : false, showHyperlinkPopup: hitElement.link ? "info" : false,
}, },
this.scene.getNonDeletedElements(), this.scene.getNonDeletedElements(),
prevState,
); );
}); });
} else { } else {
// add element to selection while keeping prev elements selected // add element to selection while keeping prev elements selected
this.setState((_prevState) => ({ this.setState((_prevState) => ({
selectedElementIds: { selectedElementIds: makeNextSelectedElementIds(
..._prevState.selectedElementIds, {
[hitElement!.id]: true, ..._prevState.selectedElementIds,
}, [hitElement!.id]: true,
},
_prevState,
),
})); }));
} }
} else { } else {
@ -6255,6 +6318,7 @@ class App extends React.Component<AppProps, AppState> {
: prevState.selectedLinearElement, : prevState.selectedLinearElement,
}, },
this.scene.getNonDeletedElements(), this.scene.getNonDeletedElements(),
prevState,
), ),
})); }));
} }
@ -6279,7 +6343,7 @@ class App extends React.Component<AppProps, AppState> {
} else { } else {
// Deselect selected elements // Deselect selected elements
this.setState({ this.setState({
selectedElementIds: {}, selectedElementIds: makeNextSelectedElementIds({}, this.state),
selectedGroupIds: {}, selectedGroupIds: {},
editingGroupId: null, editingGroupId: null,
}); });
@ -6290,13 +6354,17 @@ class App extends React.Component<AppProps, AppState> {
if ( if (
!activeTool.locked && !activeTool.locked &&
activeTool.type !== "freedraw" && activeTool.type !== "freedraw" &&
draggingElement draggingElement &&
draggingElement.type !== "selection"
) { ) {
this.setState((prevState) => ({ this.setState((prevState) => ({
selectedElementIds: { selectedElementIds: makeNextSelectedElementIds(
...prevState.selectedElementIds, {
[draggingElement.id]: true, ...prevState.selectedElementIds,
}, [draggingElement.id]: true,
},
prevState,
),
})); }));
} }
@ -6610,7 +6678,10 @@ class App extends React.Component<AppProps, AppState> {
this.initializeImageDimensions(imageElement); this.initializeImageDimensions(imageElement);
this.setState( this.setState(
{ {
selectedElementIds: { [imageElement.id]: true }, selectedElementIds: makeNextSelectedElementIds(
{ [imageElement.id]: true },
this.state,
),
}, },
() => { () => {
this.actionManager.executeAction(actionFinalize); this.actionManager.executeAction(actionFinalize);
@ -6837,7 +6908,7 @@ class App extends React.Component<AppProps, AppState> {
private clearSelection(hitElement: ExcalidrawElement | null): void { private clearSelection(hitElement: ExcalidrawElement | null): void {
this.setState((prevState) => ({ this.setState((prevState) => ({
selectedElementIds: {}, selectedElementIds: makeNextSelectedElementIds({}, prevState),
selectedGroupIds: {}, selectedGroupIds: {},
// Continue editing the same group if the user selected a different // Continue editing the same group if the user selected a different
// element from it // element from it
@ -6849,7 +6920,7 @@ class App extends React.Component<AppProps, AppState> {
: null, : null,
})); }));
this.setState({ this.setState({
selectedElementIds: {}, selectedElementIds: makeNextSelectedElementIds({}, this.state),
previousSelectedElementIds: this.state.selectedElementIds, previousSelectedElementIds: this.state.selectedElementIds,
}); });
} }
@ -6918,7 +6989,12 @@ class App extends React.Component<AppProps, AppState> {
const imageElement = this.createImageElement({ sceneX, sceneY }); const imageElement = this.createImageElement({ sceneX, sceneY });
this.insertImageElement(imageElement, file); this.insertImageElement(imageElement, file);
this.initializeImageDimensions(imageElement); this.initializeImageDimensions(imageElement);
this.setState({ selectedElementIds: { [imageElement.id]: true } }); this.setState({
selectedElementIds: makeNextSelectedElementIds(
{ [imageElement.id]: true },
this.state,
),
});
return; return;
} }
@ -7043,6 +7119,7 @@ class App extends React.Component<AppProps, AppState> {
: null, : null,
}, },
this.scene.getNonDeletedElements(), this.scene.getNonDeletedElements(),
this.state,
) )
: this.state), : this.state),
showHyperlinkPopup: false, showHyperlinkPopup: false,

135
src/excalidraw-app/debug.ts Normal file
View File

@ -0,0 +1,135 @@
declare global {
interface Window {
debug: typeof Debug;
}
}
const lessPrecise = (num: number, precision = 5) =>
parseFloat(num.toPrecision(precision));
const getAvgFrameTime = (times: number[]) =>
lessPrecise(times.reduce((a, b) => a + b) / times.length);
const getFps = (frametime: number) => lessPrecise(1000 / frametime);
export class Debug {
public static DEBUG_LOG_TIMES = true;
private static TIMES_AGGR: Record<string, { t: number; times: number[] }> =
{};
private static TIMES_AVG: Record<
string,
{ t: number; times: number[]; avg: number | null }
> = {};
private static LAST_DEBUG_LOG_CALL = 0;
private static DEBUG_LOG_INTERVAL_ID: null | number = null;
private static setupInterval = () => {
if (Debug.DEBUG_LOG_INTERVAL_ID === null) {
console.info("%c(starting perf recording)", "color: lime");
Debug.DEBUG_LOG_INTERVAL_ID = window.setInterval(Debug.debugLogger, 1000);
}
Debug.LAST_DEBUG_LOG_CALL = Date.now();
};
private static debugLogger = () => {
if (
Date.now() - Debug.LAST_DEBUG_LOG_CALL > 600 &&
Debug.DEBUG_LOG_INTERVAL_ID !== null
) {
window.clearInterval(Debug.DEBUG_LOG_INTERVAL_ID);
Debug.DEBUG_LOG_INTERVAL_ID = null;
for (const [name, { avg }] of Object.entries(Debug.TIMES_AVG)) {
if (avg != null) {
console.info(
`%c${name} run avg: ${avg}ms (${getFps(avg)} fps)`,
"color: blue",
);
}
}
console.info("%c(stopping perf recording)", "color: red");
Debug.TIMES_AGGR = {};
Debug.TIMES_AVG = {};
return;
}
if (Debug.DEBUG_LOG_TIMES) {
for (const [name, { t, times }] of Object.entries(Debug.TIMES_AGGR)) {
if (times.length) {
console.info(
name,
lessPrecise(times.reduce((a, b) => a + b)),
times.sort((a, b) => a - b).map((x) => lessPrecise(x)),
);
Debug.TIMES_AGGR[name] = { t, times: [] };
}
}
for (const [name, { t, times, avg }] of Object.entries(Debug.TIMES_AVG)) {
if (times.length) {
const avgFrameTime = getAvgFrameTime(times);
console.info(name, `${avgFrameTime}ms (${getFps(avgFrameTime)} fps)`);
Debug.TIMES_AVG[name] = {
t,
times: [],
avg:
avg != null ? getAvgFrameTime([avg, avgFrameTime]) : avgFrameTime,
};
}
}
}
};
public static logTime = (time?: number, name = "default") => {
Debug.setupInterval();
const now = performance.now();
const { t, times } = (Debug.TIMES_AGGR[name] = Debug.TIMES_AGGR[name] || {
t: 0,
times: [],
});
if (t) {
times.push(time != null ? time : now - t);
}
Debug.TIMES_AGGR[name].t = now;
};
public static logTimeAverage = (time?: number, name = "default") => {
Debug.setupInterval();
const now = performance.now();
const { t, times } = (Debug.TIMES_AVG[name] = Debug.TIMES_AVG[name] || {
t: 0,
times: [],
});
if (t) {
times.push(time != null ? time : now - t);
}
Debug.TIMES_AVG[name].t = now;
};
private static logWrapper =
(type: "logTime" | "logTimeAverage") =>
<T extends any[], R>(fn: (...args: T) => R, name = "default") => {
return (...args: T) => {
const t0 = performance.now();
const ret = fn(...args);
Debug.logTime(performance.now() - t0, name);
return ret;
};
};
public static logTimeWrap = Debug.logWrapper("logTime");
public static logTimeAverageWrap = Debug.logWrapper("logTimeAverage");
public static perfWrap = <T extends any[], R>(
fn: (...args: T) => R,
name = "default",
) => {
return (...args: T) => {
// eslint-disable-next-line no-console
console.time(name);
const ret = fn(...args);
// eslint-disable-next-line no-console
console.timeEnd(name);
return ret;
};
};
}
window.debug = Debug;

View File

@ -2,6 +2,7 @@ import { GroupId, ExcalidrawElement, NonDeleted } from "./element/types";
import { AppState } from "./types"; import { AppState } from "./types";
import { getSelectedElements } from "./scene"; import { getSelectedElements } from "./scene";
import { getBoundTextElement } from "./element/textElement"; import { getBoundTextElement } from "./element/textElement";
import { makeNextSelectedElementIds } from "./scene/selection";
export const selectGroup = ( export const selectGroup = (
groupId: GroupId, groupId: GroupId,
@ -67,13 +68,21 @@ export const getSelectedGroupIds = (appState: AppState): GroupId[] =>
export const selectGroupsForSelectedElements = ( export const selectGroupsForSelectedElements = (
appState: AppState, appState: AppState,
elements: readonly NonDeleted<ExcalidrawElement>[], elements: readonly NonDeleted<ExcalidrawElement>[],
prevAppState: AppState,
): AppState => { ): AppState => {
let nextAppState: AppState = { ...appState, selectedGroupIds: {} }; let nextAppState: AppState = { ...appState, selectedGroupIds: {} };
const selectedElements = getSelectedElements(elements, appState); const selectedElements = getSelectedElements(elements, appState);
if (!selectedElements.length) { if (!selectedElements.length) {
return { ...nextAppState, editingGroupId: null }; return {
...nextAppState,
editingGroupId: null,
selectedElementIds: makeNextSelectedElementIds(
nextAppState.selectedElementIds,
prevAppState,
),
};
} }
for (const selectedElement of selectedElements) { for (const selectedElement of selectedElements) {
@ -91,6 +100,11 @@ export const selectGroupsForSelectedElements = (
} }
} }
nextAppState.selectedElementIds = makeNextSelectedElementIds(
nextAppState.selectedElementIds,
prevAppState,
);
return nextAppState; return nextAppState;
}; };

View File

@ -0,0 +1,35 @@
import { makeNextSelectedElementIds } from "./selection";
describe("makeNextSelectedElementIds", () => {
const _makeNextSelectedElementIds = (
selectedElementIds: { [id: string]: true },
prevSelectedElementIds: { [id: string]: true },
expectUpdated: boolean,
) => {
const ret = makeNextSelectedElementIds(selectedElementIds, {
selectedElementIds: prevSelectedElementIds,
});
expect(ret === selectedElementIds).toBe(expectUpdated);
};
it("should return prevState selectedElementIds if no change", () => {
_makeNextSelectedElementIds({}, {}, false);
_makeNextSelectedElementIds({ 1: true }, { 1: true }, false);
_makeNextSelectedElementIds(
{ 1: true, 2: true },
{ 1: true, 2: true },
false,
);
});
it("should return new selectedElementIds if changed", () => {
// _makeNextSelectedElementIds({ 1: true }, { 1: false }, true);
_makeNextSelectedElementIds({ 1: true }, {}, true);
_makeNextSelectedElementIds({}, { 1: true }, true);
_makeNextSelectedElementIds({ 1: true }, { 2: true }, true);
_makeNextSelectedElementIds({ 1: true }, { 1: true, 2: true }, true);
_makeNextSelectedElementIds(
{ 1: true, 2: true },
{ 1: true, 3: true },
true,
);
});
});

View File

@ -10,6 +10,7 @@ import {
getContainingFrame, getContainingFrame,
getFrameElements, getFrameElements,
} from "../frame"; } from "../frame";
import { isShallowEqual } from "../utils";
/** /**
* Frames and their containing elements are not to be selected at the same time. * Frames and their containing elements are not to be selected at the same time.
@ -88,11 +89,41 @@ export const getElementsWithinSelection = (
return elementsInSelection; return elementsInSelection;
}; };
export const isSomeElementSelected = ( // FIXME move this into the editor instance to keep utility methods stateless
elements: readonly NonDeletedExcalidrawElement[], export const isSomeElementSelected = (function () {
appState: Pick<AppState, "selectedElementIds">, let lastElements: readonly NonDeletedExcalidrawElement[] | null = null;
): boolean => let lastSelectedElementIds: AppState["selectedElementIds"] | null = null;
elements.some((element) => appState.selectedElementIds[element.id]); let isSelected: boolean | null = null;
const ret = (
elements: readonly NonDeletedExcalidrawElement[],
appState: Pick<AppState, "selectedElementIds">,
): boolean => {
if (
isSelected != null &&
elements === lastElements &&
appState.selectedElementIds === lastSelectedElementIds
) {
return isSelected;
}
isSelected = elements.some(
(element) => appState.selectedElementIds[element.id],
);
lastElements = elements;
lastSelectedElementIds = appState.selectedElementIds;
return isSelected;
};
ret.clearCache = () => {
lastElements = null;
lastSelectedElementIds = null;
isSelected = null;
};
return ret;
})();
/** /**
* Returns common attribute (picked by `getAttribute` callback) of selected * Returns common attribute (picked by `getAttribute` callback) of selected
@ -161,3 +192,18 @@ export const getTargetElements = (
: getSelectedElements(elements, appState, { : getSelectedElements(elements, appState, {
includeBoundTextElement: true, includeBoundTextElement: true,
}); });
/**
* returns prevState's selectedElementids if no change from previous, so as to
* retain reference identity for memoization
*/
export const makeNextSelectedElementIds = (
nextSelectedElementIds: AppState["selectedElementIds"],
prevState: Pick<AppState, "selectedElementIds">,
) => {
if (isShallowEqual(prevState.selectedElementIds, nextSelectedElementIds)) {
return prevState.selectedElementIds;
}
return nextSelectedElementIds;
};

View File

@ -2179,7 +2179,6 @@ Object {
"selectedElementIds": Object { "selectedElementIds": Object {
"id0": true, "id0": true,
"id1": true, "id1": true,
"id2": true,
}, },
"selectedElementsAreBeingDragged": false, "selectedElementsAreBeingDragged": false,
"selectedGroupIds": Object { "selectedGroupIds": Object {
@ -2413,7 +2412,6 @@ Object {
"selectedElementIds": Object { "selectedElementIds": Object {
"id0": true, "id0": true,
"id1": true, "id1": true,
"id2": true,
}, },
"selectedGroupIds": Object { "selectedGroupIds": Object {
"id3": true, "id3": true,
@ -4171,7 +4169,6 @@ Object {
"selectedElementIds": Object { "selectedElementIds": Object {
"id0": true, "id0": true,
"id1": true, "id1": true,
"id2": true,
}, },
"selectedElementsAreBeingDragged": false, "selectedElementsAreBeingDragged": false,
"selectedGroupIds": Object {}, "selectedGroupIds": Object {},
@ -4399,7 +4396,6 @@ Object {
"selectedElementIds": Object { "selectedElementIds": Object {
"id0": true, "id0": true,
"id1": true, "id1": true,
"id2": true,
}, },
"selectedGroupIds": Object { "selectedGroupIds": Object {
"id3": true, "id3": true,
@ -4479,7 +4475,6 @@ Object {
"selectedElementIds": Object { "selectedElementIds": Object {
"id0": true, "id0": true,
"id1": true, "id1": true,
"id2": true,
}, },
"selectedGroupIds": Object {}, "selectedGroupIds": Object {},
"viewBackgroundColor": "#ffffff", "viewBackgroundColor": "#ffffff",
@ -4892,7 +4887,6 @@ Object {
"pendingImageElementId": null, "pendingImageElementId": null,
"previousSelectedElementIds": Object { "previousSelectedElementIds": Object {
"id0": true, "id0": true,
"id2": true,
}, },
"resizingElement": null, "resizingElement": null,
"scrollX": 0, "scrollX": 0,
@ -4901,8 +4895,6 @@ Object {
"selectedElementIds": Object { "selectedElementIds": Object {
"id0": true, "id0": true,
"id1": true, "id1": true,
"id2": true,
"id3": true,
}, },
"selectedElementsAreBeingDragged": false, "selectedElementsAreBeingDragged": false,
"selectedGroupIds": Object {}, "selectedGroupIds": Object {},
@ -5469,7 +5461,6 @@ Object {
"pendingImageElementId": null, "pendingImageElementId": null,
"previousSelectedElementIds": Object { "previousSelectedElementIds": Object {
"id0": true, "id0": true,
"id2": true,
}, },
"resizingElement": null, "resizingElement": null,
"scrollX": 0, "scrollX": 0,
@ -5478,8 +5469,6 @@ Object {
"selectedElementIds": Object { "selectedElementIds": Object {
"id0": true, "id0": true,
"id1": true, "id1": true,
"id2": true,
"id3": true,
}, },
"selectedElementsAreBeingDragged": false, "selectedElementsAreBeingDragged": false,
"selectedGroupIds": Object { "selectedGroupIds": Object {
@ -5713,8 +5702,6 @@ Object {
"selectedElementIds": Object { "selectedElementIds": Object {
"id0": true, "id0": true,
"id1": true, "id1": true,
"id2": true,
"id3": true,
}, },
"selectedGroupIds": Object { "selectedGroupIds": Object {
"id4": true, "id4": true,

View File

@ -65,9 +65,6 @@ Object {
"id0": true, "id0": true,
"id1": true, "id1": true,
"id2": true, "id2": true,
"id3": true,
"id4": true,
"id6": true,
}, },
"resizingElement": null, "resizingElement": null,
"scrollX": 0, "scrollX": 0,
@ -76,7 +73,6 @@ Object {
"selectedElementIds": Object { "selectedElementIds": Object {
"id0": true, "id0": true,
"id2": true, "id2": true,
"id7": true,
}, },
"selectedElementsAreBeingDragged": false, "selectedElementsAreBeingDragged": false,
"selectedGroupIds": Object { "selectedGroupIds": Object {
@ -443,8 +439,6 @@ Object {
"selectedElementIds": Object { "selectedElementIds": Object {
"id0": true, "id0": true,
"id2": true, "id2": true,
"id3": true,
"id4": true,
}, },
"selectedGroupIds": Object { "selectedGroupIds": Object {
"id5": true, "id5": true,
@ -618,29 +612,20 @@ Object {
"id0": true, "id0": true,
"id1": true, "id1": true,
"id2": true, "id2": true,
"id3": true,
"id5": true,
}, },
"resizingElement": null, "resizingElement": null,
"scrollX": 0, "scrollX": 0,
"scrollY": 0, "scrollY": 0,
"scrolledOutside": false, "scrolledOutside": false,
"selectedElementIds": Object { "selectedElementIds": Object {
"id0": false,
"id1": true, "id1": true,
"id2": false,
"id3": true,
"id5": true,
"id6": true,
}, },
"selectedElementsAreBeingDragged": false, "selectedElementsAreBeingDragged": false,
"selectedGroupIds": Object { "selectedGroupIds": Object {
"id0": true, "id0": true,
"id1": true, "id1": true,
"id2": true, "id2": true,
"id3": true,
"id4": false, "id4": false,
"id5": true,
}, },
"selectedLinearElement": null, "selectedLinearElement": null,
"selectionElement": null, "selectionElement": null,
@ -1003,7 +988,6 @@ Object {
"selectedElementIds": Object { "selectedElementIds": Object {
"id0": true, "id0": true,
"id2": true, "id2": true,
"id3": true,
}, },
"selectedGroupIds": Object { "selectedGroupIds": Object {
"id4": true, "id4": true,
@ -1179,7 +1163,6 @@ Object {
"scrollY": 0, "scrollY": 0,
"scrolledOutside": false, "scrolledOutside": false,
"selectedElementIds": Object { "selectedElementIds": Object {
"id12": true,
"id7": true, "id7": true,
}, },
"selectedElementsAreBeingDragged": false, "selectedElementsAreBeingDragged": false,
@ -1448,8 +1431,6 @@ Object {
"selectedElementIds": Object { "selectedElementIds": Object {
"id0": true, "id0": true,
"id1": true, "id1": true,
"id2": true,
"id3": true,
}, },
"selectedGroupIds": Object { "selectedGroupIds": Object {
"id4": true, "id4": true,
@ -1528,7 +1509,6 @@ Object {
"name": "Untitled-201933152653", "name": "Untitled-201933152653",
"selectedElementIds": Object { "selectedElementIds": Object {
"id0": true, "id0": true,
"id5": true,
}, },
"selectedGroupIds": Object {}, "selectedGroupIds": Object {},
"viewBackgroundColor": "#ffffff", "viewBackgroundColor": "#ffffff",
@ -1712,8 +1692,6 @@ Object {
"id0": true, "id0": true,
"id1": true, "id1": true,
"id7": true, "id7": true,
"id8": true,
"id9": true,
}, },
"selectedGroupIds": Object { "selectedGroupIds": Object {
"id10": true, "id10": true,
@ -1825,7 +1803,6 @@ Object {
"name": "Untitled-201933152653", "name": "Untitled-201933152653",
"selectedElementIds": Object { "selectedElementIds": Object {
"id0": true, "id0": true,
"id11": true,
}, },
"selectedGroupIds": Object {}, "selectedGroupIds": Object {},
"viewBackgroundColor": "#ffffff", "viewBackgroundColor": "#ffffff",
@ -1934,7 +1911,6 @@ Object {
"editingLinearElement": null, "editingLinearElement": null,
"name": "Untitled-201933152653", "name": "Untitled-201933152653",
"selectedElementIds": Object { "selectedElementIds": Object {
"id12": true,
"id7": true, "id7": true,
}, },
"selectedGroupIds": Object {}, "selectedGroupIds": Object {},
@ -2116,7 +2092,6 @@ Object {
"scrolledOutside": false, "scrolledOutside": false,
"selectedElementIds": Object { "selectedElementIds": Object {
"id0": true, "id0": true,
"id1": true,
}, },
"selectedElementsAreBeingDragged": false, "selectedElementsAreBeingDragged": false,
"selectedGroupIds": Object {}, "selectedGroupIds": Object {},
@ -2239,7 +2214,6 @@ Object {
"name": "Untitled-201933152653", "name": "Untitled-201933152653",
"selectedElementIds": Object { "selectedElementIds": Object {
"id0": true, "id0": true,
"id1": true,
}, },
"selectedGroupIds": Object {}, "selectedGroupIds": Object {},
"viewBackgroundColor": "#ffffff", "viewBackgroundColor": "#ffffff",
@ -2347,7 +2321,6 @@ Object {
"pendingImageElementId": null, "pendingImageElementId": null,
"previousSelectedElementIds": Object { "previousSelectedElementIds": Object {
"id0": true, "id0": true,
"id3": true,
}, },
"resizingElement": null, "resizingElement": null,
"scrollX": 0, "scrollX": 0,
@ -2356,8 +2329,6 @@ Object {
"selectedElementIds": Object { "selectedElementIds": Object {
"id0": true, "id0": true,
"id2": true, "id2": true,
"id3": true,
"id4": true,
}, },
"selectedElementsAreBeingDragged": false, "selectedElementsAreBeingDragged": false,
"selectedGroupIds": Object { "selectedGroupIds": Object {
@ -2724,8 +2695,6 @@ Object {
"selectedElementIds": Object { "selectedElementIds": Object {
"id0": true, "id0": true,
"id2": true, "id2": true,
"id3": true,
"id4": true,
}, },
"selectedGroupIds": Object { "selectedGroupIds": Object {
"id5": true, "id5": true,
@ -2904,7 +2873,6 @@ Object {
"scrolledOutside": false, "scrolledOutside": false,
"selectedElementIds": Object { "selectedElementIds": Object {
"id0": true, "id0": true,
"id1": true,
}, },
"selectedElementsAreBeingDragged": false, "selectedElementsAreBeingDragged": false,
"selectedGroupIds": Object {}, "selectedGroupIds": Object {},
@ -3059,7 +3027,6 @@ Object {
"name": "Untitled-201933152653", "name": "Untitled-201933152653",
"selectedElementIds": Object { "selectedElementIds": Object {
"id0": true, "id0": true,
"id1": true,
}, },
"selectedGroupIds": Object {}, "selectedGroupIds": Object {},
"viewBackgroundColor": "#ffffff", "viewBackgroundColor": "#ffffff",
@ -3394,7 +3361,6 @@ Object {
"scrolledOutside": false, "scrolledOutside": false,
"selectedElementIds": Object { "selectedElementIds": Object {
"id1": true, "id1": true,
"id3": true,
}, },
"selectedElementsAreBeingDragged": false, "selectedElementsAreBeingDragged": false,
"selectedGroupIds": Object {}, "selectedGroupIds": Object {},
@ -3754,7 +3720,6 @@ Object {
"name": "Untitled-201933152653", "name": "Untitled-201933152653",
"selectedElementIds": Object { "selectedElementIds": Object {
"id1": true, "id1": true,
"id3": true,
}, },
"selectedGroupIds": Object {}, "selectedGroupIds": Object {},
"viewBackgroundColor": "#ffffff", "viewBackgroundColor": "#ffffff",
@ -4247,7 +4212,6 @@ Object {
"scrolledOutside": false, "scrolledOutside": false,
"selectedElementIds": Object { "selectedElementIds": Object {
"id0": true, "id0": true,
"id1": true,
}, },
"selectedElementsAreBeingDragged": false, "selectedElementsAreBeingDragged": false,
"selectedGroupIds": Object {}, "selectedGroupIds": Object {},
@ -4370,7 +4334,6 @@ Object {
"name": "Untitled-201933152653", "name": "Untitled-201933152653",
"selectedElementIds": Object { "selectedElementIds": Object {
"id0": true, "id0": true,
"id1": true,
}, },
"selectedGroupIds": Object {}, "selectedGroupIds": Object {},
"viewBackgroundColor": "#ffffff", "viewBackgroundColor": "#ffffff",
@ -4478,7 +4441,6 @@ Object {
"pendingImageElementId": null, "pendingImageElementId": null,
"previousSelectedElementIds": Object { "previousSelectedElementIds": Object {
"id0": true, "id0": true,
"id1": true,
}, },
"resizingElement": null, "resizingElement": null,
"scrollX": 0, "scrollX": 0,
@ -4486,8 +4448,6 @@ Object {
"scrolledOutside": false, "scrolledOutside": false,
"selectedElementIds": Object { "selectedElementIds": Object {
"id0": true, "id0": true,
"id1": true,
"id2": true,
}, },
"selectedElementsAreBeingDragged": false, "selectedElementsAreBeingDragged": false,
"selectedGroupIds": Object {}, "selectedGroupIds": Object {},
@ -4610,7 +4570,6 @@ Object {
"name": "Untitled-201933152653", "name": "Untitled-201933152653",
"selectedElementIds": Object { "selectedElementIds": Object {
"id0": true, "id0": true,
"id1": true,
}, },
"selectedGroupIds": Object {}, "selectedGroupIds": Object {},
"viewBackgroundColor": "#ffffff", "viewBackgroundColor": "#ffffff",
@ -4654,8 +4613,6 @@ Object {
"name": "Untitled-201933152653", "name": "Untitled-201933152653",
"selectedElementIds": Object { "selectedElementIds": Object {
"id0": true, "id0": true,
"id1": true,
"id2": true,
}, },
"selectedGroupIds": Object {}, "selectedGroupIds": Object {},
"viewBackgroundColor": "#ffffff", "viewBackgroundColor": "#ffffff",
@ -4770,7 +4727,6 @@ Object {
"scrolledOutside": false, "scrolledOutside": false,
"selectedElementIds": Object { "selectedElementIds": Object {
"id0": true, "id0": true,
"id2": true,
}, },
"selectedElementsAreBeingDragged": false, "selectedElementsAreBeingDragged": false,
"selectedGroupIds": Object {}, "selectedGroupIds": Object {},
@ -5069,7 +5025,6 @@ Object {
"selectedElementIds": Object { "selectedElementIds": Object {
"id0": true, "id0": true,
"id1": true, "id1": true,
"id3": true,
}, },
"selectedElementsAreBeingDragged": false, "selectedElementsAreBeingDragged": false,
"selectedGroupIds": Object {}, "selectedGroupIds": Object {},
@ -5522,7 +5477,6 @@ Object {
"previousSelectedElementIds": Object { "previousSelectedElementIds": Object {
"id0": true, "id0": true,
"id1": true, "id1": true,
"id2": true,
}, },
"resizingElement": null, "resizingElement": null,
"scrollX": 0, "scrollX": 0,
@ -5875,7 +5829,6 @@ Object {
"previousSelectedElementIds": Object { "previousSelectedElementIds": Object {
"id0": true, "id0": true,
"id1": true, "id1": true,
"id2": true,
}, },
"resizingElement": null, "resizingElement": null,
"scrollX": 0, "scrollX": 0,
@ -6423,9 +6376,7 @@ Object {
"scrollX": 0, "scrollX": 0,
"scrollY": 0, "scrollY": 0,
"scrolledOutside": false, "scrolledOutside": false,
"selectedElementIds": Object { "selectedElementIds": Object {},
"id1": true,
},
"selectedElementsAreBeingDragged": false, "selectedElementsAreBeingDragged": false,
"selectedGroupIds": Object {}, "selectedGroupIds": Object {},
"selectedLinearElement": null, "selectedLinearElement": null,
@ -7157,7 +7108,6 @@ Object {
"previousSelectedElementIds": Object { "previousSelectedElementIds": Object {
"id0": true, "id0": true,
"id1": true, "id1": true,
"id2": true,
}, },
"resizingElement": null, "resizingElement": null,
"scrollX": 0, "scrollX": 0,
@ -7166,8 +7116,6 @@ Object {
"selectedElementIds": Object { "selectedElementIds": Object {
"id0": true, "id0": true,
"id1": true, "id1": true,
"id2": true,
"id3": true,
}, },
"selectedElementsAreBeingDragged": false, "selectedElementsAreBeingDragged": false,
"selectedGroupIds": Object {}, "selectedGroupIds": Object {},
@ -7395,8 +7343,6 @@ Object {
"selectedElementIds": Object { "selectedElementIds": Object {
"id0": true, "id0": true,
"id1": true, "id1": true,
"id2": true,
"id3": true,
}, },
"selectedGroupIds": Object {}, "selectedGroupIds": Object {},
"viewBackgroundColor": "#ffffff", "viewBackgroundColor": "#ffffff",
@ -7536,9 +7482,7 @@ Object {
"scrollX": 0, "scrollX": 0,
"scrollY": 0, "scrollY": 0,
"scrolledOutside": false, "scrolledOutside": false,
"selectedElementIds": Object { "selectedElementIds": Object {},
"id7": false,
},
"selectedElementsAreBeingDragged": false, "selectedElementsAreBeingDragged": false,
"selectedGroupIds": Object {}, "selectedGroupIds": Object {},
"selectedLinearElement": null, "selectedLinearElement": null,
@ -9539,9 +9483,7 @@ Object {
"editingGroupId": null, "editingGroupId": null,
"editingLinearElement": null, "editingLinearElement": null,
"name": "Untitled-201933152653", "name": "Untitled-201933152653",
"selectedElementIds": Object { "selectedElementIds": Object {},
"id7": false,
},
"selectedGroupIds": Object {}, "selectedGroupIds": Object {},
"viewBackgroundColor": "#ffffff", "viewBackgroundColor": "#ffffff",
}, },
@ -9948,7 +9890,6 @@ Object {
"previousSelectedElementIds": Object { "previousSelectedElementIds": Object {
"id0": true, "id0": true,
"id2": true, "id2": true,
"id3": true,
}, },
"resizingElement": null, "resizingElement": null,
"scrollX": 0, "scrollX": 0,
@ -9956,7 +9897,6 @@ Object {
"scrolledOutside": false, "scrolledOutside": false,
"selectedElementIds": Object { "selectedElementIds": Object {
"id1": true, "id1": true,
"id4": true,
}, },
"selectedElementsAreBeingDragged": false, "selectedElementsAreBeingDragged": false,
"selectedGroupIds": Object {}, "selectedGroupIds": Object {},
@ -10380,7 +10320,6 @@ Object {
"pendingImageElementId": null, "pendingImageElementId": null,
"previousSelectedElementIds": Object { "previousSelectedElementIds": Object {
"id0": true, "id0": true,
"id2": true,
}, },
"resizingElement": null, "resizingElement": null,
"scrollX": 0, "scrollX": 0,
@ -10389,8 +10328,6 @@ Object {
"selectedElementIds": Object { "selectedElementIds": Object {
"id0": true, "id0": true,
"id1": true, "id1": true,
"id2": true,
"id3": true,
}, },
"selectedElementsAreBeingDragged": false, "selectedElementsAreBeingDragged": false,
"selectedGroupIds": Object {}, "selectedGroupIds": Object {},
@ -10681,7 +10618,6 @@ Object {
"pendingImageElementId": null, "pendingImageElementId": null,
"previousSelectedElementIds": Object { "previousSelectedElementIds": Object {
"id0": true, "id0": true,
"id2": true,
}, },
"resizingElement": null, "resizingElement": null,
"scrollX": 0, "scrollX": 0,
@ -10689,7 +10625,6 @@ Object {
"scrolledOutside": false, "scrolledOutside": false,
"selectedElementIds": Object { "selectedElementIds": Object {
"id1": true, "id1": true,
"id3": true,
}, },
"selectedElementsAreBeingDragged": false, "selectedElementsAreBeingDragged": false,
"selectedGroupIds": Object {}, "selectedGroupIds": Object {},
@ -10801,7 +10736,6 @@ Object {
"name": "Untitled-201933152653", "name": "Untitled-201933152653",
"selectedElementIds": Object { "selectedElementIds": Object {
"id0": true, "id0": true,
"id2": true,
}, },
"selectedGroupIds": Object {}, "selectedGroupIds": Object {},
"viewBackgroundColor": "#ffffff", "viewBackgroundColor": "#ffffff",
@ -10938,7 +10872,6 @@ Object {
"pendingImageElementId": null, "pendingImageElementId": null,
"previousSelectedElementIds": Object { "previousSelectedElementIds": Object {
"id0": true, "id0": true,
"id2": true,
}, },
"resizingElement": null, "resizingElement": null,
"scrollX": 0, "scrollX": 0,
@ -10946,8 +10879,6 @@ Object {
"scrolledOutside": false, "scrolledOutside": false,
"selectedElementIds": Object { "selectedElementIds": Object {
"id0": true, "id0": true,
"id2": true,
"id3": true,
}, },
"selectedElementsAreBeingDragged": false, "selectedElementsAreBeingDragged": false,
"selectedGroupIds": Object {}, "selectedGroupIds": Object {},
@ -11059,7 +10990,6 @@ Object {
"name": "Untitled-201933152653", "name": "Untitled-201933152653",
"selectedElementIds": Object { "selectedElementIds": Object {
"id0": true, "id0": true,
"id2": true,
}, },
"selectedGroupIds": Object {}, "selectedGroupIds": Object {},
"viewBackgroundColor": "#ffffff", "viewBackgroundColor": "#ffffff",
@ -11132,8 +11062,6 @@ Object {
"name": "Untitled-201933152653", "name": "Untitled-201933152653",
"selectedElementIds": Object { "selectedElementIds": Object {
"id0": true, "id0": true,
"id2": true,
"id3": true,
}, },
"selectedGroupIds": Object {}, "selectedGroupIds": Object {},
"viewBackgroundColor": "#ffffff", "viewBackgroundColor": "#ffffff",
@ -12334,9 +12262,7 @@ Object {
"scrollX": 0, "scrollX": 0,
"scrollY": 0, "scrollY": 0,
"scrolledOutside": false, "scrolledOutside": false,
"selectedElementIds": Object { "selectedElementIds": Object {},
"id0": false,
},
"selectedElementsAreBeingDragged": false, "selectedElementsAreBeingDragged": false,
"selectedGroupIds": Object {}, "selectedGroupIds": Object {},
"selectedLinearElement": null, "selectedLinearElement": null,
@ -12435,9 +12361,7 @@ Object {
"editingGroupId": null, "editingGroupId": null,
"editingLinearElement": null, "editingLinearElement": null,
"name": "Untitled-201933152653", "name": "Untitled-201933152653",
"selectedElementIds": Object { "selectedElementIds": Object {},
"id0": false,
},
"selectedGroupIds": Object {}, "selectedGroupIds": Object {},
"viewBackgroundColor": "#ffffff", "viewBackgroundColor": "#ffffff",
}, },
@ -13439,9 +13363,7 @@ Object {
"scrollX": 0, "scrollX": 0,
"scrollY": 0, "scrollY": 0,
"scrolledOutside": false, "scrolledOutside": false,
"selectedElementIds": Object { "selectedElementIds": Object {},
"id0": false,
},
"selectedElementsAreBeingDragged": false, "selectedElementsAreBeingDragged": false,
"selectedGroupIds": Object {}, "selectedGroupIds": Object {},
"selectedLinearElement": null, "selectedLinearElement": null,
@ -13540,9 +13462,7 @@ Object {
"editingGroupId": null, "editingGroupId": null,
"editingLinearElement": null, "editingLinearElement": null,
"name": "Untitled-201933152653", "name": "Untitled-201933152653",
"selectedElementIds": Object { "selectedElementIds": Object {},
"id0": false,
},
"selectedGroupIds": Object {}, "selectedGroupIds": Object {},
"viewBackgroundColor": "#ffffff", "viewBackgroundColor": "#ffffff",
}, },
@ -13864,7 +13784,6 @@ Object {
"id0": true, "id0": true,
"id1": true, "id1": true,
"id2": true, "id2": true,
"id3": true,
}, },
"resizingElement": null, "resizingElement": null,
"scrollX": 0, "scrollX": 0,
@ -13874,8 +13793,6 @@ Object {
"id0": true, "id0": true,
"id1": true, "id1": true,
"id2": true, "id2": true,
"id3": true,
"id5": true,
}, },
"selectedElementsAreBeingDragged": false, "selectedElementsAreBeingDragged": false,
"selectedGroupIds": Object { "selectedGroupIds": Object {
@ -14347,7 +14264,6 @@ Object {
"id0": true, "id0": true,
"id1": true, "id1": true,
"id2": true, "id2": true,
"id3": true,
}, },
"selectedGroupIds": Object { "selectedGroupIds": Object {
"id4": true, "id4": true,
@ -14459,8 +14375,6 @@ Object {
"id0": true, "id0": true,
"id1": true, "id1": true,
"id2": true, "id2": true,
"id3": true,
"id5": true,
}, },
"selectedGroupIds": Object { "selectedGroupIds": Object {
"id4": true, "id4": true,
@ -14727,7 +14641,6 @@ Object {
"pendingImageElementId": null, "pendingImageElementId": null,
"previousSelectedElementIds": Object { "previousSelectedElementIds": Object {
"id0": true, "id0": true,
"id3": true,
}, },
"resizingElement": null, "resizingElement": null,
"scrollX": 0, "scrollX": 0,
@ -14735,7 +14648,6 @@ Object {
"scrolledOutside": false, "scrolledOutside": false,
"selectedElementIds": Object { "selectedElementIds": Object {
"id1": true, "id1": true,
"id4": true,
}, },
"selectedElementsAreBeingDragged": false, "selectedElementsAreBeingDragged": false,
"selectedGroupIds": Object {}, "selectedGroupIds": Object {},
@ -15029,9 +14941,7 @@ Object {
"scrollX": -2.916666666666668, "scrollX": -2.916666666666668,
"scrollY": 0, "scrollY": 0,
"scrolledOutside": false, "scrolledOutside": false,
"selectedElementIds": Object { "selectedElementIds": Object {},
"id0": true,
},
"selectedElementsAreBeingDragged": false, "selectedElementsAreBeingDragged": false,
"selectedGroupIds": Object {}, "selectedGroupIds": Object {},
"selectedLinearElement": null, "selectedLinearElement": null,
@ -15261,10 +15171,7 @@ Object {
"scrollX": 0, "scrollX": 0,
"scrollY": 0, "scrollY": 0,
"scrolledOutside": false, "scrolledOutside": false,
"selectedElementIds": Object { "selectedElementIds": Object {},
"id0": false,
"id1": true,
},
"selectedElementsAreBeingDragged": false, "selectedElementsAreBeingDragged": false,
"selectedGroupIds": Object {}, "selectedGroupIds": Object {},
"selectedLinearElement": null, "selectedLinearElement": null,
@ -15451,8 +15358,6 @@ Object {
"previousSelectedElementIds": Object { "previousSelectedElementIds": Object {
"id0": true, "id0": true,
"id1": true, "id1": true,
"id2": true,
"id3": true,
}, },
"resizingElement": null, "resizingElement": null,
"scrollX": 0, "scrollX": 0,
@ -15461,9 +15366,6 @@ Object {
"selectedElementIds": Object { "selectedElementIds": Object {
"id0": true, "id0": true,
"id1": true, "id1": true,
"id2": true,
"id3": true,
"id4": true,
}, },
"selectedElementsAreBeingDragged": false, "selectedElementsAreBeingDragged": false,
"selectedGroupIds": Object {}, "selectedGroupIds": Object {},
@ -15691,9 +15593,6 @@ Object {
"selectedElementIds": Object { "selectedElementIds": Object {
"id0": true, "id0": true,
"id1": true, "id1": true,
"id2": true,
"id3": true,
"id4": true,
}, },
"selectedGroupIds": Object {}, "selectedGroupIds": Object {},
"viewBackgroundColor": "#ffffff", "viewBackgroundColor": "#ffffff",
@ -15832,7 +15731,6 @@ Object {
"id0": true, "id0": true,
"id1": true, "id1": true,
"id2": true, "id2": true,
"id3": true,
}, },
"resizingElement": null, "resizingElement": null,
"scrollX": 0, "scrollX": 0,
@ -15842,7 +15740,6 @@ Object {
"id0": true, "id0": true,
"id1": true, "id1": true,
"id2": true, "id2": true,
"id5": true,
}, },
"selectedElementsAreBeingDragged": false, "selectedElementsAreBeingDragged": false,
"selectedGroupIds": Object {}, "selectedGroupIds": Object {},
@ -16204,7 +16101,6 @@ Object {
"id0": true, "id0": true,
"id1": true, "id1": true,
"id2": true, "id2": true,
"id3": true,
}, },
"selectedGroupIds": Object { "selectedGroupIds": Object {
"id4": true, "id4": true,
@ -16316,7 +16212,6 @@ Object {
"id0": true, "id0": true,
"id1": true, "id1": true,
"id2": true, "id2": true,
"id5": true,
}, },
"selectedGroupIds": Object {}, "selectedGroupIds": Object {},
"viewBackgroundColor": "#ffffff", "viewBackgroundColor": "#ffffff",
@ -16487,7 +16382,6 @@ Object {
"scrolledOutside": false, "scrolledOutside": false,
"selectedElementIds": Object { "selectedElementIds": Object {
"id0": true, "id0": true,
"id1": true,
}, },
"selectedElementsAreBeingDragged": false, "selectedElementsAreBeingDragged": false,
"selectedGroupIds": Object {}, "selectedGroupIds": Object {},
@ -16728,7 +16622,6 @@ Object {
"selectedElementIds": Object { "selectedElementIds": Object {
"id0": true, "id0": true,
"id1": true, "id1": true,
"id11": true,
"id5": true, "id5": true,
"id6": true, "id6": true,
}, },
@ -17036,8 +16929,6 @@ Object {
"selectedElementIds": Object { "selectedElementIds": Object {
"id0": true, "id0": true,
"id1": true, "id1": true,
"id2": true,
"id3": true,
}, },
"selectedGroupIds": Object { "selectedGroupIds": Object {
"id4": true, "id4": true,
@ -17356,8 +17247,6 @@ Object {
"selectedElementIds": Object { "selectedElementIds": Object {
"id5": true, "id5": true,
"id6": true, "id6": true,
"id7": true,
"id8": true,
}, },
"selectedGroupIds": Object { "selectedGroupIds": Object {
"id9": true, "id9": true,
@ -18309,7 +18198,6 @@ Object {
"selectedElementIds": Object { "selectedElementIds": Object {
"id0": true, "id0": true,
"id2": true, "id2": true,
"id4": true,
}, },
"selectedGroupIds": Object {}, "selectedGroupIds": Object {},
"viewBackgroundColor": "#ffffff", "viewBackgroundColor": "#ffffff",
@ -18418,7 +18306,6 @@ Object {
"selectedElementIds": Object { "selectedElementIds": Object {
"id0": true, "id0": true,
"id2": true, "id2": true,
"id4": true,
}, },
"selectedGroupIds": Object { "selectedGroupIds": Object {
"id5": true, "id5": true,
@ -18532,7 +18419,6 @@ Object {
"id0": true, "id0": true,
"id1": true, "id1": true,
"id2": true, "id2": true,
"id7": true,
}, },
"selectedGroupIds": Object { "selectedGroupIds": Object {
"id3": true, "id3": true,
@ -18737,7 +18623,6 @@ Object {
"previousSelectedElementIds": Object { "previousSelectedElementIds": Object {
"id1": true, "id1": true,
"id2": true, "id2": true,
"id3": true,
}, },
"resizingElement": null, "resizingElement": null,
"scrollX": 0, "scrollX": 0,
@ -19552,9 +19437,7 @@ Object {
"scrollX": 10, "scrollX": 10,
"scrollY": -10, "scrollY": -10,
"scrolledOutside": false, "scrolledOutside": false,
"selectedElementIds": Object { "selectedElementIds": Object {},
"id0": true,
},
"selectedElementsAreBeingDragged": false, "selectedElementsAreBeingDragged": false,
"selectedGroupIds": Object {}, "selectedGroupIds": Object {},
"selectedLinearElement": null, "selectedLinearElement": null,

View File

@ -430,7 +430,10 @@ describe("arrow", () => {
const expectedAngle = (7 * Math.PI) / 4; const expectedAngle = (7 * Math.PI) / 4;
const line = createLinearElementWithCurveInsideMinMaxPoints("arrow"); const line = createLinearElementWithCurveInsideMinMaxPoints("arrow");
h.app.scene.replaceAllElements([line]); h.app.scene.replaceAllElements([line]);
h.app.state.selectedElementIds[line.id] = true; h.state.selectedElementIds = {
...h.state.selectedElementIds,
[line.id]: true,
};
mutateElement(line, { mutateElement(line, {
angle: originalAngle, angle: originalAngle,
}); });
@ -446,7 +449,10 @@ describe("arrow", () => {
const expectedAngle = (7 * Math.PI) / 4; const expectedAngle = (7 * Math.PI) / 4;
const line = createLinearElementWithCurveInsideMinMaxPoints("arrow"); const line = createLinearElementWithCurveInsideMinMaxPoints("arrow");
h.app.scene.replaceAllElements([line]); h.app.scene.replaceAllElements([line]);
h.app.state.selectedElementIds[line.id] = true; h.state.selectedElementIds = {
...h.state.selectedElementIds,
[line.id]: true,
};
mutateElement(line, { mutateElement(line, {
angle: originalAngle, angle: originalAngle,
}); });
@ -616,7 +622,10 @@ describe("line", () => {
const expectedAngle = (7 * Math.PI) / 4; const expectedAngle = (7 * Math.PI) / 4;
const line = createLinearElementWithCurveInsideMinMaxPoints("line"); const line = createLinearElementWithCurveInsideMinMaxPoints("line");
h.app.scene.replaceAllElements([line]); h.app.scene.replaceAllElements([line]);
h.app.state.selectedElementIds[line.id] = true; h.state.selectedElementIds = {
...h.state.selectedElementIds,
[line.id]: true,
};
mutateElement(line, { mutateElement(line, {
angle: originalAngle, angle: originalAngle,
}); });
@ -632,7 +641,10 @@ describe("line", () => {
const expectedAngle = (7 * Math.PI) / 4; const expectedAngle = (7 * Math.PI) / 4;
const line = createLinearElementWithCurveInsideMinMaxPoints("line"); const line = createLinearElementWithCurveInsideMinMaxPoints("line");
h.app.scene.replaceAllElements([line]); h.app.scene.replaceAllElements([line]);
h.app.state.selectedElementIds[line.id] = true; h.state.selectedElementIds = {
...h.state.selectedElementIds,
[line.id]: true,
};
mutateElement(line, { mutateElement(line, {
angle: originalAngle, angle: originalAngle,
}); });
@ -659,14 +671,20 @@ describe("freedraw", () => {
it("flips an unrotated drawing horizontally correctly", async () => { it("flips an unrotated drawing horizontally correctly", async () => {
const draw = createAndReturnOneDraw(); const draw = createAndReturnOneDraw();
// select draw, since not done automatically // select draw, since not done automatically
h.state.selectedElementIds[draw.id] = true; h.state.selectedElementIds = {
...h.state.selectedElementIds,
[draw.id]: true,
};
await checkHorizontalFlip(); await checkHorizontalFlip();
}); });
it("flips an unrotated drawing vertically correctly", async () => { it("flips an unrotated drawing vertically correctly", async () => {
const draw = createAndReturnOneDraw(); const draw = createAndReturnOneDraw();
// select draw, since not done automatically // select draw, since not done automatically
h.state.selectedElementIds[draw.id] = true; h.state.selectedElementIds = {
...h.state.selectedElementIds,
[draw.id]: true,
};
await checkVerticalFlip(); await checkVerticalFlip();
}); });
@ -676,7 +694,10 @@ describe("freedraw", () => {
const draw = createAndReturnOneDraw(originalAngle); const draw = createAndReturnOneDraw(originalAngle);
// select draw, since not done automatically // select draw, since not done automatically
h.state.selectedElementIds[draw.id] = true; h.state.selectedElementIds = {
...h.state.selectedElementIds,
[draw.id]: true,
};
await checkRotatedHorizontalFlip(expectedAngle); await checkRotatedHorizontalFlip(expectedAngle);
}); });
@ -687,7 +708,10 @@ describe("freedraw", () => {
const draw = createAndReturnOneDraw(originalAngle); const draw = createAndReturnOneDraw(originalAngle);
// select draw, since not done automatically // select draw, since not done automatically
h.state.selectedElementIds[draw.id] = true; h.state.selectedElementIds = {
...h.state.selectedElementIds,
[draw.id]: true,
};
await checkRotatedVerticalFlip(expectedAngle); await checkRotatedVerticalFlip(expectedAngle);
}); });

View File

@ -89,6 +89,7 @@ const populateElements = (
...selectGroupsForSelectedElements( ...selectGroupsForSelectedElements(
{ ...h.state, ...appState, selectedElementIds }, { ...h.state, ...appState, selectedElementIds },
h.elements, h.elements,
h.state,
), ),
...appState, ...appState,
selectedElementIds, selectedElementIds,

View File

@ -181,8 +181,8 @@ export type AppState = {
defaultSidebarDockedPreference: boolean; defaultSidebarDockedPreference: boolean;
lastPointerDownWith: PointerType; lastPointerDownWith: PointerType;
selectedElementIds: { [id: string]: boolean }; selectedElementIds: Readonly<{ [id: string]: true }>;
previousSelectedElementIds: { [id: string]: boolean }; previousSelectedElementIds: { [id: string]: true };
selectedElementsAreBeingDragged: boolean; selectedElementsAreBeingDragged: boolean;
shouldCacheIgnoreZoom: boolean; shouldCacheIgnoreZoom: boolean;
toast: { message: string; closable?: boolean; duration?: number } | null; toast: { message: string; closable?: boolean; duration?: number } | null;