From 40cd4caeecf75d40ace8a730f2ea8e514420a53a Mon Sep 17 00:00:00 2001 From: Panayiotis Lipiridis Date: Thu, 24 Dec 2020 16:45:42 +0200 Subject: [PATCH] feat: Change grid size --- src/appState.ts | 5 +- src/components/App.tsx | 28 ++++++++-- src/components/SlidableInput.module.css | 4 ++ src/components/SlidableInput.tsx | 68 +++++++++++++++++++++++++ src/components/Stats.tsx | 14 +++++ src/components/utils/throttle.ts | 14 +++++ src/element/linearElementEditor.ts | 16 ++++-- src/math.ts | 5 +- src/packages/utils/README.md | 2 +- src/types.ts | 3 +- 10 files changed, 146 insertions(+), 13 deletions(-) create mode 100644 src/components/SlidableInput.module.css create mode 100644 src/components/SlidableInput.tsx create mode 100644 src/components/utils/throttle.ts diff --git a/src/appState.ts b/src/appState.ts index b4dc998cd..c5d4c3c43 100644 --- a/src/appState.ts +++ b/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 }, diff --git a/src/components/App.tsx b/src/components/App.tsx index e312cf316..060179754 100644 --- a/src/components/App.tsx +++ b/src/components/App.tsx @@ -1036,7 +1036,12 @@ class App extends React.Component { 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 { toggleGridMode = () => { this.setState({ - gridSize: this.state.gridSize ? null : GRID_SIZE, + showGrid: !this.state.showGrid, }); }; @@ -1275,7 +1280,7 @@ class App extends React.Component { 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 { 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 { 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 { 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 { 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 { const [gridX, gridY] = getGridPoint( pointerCoords.x, pointerCoords.y, + this.state.showGrid, this.state.gridSize, ); @@ -2830,6 +2844,7 @@ class App extends React.Component { 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 { 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 { const [gridX, gridY] = getGridPoint( pointerCoords.x, pointerCoords.y, + this.state.showGrid, this.state.gridSize, ); dragNewElement( @@ -3580,6 +3597,7 @@ class App extends React.Component { const [resizeX, resizeY] = getGridPoint( pointerCoords.x - pointerDownState.resize.offset.x, pointerCoords.y - pointerDownState.resize.offset.y, + this.state.showGrid, this.state.gridSize, ); if ( diff --git a/src/components/SlidableInput.module.css b/src/components/SlidableInput.module.css new file mode 100644 index 000000000..55a35ef55 --- /dev/null +++ b/src/components/SlidableInput.module.css @@ -0,0 +1,4 @@ +.input { + cursor: ew-resize; + user-select: none; +} diff --git a/src/components/SlidableInput.tsx b/src/components/SlidableInput.tsx new file mode 100644 index 000000000..8aeb6b039 --- /dev/null +++ b/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 = ({ + value, + style, + prefix, + suffix, + onChange, + minValue, + maxValue, +}) => { + const [isLocked, setIsLocked] = useState(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 ( + + {prefix} + {value} + {suffix} + + ); +}; diff --git a/src/components/Stats.tsx b/src/components/Stats.tsx index c8acdd07d..ea62620dc 100644 --- a/src/components/Stats.tsx +++ b/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: { )} + + + {"Misc"} + + + {"Grid size"} + + + + diff --git a/src/components/utils/throttle.ts b/src/components/utils/throttle.ts new file mode 100644 index 000000000..5bdcd147c --- /dev/null +++ b/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); + } + }; +}; diff --git a/src/element/linearElementEditor.ts b/src/element/linearElementEditor.ts index b44f80c07..6e8527157 100644 --- a/src/element/linearElementEditor.ts +++ b/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, 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; diff --git a/src/math.ts b/src/math.ts index dadc6ccca..83c7b8e74 100644 --- a/src/math.ts +++ b/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, diff --git a/src/packages/utils/README.md b/src/packages/utils/README.md index 204cab5d2..2615078b5 100644 --- a/src/packages/utils/README.md +++ b/src/packages/utils/README.md @@ -75,7 +75,7 @@ const excalidrawDiagram = { ], appState: { viewBackgroundColor: "#ffffff", - gridSize: null, + gridSize: 20, }, }; diff --git a/src/types.ts b/src/types.ts index 9c99376cb..3f73ff97d 100644 --- a/src/types.ts +++ b/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 };