feat: Change grid size
This commit is contained in:
parent
39e7b8cf4f
commit
40cd4caeec
@ -6,6 +6,7 @@ import {
|
|||||||
DEFAULT_FONT_SIZE,
|
DEFAULT_FONT_SIZE,
|
||||||
DEFAULT_FONT_FAMILY,
|
DEFAULT_FONT_FAMILY,
|
||||||
DEFAULT_TEXT_ALIGN,
|
DEFAULT_TEXT_ALIGN,
|
||||||
|
GRID_SIZE,
|
||||||
} from "./constants";
|
} from "./constants";
|
||||||
|
|
||||||
export const getDefaultAppState = (): Omit<
|
export const getDefaultAppState = (): Omit<
|
||||||
@ -65,7 +66,8 @@ export const getDefaultAppState = (): Omit<
|
|||||||
showShortcutsDialog: false,
|
showShortcutsDialog: false,
|
||||||
suggestedBindings: [],
|
suggestedBindings: [],
|
||||||
zenModeEnabled: false,
|
zenModeEnabled: false,
|
||||||
gridSize: null,
|
gridSize: GRID_SIZE,
|
||||||
|
showGrid: false,
|
||||||
editingGroupId: null,
|
editingGroupId: null,
|
||||||
selectedGroupIds: {},
|
selectedGroupIds: {},
|
||||||
width: window.innerWidth,
|
width: window.innerWidth,
|
||||||
@ -121,6 +123,7 @@ const APP_STATE_STORAGE_CONF = (<
|
|||||||
exportBackground: { browser: true, export: false },
|
exportBackground: { browser: true, export: false },
|
||||||
exportEmbedScene: { browser: true, export: false },
|
exportEmbedScene: { browser: true, export: false },
|
||||||
gridSize: { browser: true, export: true },
|
gridSize: { browser: true, export: true },
|
||||||
|
showGrid: { browser: true, export: false },
|
||||||
height: { browser: false, export: false },
|
height: { browser: false, export: false },
|
||||||
isBindingEnabled: { browser: false, export: false },
|
isBindingEnabled: { browser: false, export: false },
|
||||||
isLibraryOpen: { 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 dy = y - elementsCenterY;
|
||||||
const groupIdMap = new Map();
|
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 oldIdToDuplicatedId = new Map();
|
||||||
const newElements = clipboardElements.map((element) => {
|
const newElements = clipboardElements.map((element) => {
|
||||||
@ -1149,7 +1154,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
|
|
||||||
toggleGridMode = () => {
|
toggleGridMode = () => {
|
||||||
this.setState({
|
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)) {
|
if (isArrowKey(event.key)) {
|
||||||
const step =
|
const step =
|
||||||
(this.state.gridSize &&
|
(this.state.showGrid &&
|
||||||
(event.shiftKey ? ELEMENT_TRANSLATE_AMOUNT : this.state.gridSize)) ||
|
(event.shiftKey ? ELEMENT_TRANSLATE_AMOUNT : this.state.gridSize)) ||
|
||||||
(event.shiftKey
|
(event.shiftKey
|
||||||
? ELEMENT_SHIFT_TRANSLATE_AMOUNT
|
? ELEMENT_SHIFT_TRANSLATE_AMOUNT
|
||||||
@ -1819,6 +1824,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
scenePointerX,
|
scenePointerX,
|
||||||
scenePointerY,
|
scenePointerY,
|
||||||
this.state.editingLinearElement,
|
this.state.editingLinearElement,
|
||||||
|
this.state.showGrid,
|
||||||
this.state.gridSize,
|
this.state.gridSize,
|
||||||
);
|
);
|
||||||
if (editingLinearElement !== this.state.editingLinearElement) {
|
if (editingLinearElement !== this.state.editingLinearElement) {
|
||||||
@ -2249,7 +2255,12 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
return {
|
return {
|
||||||
origin,
|
origin,
|
||||||
originInGrid: tupleToCoors(
|
originInGrid: tupleToCoors(
|
||||||
getGridPoint(origin.x, origin.y, this.state.gridSize),
|
getGridPoint(
|
||||||
|
origin.x,
|
||||||
|
origin.y,
|
||||||
|
this.state.showGrid,
|
||||||
|
this.state.gridSize,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
scrollbars: isOverScrollBars(
|
scrollbars: isOverScrollBars(
|
||||||
currentScrollBars,
|
currentScrollBars,
|
||||||
@ -2607,7 +2618,8 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
const [gridX, gridY] = getGridPoint(
|
const [gridX, gridY] = getGridPoint(
|
||||||
pointerDownState.origin.x,
|
pointerDownState.origin.x,
|
||||||
pointerDownState.origin.y,
|
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.
|
/* 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(
|
const [gridX, gridY] = getGridPoint(
|
||||||
pointerDownState.origin.x,
|
pointerDownState.origin.x,
|
||||||
pointerDownState.origin.y,
|
pointerDownState.origin.y,
|
||||||
|
this.state.showGrid,
|
||||||
this.state.gridSize,
|
this.state.gridSize,
|
||||||
);
|
);
|
||||||
const element = newElement({
|
const element = newElement({
|
||||||
@ -2758,6 +2771,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
const [gridX, gridY] = getGridPoint(
|
const [gridX, gridY] = getGridPoint(
|
||||||
pointerCoords.x,
|
pointerCoords.x,
|
||||||
pointerCoords.y,
|
pointerCoords.y,
|
||||||
|
this.state.showGrid,
|
||||||
this.state.gridSize,
|
this.state.gridSize,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -2830,6 +2844,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
const [dragX, dragY] = getGridPoint(
|
const [dragX, dragY] = getGridPoint(
|
||||||
pointerCoords.x - pointerDownState.drag.offset.x,
|
pointerCoords.x - pointerDownState.drag.offset.x,
|
||||||
pointerCoords.y - pointerDownState.drag.offset.y,
|
pointerCoords.y - pointerDownState.drag.offset.y,
|
||||||
|
this.state.showGrid,
|
||||||
this.state.gridSize,
|
this.state.gridSize,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -2882,6 +2897,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
const [originDragX, originDragY] = getGridPoint(
|
const [originDragX, originDragY] = getGridPoint(
|
||||||
pointerDownState.origin.x - pointerDownState.drag.offset.x,
|
pointerDownState.origin.x - pointerDownState.drag.offset.x,
|
||||||
pointerDownState.origin.y - pointerDownState.drag.offset.y,
|
pointerDownState.origin.y - pointerDownState.drag.offset.y,
|
||||||
|
this.state.showGrid,
|
||||||
this.state.gridSize,
|
this.state.gridSize,
|
||||||
);
|
);
|
||||||
mutateElement(duplicatedElement, {
|
mutateElement(duplicatedElement, {
|
||||||
@ -3542,6 +3558,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
const [gridX, gridY] = getGridPoint(
|
const [gridX, gridY] = getGridPoint(
|
||||||
pointerCoords.x,
|
pointerCoords.x,
|
||||||
pointerCoords.y,
|
pointerCoords.y,
|
||||||
|
this.state.showGrid,
|
||||||
this.state.gridSize,
|
this.state.gridSize,
|
||||||
);
|
);
|
||||||
dragNewElement(
|
dragNewElement(
|
||||||
@ -3580,6 +3597,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
const [resizeX, resizeY] = getGridPoint(
|
const [resizeX, resizeY] = getGridPoint(
|
||||||
pointerCoords.x - pointerDownState.resize.offset.x,
|
pointerCoords.x - pointerDownState.resize.offset.x,
|
||||||
pointerCoords.y - pointerDownState.resize.offset.y,
|
pointerCoords.y - pointerDownState.resize.offset.y,
|
||||||
|
this.state.showGrid,
|
||||||
this.state.gridSize,
|
this.state.gridSize,
|
||||||
);
|
);
|
||||||
if (
|
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 { debounce, nFormatter } from "../utils";
|
||||||
import { close } from "./icons";
|
import { close } from "./icons";
|
||||||
import { Island } from "./Island";
|
import { Island } from "./Island";
|
||||||
|
import { SlidableInput } from "./SlidableInput";
|
||||||
import "./Stats.scss";
|
import "./Stats.scss";
|
||||||
|
|
||||||
type StorageSizes = { scene: number; total: number };
|
type StorageSizes = { scene: number; total: number };
|
||||||
@ -157,6 +158,19 @@ export const Stats = (props: {
|
|||||||
</td>
|
</td>
|
||||||
</tr>
|
</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>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</Island>
|
</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,
|
element,
|
||||||
scenePointerX - editingLinearElement.pointerOffset.x,
|
scenePointerX - editingLinearElement.pointerOffset.x,
|
||||||
scenePointerY - editingLinearElement.pointerOffset.y,
|
scenePointerY - editingLinearElement.pointerOffset.y,
|
||||||
|
appState.showGrid,
|
||||||
appState.gridSize,
|
appState.gridSize,
|
||||||
);
|
);
|
||||||
LinearElementEditor.movePoint(element, activePointIndex, newPoint);
|
LinearElementEditor.movePoint(element, activePointIndex, newPoint);
|
||||||
@ -198,6 +199,7 @@ export class LinearElementEditor {
|
|||||||
element,
|
element,
|
||||||
scenePointer.x,
|
scenePointer.x,
|
||||||
scenePointer.y,
|
scenePointer.y,
|
||||||
|
appState.showGrid,
|
||||||
appState.gridSize,
|
appState.gridSize,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@ -282,7 +284,8 @@ export class LinearElementEditor {
|
|||||||
scenePointerX: number,
|
scenePointerX: number,
|
||||||
scenePointerY: number,
|
scenePointerY: number,
|
||||||
editingLinearElement: LinearElementEditor,
|
editingLinearElement: LinearElementEditor,
|
||||||
gridSize: number | null,
|
isGridOn: boolean,
|
||||||
|
gridSize: number,
|
||||||
): LinearElementEditor {
|
): LinearElementEditor {
|
||||||
const { elementId, lastUncommittedPoint } = editingLinearElement;
|
const { elementId, lastUncommittedPoint } = editingLinearElement;
|
||||||
const element = LinearElementEditor.getElement(elementId);
|
const element = LinearElementEditor.getElement(elementId);
|
||||||
@ -304,6 +307,7 @@ export class LinearElementEditor {
|
|||||||
element,
|
element,
|
||||||
scenePointerX - editingLinearElement.pointerOffset.x,
|
scenePointerX - editingLinearElement.pointerOffset.x,
|
||||||
scenePointerY - editingLinearElement.pointerOffset.y,
|
scenePointerY - editingLinearElement.pointerOffset.y,
|
||||||
|
isGridOn,
|
||||||
gridSize,
|
gridSize,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -398,9 +402,15 @@ export class LinearElementEditor {
|
|||||||
element: NonDeleted<ExcalidrawLinearElement>,
|
element: NonDeleted<ExcalidrawLinearElement>,
|
||||||
scenePointerX: number,
|
scenePointerX: number,
|
||||||
scenePointerY: number,
|
scenePointerY: number,
|
||||||
gridSize: number | null,
|
isGridOn: boolean,
|
||||||
|
gridSize: number,
|
||||||
): Point {
|
): Point {
|
||||||
const pointerOnGrid = getGridPoint(scenePointerX, scenePointerY, gridSize);
|
const pointerOnGrid = getGridPoint(
|
||||||
|
scenePointerX,
|
||||||
|
scenePointerY,
|
||||||
|
isGridOn,
|
||||||
|
gridSize,
|
||||||
|
);
|
||||||
const [x1, y1, x2, y2] = getElementAbsoluteCoords(element);
|
const [x1, y1, x2, y2] = getElementAbsoluteCoords(element);
|
||||||
const cx = (x1 + x2) / 2;
|
const cx = (x1 + x2) / 2;
|
||||||
const cy = (y1 + y2) / 2;
|
const cy = (y1 + y2) / 2;
|
||||||
|
@ -307,9 +307,10 @@ const doSegmentsIntersect = (p1: Point, q1: Point, p2: Point, q2: Point) => {
|
|||||||
export const getGridPoint = (
|
export const getGridPoint = (
|
||||||
x: number,
|
x: number,
|
||||||
y: number,
|
y: number,
|
||||||
gridSize: number | null,
|
isGridOn: boolean,
|
||||||
|
gridSize: number,
|
||||||
): [number, number] => {
|
): [number, number] => {
|
||||||
if (gridSize) {
|
if (isGridOn) {
|
||||||
return [
|
return [
|
||||||
Math.round(x / gridSize) * gridSize,
|
Math.round(x / gridSize) * gridSize,
|
||||||
Math.round(y / gridSize) * gridSize,
|
Math.round(y / gridSize) * gridSize,
|
||||||
|
@ -75,7 +75,7 @@ const excalidrawDiagram = {
|
|||||||
],
|
],
|
||||||
appState: {
|
appState: {
|
||||||
viewBackgroundColor: "#ffffff",
|
viewBackgroundColor: "#ffffff",
|
||||||
gridSize: null,
|
gridSize: 20,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -83,7 +83,8 @@ export type AppState = {
|
|||||||
showShortcutsDialog: boolean;
|
showShortcutsDialog: boolean;
|
||||||
zenModeEnabled: boolean;
|
zenModeEnabled: boolean;
|
||||||
appearance: "light" | "dark";
|
appearance: "light" | "dark";
|
||||||
gridSize: number | null;
|
gridSize: number;
|
||||||
|
showGrid: boolean;
|
||||||
|
|
||||||
/** top-most selected groups (i.e. does not include nested groups) */
|
/** top-most selected groups (i.e. does not include nested groups) */
|
||||||
selectedGroupIds: { [groupId: string]: boolean };
|
selectedGroupIds: { [groupId: string]: boolean };
|
||||||
|
Loading…
x
Reference in New Issue
Block a user