feat: cache most of element selection (#6747)
This commit is contained in:
parent
2e46e27490
commit
9f76f8677b
@ -1,6 +1,4 @@
|
||||
import { register } from "./register";
|
||||
import { getSelectedElements } from "../scene";
|
||||
import { getNonDeletedElements } from "../element";
|
||||
import { deepCopyElement } from "../element/newElement";
|
||||
import { randomId } from "../random";
|
||||
import { t } from "../i18n";
|
||||
@ -9,14 +7,11 @@ export const actionAddToLibrary = register({
|
||||
name: "addToLibrary",
|
||||
trackEvent: { category: "element" },
|
||||
perform: (elements, appState, _, app) => {
|
||||
const selectedElements = getSelectedElements(
|
||||
getNonDeletedElements(elements),
|
||||
appState,
|
||||
{
|
||||
const selectedElements = app.scene.getSelectedElements({
|
||||
selectedElementIds: appState.selectedElementIds,
|
||||
includeBoundTextElement: true,
|
||||
includeElementsInFrames: true,
|
||||
},
|
||||
);
|
||||
});
|
||||
if (selectedElements.some((element) => element.type === "image")) {
|
||||
return {
|
||||
commitToHistory: false,
|
||||
|
@ -13,19 +13,18 @@ import { ExcalidrawElement } from "../element/types";
|
||||
import { updateFrameMembershipOfSelectedElements } from "../frame";
|
||||
import { t } from "../i18n";
|
||||
import { KEYS } from "../keys";
|
||||
import { getSelectedElements, isSomeElementSelected } from "../scene";
|
||||
import { AppState } from "../types";
|
||||
import { isSomeElementSelected } from "../scene";
|
||||
import { AppClassProperties, AppState } from "../types";
|
||||
import { arrayToMap, getShortcutKey } from "../utils";
|
||||
import { register } from "./register";
|
||||
|
||||
const alignActionsPredicate = (
|
||||
elements: readonly ExcalidrawElement[],
|
||||
appState: AppState,
|
||||
_: unknown,
|
||||
app: AppClassProperties,
|
||||
) => {
|
||||
const selectedElements = getSelectedElements(
|
||||
getNonDeletedElements(elements),
|
||||
appState,
|
||||
);
|
||||
const selectedElements = app.scene.getSelectedElements(appState);
|
||||
return (
|
||||
selectedElements.length > 1 &&
|
||||
// TODO enable aligning frames when implemented properly
|
||||
@ -36,12 +35,10 @@ const alignActionsPredicate = (
|
||||
const alignSelectedElements = (
|
||||
elements: readonly ExcalidrawElement[],
|
||||
appState: Readonly<AppState>,
|
||||
app: AppClassProperties,
|
||||
alignment: Alignment,
|
||||
) => {
|
||||
const selectedElements = getSelectedElements(
|
||||
getNonDeletedElements(elements),
|
||||
appState,
|
||||
);
|
||||
const selectedElements = app.scene.getSelectedElements(appState);
|
||||
|
||||
const updatedElements = alignElements(selectedElements, alignment);
|
||||
|
||||
@ -50,6 +47,7 @@ const alignSelectedElements = (
|
||||
return updateFrameMembershipOfSelectedElements(
|
||||
elements.map((element) => updatedElementsMap.get(element.id) || element),
|
||||
appState,
|
||||
app,
|
||||
);
|
||||
};
|
||||
|
||||
@ -57,10 +55,10 @@ export const actionAlignTop = register({
|
||||
name: "alignTop",
|
||||
trackEvent: { category: "element" },
|
||||
predicate: alignActionsPredicate,
|
||||
perform: (elements, appState) => {
|
||||
perform: (elements, appState, _, app) => {
|
||||
return {
|
||||
appState,
|
||||
elements: alignSelectedElements(elements, appState, {
|
||||
elements: alignSelectedElements(elements, appState, app, {
|
||||
position: "start",
|
||||
axis: "y",
|
||||
}),
|
||||
@ -69,9 +67,9 @@ export const actionAlignTop = register({
|
||||
},
|
||||
keyTest: (event) =>
|
||||
event[KEYS.CTRL_OR_CMD] && event.shiftKey && event.key === KEYS.ARROW_UP,
|
||||
PanelComponent: ({ elements, appState, updateData }) => (
|
||||
PanelComponent: ({ elements, appState, updateData, app }) => (
|
||||
<ToolButton
|
||||
hidden={!alignActionsPredicate(elements, appState)}
|
||||
hidden={!alignActionsPredicate(elements, appState, null, app)}
|
||||
type="button"
|
||||
icon={AlignTopIcon}
|
||||
onClick={() => updateData(null)}
|
||||
@ -88,10 +86,10 @@ export const actionAlignBottom = register({
|
||||
name: "alignBottom",
|
||||
trackEvent: { category: "element" },
|
||||
predicate: alignActionsPredicate,
|
||||
perform: (elements, appState) => {
|
||||
perform: (elements, appState, _, app) => {
|
||||
return {
|
||||
appState,
|
||||
elements: alignSelectedElements(elements, appState, {
|
||||
elements: alignSelectedElements(elements, appState, app, {
|
||||
position: "end",
|
||||
axis: "y",
|
||||
}),
|
||||
@ -100,9 +98,9 @@ export const actionAlignBottom = register({
|
||||
},
|
||||
keyTest: (event) =>
|
||||
event[KEYS.CTRL_OR_CMD] && event.shiftKey && event.key === KEYS.ARROW_DOWN,
|
||||
PanelComponent: ({ elements, appState, updateData }) => (
|
||||
PanelComponent: ({ elements, appState, updateData, app }) => (
|
||||
<ToolButton
|
||||
hidden={!alignActionsPredicate(elements, appState)}
|
||||
hidden={!alignActionsPredicate(elements, appState, null, app)}
|
||||
type="button"
|
||||
icon={AlignBottomIcon}
|
||||
onClick={() => updateData(null)}
|
||||
@ -119,10 +117,10 @@ export const actionAlignLeft = register({
|
||||
name: "alignLeft",
|
||||
trackEvent: { category: "element" },
|
||||
predicate: alignActionsPredicate,
|
||||
perform: (elements, appState) => {
|
||||
perform: (elements, appState, _, app) => {
|
||||
return {
|
||||
appState,
|
||||
elements: alignSelectedElements(elements, appState, {
|
||||
elements: alignSelectedElements(elements, appState, app, {
|
||||
position: "start",
|
||||
axis: "x",
|
||||
}),
|
||||
@ -131,9 +129,9 @@ export const actionAlignLeft = register({
|
||||
},
|
||||
keyTest: (event) =>
|
||||
event[KEYS.CTRL_OR_CMD] && event.shiftKey && event.key === KEYS.ARROW_LEFT,
|
||||
PanelComponent: ({ elements, appState, updateData }) => (
|
||||
PanelComponent: ({ elements, appState, updateData, app }) => (
|
||||
<ToolButton
|
||||
hidden={!alignActionsPredicate(elements, appState)}
|
||||
hidden={!alignActionsPredicate(elements, appState, null, app)}
|
||||
type="button"
|
||||
icon={AlignLeftIcon}
|
||||
onClick={() => updateData(null)}
|
||||
@ -150,10 +148,10 @@ export const actionAlignRight = register({
|
||||
name: "alignRight",
|
||||
trackEvent: { category: "element" },
|
||||
predicate: alignActionsPredicate,
|
||||
perform: (elements, appState) => {
|
||||
perform: (elements, appState, _, app) => {
|
||||
return {
|
||||
appState,
|
||||
elements: alignSelectedElements(elements, appState, {
|
||||
elements: alignSelectedElements(elements, appState, app, {
|
||||
position: "end",
|
||||
axis: "x",
|
||||
}),
|
||||
@ -162,9 +160,9 @@ export const actionAlignRight = register({
|
||||
},
|
||||
keyTest: (event) =>
|
||||
event[KEYS.CTRL_OR_CMD] && event.shiftKey && event.key === KEYS.ARROW_RIGHT,
|
||||
PanelComponent: ({ elements, appState, updateData }) => (
|
||||
PanelComponent: ({ elements, appState, updateData, app }) => (
|
||||
<ToolButton
|
||||
hidden={!alignActionsPredicate(elements, appState)}
|
||||
hidden={!alignActionsPredicate(elements, appState, null, app)}
|
||||
type="button"
|
||||
icon={AlignRightIcon}
|
||||
onClick={() => updateData(null)}
|
||||
@ -181,19 +179,19 @@ export const actionAlignVerticallyCentered = register({
|
||||
name: "alignVerticallyCentered",
|
||||
trackEvent: { category: "element" },
|
||||
predicate: alignActionsPredicate,
|
||||
perform: (elements, appState) => {
|
||||
perform: (elements, appState, _, app) => {
|
||||
return {
|
||||
appState,
|
||||
elements: alignSelectedElements(elements, appState, {
|
||||
elements: alignSelectedElements(elements, appState, app, {
|
||||
position: "center",
|
||||
axis: "y",
|
||||
}),
|
||||
commitToHistory: true,
|
||||
};
|
||||
},
|
||||
PanelComponent: ({ elements, appState, updateData }) => (
|
||||
PanelComponent: ({ elements, appState, updateData, app }) => (
|
||||
<ToolButton
|
||||
hidden={!alignActionsPredicate(elements, appState)}
|
||||
hidden={!alignActionsPredicate(elements, appState, null, app)}
|
||||
type="button"
|
||||
icon={CenterVerticallyIcon}
|
||||
onClick={() => updateData(null)}
|
||||
@ -208,19 +206,19 @@ export const actionAlignHorizontallyCentered = register({
|
||||
name: "alignHorizontallyCentered",
|
||||
trackEvent: { category: "element" },
|
||||
predicate: alignActionsPredicate,
|
||||
perform: (elements, appState) => {
|
||||
perform: (elements, appState, _, app) => {
|
||||
return {
|
||||
appState,
|
||||
elements: alignSelectedElements(elements, appState, {
|
||||
elements: alignSelectedElements(elements, appState, app, {
|
||||
position: "center",
|
||||
axis: "x",
|
||||
}),
|
||||
commitToHistory: true,
|
||||
};
|
||||
},
|
||||
PanelComponent: ({ elements, appState, updateData }) => (
|
||||
PanelComponent: ({ elements, appState, updateData, app }) => (
|
||||
<ToolButton
|
||||
hidden={!alignActionsPredicate(elements, appState)}
|
||||
hidden={!alignActionsPredicate(elements, appState, null, app)}
|
||||
type="button"
|
||||
icon={CenterHorizontallyIcon}
|
||||
onClick={() => updateData(null)}
|
||||
|
@ -4,7 +4,7 @@ import {
|
||||
VERTICAL_ALIGN,
|
||||
TEXT_ALIGN,
|
||||
} from "../constants";
|
||||
import { getNonDeletedElements, isTextElement, newElement } from "../element";
|
||||
import { isTextElement, newElement } from "../element";
|
||||
import { mutateElement } from "../element/mutateElement";
|
||||
import {
|
||||
computeBoundTextPosition,
|
||||
@ -29,7 +29,6 @@ import {
|
||||
ExcalidrawTextContainer,
|
||||
ExcalidrawTextElement,
|
||||
} from "../element/types";
|
||||
import { getSelectedElements } from "../scene";
|
||||
import { AppState } from "../types";
|
||||
import { Mutable } from "../utility-types";
|
||||
import { getFontString } from "../utils";
|
||||
@ -39,16 +38,13 @@ export const actionUnbindText = register({
|
||||
name: "unbindText",
|
||||
contextItemLabel: "labels.unbindText",
|
||||
trackEvent: { category: "element" },
|
||||
predicate: (elements, appState) => {
|
||||
const selectedElements = getSelectedElements(elements, appState);
|
||||
predicate: (elements, appState, _, app) => {
|
||||
const selectedElements = app.scene.getSelectedElements(appState);
|
||||
|
||||
return selectedElements.some((element) => hasBoundTextElement(element));
|
||||
},
|
||||
perform: (elements, appState) => {
|
||||
const selectedElements = getSelectedElements(
|
||||
getNonDeletedElements(elements),
|
||||
appState,
|
||||
);
|
||||
perform: (elements, appState, _, app) => {
|
||||
const selectedElements = app.scene.getSelectedElements(appState);
|
||||
selectedElements.forEach((element) => {
|
||||
const boundTextElement = getBoundTextElement(element);
|
||||
if (boundTextElement) {
|
||||
@ -93,8 +89,8 @@ export const actionBindText = register({
|
||||
name: "bindText",
|
||||
contextItemLabel: "labels.bindText",
|
||||
trackEvent: { category: "element" },
|
||||
predicate: (elements, appState) => {
|
||||
const selectedElements = getSelectedElements(elements, appState);
|
||||
predicate: (elements, appState, _, app) => {
|
||||
const selectedElements = app.scene.getSelectedElements(appState);
|
||||
|
||||
if (selectedElements.length === 2) {
|
||||
const textElement =
|
||||
@ -117,11 +113,8 @@ export const actionBindText = register({
|
||||
}
|
||||
return false;
|
||||
},
|
||||
perform: (elements, appState) => {
|
||||
const selectedElements = getSelectedElements(
|
||||
getNonDeletedElements(elements),
|
||||
appState,
|
||||
);
|
||||
perform: (elements, appState, _, app) => {
|
||||
const selectedElements = app.scene.getSelectedElements(appState);
|
||||
|
||||
let textElement: ExcalidrawTextElement;
|
||||
let container: ExcalidrawTextContainer;
|
||||
@ -201,16 +194,13 @@ export const actionWrapTextInContainer = register({
|
||||
name: "wrapTextInContainer",
|
||||
contextItemLabel: "labels.createContainerFromText",
|
||||
trackEvent: { category: "element" },
|
||||
predicate: (elements, appState) => {
|
||||
const selectedElements = getSelectedElements(elements, appState);
|
||||
predicate: (elements, appState, _, app) => {
|
||||
const selectedElements = app.scene.getSelectedElements(appState);
|
||||
const areTextElements = selectedElements.every((el) => isTextElement(el));
|
||||
return selectedElements.length > 0 && areTextElements;
|
||||
},
|
||||
perform: (elements, appState) => {
|
||||
const selectedElements = getSelectedElements(
|
||||
getNonDeletedElements(elements),
|
||||
appState,
|
||||
);
|
||||
perform: (elements, appState, _, app) => {
|
||||
const selectedElements = app.scene.getSelectedElements(appState);
|
||||
let updatedElements: readonly ExcalidrawElement[] = elements.slice();
|
||||
const containerIds: Mutable<AppState["selectedElementIds"]> = {};
|
||||
|
||||
|
@ -6,7 +6,7 @@ import { getCommonBounds, getNonDeletedElements } from "../element";
|
||||
import { ExcalidrawElement } from "../element/types";
|
||||
import { t } from "../i18n";
|
||||
import { CODES, KEYS } from "../keys";
|
||||
import { getNormalizedZoom, getSelectedElements } from "../scene";
|
||||
import { getNormalizedZoom } from "../scene";
|
||||
import { centerScrollOn } from "../scene/scroll";
|
||||
import { getStateForZoom } from "../scene/zoom";
|
||||
import { AppState, NormalizedZoomValue } from "../types";
|
||||
@ -302,11 +302,8 @@ export const zoomToFit = ({
|
||||
export const actionZoomToFitSelectionInViewport = register({
|
||||
name: "zoomToFitSelectionInViewport",
|
||||
trackEvent: { category: "canvas" },
|
||||
perform: (elements, appState) => {
|
||||
const selectedElements = getSelectedElements(
|
||||
getNonDeletedElements(elements),
|
||||
appState,
|
||||
);
|
||||
perform: (elements, appState, _, app) => {
|
||||
const selectedElements = app.scene.getSelectedElements(appState);
|
||||
return zoomToFit({
|
||||
targetElements: selectedElements.length ? selectedElements : elements,
|
||||
appState,
|
||||
@ -325,11 +322,8 @@ export const actionZoomToFitSelectionInViewport = register({
|
||||
export const actionZoomToFitSelection = register({
|
||||
name: "zoomToFitSelection",
|
||||
trackEvent: { category: "canvas" },
|
||||
perform: (elements, appState) => {
|
||||
const selectedElements = getSelectedElements(
|
||||
getNonDeletedElements(elements),
|
||||
appState,
|
||||
);
|
||||
perform: (elements, appState, _, app) => {
|
||||
const selectedElements = app.scene.getSelectedElements(appState);
|
||||
return zoomToFit({
|
||||
targetElements: selectedElements.length ? selectedElements : elements,
|
||||
appState,
|
||||
|
@ -7,7 +7,6 @@ import {
|
||||
probablySupportsClipboardWriteText,
|
||||
} from "../clipboard";
|
||||
import { actionDeleteSelected } from "./actionDeleteSelected";
|
||||
import { getSelectedElements } from "../scene/selection";
|
||||
import { exportCanvas } from "../data/index";
|
||||
import { getNonDeletedElements, isTextElement } from "../element";
|
||||
import { t } from "../i18n";
|
||||
@ -16,7 +15,8 @@ export const actionCopy = register({
|
||||
name: "copy",
|
||||
trackEvent: { category: "element" },
|
||||
perform: (elements, appState, _, app) => {
|
||||
const elementsToCopy = getSelectedElements(elements, appState, {
|
||||
const elementsToCopy = app.scene.getSelectedElements({
|
||||
selectedElementIds: appState.selectedElementIds,
|
||||
includeBoundTextElement: true,
|
||||
includeElementsInFrames: true,
|
||||
});
|
||||
@ -75,14 +75,11 @@ export const actionCopyAsSvg = register({
|
||||
commitToHistory: false,
|
||||
};
|
||||
}
|
||||
const selectedElements = getSelectedElements(
|
||||
getNonDeletedElements(elements),
|
||||
appState,
|
||||
{
|
||||
const selectedElements = app.scene.getSelectedElements({
|
||||
selectedElementIds: appState.selectedElementIds,
|
||||
includeBoundTextElement: true,
|
||||
includeElementsInFrames: true,
|
||||
},
|
||||
);
|
||||
});
|
||||
try {
|
||||
await exportCanvas(
|
||||
"clipboard-svg",
|
||||
@ -122,14 +119,11 @@ export const actionCopyAsPng = register({
|
||||
commitToHistory: false,
|
||||
};
|
||||
}
|
||||
const selectedElements = getSelectedElements(
|
||||
getNonDeletedElements(elements),
|
||||
appState,
|
||||
{
|
||||
const selectedElements = app.scene.getSelectedElements({
|
||||
selectedElementIds: appState.selectedElementIds,
|
||||
includeBoundTextElement: true,
|
||||
includeElementsInFrames: true,
|
||||
},
|
||||
);
|
||||
});
|
||||
try {
|
||||
await exportCanvas(
|
||||
"clipboard",
|
||||
@ -177,14 +171,11 @@ export const actionCopyAsPng = register({
|
||||
export const copyText = register({
|
||||
name: "copyText",
|
||||
trackEvent: { category: "element" },
|
||||
perform: (elements, appState) => {
|
||||
const selectedElements = getSelectedElements(
|
||||
getNonDeletedElements(elements),
|
||||
appState,
|
||||
{
|
||||
perform: (elements, appState, _, app) => {
|
||||
const selectedElements = app.scene.getSelectedElements({
|
||||
selectedElementIds: appState.selectedElementIds,
|
||||
includeBoundTextElement: true,
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
const text = selectedElements
|
||||
.reduce((acc: string[], element) => {
|
||||
@ -199,12 +190,15 @@ export const copyText = register({
|
||||
commitToHistory: false,
|
||||
};
|
||||
},
|
||||
predicate: (elements, appState) => {
|
||||
predicate: (elements, appState, _, app) => {
|
||||
return (
|
||||
probablySupportsClipboardWriteText &&
|
||||
getSelectedElements(elements, appState, {
|
||||
app.scene
|
||||
.getSelectedElements({
|
||||
selectedElementIds: appState.selectedElementIds,
|
||||
includeBoundTextElement: true,
|
||||
}).some(isTextElement)
|
||||
})
|
||||
.some(isTextElement)
|
||||
);
|
||||
},
|
||||
contextItemLabel: "labels.copyText",
|
||||
|
@ -9,19 +9,13 @@ import { ExcalidrawElement } from "../element/types";
|
||||
import { updateFrameMembershipOfSelectedElements } from "../frame";
|
||||
import { t } from "../i18n";
|
||||
import { CODES, KEYS } from "../keys";
|
||||
import { getSelectedElements, isSomeElementSelected } from "../scene";
|
||||
import { AppState } from "../types";
|
||||
import { isSomeElementSelected } from "../scene";
|
||||
import { AppClassProperties, AppState } from "../types";
|
||||
import { arrayToMap, getShortcutKey } from "../utils";
|
||||
import { register } from "./register";
|
||||
|
||||
const enableActionGroup = (
|
||||
elements: readonly ExcalidrawElement[],
|
||||
appState: AppState,
|
||||
) => {
|
||||
const selectedElements = getSelectedElements(
|
||||
getNonDeletedElements(elements),
|
||||
appState,
|
||||
);
|
||||
const enableActionGroup = (appState: AppState, app: AppClassProperties) => {
|
||||
const selectedElements = app.scene.getSelectedElements(appState);
|
||||
return (
|
||||
selectedElements.length > 1 &&
|
||||
// TODO enable distributing frames when implemented properly
|
||||
@ -32,12 +26,10 @@ const enableActionGroup = (
|
||||
const distributeSelectedElements = (
|
||||
elements: readonly ExcalidrawElement[],
|
||||
appState: Readonly<AppState>,
|
||||
app: AppClassProperties,
|
||||
distribution: Distribution,
|
||||
) => {
|
||||
const selectedElements = getSelectedElements(
|
||||
getNonDeletedElements(elements),
|
||||
appState,
|
||||
);
|
||||
const selectedElements = app.scene.getSelectedElements(appState);
|
||||
|
||||
const updatedElements = distributeElements(selectedElements, distribution);
|
||||
|
||||
@ -46,16 +38,17 @@ const distributeSelectedElements = (
|
||||
return updateFrameMembershipOfSelectedElements(
|
||||
elements.map((element) => updatedElementsMap.get(element.id) || element),
|
||||
appState,
|
||||
app,
|
||||
);
|
||||
};
|
||||
|
||||
export const distributeHorizontally = register({
|
||||
name: "distributeHorizontally",
|
||||
trackEvent: { category: "element" },
|
||||
perform: (elements, appState) => {
|
||||
perform: (elements, appState, _, app) => {
|
||||
return {
|
||||
appState,
|
||||
elements: distributeSelectedElements(elements, appState, {
|
||||
elements: distributeSelectedElements(elements, appState, app, {
|
||||
space: "between",
|
||||
axis: "x",
|
||||
}),
|
||||
@ -64,9 +57,9 @@ export const distributeHorizontally = register({
|
||||
},
|
||||
keyTest: (event) =>
|
||||
!event[KEYS.CTRL_OR_CMD] && event.altKey && event.code === CODES.H,
|
||||
PanelComponent: ({ elements, appState, updateData }) => (
|
||||
PanelComponent: ({ elements, appState, updateData, app }) => (
|
||||
<ToolButton
|
||||
hidden={!enableActionGroup(elements, appState)}
|
||||
hidden={!enableActionGroup(appState, app)}
|
||||
type="button"
|
||||
icon={DistributeHorizontallyIcon}
|
||||
onClick={() => updateData(null)}
|
||||
@ -82,10 +75,10 @@ export const distributeHorizontally = register({
|
||||
export const distributeVertically = register({
|
||||
name: "distributeVertically",
|
||||
trackEvent: { category: "element" },
|
||||
perform: (elements, appState) => {
|
||||
perform: (elements, appState, _, app) => {
|
||||
return {
|
||||
appState,
|
||||
elements: distributeSelectedElements(elements, appState, {
|
||||
elements: distributeSelectedElements(elements, appState, app, {
|
||||
space: "between",
|
||||
axis: "y",
|
||||
}),
|
||||
@ -94,9 +87,9 @@ export const distributeVertically = register({
|
||||
},
|
||||
keyTest: (event) =>
|
||||
!event[KEYS.CTRL_OR_CMD] && event.altKey && event.code === CODES.V,
|
||||
PanelComponent: ({ elements, appState, updateData }) => (
|
||||
PanelComponent: ({ elements, appState, updateData, app }) => (
|
||||
<ToolButton
|
||||
hidden={!enableActionGroup(elements, appState)}
|
||||
hidden={!enableActionGroup(appState, app)}
|
||||
type="button"
|
||||
icon={DistributeVerticallyIcon}
|
||||
onClick={() => updateData(null)}
|
||||
|
@ -275,6 +275,7 @@ const duplicateElements = (
|
||||
},
|
||||
getNonDeletedElements(finalElements),
|
||||
appState,
|
||||
null,
|
||||
),
|
||||
};
|
||||
};
|
||||
|
@ -1,7 +1,6 @@
|
||||
import { newElementWith } from "../element/mutateElement";
|
||||
import { ExcalidrawElement } from "../element/types";
|
||||
import { KEYS } from "../keys";
|
||||
import { getSelectedElements } from "../scene";
|
||||
import { arrayToMap } from "../utils";
|
||||
import { register } from "./register";
|
||||
|
||||
@ -11,14 +10,15 @@ const shouldLock = (elements: readonly ExcalidrawElement[]) =>
|
||||
export const actionToggleElementLock = register({
|
||||
name: "toggleElementLock",
|
||||
trackEvent: { category: "element" },
|
||||
predicate: (elements, appState) => {
|
||||
const selectedElements = getSelectedElements(elements, appState);
|
||||
predicate: (elements, appState, _, app) => {
|
||||
const selectedElements = app.scene.getSelectedElements(appState);
|
||||
return !selectedElements.some(
|
||||
(element) => element.locked && element.frameId,
|
||||
);
|
||||
},
|
||||
perform: (elements, appState) => {
|
||||
const selectedElements = getSelectedElements(elements, appState, {
|
||||
perform: (elements, appState, _, app) => {
|
||||
const selectedElements = app.scene.getSelectedElements({
|
||||
selectedElementIds: appState.selectedElementIds,
|
||||
includeBoundTextElement: true,
|
||||
includeElementsInFrames: true,
|
||||
});
|
||||
@ -46,8 +46,9 @@ export const actionToggleElementLock = register({
|
||||
commitToHistory: true,
|
||||
};
|
||||
},
|
||||
contextItemLabel: (elements, appState) => {
|
||||
const selected = getSelectedElements(elements, appState, {
|
||||
contextItemLabel: (elements, appState, app) => {
|
||||
const selected = app.scene.getSelectedElements({
|
||||
selectedElementIds: appState.selectedElementIds,
|
||||
includeBoundTextElement: false,
|
||||
});
|
||||
if (selected.length === 1 && selected[0].type !== "frame") {
|
||||
@ -60,12 +61,13 @@ export const actionToggleElementLock = register({
|
||||
? "labels.elementLock.lockAll"
|
||||
: "labels.elementLock.unlockAll";
|
||||
},
|
||||
keyTest: (event, appState, elements) => {
|
||||
keyTest: (event, appState, elements, app) => {
|
||||
return (
|
||||
event.key.toLocaleLowerCase() === KEYS.L &&
|
||||
event[KEYS.CTRL_OR_CMD] &&
|
||||
event.shiftKey &&
|
||||
getSelectedElements(elements, appState, {
|
||||
app.scene.getSelectedElements({
|
||||
selectedElementIds: appState.selectedElementIds,
|
||||
includeBoundTextElement: false,
|
||||
}).length > 0
|
||||
);
|
||||
|
@ -17,11 +17,12 @@ import { updateFrameMembershipOfSelectedElements } from "../frame";
|
||||
export const actionFlipHorizontal = register({
|
||||
name: "flipHorizontal",
|
||||
trackEvent: { category: "element" },
|
||||
perform: (elements, appState) => {
|
||||
perform: (elements, appState, _, app) => {
|
||||
return {
|
||||
elements: updateFrameMembershipOfSelectedElements(
|
||||
flipSelectedElements(elements, appState, "horizontal"),
|
||||
appState,
|
||||
app,
|
||||
),
|
||||
appState,
|
||||
commitToHistory: true,
|
||||
@ -34,11 +35,12 @@ export const actionFlipHorizontal = register({
|
||||
export const actionFlipVertical = register({
|
||||
name: "flipVertical",
|
||||
trackEvent: { category: "element" },
|
||||
perform: (elements, appState) => {
|
||||
perform: (elements, appState, _, app) => {
|
||||
return {
|
||||
elements: updateFrameMembershipOfSelectedElements(
|
||||
flipSelectedElements(elements, appState, "vertical"),
|
||||
appState,
|
||||
app,
|
||||
),
|
||||
appState,
|
||||
commitToHistory: true,
|
||||
|
@ -3,19 +3,12 @@ import { ExcalidrawElement } from "../element/types";
|
||||
import { removeAllElementsFromFrame } from "../frame";
|
||||
import { getFrameElements } from "../frame";
|
||||
import { KEYS } from "../keys";
|
||||
import { getSelectedElements } from "../scene";
|
||||
import { AppState } from "../types";
|
||||
import { AppClassProperties, AppState } from "../types";
|
||||
import { setCursorForShape, updateActiveTool } from "../utils";
|
||||
import { register } from "./register";
|
||||
|
||||
const isSingleFrameSelected = (
|
||||
elements: readonly ExcalidrawElement[],
|
||||
appState: AppState,
|
||||
) => {
|
||||
const selectedElements = getSelectedElements(
|
||||
getNonDeletedElements(elements),
|
||||
appState,
|
||||
);
|
||||
const isSingleFrameSelected = (appState: AppState, app: AppClassProperties) => {
|
||||
const selectedElements = app.scene.getSelectedElements(appState);
|
||||
|
||||
return selectedElements.length === 1 && selectedElements[0].type === "frame";
|
||||
};
|
||||
@ -23,11 +16,8 @@ const isSingleFrameSelected = (
|
||||
export const actionSelectAllElementsInFrame = register({
|
||||
name: "selectAllElementsInFrame",
|
||||
trackEvent: { category: "canvas" },
|
||||
perform: (elements, appState) => {
|
||||
const selectedFrame = getSelectedElements(
|
||||
getNonDeletedElements(elements),
|
||||
appState,
|
||||
)[0];
|
||||
perform: (elements, appState, _, app) => {
|
||||
const selectedFrame = app.scene.getSelectedElements(appState)[0];
|
||||
|
||||
if (selectedFrame && selectedFrame.type === "frame") {
|
||||
const elementsInFrame = getFrameElements(
|
||||
@ -55,17 +45,15 @@ export const actionSelectAllElementsInFrame = register({
|
||||
};
|
||||
},
|
||||
contextItemLabel: "labels.selectAllElementsInFrame",
|
||||
predicate: (elements, appState) => isSingleFrameSelected(elements, appState),
|
||||
predicate: (elements, appState, _, app) =>
|
||||
isSingleFrameSelected(appState, app),
|
||||
});
|
||||
|
||||
export const actionRemoveAllElementsFromFrame = register({
|
||||
name: "removeAllElementsFromFrame",
|
||||
trackEvent: { category: "history" },
|
||||
perform: (elements, appState) => {
|
||||
const selectedFrame = getSelectedElements(
|
||||
getNonDeletedElements(elements),
|
||||
appState,
|
||||
)[0];
|
||||
perform: (elements, appState, _, app) => {
|
||||
const selectedFrame = app.scene.getSelectedElements(appState)[0];
|
||||
|
||||
if (selectedFrame && selectedFrame.type === "frame") {
|
||||
return {
|
||||
@ -87,7 +75,8 @@ export const actionRemoveAllElementsFromFrame = register({
|
||||
};
|
||||
},
|
||||
contextItemLabel: "labels.removeAllElementsFromFrame",
|
||||
predicate: (elements, appState) => isSingleFrameSelected(elements, appState),
|
||||
predicate: (elements, appState, _, app) =>
|
||||
isSingleFrameSelected(appState, app),
|
||||
});
|
||||
|
||||
export const actionupdateFrameRendering = register({
|
||||
|
@ -4,7 +4,7 @@ import { arrayToMap, getShortcutKey } from "../utils";
|
||||
import { register } from "./register";
|
||||
import { UngroupIcon, GroupIcon } from "../components/icons";
|
||||
import { newElementWith } from "../element/mutateElement";
|
||||
import { getSelectedElements, isSomeElementSelected } from "../scene";
|
||||
import { isSomeElementSelected } from "../scene";
|
||||
import {
|
||||
getSelectedGroupIds,
|
||||
selectGroup,
|
||||
@ -22,7 +22,7 @@ import {
|
||||
ExcalidrawFrameElement,
|
||||
ExcalidrawTextElement,
|
||||
} from "../element/types";
|
||||
import { AppState } from "../types";
|
||||
import { AppClassProperties, AppState } from "../types";
|
||||
import { isBoundToContainer } from "../element/typeChecks";
|
||||
import {
|
||||
getElementsInResizingFrame,
|
||||
@ -51,14 +51,12 @@ const allElementsInSameGroup = (elements: readonly ExcalidrawElement[]) => {
|
||||
const enableActionGroup = (
|
||||
elements: readonly ExcalidrawElement[],
|
||||
appState: AppState,
|
||||
app: AppClassProperties,
|
||||
) => {
|
||||
const selectedElements = getSelectedElements(
|
||||
getNonDeletedElements(elements),
|
||||
appState,
|
||||
{
|
||||
const selectedElements = app.scene.getSelectedElements({
|
||||
selectedElementIds: appState.selectedElementIds,
|
||||
includeBoundTextElement: true,
|
||||
},
|
||||
);
|
||||
});
|
||||
return (
|
||||
selectedElements.length >= 2 && !allElementsInSameGroup(selectedElements)
|
||||
);
|
||||
@ -68,13 +66,10 @@ export const actionGroup = register({
|
||||
name: "group",
|
||||
trackEvent: { category: "element" },
|
||||
perform: (elements, appState, _, app) => {
|
||||
const selectedElements = getSelectedElements(
|
||||
getNonDeletedElements(elements),
|
||||
appState,
|
||||
{
|
||||
const selectedElements = app.scene.getSelectedElements({
|
||||
selectedElementIds: appState.selectedElementIds,
|
||||
includeBoundTextElement: true,
|
||||
},
|
||||
);
|
||||
});
|
||||
if (selectedElements.length < 2) {
|
||||
// nothing to group
|
||||
return { appState, elements, commitToHistory: false };
|
||||
@ -164,12 +159,13 @@ export const actionGroup = register({
|
||||
};
|
||||
},
|
||||
contextItemLabel: "labels.group",
|
||||
predicate: (elements, appState) => enableActionGroup(elements, appState),
|
||||
predicate: (elements, appState, _, app) =>
|
||||
enableActionGroup(elements, appState, app),
|
||||
keyTest: (event) =>
|
||||
!event.shiftKey && event[KEYS.CTRL_OR_CMD] && event.key === KEYS.G,
|
||||
PanelComponent: ({ elements, appState, updateData }) => (
|
||||
PanelComponent: ({ elements, appState, updateData, app }) => (
|
||||
<ToolButton
|
||||
hidden={!enableActionGroup(elements, appState)}
|
||||
hidden={!enableActionGroup(elements, appState, app)}
|
||||
type="button"
|
||||
icon={<GroupIcon theme={appState.theme} />}
|
||||
onClick={() => updateData(null)}
|
||||
@ -191,7 +187,7 @@ export const actionUngroup = register({
|
||||
|
||||
let nextElements = [...elements];
|
||||
|
||||
const selectedElements = getSelectedElements(nextElements, appState);
|
||||
const selectedElements = app.scene.getSelectedElements(appState);
|
||||
const frames = selectedElements
|
||||
.filter((element) => element.frameId)
|
||||
.map((element) =>
|
||||
@ -219,6 +215,7 @@ export const actionUngroup = register({
|
||||
{ ...appState, selectedGroupIds: {} },
|
||||
getNonDeletedElements(nextElements),
|
||||
appState,
|
||||
null,
|
||||
);
|
||||
|
||||
frames.forEach((frame) => {
|
||||
|
@ -1,8 +1,6 @@
|
||||
import { getNonDeletedElements } from "../element";
|
||||
import { LinearElementEditor } from "../element/linearElementEditor";
|
||||
import { isLinearElement } from "../element/typeChecks";
|
||||
import { ExcalidrawLinearElement } from "../element/types";
|
||||
import { getSelectedElements } from "../scene";
|
||||
import { register } from "./register";
|
||||
|
||||
export const actionToggleLinearEditor = register({
|
||||
@ -10,21 +8,18 @@ export const actionToggleLinearEditor = register({
|
||||
trackEvent: {
|
||||
category: "element",
|
||||
},
|
||||
predicate: (elements, appState) => {
|
||||
const selectedElements = getSelectedElements(elements, appState);
|
||||
predicate: (elements, appState, _, app) => {
|
||||
const selectedElements = app.scene.getSelectedElements(appState);
|
||||
if (selectedElements.length === 1 && isLinearElement(selectedElements[0])) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
perform(elements, appState, _, app) {
|
||||
const selectedElement = getSelectedElements(
|
||||
getNonDeletedElements(elements),
|
||||
appState,
|
||||
{
|
||||
const selectedElement = app.scene.getSelectedElements({
|
||||
selectedElementIds: appState.selectedElementIds,
|
||||
includeBoundTextElement: true,
|
||||
},
|
||||
)[0] as ExcalidrawLinearElement;
|
||||
})[0] as ExcalidrawLinearElement;
|
||||
|
||||
const editingLinearElement =
|
||||
appState.editingLinearElement?.elementId === selectedElement.id
|
||||
@ -38,14 +33,11 @@ export const actionToggleLinearEditor = register({
|
||||
commitToHistory: false,
|
||||
};
|
||||
},
|
||||
contextItemLabel: (elements, appState) => {
|
||||
const selectedElement = getSelectedElements(
|
||||
getNonDeletedElements(elements),
|
||||
appState,
|
||||
{
|
||||
contextItemLabel: (elements, appState, app) => {
|
||||
const selectedElement = app.scene.getSelectedElements({
|
||||
selectedElementIds: appState.selectedElementIds,
|
||||
includeBoundTextElement: true,
|
||||
},
|
||||
)[0] as ExcalidrawLinearElement;
|
||||
})[0] as ExcalidrawLinearElement;
|
||||
return appState.editingLinearElement?.elementId === selectedElement.id
|
||||
? "labels.lineEditor.exit"
|
||||
: "labels.lineEditor.edit";
|
||||
|
@ -42,6 +42,7 @@ export const actionSelectAll = register({
|
||||
},
|
||||
getNonDeletedElements(elements),
|
||||
appState,
|
||||
app,
|
||||
),
|
||||
commitToHistory: true,
|
||||
};
|
||||
|
@ -90,6 +90,7 @@ export class ActionManager {
|
||||
event,
|
||||
this.getAppState(),
|
||||
this.getElementsIncludingDeleted(),
|
||||
this.app,
|
||||
),
|
||||
);
|
||||
|
||||
@ -168,6 +169,7 @@ export class ActionManager {
|
||||
appState={this.getAppState()}
|
||||
updateData={updateData}
|
||||
appProps={this.app.props}
|
||||
app={this.app}
|
||||
data={data}
|
||||
/>
|
||||
);
|
||||
|
@ -130,6 +130,7 @@ export type PanelComponentProps = {
|
||||
updateData: (formData?: any) => void;
|
||||
appProps: ExcalidrawProps;
|
||||
data?: Record<string, any>;
|
||||
app: AppClassProperties;
|
||||
};
|
||||
|
||||
export interface Action {
|
||||
@ -141,12 +142,14 @@ export interface Action {
|
||||
event: React.KeyboardEvent | KeyboardEvent,
|
||||
appState: AppState,
|
||||
elements: readonly ExcalidrawElement[],
|
||||
app: AppClassProperties,
|
||||
) => boolean;
|
||||
contextItemLabel?:
|
||||
| string
|
||||
| ((
|
||||
elements: readonly ExcalidrawElement[],
|
||||
appState: Readonly<AppState>,
|
||||
app: AppClassProperties,
|
||||
) => string);
|
||||
predicate?: (
|
||||
elements: readonly ExcalidrawElement[],
|
||||
|
@ -798,10 +798,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||
};
|
||||
|
||||
public render() {
|
||||
const selectedElement = getSelectedElements(
|
||||
this.scene.getNonDeletedElements(),
|
||||
this.state,
|
||||
);
|
||||
const selectedElement = this.scene.getSelectedElements(this.state);
|
||||
const { renderTopRightUI, renderCustomStats } = this.props;
|
||||
|
||||
return (
|
||||
@ -858,6 +855,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||
!this.state.zenModeEnabled &&
|
||||
!this.scene.getElementsIncludingDeleted().length
|
||||
}
|
||||
app={this}
|
||||
>
|
||||
{this.props.children}
|
||||
</LayerUI>
|
||||
@ -963,10 +961,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||
const shouldUpdateStrokeColor =
|
||||
(type === "background" && event.altKey) ||
|
||||
(type === "stroke" && !event.altKey);
|
||||
const selectedElements = getSelectedElements(
|
||||
this.scene.getElementsIncludingDeleted(),
|
||||
this.state,
|
||||
);
|
||||
const selectedElements = this.scene.getSelectedElements(this.state);
|
||||
if (
|
||||
!selectedElements.length ||
|
||||
this.state.activeTool.type !== "selection"
|
||||
@ -2046,6 +2041,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||
},
|
||||
this.scene.getNonDeletedElements(),
|
||||
this.state,
|
||||
this,
|
||||
),
|
||||
() => {
|
||||
if (opts.files) {
|
||||
@ -2610,14 +2606,11 @@ class App extends React.Component<AppProps, AppState> {
|
||||
offsetY = step;
|
||||
}
|
||||
|
||||
const selectedElements = getSelectedElements(
|
||||
this.scene.getNonDeletedElements(),
|
||||
this.state,
|
||||
{
|
||||
const selectedElements = this.scene.getSelectedElements({
|
||||
selectedElementIds: this.state.selectedElementIds,
|
||||
includeBoundTextElement: true,
|
||||
includeElementsInFrames: true,
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
selectedElements.forEach((element) => {
|
||||
mutateElement(element, {
|
||||
@ -2634,10 +2627,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||
|
||||
event.preventDefault();
|
||||
} else if (event.key === KEYS.ENTER) {
|
||||
const selectedElements = getSelectedElements(
|
||||
this.scene.getNonDeletedElements(),
|
||||
this.state,
|
||||
);
|
||||
const selectedElements = this.scene.getSelectedElements(this.state);
|
||||
if (selectedElements.length === 1) {
|
||||
const selectedElement = selectedElements[0];
|
||||
if (event[KEYS.CTRL_OR_CMD]) {
|
||||
@ -2713,10 +2703,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||
!event.altKey &&
|
||||
!event[KEYS.CTRL_OR_CMD]
|
||||
) {
|
||||
const selectedElements = getSelectedElements(
|
||||
this.scene.getNonDeletedElements(),
|
||||
this.state,
|
||||
);
|
||||
const selectedElements = this.scene.getSelectedElements(this.state);
|
||||
if (
|
||||
this.state.activeTool.type === "selection" &&
|
||||
!selectedElements.length
|
||||
@ -2788,10 +2775,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||
this.setState({ isBindingEnabled: true });
|
||||
}
|
||||
if (isArrowKey(event.key)) {
|
||||
const selectedElements = getSelectedElements(
|
||||
this.scene.getNonDeletedElements(),
|
||||
this.state,
|
||||
);
|
||||
const selectedElements = this.scene.getSelectedElements(this.state);
|
||||
isBindingEnabled(this.state)
|
||||
? bindOrUnbindSelectedElements(selectedElements)
|
||||
: unbindLinearElements(selectedElements);
|
||||
@ -3141,10 +3125,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||
}
|
||||
let existingTextElement: NonDeleted<ExcalidrawTextElement> | null = null;
|
||||
|
||||
const selectedElements = getSelectedElements(
|
||||
this.scene.getNonDeletedElements(),
|
||||
this.state,
|
||||
);
|
||||
const selectedElements = this.scene.getSelectedElements(this.state);
|
||||
|
||||
if (selectedElements.length === 1) {
|
||||
if (isTextElement(selectedElements[0])) {
|
||||
@ -3274,10 +3255,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||
return;
|
||||
}
|
||||
|
||||
const selectedElements = getSelectedElements(
|
||||
this.scene.getNonDeletedElements(),
|
||||
this.state,
|
||||
);
|
||||
const selectedElements = this.scene.getSelectedElements(this.state);
|
||||
|
||||
if (selectedElements.length === 1 && isLinearElement(selectedElements[0])) {
|
||||
if (
|
||||
@ -3328,6 +3306,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||
},
|
||||
this.scene.getNonDeletedElements(),
|
||||
prevState,
|
||||
this,
|
||||
),
|
||||
);
|
||||
return;
|
||||
@ -3704,7 +3683,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||
|
||||
const elements = this.scene.getNonDeletedElements();
|
||||
|
||||
const selectedElements = getSelectedElements(elements, this.state);
|
||||
const selectedElements = this.scene.getSelectedElements(this.state);
|
||||
if (
|
||||
selectedElements.length === 1 &&
|
||||
!isOverScrollBar &&
|
||||
@ -4407,10 +4386,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||
event: React.PointerEvent<HTMLElement>,
|
||||
): PointerDownState {
|
||||
const origin = viewportCoordsToSceneCoords(event, this.state);
|
||||
const selectedElements = getSelectedElements(
|
||||
this.scene.getNonDeletedElements(),
|
||||
this.state,
|
||||
);
|
||||
const selectedElements = this.scene.getSelectedElements(this.state);
|
||||
const [minX, minY, maxX, maxY] = getCommonBounds(selectedElements);
|
||||
|
||||
return {
|
||||
@ -4528,7 +4504,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||
): boolean => {
|
||||
if (this.state.activeTool.type === "selection") {
|
||||
const elements = this.scene.getNonDeletedElements();
|
||||
const selectedElements = getSelectedElements(elements, this.state);
|
||||
const selectedElements = this.scene.getSelectedElements(this.state);
|
||||
if (selectedElements.length === 1 && !this.state.editingLinearElement) {
|
||||
const elementWithTransformHandleType =
|
||||
getElementWithTransformHandleType(
|
||||
@ -4771,6 +4747,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||
},
|
||||
this.scene.getNonDeletedElements(),
|
||||
prevState,
|
||||
this,
|
||||
);
|
||||
});
|
||||
pointerDownState.hit.wasAddedToSelection = true;
|
||||
@ -5198,7 +5175,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||
if (pointerDownState.drag.offset === null) {
|
||||
pointerDownState.drag.offset = tupleToCoors(
|
||||
getDragOffsetXY(
|
||||
getSelectedElements(this.scene.getNonDeletedElements(), this.state),
|
||||
this.scene.getSelectedElements(this.state),
|
||||
pointerDownState.origin.x,
|
||||
pointerDownState.origin.y,
|
||||
),
|
||||
@ -5361,10 +5338,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||
pointerDownState.hit.hasHitCommonBoundingBoxOfSelectedElements) &&
|
||||
!isSelectingPointsInLineEditor
|
||||
) {
|
||||
const selectedElements = getSelectedElements(
|
||||
this.scene.getNonDeletedElements(),
|
||||
this.state,
|
||||
);
|
||||
const selectedElements = this.scene.getSelectedElements(this.state);
|
||||
|
||||
if (selectedElements.every((element) => element.locked)) {
|
||||
return;
|
||||
@ -5435,14 +5409,18 @@ class App extends React.Component<AppProps, AppState> {
|
||||
const groupIdMap = new Map();
|
||||
const oldIdToDuplicatedId = new Map();
|
||||
const hitElement = pointerDownState.hit.element;
|
||||
const elements = this.scene.getElementsIncludingDeleted();
|
||||
const selectedElementIds = new Set(
|
||||
getSelectedElements(elements, this.state, {
|
||||
this.scene
|
||||
.getSelectedElements({
|
||||
selectedElementIds: this.state.selectedElementIds,
|
||||
includeBoundTextElement: true,
|
||||
includeElementsInFrames: true,
|
||||
}).map((element) => element.id),
|
||||
})
|
||||
.map((element) => element.id),
|
||||
);
|
||||
|
||||
const elements = this.scene.getNonDeletedElements();
|
||||
|
||||
for (const element of elements) {
|
||||
if (
|
||||
selectedElementIds.has(element.id) ||
|
||||
@ -5584,6 +5562,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||
},
|
||||
this.scene.getNonDeletedElements(),
|
||||
prevState,
|
||||
this,
|
||||
),
|
||||
);
|
||||
}
|
||||
@ -5641,6 +5620,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||
},
|
||||
this.scene.getNonDeletedElements(),
|
||||
prevState,
|
||||
this,
|
||||
);
|
||||
});
|
||||
}
|
||||
@ -5740,10 +5720,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||
pointerDownState.hit?.element?.id !==
|
||||
this.state.selectedLinearElement.elementId
|
||||
) {
|
||||
const selectedELements = getSelectedElements(
|
||||
this.scene.getNonDeletedElements(),
|
||||
this.state,
|
||||
);
|
||||
const selectedELements = this.scene.getSelectedElements(this.state);
|
||||
// set selectedLinearElement to null if there is more than one element selected since we don't want to show linear element handles
|
||||
if (selectedELements.length > 1) {
|
||||
this.setState({ selectedLinearElement: null });
|
||||
@ -5985,10 +5962,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||
const topLayerFrame =
|
||||
this.getTopLayerFrameAtSceneCoords(sceneCoords);
|
||||
|
||||
const selectedElements = getSelectedElements(
|
||||
this.scene.getNonDeletedElements(),
|
||||
this.state,
|
||||
);
|
||||
const selectedElements = this.scene.getSelectedElements(this.state);
|
||||
let nextElements = this.scene.getElementsIncludingDeleted();
|
||||
|
||||
const updateGroupIdsAfterEditingGroup = (
|
||||
@ -6067,6 +6041,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||
nextElements = updateFrameMembershipOfSelectedElements(
|
||||
this.scene.getElementsIncludingDeleted(),
|
||||
this.state,
|
||||
this,
|
||||
);
|
||||
|
||||
this.scene.replaceAllElements(nextElements);
|
||||
@ -6111,12 +6086,12 @@ class App extends React.Component<AppProps, AppState> {
|
||||
let nextElements = updateFrameMembershipOfSelectedElements(
|
||||
this.scene.getElementsIncludingDeleted(),
|
||||
this.state,
|
||||
this,
|
||||
);
|
||||
|
||||
const selectedFrames = getSelectedElements(
|
||||
this.scene.getElementsIncludingDeleted(),
|
||||
this.state,
|
||||
).filter(
|
||||
const selectedFrames = this.scene
|
||||
.getSelectedElements(this.state)
|
||||
.filter(
|
||||
(element) => element.type === "frame",
|
||||
) as ExcalidrawFrameElement[];
|
||||
|
||||
@ -6143,10 +6118,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||
this.state.selectedLinearElement?.elementId !== hitElement?.id &&
|
||||
isLinearElement(hitElement)
|
||||
) {
|
||||
const selectedELements = getSelectedElements(
|
||||
this.scene.getNonDeletedElements(),
|
||||
this.state,
|
||||
);
|
||||
const selectedELements = this.scene.getSelectedElements(this.state);
|
||||
// set selectedLinearElement when no other element selected except
|
||||
// the one we've hit
|
||||
if (selectedELements.length === 1) {
|
||||
@ -6248,7 +6220,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||
delete newSelectedElementIds[hitElement!.id];
|
||||
const newSelectedElements = getSelectedElements(
|
||||
this.scene.getNonDeletedElements(),
|
||||
{ ...prevState, selectedElementIds: newSelectedElementIds },
|
||||
{ selectedElementIds: newSelectedElementIds },
|
||||
);
|
||||
|
||||
return selectGroupsForSelectedElements(
|
||||
@ -6267,6 +6239,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||
},
|
||||
this.scene.getNonDeletedElements(),
|
||||
prevState,
|
||||
this,
|
||||
);
|
||||
});
|
||||
}
|
||||
@ -6303,6 +6276,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||
},
|
||||
this.scene.getNonDeletedElements(),
|
||||
prevState,
|
||||
this,
|
||||
);
|
||||
});
|
||||
} else {
|
||||
@ -6333,6 +6307,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||
},
|
||||
this.scene.getNonDeletedElements(),
|
||||
prevState,
|
||||
this,
|
||||
),
|
||||
}));
|
||||
}
|
||||
@ -6392,9 +6367,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||
if (pointerDownState.drag.hasOccurred || isResizing || isRotating) {
|
||||
(isBindingEnabled(this.state)
|
||||
? bindOrUnbindSelectedElements
|
||||
: unbindLinearElements)(
|
||||
getSelectedElements(this.scene.getNonDeletedElements(), this.state),
|
||||
);
|
||||
: unbindLinearElements)(this.scene.getSelectedElements(this.state));
|
||||
}
|
||||
|
||||
if (!activeTool.locked && activeTool.type !== "freedraw") {
|
||||
@ -7101,10 +7074,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||
includeLockedElements: true,
|
||||
});
|
||||
|
||||
const selectedElements = getSelectedElements(
|
||||
this.scene.getNonDeletedElements(),
|
||||
this.state,
|
||||
);
|
||||
const selectedElements = this.scene.getSelectedElements(this.state);
|
||||
const isHittignCommonBoundBox =
|
||||
this.isHittingCommonBoundingBoxOfSelectedElements(
|
||||
{ x, y },
|
||||
@ -7134,6 +7104,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||
},
|
||||
this.scene.getNonDeletedElements(),
|
||||
this.state,
|
||||
this,
|
||||
)
|
||||
: this.state),
|
||||
showHyperlinkPopup: false,
|
||||
@ -7221,10 +7192,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||
pointerDownState: PointerDownState,
|
||||
event: MouseEvent | KeyboardEvent,
|
||||
): boolean => {
|
||||
const selectedElements = getSelectedElements(
|
||||
this.scene.getNonDeletedElements(),
|
||||
this.state,
|
||||
);
|
||||
const selectedElements = this.scene.getSelectedElements(this.state);
|
||||
const selectedFrames = selectedElements.filter(
|
||||
(element) => element.type === "frame",
|
||||
) as ExcalidrawFrameElement[];
|
||||
|
@ -82,7 +82,9 @@ export const ContextMenu = React.memo(
|
||||
let label = "";
|
||||
if (item.contextItemLabel) {
|
||||
if (typeof item.contextItemLabel === "function") {
|
||||
label = t(item.contextItemLabel(elements, appState));
|
||||
label = t(
|
||||
item.contextItemLabel(elements, appState, actionManager.app),
|
||||
);
|
||||
} else {
|
||||
label = t(item.contextItemLabel);
|
||||
}
|
||||
|
@ -1,7 +1,5 @@
|
||||
import { t } from "../i18n";
|
||||
import { NonDeletedExcalidrawElement } from "../element/types";
|
||||
import { getSelectedElements } from "../scene";
|
||||
import { Device, UIAppState } from "../types";
|
||||
import { AppClassProperties, Device, UIAppState } from "../types";
|
||||
import {
|
||||
isImageElement,
|
||||
isLinearElement,
|
||||
@ -15,17 +13,12 @@ import "./HintViewer.scss";
|
||||
|
||||
interface HintViewerProps {
|
||||
appState: UIAppState;
|
||||
elements: readonly NonDeletedExcalidrawElement[];
|
||||
isMobile: boolean;
|
||||
device: Device;
|
||||
app: AppClassProperties;
|
||||
}
|
||||
|
||||
const getHints = ({
|
||||
appState,
|
||||
elements,
|
||||
isMobile,
|
||||
device,
|
||||
}: HintViewerProps) => {
|
||||
const getHints = ({ appState, isMobile, device, app }: HintViewerProps) => {
|
||||
const { activeTool, isResizing, isRotating, lastPointerDownWith } = appState;
|
||||
const multiMode = appState.multiElement !== null;
|
||||
|
||||
@ -55,7 +48,7 @@ const getHints = ({
|
||||
return t("hints.placeImage");
|
||||
}
|
||||
|
||||
const selectedElements = getSelectedElements(elements, appState);
|
||||
const selectedElements = app.scene.getSelectedElements(appState);
|
||||
|
||||
if (
|
||||
isResizing &&
|
||||
@ -115,15 +108,15 @@ const getHints = ({
|
||||
|
||||
export const HintViewer = ({
|
||||
appState,
|
||||
elements,
|
||||
isMobile,
|
||||
device,
|
||||
app,
|
||||
}: HintViewerProps) => {
|
||||
let hint = getHints({
|
||||
appState,
|
||||
elements,
|
||||
isMobile,
|
||||
device,
|
||||
app,
|
||||
});
|
||||
if (!hint) {
|
||||
return null;
|
||||
|
@ -72,6 +72,7 @@ interface LayerUIProps {
|
||||
onExportImage: AppClassProperties["onExportImage"];
|
||||
renderWelcomeScreen: boolean;
|
||||
children?: React.ReactNode;
|
||||
app: AppClassProperties;
|
||||
}
|
||||
|
||||
const DefaultMainMenu: React.FC<{
|
||||
@ -127,6 +128,7 @@ const LayerUI = ({
|
||||
onExportImage,
|
||||
renderWelcomeScreen,
|
||||
children,
|
||||
app,
|
||||
}: LayerUIProps) => {
|
||||
const device = useDevice();
|
||||
const tunnels = useInitializeTunnels();
|
||||
@ -240,9 +242,9 @@ const LayerUI = ({
|
||||
>
|
||||
<HintViewer
|
||||
appState={appState}
|
||||
elements={elements}
|
||||
isMobile={device.isMobile}
|
||||
device={device}
|
||||
app={app}
|
||||
/>
|
||||
{heading}
|
||||
<Stack.Row gap={1}>
|
||||
@ -401,6 +403,7 @@ const LayerUI = ({
|
||||
)}
|
||||
{device.isMobile && (
|
||||
<MobileMenu
|
||||
app={app}
|
||||
appState={appState}
|
||||
elements={elements}
|
||||
actionManager={actionManager}
|
||||
|
@ -1,5 +1,11 @@
|
||||
import React from "react";
|
||||
import { AppState, Device, ExcalidrawProps, UIAppState } from "../types";
|
||||
import {
|
||||
AppClassProperties,
|
||||
AppState,
|
||||
Device,
|
||||
ExcalidrawProps,
|
||||
UIAppState,
|
||||
} from "../types";
|
||||
import { ActionManager } from "../actions/manager";
|
||||
import { t } from "../i18n";
|
||||
import Stack from "./Stack";
|
||||
@ -41,6 +47,7 @@ type MobileMenuProps = {
|
||||
renderSidebars: () => JSX.Element | null;
|
||||
device: Device;
|
||||
renderWelcomeScreen: boolean;
|
||||
app: AppClassProperties;
|
||||
};
|
||||
|
||||
export const MobileMenu = ({
|
||||
@ -58,6 +65,7 @@ export const MobileMenu = ({
|
||||
renderSidebars,
|
||||
device,
|
||||
renderWelcomeScreen,
|
||||
app,
|
||||
}: MobileMenuProps) => {
|
||||
const {
|
||||
WelcomeScreenCenterTunnel,
|
||||
@ -119,9 +127,9 @@ export const MobileMenu = ({
|
||||
</Section>
|
||||
<HintViewer
|
||||
appState={appState}
|
||||
elements={elements}
|
||||
isMobile={true}
|
||||
device={device}
|
||||
app={app}
|
||||
/>
|
||||
</FixedSideContainer>
|
||||
);
|
||||
|
@ -16,7 +16,7 @@ import {
|
||||
} from "./element/textElement";
|
||||
import { arrayToMap, findIndex } from "./utils";
|
||||
import { mutateElement } from "./element/mutateElement";
|
||||
import { AppState } from "./types";
|
||||
import { AppClassProperties, AppState } from "./types";
|
||||
import { getElementsWithinSelection, getSelectedElements } from "./scene";
|
||||
import { isFrameElement } from "./element";
|
||||
import { moveOneRight } from "./zindex";
|
||||
@ -571,8 +571,13 @@ export const replaceAllElementsInFrame = (
|
||||
export const updateFrameMembershipOfSelectedElements = (
|
||||
allElements: ExcalidrawElementsIncludingDeleted,
|
||||
appState: AppState,
|
||||
app: AppClassProperties,
|
||||
) => {
|
||||
const selectedElements = getSelectedElements(allElements, appState);
|
||||
const selectedElements = app.scene.getSelectedElements({
|
||||
selectedElementIds: appState.selectedElementIds,
|
||||
// supplying elements explicitly in case we're passed non-state elements
|
||||
elements: allElements,
|
||||
});
|
||||
const elementsToFilter = new Set<ExcalidrawElement>(selectedElements);
|
||||
|
||||
if (appState.editingGroupId) {
|
||||
|
@ -1,5 +1,10 @@
|
||||
import { GroupId, ExcalidrawElement, NonDeleted } from "./element/types";
|
||||
import { AppState } from "./types";
|
||||
import {
|
||||
GroupId,
|
||||
ExcalidrawElement,
|
||||
NonDeleted,
|
||||
NonDeletedExcalidrawElement,
|
||||
} from "./element/types";
|
||||
import { AppClassProperties, AppState } from "./types";
|
||||
import { getSelectedElements } from "./scene";
|
||||
import { getBoundTextElement } from "./element/textElement";
|
||||
import { makeNextSelectedElementIds } from "./scene/selection";
|
||||
@ -67,12 +72,23 @@ export const getSelectedGroupIds = (appState: AppState): GroupId[] =>
|
||||
*/
|
||||
export const selectGroupsForSelectedElements = (
|
||||
appState: AppState,
|
||||
elements: readonly NonDeleted<ExcalidrawElement>[],
|
||||
elements: readonly NonDeletedExcalidrawElement[],
|
||||
prevAppState: AppState,
|
||||
/**
|
||||
* supply null in cases where you don't have access to App instance and
|
||||
* you don't care about optimizing selectElements retrieval
|
||||
*/
|
||||
app: AppClassProperties | null,
|
||||
): AppState => {
|
||||
let nextAppState: AppState = { ...appState, selectedGroupIds: {} };
|
||||
|
||||
const selectedElements = getSelectedElements(elements, appState);
|
||||
const selectedElements = app
|
||||
? app.scene.getSelectedElements({
|
||||
selectedElementIds: appState.selectedElementIds,
|
||||
// supplying elements explicitly in case we're passed non-state elements
|
||||
elements,
|
||||
})
|
||||
: getSelectedElements(elements, appState);
|
||||
|
||||
if (!selectedElements.length) {
|
||||
return {
|
||||
|
@ -11,6 +11,9 @@ import {
|
||||
} from "../element";
|
||||
import { LinearElementEditor } from "../element/linearElementEditor";
|
||||
import { isFrameElement } from "../element/typeChecks";
|
||||
import { getSelectedElements } from "./selection";
|
||||
import { AppState } from "../types";
|
||||
import { Assert, SameType } from "../utility-types";
|
||||
|
||||
type ElementIdKey = InstanceType<typeof LinearElementEditor>["elementId"];
|
||||
type ElementKey = ExcalidrawElement | ElementIdKey;
|
||||
@ -18,6 +21,31 @@ type ElementKey = ExcalidrawElement | ElementIdKey;
|
||||
type SceneStateCallback = () => void;
|
||||
type SceneStateCallbackRemover = () => void;
|
||||
|
||||
type SelectionHash = string & { __brand: "selectionHash" };
|
||||
|
||||
const hashSelectionOpts = (
|
||||
opts: Parameters<InstanceType<typeof Scene>["getSelectedElements"]>[0],
|
||||
) => {
|
||||
const keys = ["includeBoundTextElement", "includeElementsInFrames"] as const;
|
||||
|
||||
type HashableKeys = Omit<typeof opts, "selectedElementIds" | "elements">;
|
||||
|
||||
// just to ensure we're hashing all expected keys
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
type _ = Assert<
|
||||
SameType<
|
||||
Required<HashableKeys>,
|
||||
Pick<Required<HashableKeys>, typeof keys[number]>
|
||||
>
|
||||
>;
|
||||
|
||||
let hash = "";
|
||||
for (const key of keys) {
|
||||
hash += `${key}:${opts[key] ? "1" : "0"}`;
|
||||
}
|
||||
return hash as SelectionHash;
|
||||
};
|
||||
|
||||
// ideally this would be a branded type but it'd be insanely hard to work with
|
||||
// in our codebase
|
||||
export type ExcalidrawElementsIncludingDeleted = readonly ExcalidrawElement[];
|
||||
@ -68,6 +96,15 @@ class Scene {
|
||||
private nonDeletedFrames: readonly NonDeleted<ExcalidrawFrameElement>[] = [];
|
||||
private frames: readonly ExcalidrawFrameElement[] = [];
|
||||
private elementsMap = new Map<ExcalidrawElement["id"], ExcalidrawElement>();
|
||||
private selectedElementsCache: {
|
||||
selectedElementIds: AppState["selectedElementIds"] | null;
|
||||
elements: readonly NonDeletedExcalidrawElement[] | null;
|
||||
cache: Map<SelectionHash, NonDeletedExcalidrawElement[]>;
|
||||
} = {
|
||||
selectedElementIds: null,
|
||||
elements: null,
|
||||
cache: new Map(),
|
||||
};
|
||||
|
||||
getElementsIncludingDeleted() {
|
||||
return this.elements;
|
||||
@ -81,6 +118,52 @@ class Scene {
|
||||
return this.frames;
|
||||
}
|
||||
|
||||
getSelectedElements(opts: {
|
||||
// NOTE can be ommitted by making Scene constructor require App instance
|
||||
selectedElementIds: AppState["selectedElementIds"];
|
||||
/**
|
||||
* for specific cases where you need to use elements not from current
|
||||
* scene state. This in effect will likely result in cache-miss, and
|
||||
* the cache won't be updated in this case.
|
||||
*/
|
||||
elements?: readonly ExcalidrawElement[];
|
||||
// selection-related options
|
||||
includeBoundTextElement?: boolean;
|
||||
includeElementsInFrames?: boolean;
|
||||
}): NonDeleted<ExcalidrawElement>[] {
|
||||
const hash = hashSelectionOpts(opts);
|
||||
|
||||
const elements = opts?.elements || this.nonDeletedElements;
|
||||
if (
|
||||
this.selectedElementsCache.elements === elements &&
|
||||
this.selectedElementsCache.selectedElementIds === opts.selectedElementIds
|
||||
) {
|
||||
const cached = this.selectedElementsCache.cache.get(hash);
|
||||
if (cached) {
|
||||
return cached;
|
||||
}
|
||||
} else if (opts?.elements == null) {
|
||||
// if we're operating on latest scene elements and the cache is not
|
||||
// storing the latest elements, clear the cache
|
||||
this.selectedElementsCache.cache.clear();
|
||||
}
|
||||
|
||||
const selectedElements = getSelectedElements(
|
||||
elements,
|
||||
{ selectedElementIds: opts.selectedElementIds },
|
||||
opts,
|
||||
);
|
||||
|
||||
// cache only if we're not using custom elements
|
||||
if (opts?.elements == null) {
|
||||
this.selectedElementsCache.selectedElementIds = opts.selectedElementIds;
|
||||
this.selectedElementsCache.elements = this.nonDeletedElements;
|
||||
this.selectedElementsCache.cache.set(hash, selectedElements);
|
||||
}
|
||||
|
||||
return selectedElements;
|
||||
}
|
||||
|
||||
getNonDeletedFrames(): readonly NonDeleted<ExcalidrawFrameElement>[] {
|
||||
return this.nonDeletedFrames;
|
||||
}
|
||||
@ -168,11 +251,21 @@ class Scene {
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.nonDeletedElements = [];
|
||||
this.elements = [];
|
||||
this.nonDeletedFrames = [];
|
||||
this.frames = [];
|
||||
this.elementsMap.clear();
|
||||
this.selectedElementsCache.selectedElementIds = null;
|
||||
this.selectedElementsCache.elements = null;
|
||||
this.selectedElementsCache.cache.clear();
|
||||
|
||||
Scene.sceneMapById.forEach((scene, elementKey) => {
|
||||
if (scene === this) {
|
||||
Scene.sceneMapById.delete(elementKey);
|
||||
}
|
||||
});
|
||||
|
||||
// done not for memory leaks, but to guard against possible late fires
|
||||
// (I guess?)
|
||||
this.callbacks.clear();
|
||||
|
@ -1527,14 +1527,14 @@ Object {
|
||||
"roundness": Object {
|
||||
"type": 3,
|
||||
},
|
||||
"seed": 449462985,
|
||||
"seed": 1278240551,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"strokeStyle": "solid",
|
||||
"strokeWidth": 1,
|
||||
"type": "rectangle",
|
||||
"updated": 1,
|
||||
"version": 2,
|
||||
"versionNonce": 453191,
|
||||
"versionNonce": 449462985,
|
||||
"width": 20,
|
||||
"x": -10,
|
||||
"y": 0,
|
||||
@ -1586,14 +1586,14 @@ Object {
|
||||
"roundness": Object {
|
||||
"type": 3,
|
||||
},
|
||||
"seed": 449462985,
|
||||
"seed": 1278240551,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"strokeStyle": "solid",
|
||||
"strokeWidth": 1,
|
||||
"type": "rectangle",
|
||||
"updated": 1,
|
||||
"version": 2,
|
||||
"versionNonce": 453191,
|
||||
"versionNonce": 449462985,
|
||||
"width": 20,
|
||||
"x": -10,
|
||||
"y": 0,
|
||||
@ -4271,14 +4271,14 @@ Object {
|
||||
"roundness": Object {
|
||||
"type": 3,
|
||||
},
|
||||
"seed": 449462985,
|
||||
"seed": 1278240551,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"strokeStyle": "solid",
|
||||
"strokeWidth": 1,
|
||||
"type": "rectangle",
|
||||
"updated": 1,
|
||||
"version": 4,
|
||||
"versionNonce": 238820263,
|
||||
"versionNonce": 1014066025,
|
||||
"width": 20,
|
||||
"x": -10,
|
||||
"y": 0,
|
||||
@ -4303,14 +4303,14 @@ Object {
|
||||
"roundness": Object {
|
||||
"type": 3,
|
||||
},
|
||||
"seed": 401146281,
|
||||
"seed": 453191,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"strokeStyle": "solid",
|
||||
"strokeWidth": 1,
|
||||
"type": "rectangle",
|
||||
"updated": 1,
|
||||
"version": 4,
|
||||
"versionNonce": 400692809,
|
||||
"versionNonce": 238820263,
|
||||
"width": 20,
|
||||
"x": 20,
|
||||
"y": 30,
|
||||
@ -4362,14 +4362,14 @@ Object {
|
||||
"roundness": Object {
|
||||
"type": 3,
|
||||
},
|
||||
"seed": 449462985,
|
||||
"seed": 1278240551,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"strokeStyle": "solid",
|
||||
"strokeWidth": 1,
|
||||
"type": "rectangle",
|
||||
"updated": 1,
|
||||
"version": 2,
|
||||
"versionNonce": 453191,
|
||||
"versionNonce": 449462985,
|
||||
"width": 20,
|
||||
"x": -10,
|
||||
"y": 0,
|
||||
@ -4405,14 +4405,14 @@ Object {
|
||||
"roundness": Object {
|
||||
"type": 3,
|
||||
},
|
||||
"seed": 449462985,
|
||||
"seed": 1278240551,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"strokeStyle": "solid",
|
||||
"strokeWidth": 1,
|
||||
"type": "rectangle",
|
||||
"updated": 1,
|
||||
"version": 2,
|
||||
"versionNonce": 453191,
|
||||
"versionNonce": 449462985,
|
||||
"width": 20,
|
||||
"x": -10,
|
||||
"y": 0,
|
||||
@ -4434,14 +4434,14 @@ Object {
|
||||
"roundness": Object {
|
||||
"type": 3,
|
||||
},
|
||||
"seed": 401146281,
|
||||
"seed": 453191,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"strokeStyle": "solid",
|
||||
"strokeWidth": 1,
|
||||
"type": "rectangle",
|
||||
"updated": 1,
|
||||
"version": 2,
|
||||
"versionNonce": 2019559783,
|
||||
"versionNonce": 401146281,
|
||||
"width": 20,
|
||||
"x": 20,
|
||||
"y": 30,
|
||||
@ -4482,14 +4482,14 @@ Object {
|
||||
"roundness": Object {
|
||||
"type": 3,
|
||||
},
|
||||
"seed": 449462985,
|
||||
"seed": 1278240551,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"strokeStyle": "solid",
|
||||
"strokeWidth": 1,
|
||||
"type": "rectangle",
|
||||
"updated": 1,
|
||||
"version": 3,
|
||||
"versionNonce": 1116226695,
|
||||
"versionNonce": 1150084233,
|
||||
"width": 20,
|
||||
"x": -10,
|
||||
"y": 0,
|
||||
@ -4513,14 +4513,14 @@ Object {
|
||||
"roundness": Object {
|
||||
"type": 3,
|
||||
},
|
||||
"seed": 401146281,
|
||||
"seed": 453191,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"strokeStyle": "solid",
|
||||
"strokeWidth": 1,
|
||||
"type": "rectangle",
|
||||
"updated": 1,
|
||||
"version": 3,
|
||||
"versionNonce": 1014066025,
|
||||
"versionNonce": 1116226695,
|
||||
"width": 20,
|
||||
"x": 20,
|
||||
"y": 30,
|
||||
@ -4557,14 +4557,14 @@ Object {
|
||||
"roundness": Object {
|
||||
"type": 3,
|
||||
},
|
||||
"seed": 449462985,
|
||||
"seed": 1278240551,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"strokeStyle": "solid",
|
||||
"strokeWidth": 1,
|
||||
"type": "rectangle",
|
||||
"updated": 1,
|
||||
"version": 4,
|
||||
"versionNonce": 238820263,
|
||||
"versionNonce": 1014066025,
|
||||
"width": 20,
|
||||
"x": -10,
|
||||
"y": 0,
|
||||
@ -4586,14 +4586,14 @@ Object {
|
||||
"roundness": Object {
|
||||
"type": 3,
|
||||
},
|
||||
"seed": 401146281,
|
||||
"seed": 453191,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"strokeStyle": "solid",
|
||||
"strokeWidth": 1,
|
||||
"type": "rectangle",
|
||||
"updated": 1,
|
||||
"version": 4,
|
||||
"versionNonce": 400692809,
|
||||
"versionNonce": 238820263,
|
||||
"width": 20,
|
||||
"x": 20,
|
||||
"y": 30,
|
||||
@ -5585,14 +5585,14 @@ Object {
|
||||
"roundness": Object {
|
||||
"type": 3,
|
||||
},
|
||||
"seed": 449462985,
|
||||
"seed": 1278240551,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"strokeStyle": "solid",
|
||||
"strokeWidth": 1,
|
||||
"type": "rectangle",
|
||||
"updated": 1,
|
||||
"version": 3,
|
||||
"versionNonce": 1014066025,
|
||||
"versionNonce": 1116226695,
|
||||
"width": 10,
|
||||
"x": -10,
|
||||
"y": 0,
|
||||
@ -5619,14 +5619,14 @@ Object {
|
||||
"roundness": Object {
|
||||
"type": 3,
|
||||
},
|
||||
"seed": 401146281,
|
||||
"seed": 453191,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"strokeStyle": "solid",
|
||||
"strokeWidth": 1,
|
||||
"type": "rectangle",
|
||||
"updated": 1,
|
||||
"version": 3,
|
||||
"versionNonce": 238820263,
|
||||
"versionNonce": 1014066025,
|
||||
"width": 10,
|
||||
"x": 10,
|
||||
"y": 0,
|
||||
@ -5678,14 +5678,14 @@ Object {
|
||||
"roundness": Object {
|
||||
"type": 3,
|
||||
},
|
||||
"seed": 449462985,
|
||||
"seed": 1278240551,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"strokeStyle": "solid",
|
||||
"strokeWidth": 1,
|
||||
"type": "rectangle",
|
||||
"updated": 1,
|
||||
"version": 2,
|
||||
"versionNonce": 453191,
|
||||
"versionNonce": 449462985,
|
||||
"width": 10,
|
||||
"x": -10,
|
||||
"y": 0,
|
||||
@ -5721,14 +5721,14 @@ Object {
|
||||
"roundness": Object {
|
||||
"type": 3,
|
||||
},
|
||||
"seed": 449462985,
|
||||
"seed": 1278240551,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"strokeStyle": "solid",
|
||||
"strokeWidth": 1,
|
||||
"type": "rectangle",
|
||||
"updated": 1,
|
||||
"version": 2,
|
||||
"versionNonce": 453191,
|
||||
"versionNonce": 449462985,
|
||||
"width": 10,
|
||||
"x": -10,
|
||||
"y": 0,
|
||||
@ -5750,14 +5750,14 @@ Object {
|
||||
"roundness": Object {
|
||||
"type": 3,
|
||||
},
|
||||
"seed": 401146281,
|
||||
"seed": 453191,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"strokeStyle": "solid",
|
||||
"strokeWidth": 1,
|
||||
"type": "rectangle",
|
||||
"updated": 1,
|
||||
"version": 2,
|
||||
"versionNonce": 2019559783,
|
||||
"versionNonce": 401146281,
|
||||
"width": 10,
|
||||
"x": 10,
|
||||
"y": 0,
|
||||
@ -5798,14 +5798,14 @@ Object {
|
||||
"roundness": Object {
|
||||
"type": 3,
|
||||
},
|
||||
"seed": 449462985,
|
||||
"seed": 1278240551,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"strokeStyle": "solid",
|
||||
"strokeWidth": 1,
|
||||
"type": "rectangle",
|
||||
"updated": 1,
|
||||
"version": 3,
|
||||
"versionNonce": 1014066025,
|
||||
"versionNonce": 1116226695,
|
||||
"width": 10,
|
||||
"x": -10,
|
||||
"y": 0,
|
||||
@ -5829,14 +5829,14 @@ Object {
|
||||
"roundness": Object {
|
||||
"type": 3,
|
||||
},
|
||||
"seed": 401146281,
|
||||
"seed": 453191,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"strokeStyle": "solid",
|
||||
"strokeWidth": 1,
|
||||
"type": "rectangle",
|
||||
"updated": 1,
|
||||
"version": 3,
|
||||
"versionNonce": 238820263,
|
||||
"versionNonce": 1014066025,
|
||||
"width": 10,
|
||||
"x": 10,
|
||||
"y": 0,
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -90,6 +90,7 @@ const populateElements = (
|
||||
{ ...h.state, ...appState, selectedElementIds },
|
||||
h.elements,
|
||||
h.state,
|
||||
null,
|
||||
),
|
||||
...appState,
|
||||
selectedElementIds,
|
||||
|
@ -47,3 +47,6 @@ export type ForwardRef<T, P = any> = Parameters<
|
||||
export type ExtractSetType<T extends Set<any>> = T extends Set<infer U>
|
||||
? U
|
||||
: never;
|
||||
|
||||
export type SameType<T, U> = T extends U ? (U extends T ? true : false) : false;
|
||||
export type Assert<T extends true> = T;
|
||||
|
Loading…
x
Reference in New Issue
Block a user