feat: Change grid size
This commit is contained in:
parent
39e7b8cf4f
commit
40cd4caeec
@ -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 },
|
||||
|
@ -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
src/components/SlidableInput.module.css
Normal file
4
src/components/SlidableInput.module.css
Normal file
@ -0,0 +1,4 @@
|
||||
.input {
|
||||
cursor: ew-resize;
|
||||
user-select: none;
|
||||
}
|
68
src/components/SlidableInput.tsx
Normal file
68
src/components/SlidableInput.tsx
Normal file
@ -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>
|
||||
);
|
||||
};
|
@ -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
src/components/utils/throttle.ts
Normal file
14
src/components/utils/throttle.ts
Normal file
@ -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);
|
||||
}
|
||||
};
|
||||
};
|
@ -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;
|
||||
|
@ -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,
|
||||
|
@ -75,7 +75,7 @@ const excalidrawDiagram = {
|
||||
],
|
||||
appState: {
|
||||
viewBackgroundColor: "#ffffff",
|
||||
gridSize: null,
|
||||
gridSize: 20,
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -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 };
|
||||
|
Loading…
x
Reference in New Issue
Block a user