浏览代码

feat: Change grid size

Panayiotis Lipiridis 4 年之前
父节点
当前提交
40cd4caeec

+ 4 - 1
src/appState.ts

@@ -6,6 +6,7 @@ import {
   DEFAULT_FONT_SIZE,
   DEFAULT_FONT_FAMILY,
   DEFAULT_TEXT_ALIGN,
+  GRID_SIZE,
 } from "./constants";
 
 export const getDefaultAppState = (): Omit<
@@ -65,7 +66,8 @@ export const getDefaultAppState = (): Omit<
     showShortcutsDialog: false,
     suggestedBindings: [],
     zenModeEnabled: false,
-    gridSize: null,
+    gridSize: GRID_SIZE,
+    showGrid: false,
     editingGroupId: null,
     selectedGroupIds: {},
     width: window.innerWidth,
@@ -121,6 +123,7 @@ const APP_STATE_STORAGE_CONF = (<
   exportBackground: { browser: true, export: false },
   exportEmbedScene: { browser: true, export: false },
   gridSize: { browser: true, export: true },
+  showGrid: { browser: true, export: false },
   height: { browser: false, export: false },
   isBindingEnabled: { browser: false, export: false },
   isLibraryOpen: { browser: false, export: false },

+ 23 - 5
src/components/App.tsx

@@ -1036,7 +1036,12 @@ class App extends React.Component<ExcalidrawProps, AppState> {
     const dy = y - elementsCenterY;
     const groupIdMap = new Map();
 
-    const [gridX, gridY] = getGridPoint(dx, dy, this.state.gridSize);
+    const [gridX, gridY] = getGridPoint(
+      dx,
+      dy,
+      this.state.showGrid,
+      this.state.gridSize,
+    );
 
     const oldIdToDuplicatedId = new Map();
     const newElements = clipboardElements.map((element) => {
@@ -1149,7 +1154,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
 
   toggleGridMode = () => {
     this.setState({
-      gridSize: this.state.gridSize ? null : GRID_SIZE,
+      showGrid: !this.state.showGrid,
     });
   };
 
@@ -1275,7 +1280,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
 
     if (isArrowKey(event.key)) {
       const step =
-        (this.state.gridSize &&
+        (this.state.showGrid &&
           (event.shiftKey ? ELEMENT_TRANSLATE_AMOUNT : this.state.gridSize)) ||
         (event.shiftKey
           ? ELEMENT_SHIFT_TRANSLATE_AMOUNT
@@ -1819,6 +1824,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
         scenePointerX,
         scenePointerY,
         this.state.editingLinearElement,
+        this.state.showGrid,
         this.state.gridSize,
       );
       if (editingLinearElement !== this.state.editingLinearElement) {
@@ -2249,7 +2255,12 @@ class App extends React.Component<ExcalidrawProps, AppState> {
     return {
       origin,
       originInGrid: tupleToCoors(
-        getGridPoint(origin.x, origin.y, this.state.gridSize),
+        getGridPoint(
+          origin.x,
+          origin.y,
+          this.state.showGrid,
+          this.state.gridSize,
+        ),
       ),
       scrollbars: isOverScrollBars(
         currentScrollBars,
@@ -2607,7 +2618,8 @@ class App extends React.Component<ExcalidrawProps, AppState> {
       const [gridX, gridY] = getGridPoint(
         pointerDownState.origin.x,
         pointerDownState.origin.y,
-        elementType === "draw" ? null : this.state.gridSize,
+        elementType === "draw" ? false : this.state.showGrid,
+        this.state.gridSize,
       );
 
       /* If arrow is pre-arrowheads, it will have undefined for both start and end arrowheads.
@@ -2669,6 +2681,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
     const [gridX, gridY] = getGridPoint(
       pointerDownState.origin.x,
       pointerDownState.origin.y,
+      this.state.showGrid,
       this.state.gridSize,
     );
     const element = newElement({
@@ -2758,6 +2771,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
       const [gridX, gridY] = getGridPoint(
         pointerCoords.x,
         pointerCoords.y,
+        this.state.showGrid,
         this.state.gridSize,
       );
 
@@ -2830,6 +2844,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
           const [dragX, dragY] = getGridPoint(
             pointerCoords.x - pointerDownState.drag.offset.x,
             pointerCoords.y - pointerDownState.drag.offset.y,
+            this.state.showGrid,
             this.state.gridSize,
           );
 
@@ -2882,6 +2897,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
                 const [originDragX, originDragY] = getGridPoint(
                   pointerDownState.origin.x - pointerDownState.drag.offset.x,
                   pointerDownState.origin.y - pointerDownState.drag.offset.y,
+                  this.state.showGrid,
                   this.state.gridSize,
                 );
                 mutateElement(duplicatedElement, {
@@ -3542,6 +3558,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
       const [gridX, gridY] = getGridPoint(
         pointerCoords.x,
         pointerCoords.y,
+        this.state.showGrid,
         this.state.gridSize,
       );
       dragNewElement(
@@ -3580,6 +3597,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
     const [resizeX, resizeY] = getGridPoint(
       pointerCoords.x - pointerDownState.resize.offset.x,
       pointerCoords.y - pointerDownState.resize.offset.y,
+      this.state.showGrid,
       this.state.gridSize,
     );
     if (

+ 4 - 0
src/components/SlidableInput.module.css

@@ -0,0 +1,4 @@
+.input {
+  cursor: ew-resize;
+  user-select: none;
+}

+ 68 - 0
src/components/SlidableInput.tsx

@@ -0,0 +1,68 @@
+import React, { CSSProperties, useEffect, useState } from "react";
+import classes from "./SlidableInput.module.css";
+import { throttle } from "./utils/throttle";
+
+interface SlidableInputProps {
+  value: number;
+  prefix?: string;
+  suffix?: string;
+  minValue?: number;
+  maxValue?: number;
+  style?: CSSProperties;
+  onChange?: (value: number) => void;
+}
+
+export const SlidableInput: React.FC<SlidableInputProps> = ({
+  value,
+  style,
+  prefix,
+  suffix,
+  onChange,
+  minValue,
+  maxValue,
+}) => {
+  const [isLocked, setIsLocked] = useState<boolean>(true);
+  const previousX = React.useRef(0);
+
+  useEffect(() => {
+    const onMouseMoveHandler = throttle((event: MouseEvent) => {
+      if (isLocked) return;
+
+      const nextX = event.screenX;
+      if (nextX === previousX.current) return;
+      const nextValue = value + (nextX > previousX.current ? 1 : -1);
+
+      onChange &&
+        nextValue <= (maxValue || Infinity) &&
+        nextValue >= (typeof minValue === "number" ? minValue : -Infinity) &&
+        onChange(nextValue);
+
+      previousX.current = nextX;
+    }, 250) as EventListenerOrEventListenerObject;
+
+    window.addEventListener("mousemove", onMouseMoveHandler);
+
+    return () => {
+      window.removeEventListener("mousemove", onMouseMoveHandler);
+    };
+    // eslint-disable-next-line react-hooks/exhaustive-deps
+  }, [isLocked, value]);
+
+  const onMouseDown = () => setIsLocked(false);
+
+  useEffect(() => {
+    const onMouseUp = () => setIsLocked(true);
+    window.addEventListener("mouseup", onMouseUp);
+    return () => {
+      window.removeEventListener("mouseup", onMouseUp);
+    };
+  }, []);
+
+  return (
+    <span className={classes.input} style={style} onMouseDown={onMouseDown}>
+      {prefix}
+      {value}
+      {suffix}
+    </span>
+  );
+};

+ 14 - 0
src/components/Stats.tsx

@@ -12,6 +12,7 @@ import { AppState } from "../types";
 import { debounce, nFormatter } from "../utils";
 import { close } from "./icons";
 import { Island } from "./Island";
+import { SlidableInput } from "./SlidableInput";
 import "./Stats.scss";
 
 type StorageSizes = { scene: number; total: number };
@@ -157,6 +158,19 @@ export const Stats = (props: {
                 </td>
               </tr>
             )}
+
+            <tr>
+              <th colSpan={2}>{"Misc"}</th>
+            </tr>
+            <tr>
+              <td>{"Grid size"}</td>
+              <td>
+                <SlidableInput
+                  value={props.appState.gridSize || 8}
+                  minValue={8}
+                />
+              </td>
+            </tr>
           </tbody>
         </table>
       </Island>

+ 14 - 0
src/components/utils/throttle.ts

@@ -0,0 +1,14 @@
+export const throttle = (func: Function, limit: number): Function => {
+  let inThrottle: boolean;
+
+  return function (this: any): any {
+    const args = arguments;
+    const context = this;
+
+    if (!inThrottle) {
+      inThrottle = true;
+      func.apply(context, args);
+      setTimeout(() => (inThrottle = false), limit);
+    }
+  };
+};

+ 13 - 3
src/element/linearElementEditor.ts

@@ -102,6 +102,7 @@ export class LinearElementEditor {
         element,
         scenePointerX - editingLinearElement.pointerOffset.x,
         scenePointerY - editingLinearElement.pointerOffset.y,
+        appState.showGrid,
         appState.gridSize,
       );
       LinearElementEditor.movePoint(element, activePointIndex, newPoint);
@@ -198,6 +199,7 @@ export class LinearElementEditor {
               element,
               scenePointer.x,
               scenePointer.y,
+              appState.showGrid,
               appState.gridSize,
             ),
           ],
@@ -282,7 +284,8 @@ export class LinearElementEditor {
     scenePointerX: number,
     scenePointerY: number,
     editingLinearElement: LinearElementEditor,
-    gridSize: number | null,
+    isGridOn: boolean,
+    gridSize: number,
   ): LinearElementEditor {
     const { elementId, lastUncommittedPoint } = editingLinearElement;
     const element = LinearElementEditor.getElement(elementId);
@@ -304,6 +307,7 @@ export class LinearElementEditor {
       element,
       scenePointerX - editingLinearElement.pointerOffset.x,
       scenePointerY - editingLinearElement.pointerOffset.y,
+      isGridOn,
       gridSize,
     );
 
@@ -398,9 +402,15 @@ export class LinearElementEditor {
     element: NonDeleted<ExcalidrawLinearElement>,
     scenePointerX: number,
     scenePointerY: number,
-    gridSize: number | null,
+    isGridOn: boolean,
+    gridSize: number,
   ): Point {
-    const pointerOnGrid = getGridPoint(scenePointerX, scenePointerY, gridSize);
+    const pointerOnGrid = getGridPoint(
+      scenePointerX,
+      scenePointerY,
+      isGridOn,
+      gridSize,
+    );
     const [x1, y1, x2, y2] = getElementAbsoluteCoords(element);
     const cx = (x1 + x2) / 2;
     const cy = (y1 + y2) / 2;

+ 3 - 2
src/math.ts

@@ -307,9 +307,10 @@ const doSegmentsIntersect = (p1: Point, q1: Point, p2: Point, q2: Point) => {
 export const getGridPoint = (
   x: number,
   y: number,
-  gridSize: number | null,
+  isGridOn: boolean,
+  gridSize: number,
 ): [number, number] => {
-  if (gridSize) {
+  if (isGridOn) {
     return [
       Math.round(x / gridSize) * gridSize,
       Math.round(y / gridSize) * gridSize,

+ 1 - 1
src/packages/utils/README.md

@@ -75,7 +75,7 @@ const excalidrawDiagram = {
   ],
   appState: {
     viewBackgroundColor: "#ffffff",
-    gridSize: null,
+    gridSize: 20,
   },
 };
 

+ 2 - 1
src/types.ts

@@ -83,7 +83,8 @@ export type AppState = {
   showShortcutsDialog: boolean;
   zenModeEnabled: boolean;
   appearance: "light" | "dark";
-  gridSize: number | null;
+  gridSize: number;
+  showGrid: boolean;
 
   /** top-most selected groups (i.e. does not include nested groups) */
   selectedGroupIds: { [groupId: string]: boolean };