|
@@ -4,12 +4,14 @@ import { getDefaultAppState } from "../appState";
|
|
|
import { trash, zoomIn, zoomOut, resetZoom } from "../components/icons";
|
|
|
import { ToolButton } from "../components/ToolButton";
|
|
|
import { t } from "../i18n";
|
|
|
-import { getNormalizedZoom } from "../scene";
|
|
|
+import { getNormalizedZoom, getSelectedElements } from "../scene";
|
|
|
+import { getNonDeletedElements } from "../element";
|
|
|
import { CODES, KEYS } from "../keys";
|
|
|
import { getShortcutKey } from "../utils";
|
|
|
import useIsMobile from "../is-mobile";
|
|
|
import { register } from "./register";
|
|
|
import { newElementWith } from "../element/mutateElement";
|
|
|
+import { ExcalidrawElement } from "../element/types";
|
|
|
import { AppState, NormalizedZoomValue } from "../types";
|
|
|
import { getCommonBounds } from "../element";
|
|
|
import { getNewZoom } from "../scene/zoom";
|
|
@@ -204,38 +206,60 @@ const zoomValueToFitBoundsOnViewport = (
|
|
|
return clampedZoomValueToFitElements as NormalizedZoomValue;
|
|
|
};
|
|
|
|
|
|
-export const actionZoomToFit = register({
|
|
|
- name: "zoomToFit",
|
|
|
- perform: (elements, appState) => {
|
|
|
- const nonDeletedElements = elements.filter((element) => !element.isDeleted);
|
|
|
- const commonBounds = getCommonBounds(nonDeletedElements);
|
|
|
+const zoomToFitElements = (
|
|
|
+ elements: readonly ExcalidrawElement[],
|
|
|
+ appState: Readonly<AppState>,
|
|
|
+ zoomToSelection: boolean,
|
|
|
+) => {
|
|
|
+ const nonDeletedElements = getNonDeletedElements(elements);
|
|
|
+ const selectedElements = getSelectedElements(nonDeletedElements, appState);
|
|
|
|
|
|
- const zoomValue = zoomValueToFitBoundsOnViewport(commonBounds, {
|
|
|
- width: appState.width,
|
|
|
- height: appState.height,
|
|
|
- });
|
|
|
- const newZoom = getNewZoom(zoomValue, appState.zoom);
|
|
|
+ const commonBounds =
|
|
|
+ zoomToSelection && selectedElements.length > 0
|
|
|
+ ? getCommonBounds(selectedElements)
|
|
|
+ : getCommonBounds(nonDeletedElements);
|
|
|
|
|
|
- const [x1, y1, x2, y2] = commonBounds;
|
|
|
- const centerX = (x1 + x2) / 2;
|
|
|
- const centerY = (y1 + y2) / 2;
|
|
|
- trackEvent(EVENT_ACTION, "zoom", "fit", newZoom.value * 100);
|
|
|
- return {
|
|
|
- appState: {
|
|
|
- ...appState,
|
|
|
- ...centerScrollOn({
|
|
|
- scenePoint: { x: centerX, y: centerY },
|
|
|
- viewportDimensions: {
|
|
|
- width: appState.width,
|
|
|
- height: appState.height,
|
|
|
- },
|
|
|
- zoom: newZoom,
|
|
|
- }),
|
|
|
+ const zoomValue = zoomValueToFitBoundsOnViewport(commonBounds, {
|
|
|
+ width: appState.width,
|
|
|
+ height: appState.height,
|
|
|
+ });
|
|
|
+ const newZoom = getNewZoom(zoomValue, appState.zoom);
|
|
|
+ const action = zoomToSelection ? "selection" : "fit";
|
|
|
+
|
|
|
+ const [x1, y1, x2, y2] = commonBounds;
|
|
|
+ const centerX = (x1 + x2) / 2;
|
|
|
+ const centerY = (y1 + y2) / 2;
|
|
|
+ trackEvent(EVENT_ACTION, "zoom", action, newZoom.value * 100);
|
|
|
+ return {
|
|
|
+ appState: {
|
|
|
+ ...appState,
|
|
|
+ ...centerScrollOn({
|
|
|
+ scenePoint: { x: centerX, y: centerY },
|
|
|
+ viewportDimensions: {
|
|
|
+ width: appState.width,
|
|
|
+ height: appState.height,
|
|
|
+ },
|
|
|
zoom: newZoom,
|
|
|
- },
|
|
|
- commitToHistory: false,
|
|
|
- };
|
|
|
- },
|
|
|
+ }),
|
|
|
+ zoom: newZoom,
|
|
|
+ },
|
|
|
+ commitToHistory: false,
|
|
|
+ };
|
|
|
+};
|
|
|
+
|
|
|
+export const actionZoomToSelected = register({
|
|
|
+ name: "zoomToSelection",
|
|
|
+ perform: (elements, appState) => zoomToFitElements(elements, appState, true),
|
|
|
+ keyTest: (event) =>
|
|
|
+ event.code === CODES.TWO &&
|
|
|
+ event.shiftKey &&
|
|
|
+ !event.altKey &&
|
|
|
+ !event[KEYS.CTRL_OR_CMD],
|
|
|
+});
|
|
|
+
|
|
|
+export const actionZoomToFit = register({
|
|
|
+ name: "zoomToFit",
|
|
|
+ perform: (elements, appState) => zoomToFitElements(elements, appState, false),
|
|
|
keyTest: (event) =>
|
|
|
event.code === CODES.ONE &&
|
|
|
event.shiftKey &&
|