From c427aa3cce801bef4dd9107e1044d3a4f61a201e Mon Sep 17 00:00:00 2001 From: Lipis Date: Wed, 20 May 2020 16:21:37 +0300 Subject: [PATCH] Prefer arrow functions and callbacks (#1210) --- src/actions/actionZindex.tsx | 16 +- src/actions/register.ts | 4 +- src/appState.ts | 18 +-- src/clipboard.ts | 37 +++-- src/components/Actions.tsx | 100 ++++++------- src/components/App.tsx | 13 +- src/components/ButtonSelect.tsx | 40 +++-- src/components/ColorPicker.tsx | 14 +- src/components/ContextMenu.tsx | 58 ++++---- src/components/Dialog.tsx | 14 +- src/components/ErrorDialog.tsx | 6 +- src/components/ExportDialog.tsx | 12 +- src/components/FixedSideContainer.tsx | 18 +-- src/components/HelpIcon.tsx | 12 +- src/components/LanguageList.tsx | 40 +++-- src/components/LockIcon.tsx | 4 +- src/components/MobileMenu.tsx | 202 +++++++++++++------------- src/components/Modal.tsx | 14 +- src/components/Popover.tsx | 6 +- src/components/RoomDialog.tsx | 20 +-- src/components/Section.tsx | 4 +- src/components/Stack.tsx | 12 +- src/components/ToolButton.tsx | 5 +- src/data/blob.ts | 4 +- src/data/index.ts | 63 ++++---- src/data/json.ts | 17 +-- src/data/localStorage.ts | 18 +-- src/data/restore.ts | 6 +- src/element/bounds.ts | 20 +-- src/element/collision.ts | 12 +- src/element/handlerRectangles.ts | 18 +-- src/element/index.ts | 29 ++-- src/element/mutateElement.ts | 22 ++- src/element/newElement.test.ts | 8 +- src/element/newElement.ts | 73 +++++----- src/element/resizeTest.ts | 46 +++--- src/element/sizeHelpers.ts | 24 +-- src/element/textWysiwyg.tsx | 22 +-- src/element/typeChecks.ts | 16 +- src/gesture.ts | 14 +- src/history.ts | 7 +- src/i18n.ts | 20 +-- src/index.tsx | 2 +- src/is-mobile.tsx | 8 +- src/keys.ts | 14 +- src/math.ts | 39 +++-- src/points.ts | 10 +- src/random.ts | 13 +- src/renderer/renderElement.ts | 48 +++--- src/renderer/renderScene.ts | 34 ++--- src/renderer/roundRect.ts | 6 +- src/scene/comparisons.ts | 12 +- src/scene/export.ts | 21 ++- src/scene/scroll.ts | 11 +- src/scene/scrollbars.ts | 14 +- src/scene/selection.ts | 36 ++--- src/scene/zoom.ts | 11 +- src/serviceWorker.tsx | 16 +- src/shapes.tsx | 11 +- src/tests/regressionTests.test.tsx | 80 +++++----- src/tests/zindex.test.tsx | 12 +- src/utils.ts | 101 ++++++------- src/zindex.test.ts | 6 +- src/zindex.ts | 20 +-- 64 files changed, 785 insertions(+), 848 deletions(-) diff --git a/src/actions/actionZindex.tsx b/src/actions/actionZindex.tsx index f42ef11c5..97a63c16a 100644 --- a/src/actions/actionZindex.tsx +++ b/src/actions/actionZindex.tsx @@ -18,15 +18,15 @@ import { import { ExcalidrawElement } from "../element/types"; import { AppState } from "../types"; -function getElementIndices( +const getElementIndices = ( direction: "left" | "right", elements: readonly ExcalidrawElement[], appState: AppState, -) { +) => { const selectedIndices: number[] = []; let deletedIndicesCache: number[] = []; - function cb(element: ExcalidrawElement, index: number) { + const cb = (element: ExcalidrawElement, index: number) => { if (element.isDeleted) { // we want to build an array of deleted elements that are preceeding // a selected element so that we move them together @@ -39,7 +39,7 @@ function getElementIndices( // of selected/deleted elements, of after encountering non-deleted elem deletedIndicesCache = []; } - } + }; // sending back → select contiguous deleted elements that are to the left of // selected element(s) @@ -59,19 +59,19 @@ function getElementIndices( } // sort in case we were gathering indexes from right to left return selectedIndices.sort(); -} +}; -function moveElements( +const moveElements = ( func: typeof moveOneLeft, elements: readonly ExcalidrawElement[], appState: AppState, -) { +) => { const _elements = elements.slice(); const direction = func === moveOneLeft || func === moveAllLeft ? "left" : "right"; const indices = getElementIndices(direction, _elements, appState); return func(_elements, indices); -} +}; export const actionSendBackward = register({ name: "sendBackward", diff --git a/src/actions/register.ts b/src/actions/register.ts index 1eeb99c3f..59d7aad24 100644 --- a/src/actions/register.ts +++ b/src/actions/register.ts @@ -2,7 +2,7 @@ import { Action } from "./types"; export let actions: readonly Action[] = []; -export function register(action: Action): Action { +export const register = (action: Action): Action => { actions = actions.concat(action); return action; -} +}; diff --git a/src/appState.ts b/src/appState.ts index 901896387..506e3f347 100644 --- a/src/appState.ts +++ b/src/appState.ts @@ -6,7 +6,7 @@ import { t } from "./i18n"; export const DEFAULT_FONT = "20px Virgil"; export const DEFAULT_TEXT_ALIGN = "left"; -export function getDefaultAppState(): AppState { +export const getDefaultAppState = (): AppState => { return { isLoading: false, errorMessage: null, @@ -49,9 +49,9 @@ export function getDefaultAppState(): AppState { showShortcutsDialog: false, zenModeEnabled: false, }; -} +}; -export function clearAppStateForLocalStorage(appState: AppState) { +export const clearAppStateForLocalStorage = (appState: AppState) => { const { draggingElement, resizingElement, @@ -68,11 +68,11 @@ export function clearAppStateForLocalStorage(appState: AppState) { ...exportedState } = appState; return exportedState; -} +}; -export function clearAppStatePropertiesForHistory( +export const clearAppStatePropertiesForHistory = ( appState: AppState, -): Partial { +): Partial => { return { selectedElementIds: appState.selectedElementIds, exportBackground: appState.exportBackground, @@ -88,10 +88,10 @@ export function clearAppStatePropertiesForHistory( viewBackgroundColor: appState.viewBackgroundColor, name: appState.name, }; -} +}; -export function cleanAppStateForExport(appState: AppState) { +export const cleanAppStateForExport = (appState: AppState) => { return { viewBackgroundColor: appState.viewBackgroundColor, }; -} +}; diff --git a/src/clipboard.ts b/src/clipboard.ts index 959f861f7..31d117691 100644 --- a/src/clipboard.ts +++ b/src/clipboard.ts @@ -21,10 +21,10 @@ export const probablySupportsClipboardBlob = "ClipboardItem" in window && "toBlob" in HTMLCanvasElement.prototype; -export async function copyToAppClipboard( +export const copyToAppClipboard = async ( elements: readonly NonDeletedExcalidrawElement[], appState: AppState, -) { +) => { CLIPBOARD = JSON.stringify(getSelectedElements(elements, appState)); try { // when copying to in-app clipboard, clear system clipboard so that if @@ -38,11 +38,11 @@ export async function copyToAppClipboard( // we can't be sure of the order of copy operations PREFER_APP_CLIPBOARD = true; } -} +}; -export function getAppClipboard(): { +export const getAppClipboard = (): { elements?: readonly ExcalidrawElement[]; -} { +} => { if (!CLIPBOARD) { return {}; } @@ -62,14 +62,14 @@ export function getAppClipboard(): { } return {}; -} +}; -export async function getClipboardContent( +export const getClipboardContent = async ( event: ClipboardEvent | null, ): Promise<{ text?: string; elements?: readonly ExcalidrawElement[]; -}> { +}> => { try { const text = event ? event.clipboardData?.getData("text/plain").trim() @@ -84,12 +84,12 @@ export async function getClipboardContent( } return getAppClipboard(); -} +}; -export async function copyCanvasToClipboardAsPng(canvas: HTMLCanvasElement) { - return new Promise((resolve, reject) => { +export const copyCanvasToClipboardAsPng = async (canvas: HTMLCanvasElement) => + new Promise((resolve, reject) => { try { - canvas.toBlob(async function (blob: any) { + canvas.toBlob(async (blob: any) => { try { await navigator.clipboard.write([ new window.ClipboardItem({ "image/png": blob }), @@ -103,17 +103,16 @@ export async function copyCanvasToClipboardAsPng(canvas: HTMLCanvasElement) { reject(error); } }); -} -export async function copyCanvasToClipboardAsSvg(svgroot: SVGSVGElement) { +export const copyCanvasToClipboardAsSvg = async (svgroot: SVGSVGElement) => { try { await navigator.clipboard.writeText(svgroot.outerHTML); } catch (error) { console.error(error); } -} +}; -export async function copyTextToSystemClipboard(text: string | null) { +export const copyTextToSystemClipboard = async (text: string | null) => { let copied = false; if (probablySupportsClipboardWriteText) { try { @@ -131,10 +130,10 @@ export async function copyTextToSystemClipboard(text: string | null) { if (!copied && !copyTextViaExecCommand(text || " ")) { throw new Error("couldn't copy"); } -} +}; // adapted from https://github.com/zenorocha/clipboard.js/blob/ce79f170aa655c408b6aab33c9472e8e4fa52e19/src/clipboard-action.js#L48 -function copyTextViaExecCommand(text: string) { +const copyTextViaExecCommand = (text: string) => { const isRTL = document.documentElement.getAttribute("dir") === "rtl"; const textarea = document.createElement("textarea"); @@ -168,4 +167,4 @@ function copyTextViaExecCommand(text: string) { textarea.remove(); return success; -} +}; diff --git a/src/components/Actions.tsx b/src/components/Actions.tsx index e3313e672..c0fbf3b17 100644 --- a/src/components/Actions.tsx +++ b/src/components/Actions.tsx @@ -11,7 +11,7 @@ import Stack from "./Stack"; import useIsMobile from "../is-mobile"; import { getNonDeletedElements } from "../element"; -export function SelectedShapeActions({ +export const SelectedShapeActions = ({ appState, elements, renderAction, @@ -21,7 +21,7 @@ export function SelectedShapeActions({ elements: readonly ExcalidrawElement[]; renderAction: ActionManager["renderAction"]; elementType: ExcalidrawElement["type"]; -}) { +}) => { const targetElements = getTargetElement( getNonDeletedElements(elements), appState, @@ -83,65 +83,61 @@ export function SelectedShapeActions({ )} ); -} +}; -export function ShapesSwitcher({ +export const ShapesSwitcher = ({ elementType, setAppState, }: { elementType: ExcalidrawElement["type"]; setAppState: any; -}) { - return ( - <> - {SHAPES.map(({ value, icon, key }, index) => { - const label = t(`toolBar.${value}`); - const shortcut = `${capitalizeString(key)} ${t("shortcutsDialog.or")} ${ - index + 1 - }`; - return ( - { - setAppState({ - elementType: value, - multiElement: null, - selectedElementIds: {}, - }); - setCursorForShape(value); - setAppState({}); - }} - > - ); - })} - - ); -} +}) => ( + <> + {SHAPES.map(({ value, icon, key }, index) => { + const label = t(`toolBar.${value}`); + const shortcut = `${capitalizeString(key)} ${t("shortcutsDialog.or")} ${ + index + 1 + }`; + return ( + { + setAppState({ + elementType: value, + multiElement: null, + selectedElementIds: {}, + }); + setCursorForShape(value); + setAppState({}); + }} + > + ); + })} + +); -export function ZoomActions({ +export const ZoomActions = ({ renderAction, zoom, }: { renderAction: ActionManager["renderAction"]; zoom: number; -}) { - return ( - - - {renderAction("zoomIn")} - {renderAction("zoomOut")} - {renderAction("resetZoom")} -
{(zoom * 100).toFixed(0)}%
-
-
- ); -} +}) => ( + + + {renderAction("zoomIn")} + {renderAction("zoomOut")} + {renderAction("resetZoom")} +
{(zoom * 100).toFixed(0)}%
+
+
+); diff --git a/src/components/App.tsx b/src/components/App.tsx index d9e6cddd5..1ef7b4b0d 100644 --- a/src/components/App.tsx +++ b/src/components/App.tsx @@ -136,13 +136,14 @@ import throttle from "lodash.throttle"; /** * @param func handler taking at most single parameter (event). */ -function withBatchedUpdates< +const withBatchedUpdates = < TFunction extends ((event: any) => void) | (() => void) ->(func: Parameters["length"] extends 0 | 1 ? TFunction : never) { - return ((event) => { +>( + func: Parameters["length"] extends 0 | 1 ? TFunction : never, +) => + ((event) => { unstable_batchedUpdates(func as TFunction, event); }) as TFunction; -} const { history } = createHistory(); @@ -2748,9 +2749,7 @@ if ( }, }, history: { - get() { - return history; - }, + get: () => history, }, }); } diff --git a/src/components/ButtonSelect.tsx b/src/components/ButtonSelect.tsx index 36daad8e0..19730069f 100644 --- a/src/components/ButtonSelect.tsx +++ b/src/components/ButtonSelect.tsx @@ -1,6 +1,6 @@ import React from "react"; -export function ButtonSelect({ +export const ButtonSelect = ({ options, value, onChange, @@ -10,23 +10,21 @@ export function ButtonSelect({ value: T | null; onChange: (value: T) => void; group: string; -}) { - return ( -
- {options.map((option) => ( - - ))} -
- ); -} +}) => ( +
+ {options.map((option) => ( + + ))} +
+); diff --git a/src/components/ColorPicker.tsx b/src/components/ColorPicker.tsx index ac4aa163a..ae6e0f4ee 100644 --- a/src/components/ColorPicker.tsx +++ b/src/components/ColorPicker.tsx @@ -7,11 +7,11 @@ import { t, getLanguage } from "../i18n"; import { isWritableElement } from "../utils"; import colors from "../colors"; -function isValidColor(color: string) { +const isValidColor = (color: string) => { const style = new Option().style; style.color = color; return !!style.color; -} +}; const getColor = (color: string): string | null => { if (color === "transparent") { @@ -36,7 +36,7 @@ const keyBindings = [ ["a", "s", "d", "f", "g"], ].flat(); -const Picker = function ({ +const Picker = ({ colors, color, onChange, @@ -50,7 +50,7 @@ const Picker = function ({ onClose: () => void; label: string; showInput: boolean; -}) { +}) => { const firstItem = React.useRef(); const activeItem = React.useRef(); const gallery = React.useRef(); @@ -235,7 +235,7 @@ const ColorInput = React.forwardRef( }, ); -export function ColorPicker({ +export const ColorPicker = ({ type, color, onChange, @@ -245,7 +245,7 @@ export function ColorPicker({ color: string | null; onChange: (color: string) => void; label: string; -}) { +}) => { const [isActive, setActive] = React.useState(false); const pickerButton = React.useRef(null); @@ -296,4 +296,4 @@ export function ColorPicker({ ); -} +}; diff --git a/src/components/ContextMenu.tsx b/src/components/ContextMenu.tsx index 3fc13f30f..0bf83ca58 100644 --- a/src/components/ContextMenu.tsx +++ b/src/components/ContextMenu.tsx @@ -16,45 +16,41 @@ type Props = { left: number; }; -function ContextMenu({ options, onCloseRequest, top, left }: Props) { - return ( - ( + +
    event.preventDefault()} > -
      event.preventDefault()} - > - {options.map((option, idx) => ( -
    • - -
    • - ))} -
    - - ); -} + {options.map((option, idx) => ( +
  • + +
  • + ))} +
+
+); -function ContextMenuOption({ label, action }: ContextMenuOption) { - return ( - - ); -} +const ContextMenuOption = ({ label, action }: ContextMenuOption) => ( + +); let contextMenuNode: HTMLDivElement; -function getContextMenuNode(): HTMLDivElement { +const getContextMenuNode = (): HTMLDivElement => { if (contextMenuNode) { return contextMenuNode; } const div = document.createElement("div"); document.body.appendChild(div); return (contextMenuNode = div); -} +}; type ContextMenuParams = { options: (ContextMenuOption | false | null | undefined)[]; @@ -62,9 +58,9 @@ type ContextMenuParams = { left: number; }; -function handleClose() { +const handleClose = () => { unmountComponentAtNode(getContextMenuNode()); -} +}; export default { push(params: ContextMenuParams) { diff --git a/src/components/Dialog.tsx b/src/components/Dialog.tsx index 98f90ae6f..160254061 100644 --- a/src/components/Dialog.tsx +++ b/src/components/Dialog.tsx @@ -8,13 +8,13 @@ import { KEYS } from "../keys"; import "./Dialog.scss"; -export function Dialog(props: { +export const Dialog = (props: { children: React.ReactNode; className?: string; maxWidth?: number; onCloseRequest(): void; title: React.ReactNode; -}) { +}) => { const islandRef = useRef(null); useEffect(() => { @@ -31,7 +31,7 @@ export function Dialog(props: { return; } - function handleKeyDown(event: KeyboardEvent) { + const handleKeyDown = (event: KeyboardEvent) => { if (event.key === KEYS.TAB) { const focusableElements = queryFocusableElements(); const { activeElement } = document; @@ -50,7 +50,7 @@ export function Dialog(props: { event.preventDefault(); } } - } + }; const node = islandRef.current; node.addEventListener("keydown", handleKeyDown); @@ -58,13 +58,13 @@ export function Dialog(props: { return () => node.removeEventListener("keydown", handleKeyDown); }, []); - function queryFocusableElements() { + const queryFocusableElements = () => { const focusableElements = islandRef.current?.querySelectorAll( "button, a, input, select, textarea, div[tabindex]", ); return focusableElements ? Array.from(focusableElements) : []; - } + }; return ( ); -} +}; diff --git a/src/components/ErrorDialog.tsx b/src/components/ErrorDialog.tsx index 10ace9454..10dce46dd 100644 --- a/src/components/ErrorDialog.tsx +++ b/src/components/ErrorDialog.tsx @@ -3,13 +3,13 @@ import { t } from "../i18n"; import { Dialog } from "./Dialog"; -export function ErrorDialog({ +export const ErrorDialog = ({ message, onClose, }: { message: string; onClose?: () => void; -}) { +}) => { const [modalIsShown, setModalIsShown] = useState(!!message); const handleClose = React.useCallback(() => { @@ -33,4 +33,4 @@ export function ErrorDialog({ )} ); -} +}; diff --git a/src/components/ExportDialog.tsx b/src/components/ExportDialog.tsx index 4c9eceec9..36b243f68 100644 --- a/src/components/ExportDialog.tsx +++ b/src/components/ExportDialog.tsx @@ -24,7 +24,7 @@ export type ExportCB = ( scale?: number, ) => void; -function ExportModal({ +const ExportModal = ({ elements, appState, exportPadding = 10, @@ -43,7 +43,7 @@ function ExportModal({ onExportToClipboard: ExportCB; onExportToBackend: ExportCB; onCloseRequest: () => void; -}) { +}) => { const someElementIsSelected = isSomeElementSelected(elements, appState); const [scale, setScale] = useState(defaultScale); const [exportSelected, setExportSelected] = useState(someElementIsSelected); @@ -160,9 +160,9 @@ function ExportModal({ ); -} +}; -export function ExportDialog({ +export const ExportDialog = ({ elements, appState, exportPadding = 10, @@ -180,7 +180,7 @@ export function ExportDialog({ onExportToSvg: ExportCB; onExportToClipboard: ExportCB; onExportToBackend: ExportCB; -}) { +}) => { const [modalIsShown, setModalIsShown] = useState(false); const triggerButton = useRef(null); @@ -221,4 +221,4 @@ export function ExportDialog({ )} ); -} +}; diff --git a/src/components/FixedSideContainer.tsx b/src/components/FixedSideContainer.tsx index ba9b81dbd..d65885949 100644 --- a/src/components/FixedSideContainer.tsx +++ b/src/components/FixedSideContainer.tsx @@ -8,16 +8,14 @@ type FixedSideContainerProps = { className?: string; }; -export function FixedSideContainer({ +export const FixedSideContainer = ({ children, side, className, -}: FixedSideContainerProps) { - return ( -
- {children} -
- ); -} +}: FixedSideContainerProps) => ( +
+ {children} +
+); diff --git a/src/components/HelpIcon.tsx b/src/components/HelpIcon.tsx index d5ed178b8..d114117be 100644 --- a/src/components/HelpIcon.tsx +++ b/src/components/HelpIcon.tsx @@ -18,10 +18,8 @@ const ICON = ( ); -export function HelpIcon(props: HelpIconProps) { - return ( - - ); -} +export const HelpIcon = (props: HelpIconProps) => ( + +); diff --git a/src/components/LanguageList.tsx b/src/components/LanguageList.tsx index f7d56d0c9..00fa7bbc2 100644 --- a/src/components/LanguageList.tsx +++ b/src/components/LanguageList.tsx @@ -1,7 +1,7 @@ import React from "react"; import * as i18n from "../i18n"; -export function LanguageList({ +export const LanguageList = ({ onChange, languages = i18n.languages, currentLanguage = i18n.getLanguage().lng, @@ -11,23 +11,21 @@ export function LanguageList({ onChange: (value: string) => void; currentLanguage?: string; floating?: boolean; -}) { - return ( - - - - ); -} +}) => ( + + + +); diff --git a/src/components/LockIcon.tsx b/src/components/LockIcon.tsx index dfd329802..aac789b1e 100644 --- a/src/components/LockIcon.tsx +++ b/src/components/LockIcon.tsx @@ -40,7 +40,7 @@ const ICONS = { ), }; -export function LockIcon(props: LockIconProps) { +export const LockIcon = (props: LockIconProps) => { const sizeCn = `ToolIcon_size_${props.size || DEFAULT_SIZE}`; return ( @@ -64,4 +64,4 @@ export function LockIcon(props: LockIconProps) { ); -} +}; diff --git a/src/components/MobileMenu.tsx b/src/components/MobileMenu.tsx index 1222c5a31..85559979a 100644 --- a/src/components/MobileMenu.tsx +++ b/src/components/MobileMenu.tsx @@ -29,7 +29,7 @@ type MobileMenuProps = { onLockToggle: () => void; }; -export function MobileMenu({ +export const MobileMenu = ({ appState, elements, actionManager, @@ -39,108 +39,106 @@ export function MobileMenu({ onUsernameChange, onRoomDestroy, onLockToggle, -}: MobileMenuProps) { - return ( - <> - {appState.isLoading && } - -
- {(heading) => ( - - - - {heading} - - - - - - - - )} -
- -
-
- - {appState.openMenu === "canvas" ? ( -
-
- - {actionManager.renderAction("loadScene")} - {actionManager.renderAction("saveScene")} - {exportButton} - {actionManager.renderAction("clearCanvas")} - ( + <> + {appState.isLoading && } + +
+ {(heading) => ( + + + + {heading} + + - {actionManager.renderAction("changeViewBackgroundColor")} -
- {t("labels.language")} - { - setLanguage(lng); - setAppState({}); - }} - /> -
-
-
-
- ) : appState.openMenu === "shape" && - showSelectedShapeActions(appState, elements) ? ( -
- + + -
- ) : null} -
-
- {actionManager.renderAction("toggleCanvasMenu")} - {actionManager.renderAction("toggleEditMenu")} - {actionManager.renderAction("undo")} - {actionManager.renderAction("redo")} - {actionManager.renderAction( - appState.multiElement ? "finalize" : "duplicateSelection", - )} - {actionManager.renderAction("deleteSelectedElements")} + + + )} + + + +
+ + {appState.openMenu === "canvas" ? ( +
+
+ + {actionManager.renderAction("loadScene")} + {actionManager.renderAction("saveScene")} + {exportButton} + {actionManager.renderAction("clearCanvas")} + + {actionManager.renderAction("changeViewBackgroundColor")} +
+ {t("labels.language")} + { + setLanguage(lng); + setAppState({}); + }} + /> +
+
- {appState.scrolledOutside && ( - +
+ ) : appState.openMenu === "shape" && + showSelectedShapeActions(appState, elements) ? ( +
+ +
+ ) : null} +
+
+ {actionManager.renderAction("toggleCanvasMenu")} + {actionManager.renderAction("toggleEditMenu")} + {actionManager.renderAction("undo")} + {actionManager.renderAction("redo")} + {actionManager.renderAction( + appState.multiElement ? "finalize" : "duplicateSelection", )} -
-
-
- - ); -} + {actionManager.renderAction("deleteSelectedElements")} +
+ {appState.scrolledOutside && ( + + )} +
+
+
+ +); diff --git a/src/components/Modal.tsx b/src/components/Modal.tsx index 136a22c36..a43006def 100644 --- a/src/components/Modal.tsx +++ b/src/components/Modal.tsx @@ -4,13 +4,13 @@ import React, { useEffect, useState } from "react"; import { createPortal } from "react-dom"; import { KEYS } from "../keys"; -export function Modal(props: { +export const Modal = (props: { className?: string; children: React.ReactNode; maxWidth?: number; onCloseRequest(): void; labelledBy: string; -}) { +}) => { const modalRoot = useBodyRoot(); const handleKeydown = (event: React.KeyboardEvent) => { @@ -44,14 +44,14 @@ export function Modal(props: { , modalRoot, ); -} +}; -function useBodyRoot() { - function createDiv() { +const useBodyRoot = () => { + const createDiv = () => { const div = document.createElement("div"); document.body.appendChild(div); return div; - } + }; const [div] = useState(createDiv); useEffect(() => { return () => { @@ -59,4 +59,4 @@ function useBodyRoot() { }; }, [div]); return div; -} +}; diff --git a/src/components/Popover.tsx b/src/components/Popover.tsx index 3daec109a..41d69d019 100644 --- a/src/components/Popover.tsx +++ b/src/components/Popover.tsx @@ -10,13 +10,13 @@ type Props = { fitInViewport?: boolean; }; -export function Popover({ +export const Popover = ({ children, left, top, onCloseRequest, fitInViewport = false, -}: Props) { +}: Props) => { const popoverRef = useRef(null); // ensure the popover doesn't overflow the viewport @@ -53,4 +53,4 @@ export function Popover({ {children} ); -} +}; diff --git a/src/components/RoomDialog.tsx b/src/components/RoomDialog.tsx index 08a0a798e..bbaaacc5b 100644 --- a/src/components/RoomDialog.tsx +++ b/src/components/RoomDialog.tsx @@ -9,7 +9,7 @@ import { copyTextToSystemClipboard } from "../clipboard"; import { Dialog } from "./Dialog"; import { AppState } from "../types"; -function RoomModal({ +const RoomModal = ({ activeRoomLink, username, onUsernameChange, @@ -23,21 +23,21 @@ function RoomModal({ onRoomCreate: () => void; onRoomDestroy: () => void; onPressingEnter: () => void; -}) { +}) => { const roomLinkInput = useRef(null); - function copyRoomLink() { + const copyRoomLink = () => { copyTextToSystemClipboard(activeRoomLink); if (roomLinkInput.current) { roomLinkInput.current.select(); } - } - function selectInput(event: React.MouseEvent) { + }; + const selectInput = (event: React.MouseEvent) => { if (event.target !== document.activeElement) { event.preventDefault(); (event.target as HTMLInputElement).select(); } - } + }; return (
@@ -113,9 +113,9 @@ function RoomModal({ )}
); -} +}; -export function RoomDialog({ +export const RoomDialog = ({ isCollaborating, collaboratorCount, username, @@ -129,7 +129,7 @@ export function RoomDialog({ onUsernameChange: (username: string) => void; onRoomCreate: () => void; onRoomDestroy: () => void; -}) { +}) => { const [modalIsShown, setModalIsShown] = useState(false); const [activeRoomLink, setActiveRoomLink] = useState(""); @@ -182,4 +182,4 @@ export function RoomDialog({ )} ); -} +}; diff --git a/src/components/Section.tsx b/src/components/Section.tsx index fc1c88d5d..431bb27fd 100644 --- a/src/components/Section.tsx +++ b/src/components/Section.tsx @@ -6,7 +6,7 @@ interface SectionProps extends React.HTMLProps { children: React.ReactNode | ((header: React.ReactNode) => React.ReactNode); } -export function Section({ heading, children, ...props }: SectionProps) { +export const Section = ({ heading, children, ...props }: SectionProps) => { const header = (

{t(`headings.${heading}`)} @@ -24,4 +24,4 @@ export function Section({ heading, children, ...props }: SectionProps) { )} ); -} +}; diff --git a/src/components/Stack.tsx b/src/components/Stack.tsx index 17d14bcc3..b77aba78e 100644 --- a/src/components/Stack.tsx +++ b/src/components/Stack.tsx @@ -10,13 +10,13 @@ type StackProps = { className?: string | boolean; }; -function RowStack({ +const RowStack = ({ children, gap, align, justifyContent, className, -}: StackProps) { +}: StackProps) => { return (
); -} +}; -function ColStack({ +const ColStack = ({ children, gap, align, justifyContent, className, -}: StackProps) { +}: StackProps) => { return (
); -} +}; export default { Row: RowStack, diff --git a/src/components/ToolButton.tsx b/src/components/ToolButton.tsx index b46483200..3c0b8327f 100644 --- a/src/components/ToolButton.tsx +++ b/src/components/ToolButton.tsx @@ -36,10 +36,7 @@ type ToolButtonProps = const DEFAULT_SIZE: ToolIconSize = "m"; -export const ToolButton = React.forwardRef(function ( - props: ToolButtonProps, - ref, -) { +export const ToolButton = React.forwardRef((props: ToolButtonProps, ref) => { const innerRef = React.useRef(null); React.useImperativeHandle(ref, () => innerRef.current); const sizeCn = `ToolIcon_size_${props.size || DEFAULT_SIZE}`; diff --git a/src/data/blob.ts b/src/data/blob.ts index 7d36208aa..4d770e8cd 100644 --- a/src/data/blob.ts +++ b/src/data/blob.ts @@ -2,7 +2,7 @@ import { getDefaultAppState } from "../appState"; import { restore } from "./restore"; import { t } from "../i18n"; -export async function loadFromBlob(blob: any) { +export const loadFromBlob = async (blob: any) => { const updateAppState = (contents: string) => { const defaultAppState = getDefaultAppState(); let elements = []; @@ -40,4 +40,4 @@ export async function loadFromBlob(blob: any) { const { elements, appState } = updateAppState(contents); return restore(elements, appState, { scrollToContent: true }); -} +}; diff --git a/src/data/index.ts b/src/data/index.ts index 4779a8100..6ce40c0d7 100644 --- a/src/data/index.ts +++ b/src/data/index.ts @@ -72,17 +72,15 @@ export type SocketUpdateDataIncoming = // part of `AppState`. (window as any).handle = null; -function byteToHex(byte: number): string { - return `0${byte.toString(16)}`.slice(-2); -} +const byteToHex = (byte: number): string => `0${byte.toString(16)}`.slice(-2); -async function generateRandomID() { +const generateRandomID = async () => { const arr = new Uint8Array(10); window.crypto.getRandomValues(arr); return Array.from(arr, byteToHex).join(""); -} +}; -async function generateEncryptionKey() { +const generateEncryptionKey = async () => { const key = await window.crypto.subtle.generateKey( { name: "AES-GCM", @@ -92,29 +90,29 @@ async function generateEncryptionKey() { ["encrypt", "decrypt"], ); return (await window.crypto.subtle.exportKey("jwk", key)).k; -} +}; -function createIV() { +const createIV = () => { const arr = new Uint8Array(12); return window.crypto.getRandomValues(arr); -} +}; -export function getCollaborationLinkData(link: string) { +export const getCollaborationLinkData = (link: string) => { if (link.length === 0) { return; } const hash = new URL(link).hash; return hash.match(/^#room=([a-zA-Z0-9_-]+),([a-zA-Z0-9_-]+)$/); -} +}; -export async function generateCollaborationLink() { +export const generateCollaborationLink = async () => { const id = await generateRandomID(); const key = await generateEncryptionKey(); return `${window.location.origin}${window.location.pathname}#room=${id},${key}`; -} +}; -function getImportedKey(key: string, usage: string) { - return window.crypto.subtle.importKey( +const getImportedKey = (key: string, usage: string) => + window.crypto.subtle.importKey( "jwk", { alg: "A128GCM", @@ -130,12 +128,11 @@ function getImportedKey(key: string, usage: string) { false, // extractable [usage], ); -} -export async function encryptAESGEM( +export const encryptAESGEM = async ( data: Uint8Array, key: string, -): Promise { +): Promise => { const importedKey = await getImportedKey(key, "encrypt"); const iv = createIV(); return { @@ -149,13 +146,13 @@ export async function encryptAESGEM( ), iv, }; -} +}; -export async function decryptAESGEM( +export const decryptAESGEM = async ( data: ArrayBuffer, key: string, iv: Uint8Array, -): Promise { +): Promise => { try { const importedKey = await getImportedKey(key, "decrypt"); const decrypted = await window.crypto.subtle.decrypt( @@ -178,12 +175,12 @@ export async function decryptAESGEM( return { type: "INVALID_RESPONSE", }; -} +}; -export async function exportToBackend( +export const exportToBackend = async ( elements: readonly ExcalidrawElement[], appState: AppState, -) { +) => { const json = serializeAsJSON(elements, appState); const encoded = new TextEncoder().encode(json); @@ -233,12 +230,12 @@ export async function exportToBackend( console.error(error); window.alert(t("alerts.couldNotCreateShareableLink")); } -} +}; -export async function importFromBackend( +export const importFromBackend = async ( id: string | null, privateKey: string | undefined, -) { +) => { let elements: readonly ExcalidrawElement[] = []; let appState: AppState = getDefaultAppState(); @@ -281,9 +278,9 @@ export async function importFromBackend( } finally { return restore(elements, appState, { scrollToContent: true }); } -} +}; -export async function exportCanvas( +export const exportCanvas = async ( type: ExportType, elements: readonly NonDeletedExcalidrawElement[], appState: AppState, @@ -303,7 +300,7 @@ export async function exportCanvas( scale?: number; shouldAddWatermark: boolean; }, -) { +) => { if (elements.length === 0) { return window.alert(t("alerts.cannotExportEmptyCanvas")); } @@ -362,9 +359,9 @@ export async function exportCanvas( if (tempCanvas !== canvas) { tempCanvas.remove(); } -} +}; -export async function loadScene(id: string | null, privateKey?: string) { +export const loadScene = async (id: string | null, privateKey?: string) => { let data; if (id != null) { // the private key is used to decrypt the content from the server, take @@ -380,4 +377,4 @@ export async function loadScene(id: string | null, privateKey?: string) { appState: data.appState && { ...data.appState }, commitToHistory: false, }; -} +}; diff --git a/src/data/json.ts b/src/data/json.ts index cb657e6b2..f005c043f 100644 --- a/src/data/json.ts +++ b/src/data/json.ts @@ -5,11 +5,11 @@ import { cleanAppStateForExport } from "../appState"; import { fileOpen, fileSave } from "browser-nativefs"; import { loadFromBlob } from "./blob"; -export function serializeAsJSON( +export const serializeAsJSON = ( elements: readonly ExcalidrawElement[], appState: AppState, -): string { - return JSON.stringify( +): string => + JSON.stringify( { type: "excalidraw", version: 1, @@ -20,12 +20,11 @@ export function serializeAsJSON( null, 2, ); -} -export async function saveAsJSON( +export const saveAsJSON = async ( elements: readonly ExcalidrawElement[], appState: AppState, -) { +) => { const serialized = serializeAsJSON(elements, appState); const name = `${appState.name}.excalidraw`; @@ -41,12 +40,12 @@ export async function saveAsJSON( }, (window as any).handle, ); -} -export async function loadFromJSON() { +}; +export const loadFromJSON = async () => { const blob = await fileOpen({ description: "Excalidraw files", extensions: ["json", "excalidraw"], mimeTypes: ["application/json", "application/vnd.excalidraw+json"], }); return loadFromBlob(blob); -} +}; diff --git a/src/data/localStorage.ts b/src/data/localStorage.ts index 2f8e807d6..ce52cede8 100644 --- a/src/data/localStorage.ts +++ b/src/data/localStorage.ts @@ -7,7 +7,7 @@ const LOCAL_STORAGE_KEY = "excalidraw"; const LOCAL_STORAGE_KEY_STATE = "excalidraw-state"; const LOCAL_STORAGE_KEY_COLLAB = "excalidraw-collab"; -export function saveUsernameToLocalStorage(username: string) { +export const saveUsernameToLocalStorage = (username: string) => { try { localStorage.setItem( LOCAL_STORAGE_KEY_COLLAB, @@ -17,9 +17,9 @@ export function saveUsernameToLocalStorage(username: string) { // Unable to access window.localStorage console.error(error); } -} +}; -export function restoreUsernameFromLocalStorage(): string | null { +export const restoreUsernameFromLocalStorage = (): string | null => { try { const data = localStorage.getItem(LOCAL_STORAGE_KEY_COLLAB); if (data) { @@ -31,12 +31,12 @@ export function restoreUsernameFromLocalStorage(): string | null { } return null; -} +}; -export function saveToLocalStorage( +export const saveToLocalStorage = ( elements: readonly ExcalidrawElement[], appState: AppState, -) { +) => { try { localStorage.setItem( LOCAL_STORAGE_KEY, @@ -50,9 +50,9 @@ export function saveToLocalStorage( // Unable to access window.localStorage console.error(error); } -} +}; -export function restoreFromLocalStorage() { +export const restoreFromLocalStorage = () => { let savedElements = null; let savedState = null; @@ -86,4 +86,4 @@ export function restoreFromLocalStorage() { } return restore(elements, appState); -} +}; diff --git a/src/data/restore.ts b/src/data/restore.ts index 1f98c8bc4..ebcfc6049 100644 --- a/src/data/restore.ts +++ b/src/data/restore.ts @@ -12,13 +12,13 @@ import { calculateScrollCenter } from "../scene"; import { randomId } from "../random"; import { DEFAULT_TEXT_ALIGN } from "../appState"; -export function restore( +export const restore = ( // we're making the elements mutable for this API because we want to // efficiently remove/tweak properties on them (to migrate old scenes) savedElements: readonly Mutable[], savedState: AppState | null, opts?: { scrollToContent: boolean }, -): DataState { +): DataState => { const elements = savedElements .filter((el) => { // filtering out selection, which is legacy, no longer kept in elements, @@ -94,4 +94,4 @@ export function restore( elements: elements, appState: savedState, }; -} +}; diff --git a/src/element/bounds.ts b/src/element/bounds.ts index b77055cf6..7e3499d63 100644 --- a/src/element/bounds.ts +++ b/src/element/bounds.ts @@ -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, diff --git a/src/element/collision.ts b/src/element/collision.ts index b49a5188d..3d5d47b5e 100644 --- a/src/element/collision.ts +++ b/src/element/collision.ts @@ -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, diff --git a/src/element/handlerRectangles.ts b/src/element/handlerRectangles.ts index 2b222ab92..0798c9edf 100644 --- a/src/element/handlerRectangles.ts +++ b/src/element/handlerRectangles.ts @@ -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; -} +}; diff --git a/src/element/index.ts b/src/element/index.ts index 7cecd0104..77f1ea705 100644 --- a/src/element/index.ts +++ b/src/element/index.ts @@ -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( +export const isNonDeletedElement = ( element: T, -): element is NonDeleted { - return !element.isDeleted; -} +): element is NonDeleted => !element.isDeleted; diff --git a/src/element/mutateElement.ts b/src/element/mutateElement.ts index 400e06ab3..2f15687f4 100644 --- a/src/element/mutateElement.ts +++ b/src/element/mutateElement.ts @@ -13,10 +13,10 @@ type ElementUpdate = 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>( +export const mutateElement = >( element: TElement, updates: ElementUpdate, -) { +) => { // 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>( element.versionNonce = randomInteger(); globalSceneState.informMutation(); -} +}; -export function newElementWith( +export const newElementWith = ( element: TElement, updates: ElementUpdate, -): TElement { - return { - ...element, - version: element.version + 1, - versionNonce: randomInteger(), - ...updates, - }; -} +): TElement => ({ + ...element, + version: element.version + 1, + versionNonce: randomInteger(), + ...updates, +}); diff --git a/src/element/newElement.test.ts b/src/element/newElement.test.ts index a04497ae2..51b798369 100644 --- a/src/element/newElement.test.ts +++ b/src/element/newElement.test.ts @@ -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({ diff --git a/src/element/newElement.ts b/src/element/newElement.ts index 69cf3c0db..4c00c2917 100644 --- a/src/element/newElement.ts +++ b/src/element/newElement.ts @@ -25,7 +25,7 @@ type ElementConstructorOpts = { angle?: ExcalidrawGenericElement["angle"]; }; -function _newElementBase( +const _newElementBase = ( type: T["type"], { x, @@ -42,44 +42,41 @@ function _newElementBase( angle = 0, ...rest }: ElementConstructorOpts & Partial, -) { - 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 { - return _newElementBase(opts.type, opts); -} +): NonDeleted => + _newElementBase(opts.type, opts); -export function newTextElement( +export const newTextElement = ( opts: { text: string; font: string; textAlign: TextAlign; } & ElementConstructorOpts, -): NonDeleted { +): NonDeleted => { 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 { +): NonDeleted => { return { ..._newElementBase(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>( +export const duplicateElement = >( element: TElement, overrides?: Partial, -): TElement { +): TElement => { let copy: TElement = _duplicateElement(element); copy.id = randomId(); copy.seed = randomInteger(); @@ -162,4 +159,4 @@ export function duplicateElement>( copy = Object.assign(copy, overrides); } return copy; -} +}; diff --git a/src/element/resizeTest.ts b/src/element/resizeTest.ts index e7921314c..6f151f270 100644 --- a/src/element/resizeTest.ts +++ b/src/element/resizeTest.ts @@ -13,27 +13,24 @@ import { AppState } from "../types"; type HandlerRectanglesRet = keyof ReturnType; -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 } | 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; -}): 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; -} +}; diff --git a/src/element/sizeHelpers.ts b/src/element/sizeHelpers.ts index b8f10a452..08d615e7e 100644 --- a/src/element/sizeHelpers.ts +++ b/src/element/sizeHelpers.ts @@ -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; -} +}; diff --git a/src/element/textWysiwyg.tsx b/src/element/textWysiwyg.tsx index 394359cb6..3942bf003 100644 --- a/src/element/textWysiwyg.tsx +++ b/src/element/textWysiwyg.tsx @@ -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); -} +}; diff --git a/src/element/typeChecks.ts b/src/element/typeChecks.ts index 8d1fdce37..10a40e8b3 100644 --- a/src/element/typeChecks.ts +++ b/src/element/typeChecks.ts @@ -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" ); -} +}; diff --git a/src/gesture.ts b/src/gesture.ts index 66679a15c..66b7ef485 100644 --- a/src/gesture.ts +++ b/src/gesture.ts @@ -1,18 +1,16 @@ import { PointerCoords } from "./types"; import { normalizeScroll } from "./scene"; -export function getCenter(pointers: Map) { +export const getCenter = (pointers: Map) => { const allCoords = Array.from(pointers.values()); return { x: normalizeScroll(sum(allCoords, (coords) => coords.x) / allCoords.length), y: normalizeScroll(sum(allCoords, (coords) => coords.y) / allCoords.length), }; -} +}; -export function getDistance([a, b]: readonly PointerCoords[]) { - return Math.hypot(a.x - b.x, a.y - b.y); -} +export const getDistance = ([a, b]: readonly PointerCoords[]) => + Math.hypot(a.x - b.x, a.y - b.y); -function sum(array: readonly T[], mapper: (item: T) => number): number { - return array.reduce((acc, item) => acc + mapper(item), 0); -} +const sum = (array: readonly T[], mapper: (item: T) => number): number => + array.reduce((acc, item) => acc + mapper(item), 0); diff --git a/src/history.ts b/src/history.ts index bccf3bf3c..6fd8b0be7 100644 --- a/src/history.ts +++ b/src/history.ts @@ -27,11 +27,11 @@ export class SceneHistory { this.redoStack.length = 0; } - private generateEntry( + private generateEntry = ( appState: AppState, elements: readonly ExcalidrawElement[], - ) { - return JSON.stringify({ + ) => + JSON.stringify({ appState: clearAppStatePropertiesForHistory(appState), elements: elements.reduce((elements, element) => { if ( @@ -69,7 +69,6 @@ export class SceneHistory { return elements; }, [] as Mutable), }); - } pushEntry(appState: AppState, elements: readonly ExcalidrawElement[]) { const newEntry = this.generateEntry(appState, elements); diff --git a/src/i18n.ts b/src/i18n.ts index 0a99c50e7..53dcefa3e 100644 --- a/src/i18n.ts +++ b/src/i18n.ts @@ -43,20 +43,18 @@ export const languages = [ let currentLanguage = languages[0]; const fallbackLanguage = languages[0]; -export function setLanguage(newLng: string | undefined) { +export const setLanguage = (newLng: string | undefined) => { currentLanguage = languages.find((language) => language.lng === newLng) || fallbackLanguage; document.documentElement.dir = currentLanguage.rtl ? "rtl" : "ltr"; languageDetector.cacheUserLanguage(currentLanguage.lng); -} +}; -export function getLanguage() { - return currentLanguage; -} +export const getLanguage = () => currentLanguage; -function findPartsForData(data: any, parts: string[]) { +const findPartsForData = (data: any, parts: string[]) => { for (var i = 0; i < parts.length; ++i) { const part = parts[i]; if (data[part] === undefined) { @@ -68,9 +66,9 @@ function findPartsForData(data: any, parts: string[]) { return undefined; } return data; -} +}; -export function t(path: string, replacement?: { [key: string]: string }) { +export const t = (path: string, replacement?: { [key: string]: string }) => { const parts = path.split("."); let translation = findPartsForData(currentLanguage.data, parts) || @@ -85,14 +83,12 @@ export function t(path: string, replacement?: { [key: string]: string }) { } } return translation; -} +}; const languageDetector = new LanguageDetector(); languageDetector.init({ languageUtils: { - formatLanguageCode: function (lng: string) { - return lng; - }, + formatLanguageCode: (lng: string) => lng, isWhitelisted: () => true, }, checkWhitelist: false, diff --git a/src/index.tsx b/src/index.tsx index 00f518bda..265580ff5 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -50,7 +50,7 @@ Sentry.init({ // Block pinch-zooming on iOS outside of the content area document.addEventListener( "touchmove", - function (event) { + (event) => { // @ts-ignore if (event.scale !== 1) { event.preventDefault(); diff --git a/src/is-mobile.tsx b/src/is-mobile.tsx index db3a6516b..9a8d5fe2b 100644 --- a/src/is-mobile.tsx +++ b/src/is-mobile.tsx @@ -2,7 +2,11 @@ import React, { useState, useEffect, useRef, useContext } from "react"; const context = React.createContext(false); -export function IsMobileProvider({ children }: { children: React.ReactNode }) { +export const IsMobileProvider = ({ + children, +}: { + children: React.ReactNode; +}) => { const query = useRef(); if (!query.current) { query.current = window.matchMedia @@ -24,7 +28,7 @@ export function IsMobileProvider({ children }: { children: React.ReactNode }) { }, []); return {children}; -} +}; export default function useIsMobile() { return useContext(context); diff --git a/src/keys.ts b/src/keys.ts index 2cf5e04ba..64d2eb5d3 100644 --- a/src/keys.ts +++ b/src/keys.ts @@ -20,16 +20,14 @@ export const KEYS = { export type Key = keyof typeof KEYS; -export function isArrowKey(keyCode: string) { - return ( - keyCode === KEYS.ARROW_LEFT || - keyCode === KEYS.ARROW_RIGHT || - keyCode === KEYS.ARROW_DOWN || - keyCode === KEYS.ARROW_UP - ); -} +export const isArrowKey = (keyCode: string) => + keyCode === KEYS.ARROW_LEFT || + keyCode === KEYS.ARROW_RIGHT || + keyCode === KEYS.ARROW_DOWN || + keyCode === KEYS.ARROW_UP; export const getResizeCenterPointKey = (event: MouseEvent | KeyboardEvent) => event.altKey || event.which === KEYS.ALT_KEY_CODE; + export const getResizeWithSidesSameLengthKey = (event: MouseEvent) => event.shiftKey; diff --git a/src/math.ts b/src/math.ts index 08da7405a..cc51630dc 100644 --- a/src/math.ts +++ b/src/math.ts @@ -2,14 +2,14 @@ import { Point } from "./types"; import { LINE_CONFIRM_THRESHOLD } from "./constants"; // https://stackoverflow.com/a/6853926/232122 -export function distanceBetweenPointAndSegment( +export const distanceBetweenPointAndSegment = ( x: number, y: number, x1: number, y1: number, x2: number, y2: number, -) { +) => { const A = x - x1; const B = y - y1; const C = x2 - x1; @@ -38,23 +38,22 @@ export function distanceBetweenPointAndSegment( const dx = x - xx; const dy = y - yy; return Math.hypot(dx, dy); -} +}; -export function rotate( +export const rotate = ( x1: number, y1: number, x2: number, y2: number, angle: number, -): [number, number] { +): [number, number] => // 𝑎′𝑥=(𝑎𝑥−𝑐𝑥)cos𝜃−(𝑎𝑦−𝑐𝑦)sin𝜃+𝑐𝑥 // 𝑎′𝑦=(𝑎𝑥−𝑐𝑥)sin𝜃+(𝑎𝑦−𝑐𝑦)cos𝜃+𝑐𝑦. // https://math.stackexchange.com/questions/2204520/how-do-i-rotate-a-line-segment-in-a-specific-point-on-the-line - return [ + [ (x1 - x2) * Math.cos(angle) - (y1 - y2) * Math.sin(angle) + x2, (x1 - x2) * Math.sin(angle) + (y1 - y2) * Math.cos(angle) + y2, ]; -} export const adjustXYWithRotation = ( side: "n" | "s" | "w" | "e" | "nw" | "ne" | "sw" | "se", @@ -233,15 +232,15 @@ export const getPointOnAPath = (point: Point, path: Point[]) => { return null; }; -export function distance2d(x1: number, y1: number, x2: number, y2: number) { +export const distance2d = (x1: number, y1: number, x2: number, y2: number) => { const xd = x2 - x1; const yd = y2 - y1; return Math.hypot(xd, yd); -} +}; // Checks if the first and last point are close enough // to be considered a loop -export function isPathALoop(points: Point[]): boolean { +export const isPathALoop = (points: Point[]): boolean => { if (points.length >= 3) { const [firstPoint, lastPoint] = [points[0], points[points.length - 1]]; return ( @@ -250,16 +249,16 @@ export function isPathALoop(points: Point[]): boolean { ); } return false; -} +}; // Draw a line from the point to the right till infiinty // Check how many lines of the polygon does this infinite line intersects with // If the number of intersections is odd, point is in the polygon -export function isPointInPolygon( +export const isPointInPolygon = ( points: Point[], x: number, y: number, -): boolean { +): boolean => { const vertices = points.length; // There must be at least 3 vertices in polygon @@ -281,32 +280,32 @@ export function isPointInPolygon( } // true if count is off return count % 2 === 1; -} +}; // Check if q lies on the line segment pr -function onSegment(p: Point, q: Point, r: Point) { +const onSegment = (p: Point, q: Point, r: Point) => { return ( q[0] <= Math.max(p[0], r[0]) && q[0] >= Math.min(p[0], r[0]) && q[1] <= Math.max(p[1], r[1]) && q[1] >= Math.min(p[1], r[1]) ); -} +}; // For the ordered points p, q, r, return // 0 if p, q, r are collinear // 1 if Clockwise // 2 if counterclickwise -function orientation(p: Point, q: Point, r: Point) { +const orientation = (p: Point, q: Point, r: Point) => { const val = (q[1] - p[1]) * (r[0] - q[0]) - (q[0] - p[0]) * (r[1] - q[1]); if (val === 0) { return 0; } return val > 0 ? 1 : 2; -} +}; // Check is p1q1 intersects with p2q2 -function doIntersect(p1: Point, q1: Point, p2: Point, q2: Point) { +const doIntersect = (p1: Point, q1: Point, p2: Point, q2: Point) => { const o1 = orientation(p1, q1, p2); const o2 = orientation(p1, q1, q2); const o3 = orientation(p2, q2, p1); @@ -337,4 +336,4 @@ function doIntersect(p1: Point, q1: Point, p2: Point, q2: Point) { } return false; -} +}; diff --git a/src/points.ts b/src/points.ts index 451a0df9c..f6f3c9006 100644 --- a/src/points.ts +++ b/src/points.ts @@ -1,18 +1,18 @@ import { Point } from "./types"; -export function getSizeFromPoints(points: readonly Point[]) { +export const getSizeFromPoints = (points: readonly Point[]) => { const xs = points.map((point) => point[0]); const ys = points.map((point) => point[1]); return { width: Math.max(...xs) - Math.min(...xs), height: Math.max(...ys) - Math.min(...ys), }; -} -export function rescalePoints( +}; +export const rescalePoints = ( dimension: 0 | 1, nextDimensionSize: number, prevPoints: readonly Point[], -): Point[] { +): Point[] => { const prevDimValues = prevPoints.map((point) => point[dimension]); const prevMaxDimension = Math.max(...prevDimValues); const prevMinDimension = Math.min(...prevDimValues); @@ -50,4 +50,4 @@ export function rescalePoints( ); return nextPoints; -} +}; diff --git a/src/random.ts b/src/random.ts index f9d070fad..b0c8b7a08 100644 --- a/src/random.ts +++ b/src/random.ts @@ -4,15 +4,12 @@ import nanoid from "nanoid"; let random = new Random(Date.now()); let testIdBase = 0; -export function randomInteger() { - return Math.floor(random.next() * 2 ** 31); -} +export const randomInteger = () => Math.floor(random.next() * 2 ** 31); -export function reseed(seed: number) { +export const reseed = (seed: number) => { random = new Random(seed); testIdBase = 0; -} +}; -export function randomId() { - return process.env.NODE_ENV === "test" ? `id${testIdBase++}` : nanoid(); -} +export const randomId = () => + process.env.NODE_ENV === "test" ? `id${testIdBase++}` : nanoid(); diff --git a/src/renderer/renderElement.ts b/src/renderer/renderElement.ts index 2af0ca04a..836fa871d 100644 --- a/src/renderer/renderElement.ts +++ b/src/renderer/renderElement.ts @@ -31,10 +31,10 @@ export interface ExcalidrawElementWithCanvas { canvasOffsetY: number; } -function generateElementCanvas( +const generateElementCanvas = ( element: NonDeletedExcalidrawElement, zoom: number, -): ExcalidrawElementWithCanvas { +): ExcalidrawElementWithCanvas => { const canvas = document.createElement("canvas"); const context = canvas.getContext("2d")!; @@ -75,13 +75,13 @@ function generateElementCanvas( 1 / (window.devicePixelRatio * zoom), ); return { element, canvas, canvasZoom: zoom, canvasOffsetX, canvasOffsetY }; -} +}; -function drawElementOnCanvas( +const drawElementOnCanvas = ( element: NonDeletedExcalidrawElement, rc: RoughCanvas, context: CanvasRenderingContext2D, -) { +) => { context.globalAlpha = element.opacity / 100; switch (element.type) { case "rectangle": @@ -132,7 +132,7 @@ function drawElementOnCanvas( } } context.globalAlpha = 1; -} +}; const elementWithCanvasCache = new WeakMap< ExcalidrawElement, @@ -144,15 +144,13 @@ const shapeCache = new WeakMap< Drawable | Drawable[] | null >(); -export function getShapeForElement(element: ExcalidrawElement) { - return shapeCache.get(element); -} +export const getShapeForElement = (element: ExcalidrawElement) => + shapeCache.get(element); -export function invalidateShapeForElement(element: ExcalidrawElement) { +export const invalidateShapeForElement = (element: ExcalidrawElement) => shapeCache.delete(element); -} -export function generateRoughOptions(element: ExcalidrawElement): Options { +export const generateRoughOptions = (element: ExcalidrawElement): Options => { const options: Options = { seed: element.seed, strokeLineDash: @@ -214,13 +212,13 @@ export function generateRoughOptions(element: ExcalidrawElement): Options { throw new Error(`Unimplemented type ${element.type}`); } } -} +}; -function generateElement( +const generateElement = ( element: NonDeletedExcalidrawElement, generator: RoughGenerator, sceneState?: SceneState, -) { +) => { let shape = shapeCache.get(element) || null; if (!shape) { elementWithCanvasCache.delete(element); @@ -319,14 +317,14 @@ function generateElement( return elementWithCanvas; } return prevElementWithCanvas; -} +}; -function drawElementFromCanvas( +const drawElementFromCanvas = ( elementWithCanvas: ExcalidrawElementWithCanvas, rc: RoughCanvas, context: CanvasRenderingContext2D, sceneState: SceneState, -) { +) => { const element = elementWithCanvas.element; const [x1, y1, x2, y2] = getElementAbsoluteCoords(element); const cx = ((x1 + x2) / 2 + sceneState.scrollX) * window.devicePixelRatio; @@ -346,15 +344,15 @@ function drawElementFromCanvas( context.rotate(-element.angle); context.translate(-cx, -cy); context.scale(window.devicePixelRatio, window.devicePixelRatio); -} +}; -export function renderElement( +export const renderElement = ( element: NonDeletedExcalidrawElement, rc: RoughCanvas, context: CanvasRenderingContext2D, renderOptimizations: boolean, sceneState: SceneState, -) { +) => { const generator = rc.generator; switch (element.type) { case "selection": { @@ -404,15 +402,15 @@ export function renderElement( throw new Error(`Unimplemented type ${element.type}`); } } -} +}; -export function renderElementToSvg( +export const renderElementToSvg = ( element: NonDeletedExcalidrawElement, rsvg: RoughSVG, svgRoot: SVGElement, offsetX?: number, offsetY?: number, -) { +) => { const [x1, y1, x2, y2] = getElementAbsoluteCoords(element); const cx = (x2 - x1) / 2 - (element.x - x1); const cy = (y2 - y1) / 2 - (element.y - y1); @@ -528,4 +526,4 @@ export function renderElementToSvg( } } } -} +}; diff --git a/src/renderer/renderScene.ts b/src/renderer/renderScene.ts index 541759d4a..752a864b8 100644 --- a/src/renderer/renderScene.ts +++ b/src/renderer/renderScene.ts @@ -30,7 +30,7 @@ import colors from "../colors"; type HandlerRectanglesRet = keyof ReturnType; -function colorsForClientId(clientId: string) { +const colorsForClientId = (clientId: string) => { // Naive way of getting an integer out of the clientId const sum = clientId.split("").reduce((a, str) => a + str.charCodeAt(0), 0); @@ -41,9 +41,9 @@ function colorsForClientId(clientId: string) { background: backgrounds[sum % backgrounds.length], stroke: strokes[sum % strokes.length], }; -} +}; -function strokeRectWithRotation( +const strokeRectWithRotation = ( context: CanvasRenderingContext2D, x: number, y: number, @@ -53,7 +53,7 @@ function strokeRectWithRotation( cy: number, angle: number, fill?: boolean, -) { +) => { context.translate(cx, cy); context.rotate(angle); if (fill) { @@ -62,22 +62,22 @@ function strokeRectWithRotation( context.strokeRect(x - cx, y - cy, width, height); context.rotate(-angle); context.translate(-cx, -cy); -} +}; -function strokeCircle( +const strokeCircle = ( context: CanvasRenderingContext2D, x: number, y: number, width: number, height: number, -) { +) => { context.beginPath(); context.arc(x + width / 2, y + height / 2, width / 2, 0, Math.PI * 2); context.fill(); context.stroke(); -} +}; -export function renderScene( +export const renderScene = ( elements: readonly NonDeletedExcalidrawElement[], appState: AppState, selectionElement: NonDeletedExcalidrawElement | null, @@ -98,7 +98,7 @@ export function renderScene( renderSelection?: boolean; renderOptimizations?: boolean; } = {}, -) { +) => { if (!canvas) { return { atLeastOneVisibleElement: false }; } @@ -461,9 +461,9 @@ export function renderScene( context.scale(1 / scale, 1 / scale); return { atLeastOneVisibleElement: visibleElements.length > 0, scrollBars }; -} +}; -function isVisibleElement( +const isVisibleElement = ( element: ExcalidrawElement, viewportWidth: number, viewportHeight: number, @@ -476,7 +476,7 @@ function isVisibleElement( scrollY: FlooredNumber; zoom: number; }, -) { +) => { const [x1, y1, x2, y2] = getElementAbsoluteCoords(element); // Apply zoom @@ -492,10 +492,10 @@ function isVisibleElement( y2 + scrollY - viewportHeightDiff / 2 >= 0 && y1 + scrollY - viewportHeightDiff / 2 <= viewportHeightWithZoom ); -} +}; // This should be only called for exporting purposes -export function renderSceneToSvg( +export const renderSceneToSvg = ( elements: readonly NonDeletedExcalidrawElement[], rsvg: RoughSVG, svgRoot: SVGElement, @@ -506,7 +506,7 @@ export function renderSceneToSvg( offsetX?: number; offsetY?: number; } = {}, -) { +) => { if (!svgRoot) { return; } @@ -522,4 +522,4 @@ export function renderSceneToSvg( ); } }); -} +}; diff --git a/src/renderer/roundRect.ts b/src/renderer/roundRect.ts index 14bf4367d..be842a522 100644 --- a/src/renderer/roundRect.ts +++ b/src/renderer/roundRect.ts @@ -8,14 +8,14 @@ * @param {Number} height The height of the rectangle * @param {Number} radius The corner radius */ -export function roundRect( +export const roundRect = ( context: CanvasRenderingContext2D, x: number, y: number, width: number, height: number, radius: number, -) { +) => { context.beginPath(); context.moveTo(x + radius, y); context.lineTo(x + width - radius, y); @@ -34,4 +34,4 @@ export function roundRect( context.closePath(); context.fill(); context.stroke(); -} +}; diff --git a/src/scene/comparisons.ts b/src/scene/comparisons.ts index 2f221693e..3ab66e24f 100644 --- a/src/scene/comparisons.ts +++ b/src/scene/comparisons.ts @@ -23,13 +23,13 @@ export const hasStroke = (type: string) => export const hasText = (type: string) => type === "text"; -export function getElementAtPosition( +export const getElementAtPosition = ( elements: readonly NonDeletedExcalidrawElement[], appState: AppState, x: number, y: number, zoom: number, -) { +) => { let hitElement = null; // We need to to hit testing from front (end of the array) to back (beginning of the array) for (let i = elements.length - 1; i >= 0; --i) { @@ -43,13 +43,13 @@ export function getElementAtPosition( } return hitElement; -} +}; -export function getElementContainingPosition( +export const getElementContainingPosition = ( elements: readonly ExcalidrawElement[], x: number, y: number, -) { +) => { let hitElement = null; // We need to to hit testing from front (end of the array) to back (beginning of the array) for (let i = elements.length - 1; i >= 0; --i) { @@ -63,4 +63,4 @@ export function getElementContainingPosition( } } return hitElement; -} +}; diff --git a/src/scene/export.ts b/src/scene/export.ts index 33ad8bc66..4facda45f 100644 --- a/src/scene/export.ts +++ b/src/scene/export.ts @@ -11,7 +11,7 @@ import { t } from "../i18n"; export const SVG_EXPORT_TAG = ``; -export function exportToCanvas( +export const exportToCanvas = ( elements: readonly NonDeletedExcalidrawElement[], appState: AppState, { @@ -27,16 +27,13 @@ export function exportToCanvas( viewBackgroundColor: string; shouldAddWatermark: boolean; }, - createCanvas: (width: number, height: number) => any = function ( - width, - height, - ) { + createCanvas: (width: number, height: number) => any = (width, height) => { const tempCanvas = document.createElement("canvas"); tempCanvas.width = width * scale; tempCanvas.height = height * scale; return tempCanvas; }, -) { +) => { let sceneElements = elements; if (shouldAddWatermark) { const [, , maxX, maxY] = getCommonBounds(elements); @@ -78,9 +75,9 @@ export function exportToCanvas( ); return tempCanvas; -} +}; -export function exportToSvg( +export const exportToSvg = ( elements: readonly NonDeletedExcalidrawElement[], { exportBackground, @@ -93,7 +90,7 @@ export function exportToSvg( viewBackgroundColor: string; shouldAddWatermark: boolean; }, -): SVGSVGElement { +): SVGSVGElement => { let sceneElements = elements; if (shouldAddWatermark) { const [, , maxX, maxY] = getCommonBounds(elements); @@ -148,9 +145,9 @@ export function exportToSvg( }); return svgRoot; -} +}; -function getWatermarkElement(maxX: number, maxY: number) { +const getWatermarkElement = (maxX: number, maxY: number) => { const text = t("labels.madeWithExcalidraw"); const font = "16px Virgil"; const { width: textWidth } = measureText(text, font); @@ -169,4 +166,4 @@ function getWatermarkElement(maxX: number, maxY: number) { roughness: 1, opacity: 100, }); -} +}; diff --git a/src/scene/scroll.ts b/src/scene/scroll.ts index e77c00faf..43d4a42d7 100644 --- a/src/scene/scroll.ts +++ b/src/scene/scroll.ts @@ -2,13 +2,12 @@ import { FlooredNumber } from "../types"; import { ExcalidrawElement } from "../element/types"; import { getCommonBounds } from "../element"; -export function normalizeScroll(pos: number) { - return Math.floor(pos) as FlooredNumber; -} +export const normalizeScroll = (pos: number) => + Math.floor(pos) as FlooredNumber; -export function calculateScrollCenter( +export const calculateScrollCenter = ( elements: readonly ExcalidrawElement[], -): { scrollX: FlooredNumber; scrollY: FlooredNumber } { +): { scrollX: FlooredNumber; scrollY: FlooredNumber } => { if (!elements.length) { return { scrollX: normalizeScroll(0), @@ -25,4 +24,4 @@ export function calculateScrollCenter( scrollX: normalizeScroll(window.innerWidth / 2 - centerX), scrollY: normalizeScroll(window.innerHeight / 2 - centerY), }; -} +}; diff --git a/src/scene/scrollbars.ts b/src/scene/scrollbars.ts index 7c9860e96..8b958f1d7 100644 --- a/src/scene/scrollbars.ts +++ b/src/scene/scrollbars.ts @@ -9,7 +9,7 @@ export const SCROLLBAR_MARGIN = 4; export const SCROLLBAR_WIDTH = 6; export const SCROLLBAR_COLOR = "rgba(0,0,0,0.3)"; -export function getScrollBars( +export const getScrollBars = ( elements: readonly ExcalidrawElement[], viewportWidth: number, viewportHeight: number, @@ -22,7 +22,7 @@ export function getScrollBars( scrollY: FlooredNumber; zoom: number; }, -): ScrollBars { +): ScrollBars => { // This is the bounding box of all the elements const [ elementsMinX, @@ -100,9 +100,13 @@ export function getScrollBars( Math.max(SCROLLBAR_MARGIN * 2, safeArea.top + safeArea.bottom), }, }; -} +}; -export function isOverScrollBars(scrollBars: ScrollBars, x: number, y: number) { +export const isOverScrollBars = ( + scrollBars: ScrollBars, + x: number, + y: number, +) => { const [isOverHorizontalScrollBar, isOverVerticalScrollBar] = [ scrollBars.horizontal, scrollBars.vertical, @@ -120,4 +124,4 @@ export function isOverScrollBars(scrollBars: ScrollBars, x: number, y: number) { isOverHorizontalScrollBar, isOverVerticalScrollBar, }; -} +}; diff --git a/src/scene/selection.ts b/src/scene/selection.ts index ae93877ca..88dcbc0b3 100644 --- a/src/scene/selection.ts +++ b/src/scene/selection.ts @@ -6,10 +6,10 @@ import { getElementAbsoluteCoords, getElementBounds } from "../element"; import { AppState } from "../types"; import { newElementWith } from "../element/mutateElement"; -export function getElementsWithinSelection( +export const getElementsWithinSelection = ( elements: readonly NonDeletedExcalidrawElement[], selection: NonDeletedExcalidrawElement, -) { +) => { const [ selectionX1, selectionY1, @@ -29,12 +29,12 @@ export function getElementsWithinSelection( selectionY2 >= elementY2 ); }); -} +}; -export function deleteSelectedElements( +export const deleteSelectedElements = ( elements: readonly ExcalidrawElement[], appState: AppState, -) { +) => { return { elements: elements.map((el) => { if (appState.selectedElementIds[el.id]) { @@ -47,24 +47,24 @@ export function deleteSelectedElements( selectedElementIds: {}, }, }; -} +}; -export function isSomeElementSelected( +export const isSomeElementSelected = ( elements: readonly NonDeletedExcalidrawElement[], appState: AppState, -): boolean { +): boolean => { return elements.some((element) => appState.selectedElementIds[element.id]); -} +}; /** * Returns common attribute (picked by `getAttribute` callback) of selected * elements. If elements don't share the same value, returns `null`. */ -export function getCommonAttributeOfSelectedElements( +export const getCommonAttributeOfSelectedElements = ( elements: readonly NonDeletedExcalidrawElement[], appState: AppState, getAttribute: (element: ExcalidrawElement) => T, -): T | null { +): T | null => { const attributes = Array.from( new Set( getSelectedElements(elements, appState).map((element) => @@ -73,20 +73,20 @@ export function getCommonAttributeOfSelectedElements( ), ); return attributes.length === 1 ? attributes[0] : null; -} +}; -export function getSelectedElements( +export const getSelectedElements = ( elements: readonly NonDeletedExcalidrawElement[], appState: AppState, -) { +) => { return elements.filter((element) => appState.selectedElementIds[element.id]); -} +}; -export function getTargetElement( +export const getTargetElement = ( elements: readonly NonDeletedExcalidrawElement[], appState: AppState, -) { +) => { return appState.editingElement ? [appState.editingElement] : getSelectedElements(elements, appState); -} +}; diff --git a/src/scene/zoom.ts b/src/scene/zoom.ts index 52aba7925..e300a2df4 100644 --- a/src/scene/zoom.ts +++ b/src/scene/zoom.ts @@ -1,4 +1,7 @@ -export function getZoomOrigin(canvas: HTMLCanvasElement | null, scale: number) { +export const getZoomOrigin = ( + canvas: HTMLCanvasElement | null, + scale: number, +) => { if (canvas === null) { return { x: 0, y: 0 }; } @@ -14,10 +17,10 @@ export function getZoomOrigin(canvas: HTMLCanvasElement | null, scale: number) { x: normalizedCanvasWidth / 2, y: normalizedCanvasHeight / 2, }; -} +}; -export function getNormalizedZoom(zoom: number): number { +export const getNormalizedZoom = (zoom: number): number => { const normalizedZoom = parseFloat(zoom.toFixed(2)); const clampedZoom = Math.max(0.1, Math.min(normalizedZoom, 2)); return clampedZoom; -} +}; diff --git a/src/serviceWorker.tsx b/src/serviceWorker.tsx index c605c7b91..62ffdda09 100644 --- a/src/serviceWorker.tsx +++ b/src/serviceWorker.tsx @@ -25,7 +25,7 @@ type Config = { onUpdate?: (registration: ServiceWorkerRegistration) => void; }; -export function register(config?: Config) { +export const register = (config?: Config) => { if (process.env.NODE_ENV === "production" && "serviceWorker" in navigator) { // The URL constructor is available in all browsers that support SW. const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href); @@ -57,9 +57,9 @@ export function register(config?: Config) { } }); } -} +}; -function registerValidSW(swUrl: string, config?: Config) { +const registerValidSW = (swUrl: string, config?: Config) => { navigator.serviceWorker .register(swUrl) .then((registration) => { @@ -103,9 +103,9 @@ function registerValidSW(swUrl: string, config?: Config) { .catch((error) => { console.error("Error during service worker registration:", error); }); -} +}; -function checkValidServiceWorker(swUrl: string, config?: Config) { +const checkValidServiceWorker = (swUrl: string, config?: Config) => { // Check if the service worker can be found. If it can't reload the page. fetch(swUrl, { headers: { "Service-Worker": "script" }, @@ -133,9 +133,9 @@ function checkValidServiceWorker(swUrl: string, config?: Config) { // "No internet connection found. App is running in offline mode.", // ); }); -} +}; -export function unregister() { +export const unregister = () => { if ("serviceWorker" in navigator) { navigator.serviceWorker.ready .then((registration) => { @@ -145,4 +145,4 @@ export function unregister() { console.error(error.message); }); } -} +}; diff --git a/src/shapes.tsx b/src/shapes.tsx index 0941d01d2..52947a257 100644 --- a/src/shapes.tsx +++ b/src/shapes.tsx @@ -100,10 +100,7 @@ export const shapesShortcutKeys = SHAPES.map((shape, index) => [ (index + 1).toString(), ]).flat(1); -export function findShapeByKey(key: string) { - return ( - SHAPES.find((shape, index) => { - return shape.key === key.toLowerCase() || key === (index + 1).toString(); - })?.value || "selection" - ); -} +export const findShapeByKey = (key: string) => + SHAPES.find((shape, index) => { + return shape.key === key.toLowerCase() || key === (index + 1).toString(); + })?.value || "selection"; diff --git a/src/tests/regressionTests.test.tsx b/src/tests/regressionTests.test.tsx index 0ae487d51..81294008a 100644 --- a/src/tests/regressionTests.test.tsx +++ b/src/tests/regressionTests.test.tsx @@ -16,20 +16,20 @@ const renderScene = jest.spyOn(Renderer, "renderScene"); let getByToolName: (name: string) => HTMLElement = null!; let canvas: HTMLCanvasElement = null!; -function clickTool(toolName: ToolName) { +const clickTool = (toolName: ToolName) => { fireEvent.click(getByToolName(toolName)); -} +}; let lastClientX = 0; let lastClientY = 0; let pointerType: "mouse" | "pen" | "touch" = "mouse"; -function pointerDown( +const pointerDown = ( clientX: number = lastClientX, clientY: number = lastClientY, altKey: boolean = false, shiftKey: boolean = false, -) { +) => { lastClientX = clientX; lastClientY = clientY; fireEvent.pointerDown(canvas, { @@ -40,41 +40,41 @@ function pointerDown( pointerId: 1, pointerType, }); -} +}; -function pointer2Down(clientX: number, clientY: number) { +const pointer2Down = (clientX: number, clientY: number) => { fireEvent.pointerDown(canvas, { clientX, clientY, pointerId: 2, pointerType, }); -} +}; -function pointer2Move(clientX: number, clientY: number) { +const pointer2Move = (clientX: number, clientY: number) => { fireEvent.pointerMove(canvas, { clientX, clientY, pointerId: 2, pointerType, }); -} +}; -function pointer2Up(clientX: number, clientY: number) { +const pointer2Up = (clientX: number, clientY: number) => { fireEvent.pointerUp(canvas, { clientX, clientY, pointerId: 2, pointerType, }); -} +}; -function pointerMove( +const pointerMove = ( clientX: number = lastClientX, clientY: number = lastClientY, altKey: boolean = false, shiftKey: boolean = false, -) { +) => { lastClientX = clientX; lastClientY = clientY; fireEvent.pointerMove(canvas, { @@ -85,72 +85,72 @@ function pointerMove( pointerId: 1, pointerType, }); -} +}; -function pointerUp( +const pointerUp = ( clientX: number = lastClientX, clientY: number = lastClientY, altKey: boolean = false, shiftKey: boolean = false, -) { +) => { lastClientX = clientX; lastClientY = clientY; fireEvent.pointerUp(canvas, { pointerId: 1, pointerType, shiftKey, altKey }); -} +}; -function hotkeyDown(key: Key) { +const hotkeyDown = (key: Key) => { fireEvent.keyDown(document, { key: KEYS[key] }); -} +}; -function hotkeyUp(key: Key) { +const hotkeyUp = (key: Key) => { fireEvent.keyUp(document, { key: KEYS[key], }); -} +}; -function keyDown( +const keyDown = ( key: string, ctrlKey: boolean = false, shiftKey: boolean = false, -) { +) => { fireEvent.keyDown(document, { key, ctrlKey, shiftKey }); -} +}; -function keyUp( +const keyUp = ( key: string, ctrlKey: boolean = false, shiftKey: boolean = false, -) { +) => { fireEvent.keyUp(document, { key, ctrlKey, shiftKey, }); -} +}; -function hotkeyPress(key: Key) { +const hotkeyPress = (key: Key) => { hotkeyDown(key); hotkeyUp(key); -} +}; -function keyPress( +const keyPress = ( key: string, ctrlKey: boolean = false, shiftKey: boolean = false, -) { +) => { keyDown(key, ctrlKey, shiftKey); keyUp(key, ctrlKey, shiftKey); -} +}; -function clickLabeledElement(label: string) { +const clickLabeledElement = (label: string) => { const element = document.querySelector(`[aria-label='${label}']`); if (!element) { throw new Error(`No labeled element found: ${label}`); } fireEvent.click(element); -} +}; -function getSelectedElement(): ExcalidrawElement { +const getSelectedElement = (): ExcalidrawElement => { const selectedElements = h.elements.filter( (element) => h.state.selectedElementIds[element.id], ); @@ -160,10 +160,10 @@ function getSelectedElement(): ExcalidrawElement { ); } return selectedElements[0]; -} +}; type HandlerRectanglesRet = keyof ReturnType; -function getResizeHandles() { +const getResizeHandles = () => { const rects = handlerRectangles( getSelectedElement(), h.state.zoom, @@ -181,14 +181,14 @@ function getResizeHandles() { } return rv; -} +}; /** * This is always called at the end of your test, so usually you don't need to call it. * However, if you have a long test, you might want to call it during the test so it's easier * to debug where a test failure came from. */ -function checkpoint(name: string) { +const checkpoint = (name: string) => { expect(renderScene.mock.calls.length).toMatchSnapshot( `[${name}] number of renders`, ); @@ -198,7 +198,7 @@ function checkpoint(name: string) { h.elements.forEach((element, i) => expect(element).toMatchSnapshot(`[${name}] element ${i}`), ); -} +}; beforeEach(() => { // Unmount ReactDOM from root diff --git a/src/tests/zindex.test.tsx b/src/tests/zindex.test.tsx index 18e952557..ea66efeb6 100644 --- a/src/tests/zindex.test.tsx +++ b/src/tests/zindex.test.tsx @@ -22,9 +22,9 @@ beforeEach(() => { const { h } = window; -function populateElements( +const populateElements = ( elements: { id: string; isDeleted?: boolean; isSelected?: boolean }[], -) { +) => { const selectedElementIds: any = {}; h.elements = elements.map(({ id, isDeleted = false, isSelected = false }) => { @@ -54,7 +54,7 @@ function populateElements( }); return selectedElementIds; -} +}; type Actions = | typeof actionBringForward @@ -62,20 +62,20 @@ type Actions = | typeof actionBringToFront | typeof actionSendToBack; -function assertZindex({ +const assertZindex = ({ elements, operations, }: { elements: { id: string; isDeleted?: true; isSelected?: true }[]; operations: [Actions, string[]][]; -}) { +}) => { const selectedElementIds = populateElements(elements); operations.forEach(([action, expected]) => { h.app.actionManager.executeAction(action); expect(h.elements.map((element) => element.id)).toEqual(expected); expect(h.state.selectedElementIds).toEqual(selectedElementIds); }); -} +}; describe("z-index manipulation", () => { it("send back", () => { diff --git a/src/utils.ts b/src/utils.ts index f0e7d7115..d722e1a18 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -6,9 +6,9 @@ export const SVG_NS = "http://www.w3.org/2000/svg"; let mockDateTime: string | null = null; -export function setDateTimeForTests(dateTime: string) { +export const setDateTimeForTests = (dateTime: string) => { mockDateTime = dateTime; -} +}; export const getDateTime = () => { if (mockDateTime) { @@ -25,51 +25,43 @@ export const getDateTime = () => { return `${year}-${month}-${day}-${hr}${min}`; }; -export function capitalizeString(str: string) { - return str.charAt(0).toUpperCase() + str.slice(1); -} +export const capitalizeString = (str: string) => + str.charAt(0).toUpperCase() + str.slice(1); -export function isToolIcon( +export const isToolIcon = ( target: Element | EventTarget | null, -): target is HTMLElement { - return target instanceof HTMLElement && target.className.includes("ToolIcon"); -} +): target is HTMLElement => + target instanceof HTMLElement && target.className.includes("ToolIcon"); -export function isInputLike( +export const isInputLike = ( target: Element | EventTarget | null, ): target is | HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement | HTMLBRElement - | HTMLDivElement { - return ( - (target instanceof HTMLElement && target.dataset.type === "wysiwyg") || - target instanceof HTMLBRElement || // newline in wysiwyg - target instanceof HTMLInputElement || - target instanceof HTMLTextAreaElement || - target instanceof HTMLSelectElement - ); -} - -export function isWritableElement( + | HTMLDivElement => + (target instanceof HTMLElement && target.dataset.type === "wysiwyg") || + target instanceof HTMLBRElement || // newline in wysiwyg + target instanceof HTMLInputElement || + target instanceof HTMLTextAreaElement || + target instanceof HTMLSelectElement; + +export const isWritableElement = ( target: Element | EventTarget | null, ): target is | HTMLInputElement | HTMLTextAreaElement | HTMLBRElement - | HTMLDivElement { - return ( - (target instanceof HTMLElement && target.dataset.type === "wysiwyg") || - target instanceof HTMLBRElement || // newline in wysiwyg - target instanceof HTMLTextAreaElement || - (target instanceof HTMLInputElement && - (target.type === "text" || target.type === "number")) - ); -} + | HTMLDivElement => + (target instanceof HTMLElement && target.dataset.type === "wysiwyg") || + target instanceof HTMLBRElement || // newline in wysiwyg + target instanceof HTMLTextAreaElement || + (target instanceof HTMLInputElement && + (target.type === "text" || target.type === "number")); // https://github.com/grassator/canvas-text-editor/blob/master/lib/FontMetrics.js -export function measureText(text: string, font: string) { +export const measureText = (text: string, font: string) => { const line = document.createElement("div"); const body = document.body; line.style.position = "absolute"; @@ -93,12 +85,12 @@ export function measureText(text: string, font: string) { document.body.removeChild(line); return { width, height, baseline }; -} +}; -export function debounce( +export const debounce = ( fn: (...args: T) => void, timeout: number, -) { +) => { let handle = 0; let lastArgs: T; const ret = (...args: T) => { @@ -111,9 +103,9 @@ export function debounce( fn(...lastArgs); }; return ret; -} +}; -export function selectNode(node: Element) { +export const selectNode = (node: Element) => { const selection = window.getSelection(); if (selection) { const range = document.createRange(); @@ -121,30 +113,28 @@ export function selectNode(node: Element) { selection.removeAllRanges(); selection.addRange(range); } -} +}; -export function removeSelection() { +export const removeSelection = () => { const selection = window.getSelection(); if (selection) { selection.removeAllRanges(); } -} +}; -export function distance(x: number, y: number) { - return Math.abs(x - y); -} +export const distance = (x: number, y: number) => Math.abs(x - y); -export function resetCursor() { +export const resetCursor = () => { document.documentElement.style.cursor = ""; -} +}; -export function setCursorForShape(shape: string) { +export const setCursorForShape = (shape: string) => { if (shape === "selection") { resetCursor(); } else { document.documentElement.style.cursor = CURSOR_TYPE.CROSSHAIR; } -} +}; export const isFullScreen = () => document.fullscreenElement?.nodeName === "HTML"; @@ -165,7 +155,7 @@ export const getShortcutKey = (shortcut: string): string => { } return `${shortcut.replace(/CtrlOrCmd/i, "Ctrl")}`; }; -export function viewportCoordsToSceneCoords( +export const viewportCoordsToSceneCoords = ( { clientX, clientY }: { clientX: number; clientY: number }, { scrollX, @@ -178,7 +168,7 @@ export function viewportCoordsToSceneCoords( }, canvas: HTMLCanvasElement | null, scale: number, -) { +) => { const zoomOrigin = getZoomOrigin(canvas, scale); const clientXWithZoom = zoomOrigin.x + (clientX - zoomOrigin.x) / zoom; const clientYWithZoom = zoomOrigin.y + (clientY - zoomOrigin.y) / zoom; @@ -187,9 +177,9 @@ export function viewportCoordsToSceneCoords( const y = clientYWithZoom - scrollY; return { x, y }; -} +}; -export function sceneCoordsToViewportCoords( +export const sceneCoordsToViewportCoords = ( { sceneX, sceneY }: { sceneX: number; sceneY: number }, { scrollX, @@ -202,7 +192,7 @@ export function sceneCoordsToViewportCoords( }, canvas: HTMLCanvasElement | null, scale: number, -) { +) => { const zoomOrigin = getZoomOrigin(canvas, scale); const sceneXWithZoomAndScroll = zoomOrigin.x - (zoomOrigin.x - sceneX - scrollX) * zoom; @@ -213,10 +203,7 @@ export function sceneCoordsToViewportCoords( const y = sceneYWithZoomAndScroll; return { x, y }; -} +}; -export function getGlobalCSSVariable(name: string) { - return getComputedStyle(document.documentElement).getPropertyValue( - `--${name}`, - ); -} +export const getGlobalCSSVariable = (name: string) => + getComputedStyle(document.documentElement).getPropertyValue(`--${name}`); diff --git a/src/zindex.test.ts b/src/zindex.test.ts index 40485c327..df80fcf90 100644 --- a/src/zindex.test.ts +++ b/src/zindex.test.ts @@ -1,14 +1,14 @@ import { moveOneLeft, moveOneRight, moveAllLeft, moveAllRight } from "./zindex"; -function expectMove( +const expectMove = ( fn: (elements: T[], indicesToMove: number[]) => void, elems: T[], indices: number[], equal: T[], -) { +) => { fn(elems, indices); expect(elems).toEqual(equal); -} +}; it("should moveOneLeft", () => { expectMove(moveOneLeft, ["a", "b", "c", "d"], [1, 2], ["b", "c", "a", "d"]); diff --git a/src/zindex.ts b/src/zindex.ts index eedde8822..ebc9dcf23 100644 --- a/src/zindex.ts +++ b/src/zindex.ts @@ -1,10 +1,10 @@ -function swap(elements: T[], indexA: number, indexB: number) { +const swap = (elements: T[], indexA: number, indexB: number) => { const element = elements[indexA]; elements[indexA] = elements[indexB]; elements[indexB] = element; -} +}; -export function moveOneLeft(elements: T[], indicesToMove: number[]) { +export const moveOneLeft = (elements: T[], indicesToMove: number[]) => { indicesToMove.sort((a: number, b: number) => a - b); let isSorted = true; // We go from left to right to avoid overriding the wrong elements @@ -19,9 +19,9 @@ export function moveOneLeft(elements: T[], indicesToMove: number[]) { }); return elements; -} +}; -export function moveOneRight(elements: T[], indicesToMove: number[]) { +export const moveOneRight = (elements: T[], indicesToMove: number[]) => { const reversedIndicesToMove = indicesToMove.sort( (a: number, b: number) => b - a, ); @@ -38,7 +38,7 @@ export function moveOneRight(elements: T[], indicesToMove: number[]) { swap(elements, index + 1, index); }); return elements; -} +}; // Let's go through an example // | | @@ -86,7 +86,7 @@ export function moveOneRight(elements: T[], indicesToMove: number[]) { // [c, f, a, b, d, e, g] // // And we are done! -export function moveAllLeft(elements: T[], indicesToMove: number[]) { +export const moveAllLeft = (elements: T[], indicesToMove: number[]) => { indicesToMove.sort((a: number, b: number) => a - b); // Copy the elements to move @@ -117,7 +117,7 @@ export function moveAllLeft(elements: T[], indicesToMove: number[]) { }); return elements; -} +}; // Let's go through an example // | | @@ -164,7 +164,7 @@ export function moveAllLeft(elements: T[], indicesToMove: number[]) { // [a, b, d, e, g, c, f] // // And we are done! -export function moveAllRight(elements: T[], indicesToMove: number[]) { +export const moveAllRight = (elements: T[], indicesToMove: number[]) => { const reversedIndicesToMove = indicesToMove.sort( (a: number, b: number) => b - a, ); @@ -199,4 +199,4 @@ export function moveAllRight(elements: T[], indicesToMove: number[]) { }); return elements; -} +};