mirror of
https://github.com/excalidraw/excalidraw.git
synced 2025-05-03 10:00:07 -04:00
feat: cache most of element selection (#6747)
This commit is contained in:
parent
2e46e27490
commit
9f76f8677b
27 changed files with 452 additions and 3755 deletions
|
@ -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,
|
||||
{
|
||||
includeBoundTextElement: true,
|
||||
includeElementsInFrames: true,
|
||||
},
|
||||
);
|
||||
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, {
|
||||
includeBoundTextElement: true,
|
||||
includeElementsInFrames: true,
|
||||
}).map((element) => element.id),
|
||||
this.scene
|
||||
.getSelectedElements({
|
||||
selectedElementIds: this.state.selectedElementIds,
|
||||
includeBoundTextElement: true,
|
||||
includeElementsInFrames: true,
|
||||
})
|
||||
.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,14 +6086,14 @@ 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(
|
||||
(element) => element.type === "frame",
|
||||
) as ExcalidrawFrameElement[];
|
||||
const selectedFrames = this.scene
|
||||
.getSelectedElements(this.state)
|
||||
.filter(
|
||||
(element) => element.type === "frame",
|
||||
) as ExcalidrawFrameElement[];
|
||||
|
||||
for (const frame of selectedFrames) {
|
||||
nextElements = replaceAllElementsInFrame(
|
||||
|
@ -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>
|
||||
);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue