Prefer arrow functions and callbacks (#1210)

This commit is contained in:
Lipis
2020-05-20 16:21:37 +03:00
committed by GitHub
parent 33fe223b5d
commit c427aa3cce
64 changed files with 784 additions and 847 deletions

View File

@ -12,9 +12,9 @@ import { rescalePoints } from "../points";
// If the element is created from right to left, the width is going to be negative
// This set of functions retrieves the absolute position of the 4 points.
export function getElementAbsoluteCoords(
export const getElementAbsoluteCoords = (
element: ExcalidrawElement,
): [number, number, number, number] {
): [number, number, number, number] => {
if (isLinearElement(element)) {
return getLinearElementAbsoluteCoords(element);
}
@ -24,9 +24,9 @@ export function getElementAbsoluteCoords(
element.x + element.width,
element.y + element.height,
];
}
};
export function getDiamondPoints(element: ExcalidrawElement) {
export const getDiamondPoints = (element: ExcalidrawElement) => {
// Here we add +1 to avoid these numbers to be 0
// otherwise rough.js will throw an error complaining about it
const topX = Math.floor(element.width / 2) + 1;
@ -39,16 +39,16 @@ export function getDiamondPoints(element: ExcalidrawElement) {
const leftY = rightY;
return [topX, topY, rightX, rightY, bottomX, bottomY, leftX, leftY];
}
};
export function getCurvePathOps(shape: Drawable): Op[] {
export const getCurvePathOps = (shape: Drawable): Op[] => {
for (const set of shape.sets) {
if (set.type === "path") {
return set.ops;
}
}
return shape.sets[0].ops;
}
};
const getMinMaxXYFromCurvePathOps = (
ops: Op[],
@ -150,10 +150,10 @@ const getLinearElementAbsoluteCoords = (
];
};
export function getArrowPoints(
export const getArrowPoints = (
element: ExcalidrawLinearElement,
shape: Drawable[],
) {
) => {
const ops = getCurvePathOps(shape[0]);
const data = ops[ops.length - 1].data;
@ -212,7 +212,7 @@ export function getArrowPoints(
const [x4, y4] = rotate(xs, ys, x2, y2, (angle * Math.PI) / 180);
return [x2, y2, x3, y3, x4, y4];
}
};
const getLinearElementRotatedBounds = (
element: ExcalidrawLinearElement,

View File

@ -19,10 +19,10 @@ import { AppState } from "../types";
import { getShapeForElement } from "../renderer/renderElement";
import { isLinearElement } from "./typeChecks";
function isElementDraggableFromInside(
const isElementDraggableFromInside = (
element: NonDeletedExcalidrawElement,
appState: AppState,
): boolean {
): boolean => {
const dragFromInside =
element.backgroundColor !== "transparent" ||
appState.selectedElementIds[element.id];
@ -30,15 +30,15 @@ function isElementDraggableFromInside(
return dragFromInside && isPathALoop(element.points);
}
return dragFromInside;
}
};
export function hitTest(
export const hitTest = (
element: NonDeletedExcalidrawElement,
appState: AppState,
x: number,
y: number,
zoom: number,
): boolean {
): boolean => {
// For shapes that are composed of lines, we only enable point-selection when the distance
// of the click is less than x pixels of any of the lines that the shape is composed of
const lineThreshold = 10 / zoom;
@ -210,7 +210,7 @@ export function hitTest(
return false;
}
throw new Error(`Unimplemented type ${element.type}`);
}
};
const pointInBezierEquation = (
p0: Point,

View File

@ -21,7 +21,7 @@ export const OMIT_SIDES_FOR_MULTIPLE_ELEMENTS = {
rotation: true,
};
function generateHandler(
const generateHandler = (
x: number,
y: number,
width: number,
@ -29,18 +29,18 @@ function generateHandler(
cx: number,
cy: number,
angle: number,
): [number, number, number, number] {
): [number, number, number, number] => {
const [xx, yy] = rotate(x + width / 2, y + height / 2, cx, cy, angle);
return [xx - width / 2, yy - height / 2, width, height];
}
};
export function handlerRectanglesFromCoords(
export const handlerRectanglesFromCoords = (
[x1, y1, x2, y2]: [number, number, number, number],
angle: number,
zoom: number,
pointerType: PointerType = "mouse",
omitSides: { [T in Sides]?: boolean } = {},
): Partial<{ [T in Sides]: [number, number, number, number] }> {
): Partial<{ [T in Sides]: [number, number, number, number] }> => {
const size = handleSizes[pointerType];
const handlerWidth = size / zoom;
const handlerHeight = size / zoom;
@ -173,13 +173,13 @@ export function handlerRectanglesFromCoords(
}
return handlers;
}
};
export function handlerRectangles(
export const handlerRectangles = (
element: ExcalidrawElement,
zoom: number,
pointerType: PointerType = "mouse",
) {
) => {
const handlers = handlerRectanglesFromCoords(
getElementAbsoluteCoords(element),
element.angle,
@ -234,4 +234,4 @@ export function handlerRectangles(
}
return handlers;
}
};

View File

@ -49,35 +49,30 @@ export {
} from "./sizeHelpers";
export { showSelectedShapeActions } from "./showSelectedShapeActions";
export function getSyncableElements(elements: readonly ExcalidrawElement[]) {
// There are places in Excalidraw where synthetic invisibly small elements are added and removed.
export const getSyncableElements = (
elements: readonly ExcalidrawElement[], // There are places in Excalidraw where synthetic invisibly small elements are added and removed.
) =>
// It's probably best to keep those local otherwise there might be a race condition that
// gets the app into an invalid state. I've never seen it happen but I'm worried about it :)
return elements.filter((el) => el.isDeleted || !isInvisiblySmallElement(el));
}
elements.filter((el) => el.isDeleted || !isInvisiblySmallElement(el));
export function getElementMap(elements: readonly ExcalidrawElement[]) {
return elements.reduce(
export const getElementMap = (elements: readonly ExcalidrawElement[]) =>
elements.reduce(
(acc: { [key: string]: ExcalidrawElement }, element: ExcalidrawElement) => {
acc[element.id] = element;
return acc;
},
{},
);
}
export function getDrawingVersion(elements: readonly ExcalidrawElement[]) {
return elements.reduce((acc, el) => acc + el.version, 0);
}
export const getDrawingVersion = (elements: readonly ExcalidrawElement[]) =>
elements.reduce((acc, el) => acc + el.version, 0);
export function getNonDeletedElements(elements: readonly ExcalidrawElement[]) {
return elements.filter(
export const getNonDeletedElements = (elements: readonly ExcalidrawElement[]) =>
elements.filter(
(element) => !element.isDeleted,
) as readonly NonDeletedExcalidrawElement[];
}
export function isNonDeletedElement<T extends ExcalidrawElement>(
export const isNonDeletedElement = <T extends ExcalidrawElement>(
element: T,
): element is NonDeleted<T> {
return !element.isDeleted;
}
): element is NonDeleted<T> => !element.isDeleted;

View File

@ -13,10 +13,10 @@ type ElementUpdate<TElement extends ExcalidrawElement> = Omit<
// The version is used to compare updates when more than one user is working in
// the same drawing. Note: this will trigger the component to update. Make sure you
// are calling it either from a React event handler or within unstable_batchedUpdates().
export function mutateElement<TElement extends Mutable<ExcalidrawElement>>(
export const mutateElement = <TElement extends Mutable<ExcalidrawElement>>(
element: TElement,
updates: ElementUpdate<TElement>,
) {
) => {
// casting to any because can't use `in` operator
// (see https://github.com/microsoft/TypeScript/issues/21732)
const { points } = updates as any;
@ -45,16 +45,14 @@ export function mutateElement<TElement extends Mutable<ExcalidrawElement>>(
element.versionNonce = randomInteger();
globalSceneState.informMutation();
}
};
export function newElementWith<TElement extends ExcalidrawElement>(
export const newElementWith = <TElement extends ExcalidrawElement>(
element: TElement,
updates: ElementUpdate<TElement>,
): TElement {
return {
...element,
version: element.version + 1,
versionNonce: randomInteger(),
...updates,
};
}
): TElement => ({
...element,
version: element.version + 1,
versionNonce: randomInteger(),
...updates,
});

View File

@ -5,12 +5,12 @@ import {
} from "./newElement";
import { mutateElement } from "./mutateElement";
function isPrimitive(val: any) {
const isPrimitive = (val: any) => {
const type = typeof val;
return val == null || (type !== "object" && type !== "function");
}
};
function assertCloneObjects(source: any, clone: any) {
const assertCloneObjects = (source: any, clone: any) => {
for (const key in clone) {
if (clone.hasOwnProperty(key) && !isPrimitive(clone[key])) {
expect(clone[key]).not.toBe(source[key]);
@ -19,7 +19,7 @@ function assertCloneObjects(source: any, clone: any) {
}
}
}
}
};
it("clones arrow element", () => {
const element = newLinearElement({

View File

@ -25,7 +25,7 @@ type ElementConstructorOpts = {
angle?: ExcalidrawGenericElement["angle"];
};
function _newElementBase<T extends ExcalidrawElement>(
const _newElementBase = <T extends ExcalidrawElement>(
type: T["type"],
{
x,
@ -42,44 +42,41 @@ function _newElementBase<T extends ExcalidrawElement>(
angle = 0,
...rest
}: ElementConstructorOpts & Partial<ExcalidrawGenericElement>,
) {
return {
id: rest.id || randomId(),
type,
x,
y,
width,
height,
angle,
strokeColor,
backgroundColor,
fillStyle,
strokeWidth,
strokeStyle,
roughness,
opacity,
seed: rest.seed ?? randomInteger(),
version: rest.version || 1,
versionNonce: rest.versionNonce ?? 0,
isDeleted: false as false,
};
}
) => ({
id: rest.id || randomId(),
type,
x,
y,
width,
height,
angle,
strokeColor,
backgroundColor,
fillStyle,
strokeWidth,
strokeStyle,
roughness,
opacity,
seed: rest.seed ?? randomInteger(),
version: rest.version || 1,
versionNonce: rest.versionNonce ?? 0,
isDeleted: false as false,
});
export function newElement(
export const newElement = (
opts: {
type: ExcalidrawGenericElement["type"];
} & ElementConstructorOpts,
): NonDeleted<ExcalidrawGenericElement> {
return _newElementBase<ExcalidrawGenericElement>(opts.type, opts);
}
): NonDeleted<ExcalidrawGenericElement> =>
_newElementBase<ExcalidrawGenericElement>(opts.type, opts);
export function newTextElement(
export const newTextElement = (
opts: {
text: string;
font: string;
textAlign: TextAlign;
} & ElementConstructorOpts,
): NonDeleted<ExcalidrawTextElement> {
): NonDeleted<ExcalidrawTextElement> => {
const metrics = measureText(opts.text, opts.font);
const textElement = newElementWith(
{
@ -98,26 +95,26 @@ export function newTextElement(
);
return textElement;
}
};
export function newLinearElement(
export const newLinearElement = (
opts: {
type: ExcalidrawLinearElement["type"];
lastCommittedPoint?: ExcalidrawLinearElement["lastCommittedPoint"];
} & ElementConstructorOpts,
): NonDeleted<ExcalidrawLinearElement> {
): NonDeleted<ExcalidrawLinearElement> => {
return {
..._newElementBase<ExcalidrawLinearElement>(opts.type, opts),
points: [],
lastCommittedPoint: opts.lastCommittedPoint || null,
};
}
};
// Simplified deep clone for the purpose of cloning ExcalidrawElement only
// (doesn't clone Date, RegExp, Map, Set, Typed arrays etc.)
//
// Adapted from https://github.com/lukeed/klona
function _duplicateElement(val: any, depth: number = 0) {
const _duplicateElement = (val: any, depth: number = 0) => {
if (val == null || typeof val !== "object") {
return val;
}
@ -149,12 +146,12 @@ function _duplicateElement(val: any, depth: number = 0) {
}
return val;
}
};
export function duplicateElement<TElement extends Mutable<ExcalidrawElement>>(
export const duplicateElement = <TElement extends Mutable<ExcalidrawElement>>(
element: TElement,
overrides?: Partial<TElement>,
): TElement {
): TElement => {
let copy: TElement = _duplicateElement(element);
copy.id = randomId();
copy.seed = randomInteger();
@ -162,4 +159,4 @@ export function duplicateElement<TElement extends Mutable<ExcalidrawElement>>(
copy = Object.assign(copy, overrides);
}
return copy;
}
};

View File

@ -13,27 +13,24 @@ import { AppState } from "../types";
type HandlerRectanglesRet = keyof ReturnType<typeof handlerRectangles>;
function isInHandlerRect(
const isInHandlerRect = (
handler: [number, number, number, number],
x: number,
y: number,
) {
return (
x >= handler[0] &&
x <= handler[0] + handler[2] &&
y >= handler[1] &&
y <= handler[1] + handler[3]
);
}
) =>
x >= handler[0] &&
x <= handler[0] + handler[2] &&
y >= handler[1] &&
y <= handler[1] + handler[3];
export function resizeTest(
export const resizeTest = (
element: NonDeletedExcalidrawElement,
appState: AppState,
x: number,
y: number,
zoom: number,
pointerType: PointerType,
): HandlerRectanglesRet | false {
): HandlerRectanglesRet | false => {
if (!appState.selectedElementIds[element.id]) {
return false;
}
@ -66,30 +63,29 @@ export function resizeTest(
}
return false;
}
};
export function getElementWithResizeHandler(
export const getElementWithResizeHandler = (
elements: readonly NonDeletedExcalidrawElement[],
appState: AppState,
{ x, y }: { x: number; y: number },
zoom: number,
pointerType: PointerType,
) {
return elements.reduce((result, element) => {
) =>
elements.reduce((result, element) => {
if (result) {
return result;
}
const resizeHandle = resizeTest(element, appState, x, y, zoom, pointerType);
return resizeHandle ? { element, resizeHandle } : null;
}, null as { element: NonDeletedExcalidrawElement; resizeHandle: ReturnType<typeof resizeTest> } | null);
}
export function getResizeHandlerFromCoords(
export const getResizeHandlerFromCoords = (
[x1, y1, x2, y2]: readonly [number, number, number, number],
{ x, y }: { x: number; y: number },
zoom: number,
pointerType: PointerType,
) {
) => {
const handlers = handlerRectanglesFromCoords(
[x1, y1, x2, y2],
0,
@ -103,7 +99,7 @@ export function getResizeHandlerFromCoords(
return handler && isInHandlerRect(handler, x, y);
});
return (found || false) as HandlerRectanglesRet;
}
};
const RESIZE_CURSORS = ["ns", "nesw", "ew", "nwse"];
const rotateResizeCursor = (cursor: string, angle: number) => {
@ -118,10 +114,10 @@ const rotateResizeCursor = (cursor: string, angle: number) => {
/*
* Returns bi-directional cursor for the element being resized
*/
export function getCursorForResizingElement(resizingElement: {
export const getCursorForResizingElement = (resizingElement: {
element?: ExcalidrawElement;
resizeHandle: ReturnType<typeof resizeTest>;
}): string {
}): string => {
const { element, resizeHandle } = resizingElement;
const shouldSwapCursors =
element && Math.sign(element.height) * Math.sign(element.width) === -1;
@ -161,12 +157,12 @@ export function getCursorForResizingElement(resizingElement: {
}
return cursor ? `${cursor}-resize` : "";
}
};
export function normalizeResizeHandle(
export const normalizeResizeHandle = (
element: ExcalidrawElement,
resizeHandle: HandlerRectanglesRet,
): HandlerRectanglesRet {
): HandlerRectanglesRet => {
if (element.width >= 0 && element.height >= 0) {
return resizeHandle;
}
@ -215,4 +211,4 @@ export function normalizeResizeHandle(
}
return resizeHandle;
}
};

View File

@ -3,21 +3,23 @@ import { mutateElement } from "./mutateElement";
import { isLinearElement } from "./typeChecks";
import { SHIFT_LOCKING_ANGLE } from "../constants";
export function isInvisiblySmallElement(element: ExcalidrawElement): boolean {
export const isInvisiblySmallElement = (
element: ExcalidrawElement,
): boolean => {
if (isLinearElement(element)) {
return element.points.length < 2;
}
return element.width === 0 && element.height === 0;
}
};
/**
* Makes a perfect shape or diagonal/horizontal/vertical line
*/
export function getPerfectElementSize(
export const getPerfectElementSize = (
elementType: string,
width: number,
height: number,
): { width: number; height: number } {
): { width: number; height: number } => {
const absWidth = Math.abs(width);
const absHeight = Math.abs(height);
@ -42,13 +44,13 @@ export function getPerfectElementSize(
height = absWidth * Math.sign(height);
}
return { width, height };
}
};
export function resizePerfectLineForNWHandler(
export const resizePerfectLineForNWHandler = (
element: ExcalidrawElement,
x: number,
y: number,
) {
) => {
const anchorX = element.x + element.width;
const anchorY = element.y + element.height;
const distanceToAnchorX = x - anchorX;
@ -77,14 +79,14 @@ export function resizePerfectLineForNWHandler(
height: nextHeight,
});
}
}
};
/**
* @returns {boolean} whether element was normalized
*/
export function normalizeDimensions(
export const normalizeDimensions = (
element: ExcalidrawElement | null,
): element is ExcalidrawElement {
): element is ExcalidrawElement => {
if (!element || (element.width >= 0 && element.height >= 0)) {
return false;
}
@ -106,4 +108,4 @@ export function normalizeDimensions(
}
return true;
}
};

View File

@ -4,7 +4,7 @@ import { globalSceneState } from "../scene";
import { isTextElement } from "./typeChecks";
import { CLASSES } from "../constants";
function trimText(text: string) {
const trimText = (text: string) => {
// whitespace only → trim all because we'd end up inserting invisible element
if (!text.trim()) {
return "";
@ -13,7 +13,7 @@ function trimText(text: string) {
// box calculation (there's also a bug in FF which inserts trailing newline
// for multiline texts)
return text.replace(/^\n+|\n+$/g, "");
}
};
type TextWysiwygParams = {
id: string;
@ -31,7 +31,7 @@ type TextWysiwygParams = {
onCancel: () => void;
};
export function textWysiwyg({
export const textWysiwyg = ({
id,
initText,
x,
@ -45,7 +45,7 @@ export function textWysiwyg({
textAlign,
onSubmit,
onCancel,
}: TextWysiwygParams) {
}: TextWysiwygParams) => {
const editable = document.createElement("div");
try {
editable.contentEditable = "plaintext-only";
@ -126,20 +126,20 @@ export function textWysiwyg({
}
};
function stopEvent(event: Event) {
const stopEvent = (event: Event) => {
event.stopPropagation();
}
};
function handleSubmit() {
const handleSubmit = () => {
if (editable.innerText) {
onSubmit(trimText(editable.innerText));
} else {
onCancel();
}
cleanup();
}
};
function cleanup() {
const cleanup = () => {
if (isDestroyed) {
return;
}
@ -158,7 +158,7 @@ export function textWysiwyg({
unbindUpdate();
document.body.removeChild(editable);
}
};
const rebindBlur = () => {
window.removeEventListener("pointerup", rebindBlur);
@ -210,4 +210,4 @@ export function textWysiwyg({
document.body.appendChild(editable);
editable.focus();
selectNode(editable);
}
};

View File

@ -4,24 +4,24 @@ import {
ExcalidrawLinearElement,
} from "./types";
export function isTextElement(
export const isTextElement = (
element: ExcalidrawElement | null,
): element is ExcalidrawTextElement {
): element is ExcalidrawTextElement => {
return element != null && element.type === "text";
}
};
export function isLinearElement(
export const isLinearElement = (
element?: ExcalidrawElement | null,
): element is ExcalidrawLinearElement {
): element is ExcalidrawLinearElement => {
return (
element != null &&
(element.type === "arrow" ||
element.type === "line" ||
element.type === "draw")
);
}
};
export function isExcalidrawElement(element: any): boolean {
export const isExcalidrawElement = (element: any): boolean => {
return (
element?.type === "text" ||
element?.type === "diamond" ||
@ -31,4 +31,4 @@ export function isExcalidrawElement(element: any): boolean {
element?.type === "draw" ||
element?.type === "line"
);
}
};