Procházet zdrojové kódy

feat: Add zoom to fit for selected elements (#2522)

Zen Tang před 4 roky
rodič
revize
5abe9b93e8

+ 54 - 30
src/actions/actionCanvas.tsx

@@ -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 &&

+ 1 - 0
src/actions/types.ts

@@ -58,6 +58,7 @@ export type ActionName =
   | "zoomOut"
   | "resetZoom"
   | "zoomToFit"
+  | "zoomToSelection"
   | "changeFontFamily"
   | "changeTextAlign"
   | "toggleFullScreen"

+ 4 - 0
src/components/ShortcutsDialog.tsx

@@ -207,6 +207,10 @@ export const ShortcutsDialog = ({ onClose }: { onClose?: () => void }) => {
                 shortcuts={["Shift+1"]}
               />
               <Shortcut
+                label={t("shortcutsDialog.zoomToSelection")}
+                shortcuts={["Shift+2"]}
+              />
+              <Shortcut
                 label={t("buttons.toggleFullScreen")}
                 shortcuts={["F"]}
               />

+ 1 - 0
src/keys.ts

@@ -9,6 +9,7 @@ export const CODES = {
   BRACKET_RIGHT: "BracketRight",
   BRACKET_LEFT: "BracketLeft",
   ONE: "Digit1",
+  TWO: "Digit2",
   NINE: "Digit9",
   QUOTE: "Quote",
   ZERO: "Digit0",

+ 1 - 0
src/locales/en.json

@@ -213,6 +213,7 @@
     "textNewLine": "Add new line (text)",
     "textFinish": "Finish editing (text)",
     "zoomToFit": "Zoom to fit all elements",
+    "zoomToSelection": "Zoom to selection",
     "preventBinding": "Prevent arrow binding"
   },
   "encrypted": {

+ 3 - 0
src/packages/excalidraw/CHANGELOG.MD

@@ -7,11 +7,14 @@ The change should be grouped under one of the below section and must contain PR
 - Chore: Changes for non src files example package.json.
 - Improvements: For any improvements.
 - Refactor: For any refactoring.
+
+Please add the latest change on the top under the correct section.
 -->
 
 ## [Unreleased]
 
 ### Features
+- Add zoom to selection [#2522](https://github.com/excalidraw/excalidraw/pull/2522)
 - Insert Library items in the middle of the screen [#2527](https://github.com/excalidraw/excalidraw/pull/2527)
 - Show shortcut context menu [#2501](https://github.com/excalidraw/excalidraw/pull/2501)
 - Aligns arrowhead schemas [#2517](https://github.com/excalidraw/excalidraw/pull/2517)