Browse Source

Prefer arrow functions and callbacks (#1210)

Lipis 4 năm trước cách đây
mục cha
commit
c427aa3cce
64 tập tin đã thay đổi với 785 bổ sung848 xóa
  1. 8 8
      src/actions/actionZindex.tsx
  2. 2 2
      src/actions/register.ts
  3. 9 9
      src/appState.ts
  4. 18 19
      src/clipboard.ts
  5. 48 52
      src/components/Actions.tsx
  6. 6 7
      src/components/App.tsx
  7. 19 21
      src/components/ButtonSelect.tsx
  8. 7 7
      src/components/ColorPicker.tsx
  9. 27 31
      src/components/ContextMenu.tsx
  10. 7 7
      src/components/Dialog.tsx
  11. 3 3
      src/components/ErrorDialog.tsx
  12. 6 6
      src/components/ExportDialog.tsx
  13. 8 10
      src/components/FixedSideContainer.tsx
  14. 5 7
      src/components/HelpIcon.tsx
  15. 19 21
      src/components/LanguageList.tsx
  16. 2 2
      src/components/LockIcon.tsx
  17. 100 102
      src/components/MobileMenu.tsx
  18. 7 7
      src/components/Modal.tsx
  19. 3 3
      src/components/Popover.tsx
  20. 10 10
      src/components/RoomDialog.tsx
  21. 2 2
      src/components/Section.tsx
  22. 6 6
      src/components/Stack.tsx
  23. 1 4
      src/components/ToolButton.tsx
  24. 2 2
      src/data/blob.ts
  25. 30 33
      src/data/index.ts
  26. 8 9
      src/data/json.ts
  27. 9 9
      src/data/localStorage.ts
  28. 3 3
      src/data/restore.ts
  29. 10 10
      src/element/bounds.ts
  30. 6 6
      src/element/collision.ts
  31. 9 9
      src/element/handlerRectangles.ts
  32. 12 17
      src/element/index.ts
  33. 10 12
      src/element/mutateElement.ts
  34. 4 4
      src/element/newElement.test.ts
  35. 35 38
      src/element/newElement.ts
  36. 21 25
      src/element/resizeTest.ts
  37. 13 11
      src/element/sizeHelpers.ts
  38. 11 11
      src/element/textWysiwyg.tsx
  39. 8 8
      src/element/typeChecks.ts
  40. 6 8
      src/gesture.ts
  41. 3 4
      src/history.ts
  42. 8 12
      src/i18n.ts
  43. 1 1
      src/index.tsx
  44. 6 2
      src/is-mobile.tsx
  45. 6 8
      src/keys.ts
  46. 19 20
      src/math.ts
  47. 5 5
      src/points.ts
  48. 5 8
      src/random.ts
  49. 23 25
      src/renderer/renderElement.ts
  50. 17 17
      src/renderer/renderScene.ts
  51. 3 3
      src/renderer/roundRect.ts
  52. 6 6
      src/scene/comparisons.ts
  53. 9 12
      src/scene/export.ts
  54. 5 6
      src/scene/scroll.ts
  55. 9 5
      src/scene/scrollbars.ts
  56. 18 18
      src/scene/selection.ts
  57. 7 4
      src/scene/zoom.ts
  58. 8 8
      src/serviceWorker.tsx
  59. 4 7
      src/shapes.tsx
  60. 40 40
      src/tests/regressionTests.test.tsx
  61. 6 6
      src/tests/zindex.test.tsx
  62. 44 57
      src/utils.ts
  63. 3 3
      src/zindex.test.ts
  64. 10 10
      src/zindex.ts

+ 8 - 8
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",

+ 2 - 2
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;
-}
+};

+ 9 - 9
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<AppState> {
+): Partial<AppState> => {
   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,
   };
-}
+};

+ 18 - 19
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;
-}
+};

+ 48 - 52
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({
       )}
     </div>
   );
-}
+};
 
-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 (
-          <ToolButton
-            key={value}
-            type="radio"
-            icon={icon}
-            checked={elementType === value}
-            name="editor-current-shape"
-            title={`${capitalizeString(label)} — ${shortcut}`}
-            keyBindingLabel={`${index + 1}`}
-            aria-label={capitalizeString(label)}
-            aria-keyshortcuts={`${key} ${index + 1}`}
-            data-testid={value}
-            onChange={() => {
-              setAppState({
-                elementType: value,
-                multiElement: null,
-                selectedElementIds: {},
-              });
-              setCursorForShape(value);
-              setAppState({});
-            }}
-          ></ToolButton>
-        );
-      })}
-    </>
-  );
-}
+}) => (
+  <>
+    {SHAPES.map(({ value, icon, key }, index) => {
+      const label = t(`toolBar.${value}`);
+      const shortcut = `${capitalizeString(key)} ${t("shortcutsDialog.or")} ${
+        index + 1
+      }`;
+      return (
+        <ToolButton
+          key={value}
+          type="radio"
+          icon={icon}
+          checked={elementType === value}
+          name="editor-current-shape"
+          title={`${capitalizeString(label)} — ${shortcut}`}
+          keyBindingLabel={`${index + 1}`}
+          aria-label={capitalizeString(label)}
+          aria-keyshortcuts={`${key} ${index + 1}`}
+          data-testid={value}
+          onChange={() => {
+            setAppState({
+              elementType: value,
+              multiElement: null,
+              selectedElementIds: {},
+            });
+            setCursorForShape(value);
+            setAppState({});
+          }}
+        ></ToolButton>
+      );
+    })}
+  </>
+);
 
-export function ZoomActions({
+export const ZoomActions = ({
   renderAction,
   zoom,
 }: {
   renderAction: ActionManager["renderAction"];
   zoom: number;
-}) {
-  return (
-    <Stack.Col gap={1}>
-      <Stack.Row gap={1} align="center">
-        {renderAction("zoomIn")}
-        {renderAction("zoomOut")}
-        {renderAction("resetZoom")}
-        <div style={{ marginInlineStart: 4 }}>{(zoom * 100).toFixed(0)}%</div>
-      </Stack.Row>
-    </Stack.Col>
-  );
-}
+}) => (
+  <Stack.Col gap={1}>
+    <Stack.Row gap={1} align="center">
+      {renderAction("zoomIn")}
+      {renderAction("zoomOut")}
+      {renderAction("resetZoom")}
+      <div style={{ marginInlineStart: 4 }}>{(zoom * 100).toFixed(0)}%</div>
+    </Stack.Row>
+  </Stack.Col>
+);

+ 6 - 7
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<TFunction>["length"] extends 0 | 1 ? TFunction : never) {
-  return ((event) => {
+>(
+  func: Parameters<TFunction>["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,
     },
   });
 }

+ 19 - 21
src/components/ButtonSelect.tsx

@@ -1,6 +1,6 @@
 import React from "react";
 
-export function ButtonSelect<T>({
+export const ButtonSelect = <T extends Object>({
   options,
   value,
   onChange,
@@ -10,23 +10,21 @@ export function ButtonSelect<T>({
   value: T | null;
   onChange: (value: T) => void;
   group: string;
-}) {
-  return (
-    <div className="buttonList">
-      {options.map((option) => (
-        <label
-          key={option.text}
-          className={value === option.value ? "active" : ""}
-        >
-          <input
-            type="radio"
-            name={group}
-            onChange={() => onChange(option.value)}
-            checked={value === option.value ? true : false}
-          />
-          {option.text}
-        </label>
-      ))}
-    </div>
-  );
-}
+}) => (
+  <div className="buttonList">
+    {options.map((option) => (
+      <label
+        key={option.text}
+        className={value === option.value ? "active" : ""}
+      >
+        <input
+          type="radio"
+          name={group}
+          onChange={() => onChange(option.value)}
+          checked={value === option.value ? true : false}
+        />
+        {option.text}
+      </label>
+    ))}
+  </div>
+);

+ 7 - 7
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<HTMLButtonElement>();
   const activeItem = React.useRef<HTMLButtonElement>();
   const gallery = React.useRef<HTMLDivElement>();
@@ -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<HTMLButtonElement>(null);
 
@@ -296,4 +296,4 @@ export function ColorPicker({
       </React.Suspense>
     </div>
   );
-}
+};

+ 27 - 31
src/components/ContextMenu.tsx

@@ -16,45 +16,41 @@ type Props = {
   left: number;
 };
 
-function ContextMenu({ options, onCloseRequest, top, left }: Props) {
-  return (
-    <Popover
-      onCloseRequest={onCloseRequest}
-      top={top}
-      left={left}
-      fitInViewport={true}
+const ContextMenu = ({ options, onCloseRequest, top, left }: Props) => (
+  <Popover
+    onCloseRequest={onCloseRequest}
+    top={top}
+    left={left}
+    fitInViewport={true}
+  >
+    <ul
+      className="context-menu"
+      onContextMenu={(event) => event.preventDefault()}
     >
-      <ul
-        className="context-menu"
-        onContextMenu={(event) => event.preventDefault()}
-      >
-        {options.map((option, idx) => (
-          <li key={idx} onClick={onCloseRequest}>
-            <ContextMenuOption {...option} />
-          </li>
-        ))}
-      </ul>
-    </Popover>
-  );
-}
+      {options.map((option, idx) => (
+        <li key={idx} onClick={onCloseRequest}>
+          <ContextMenuOption {...option} />
+        </li>
+      ))}
+    </ul>
+  </Popover>
+);
 
-function ContextMenuOption({ label, action }: ContextMenuOption) {
-  return (
-    <button className="context-menu-option" onClick={action}>
-      {label}
-    </button>
-  );
-}
+const ContextMenuOption = ({ label, action }: ContextMenuOption) => (
+  <button className="context-menu-option" onClick={action}>
+    {label}
+  </button>
+);
 
 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) {

+ 7 - 7
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<HTMLDivElement>(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<HTMLElement>(
       "button, a, input, select, textarea, div[tabindex]",
     );
 
     return focusableElements ? Array.from(focusableElements) : [];
-  }
+  };
 
   return (
     <Modal
@@ -88,4 +88,4 @@ export function Dialog(props: {
       </Island>
     </Modal>
   );
-}
+};

+ 3 - 3
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({
       )}
     </>
   );
-}
+};

+ 6 - 6
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({
       </Stack.Col>
     </div>
   );
-}
+};
 
-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<HTMLButtonElement>(null);
 
@@ -221,4 +221,4 @@ export function ExportDialog({
       )}
     </>
   );
-}
+};

+ 8 - 10
src/components/FixedSideContainer.tsx

@@ -8,16 +8,14 @@ type FixedSideContainerProps = {
   className?: string;
 };
 
-export function FixedSideContainer({
+export const FixedSideContainer = ({
   children,
   side,
   className,
-}: FixedSideContainerProps) {
-  return (
-    <div
-      className={`FixedSideContainer FixedSideContainer_side_${side} ${className}`}
-    >
-      {children}
-    </div>
-  );
-}
+}: FixedSideContainerProps) => (
+  <div
+    className={`FixedSideContainer FixedSideContainer_side_${side} ${className}`}
+  >
+    {children}
+  </div>
+);

+ 5 - 7
src/components/HelpIcon.tsx

@@ -18,10 +18,8 @@ const ICON = (
   </svg>
 );
 
-export function HelpIcon(props: HelpIconProps) {
-  return (
-    <label title={`${props.title} — ?`} className="help-icon">
-      <div onClick={props.onClick}>{ICON}</div>
-    </label>
-  );
-}
+export const HelpIcon = (props: HelpIconProps) => (
+  <label title={`${props.title} — ?`} className="help-icon">
+    <div onClick={props.onClick}>{ICON}</div>
+  </label>
+);

+ 19 - 21
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 (
-    <React.Fragment>
-      <select
-        className={`dropdown-select dropdown-select__language${
-          floating ? " dropdown-select--floating" : ""
-        }`}
-        onChange={({ target }) => onChange(target.value)}
-        value={currentLanguage}
-        aria-label={i18n.t("buttons.selectLanguage")}
-      >
-        {languages.map((language) => (
-          <option key={language.lng} value={language.lng}>
-            {language.label}
-          </option>
-        ))}
-      </select>
-    </React.Fragment>
-  );
-}
+}) => (
+  <React.Fragment>
+    <select
+      className={`dropdown-select dropdown-select__language${
+        floating ? " dropdown-select--floating" : ""
+      }`}
+      onChange={({ target }) => onChange(target.value)}
+      value={currentLanguage}
+      aria-label={i18n.t("buttons.selectLanguage")}
+    >
+      {languages.map((language) => (
+        <option key={language.lng} value={language.lng}>
+          {language.label}
+        </option>
+      ))}
+    </select>
+  </React.Fragment>
+);

+ 2 - 2
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) {
       </div>
     </label>
   );
-}
+};

+ 100 - 102
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 && <LoadingMessage />}
-      <FixedSideContainer side="top">
-        <Section heading="shapes">
-          {(heading) => (
-            <Stack.Col gap={4} align="center">
-              <Stack.Row gap={1}>
-                <Island padding={1}>
-                  {heading}
-                  <Stack.Row gap={1}>
-                    <ShapesSwitcher
-                      elementType={appState.elementType}
-                      setAppState={setAppState}
-                    />
-                  </Stack.Row>
-                </Island>
-                <LockIcon
-                  checked={appState.elementLocked}
-                  onChange={onLockToggle}
-                  title={t("toolBar.lock")}
-                />
-              </Stack.Row>
-            </Stack.Col>
-          )}
-        </Section>
-        <HintViewer appState={appState} elements={elements} />
-      </FixedSideContainer>
-      <div
-        className="App-bottom-bar"
-        style={{
-          marginBottom: SCROLLBAR_WIDTH + SCROLLBAR_MARGIN * 2,
-          marginLeft: SCROLLBAR_WIDTH + SCROLLBAR_MARGIN * 2,
-          marginRight: SCROLLBAR_WIDTH + SCROLLBAR_MARGIN * 2,
-        }}
-      >
-        <Island padding={3}>
-          {appState.openMenu === "canvas" ? (
-            <Section className="App-mobile-menu" heading="canvasActions">
-              <div className="panelColumn">
-                <Stack.Col gap={4}>
-                  {actionManager.renderAction("loadScene")}
-                  {actionManager.renderAction("saveScene")}
-                  {exportButton}
-                  {actionManager.renderAction("clearCanvas")}
-                  <RoomDialog
-                    isCollaborating={appState.isCollaborating}
-                    collaboratorCount={appState.collaborators.size}
-                    username={appState.username}
-                    onUsernameChange={onUsernameChange}
-                    onRoomCreate={onRoomCreate}
-                    onRoomDestroy={onRoomDestroy}
+}: MobileMenuProps) => (
+  <>
+    {appState.isLoading && <LoadingMessage />}
+    <FixedSideContainer side="top">
+      <Section heading="shapes">
+        {(heading) => (
+          <Stack.Col gap={4} align="center">
+            <Stack.Row gap={1}>
+              <Island padding={1}>
+                {heading}
+                <Stack.Row gap={1}>
+                  <ShapesSwitcher
+                    elementType={appState.elementType}
+                    setAppState={setAppState}
                   />
-                  {actionManager.renderAction("changeViewBackgroundColor")}
-                  <fieldset>
-                    <legend>{t("labels.language")}</legend>
-                    <LanguageList
-                      onChange={(lng) => {
-                        setLanguage(lng);
-                        setAppState({});
-                      }}
-                    />
-                  </fieldset>
-                </Stack.Col>
-              </div>
-            </Section>
-          ) : appState.openMenu === "shape" &&
-            showSelectedShapeActions(appState, elements) ? (
-            <Section className="App-mobile-menu" heading="selectedShapeActions">
-              <SelectedShapeActions
-                appState={appState}
-                elements={elements}
-                renderAction={actionManager.renderAction}
-                elementType={appState.elementType}
+                </Stack.Row>
+              </Island>
+              <LockIcon
+                checked={appState.elementLocked}
+                onChange={onLockToggle}
+                title={t("toolBar.lock")}
               />
-            </Section>
-          ) : null}
-          <footer className="App-toolbar">
-            <div className="App-toolbar-content">
-              {actionManager.renderAction("toggleCanvasMenu")}
-              {actionManager.renderAction("toggleEditMenu")}
-              {actionManager.renderAction("undo")}
-              {actionManager.renderAction("redo")}
-              {actionManager.renderAction(
-                appState.multiElement ? "finalize" : "duplicateSelection",
-              )}
-              {actionManager.renderAction("deleteSelectedElements")}
+            </Stack.Row>
+          </Stack.Col>
+        )}
+      </Section>
+      <HintViewer appState={appState} elements={elements} />
+    </FixedSideContainer>
+    <div
+      className="App-bottom-bar"
+      style={{
+        marginBottom: SCROLLBAR_WIDTH + SCROLLBAR_MARGIN * 2,
+        marginLeft: SCROLLBAR_WIDTH + SCROLLBAR_MARGIN * 2,
+        marginRight: SCROLLBAR_WIDTH + SCROLLBAR_MARGIN * 2,
+      }}
+    >
+      <Island padding={3}>
+        {appState.openMenu === "canvas" ? (
+          <Section className="App-mobile-menu" heading="canvasActions">
+            <div className="panelColumn">
+              <Stack.Col gap={4}>
+                {actionManager.renderAction("loadScene")}
+                {actionManager.renderAction("saveScene")}
+                {exportButton}
+                {actionManager.renderAction("clearCanvas")}
+                <RoomDialog
+                  isCollaborating={appState.isCollaborating}
+                  collaboratorCount={appState.collaborators.size}
+                  username={appState.username}
+                  onUsernameChange={onUsernameChange}
+                  onRoomCreate={onRoomCreate}
+                  onRoomDestroy={onRoomDestroy}
+                />
+                {actionManager.renderAction("changeViewBackgroundColor")}
+                <fieldset>
+                  <legend>{t("labels.language")}</legend>
+                  <LanguageList
+                    onChange={(lng) => {
+                      setLanguage(lng);
+                      setAppState({});
+                    }}
+                  />
+                </fieldset>
+              </Stack.Col>
             </div>
-            {appState.scrolledOutside && (
-              <button
-                className="scroll-back-to-content"
-                onClick={() => {
-                  setAppState({ ...calculateScrollCenter(elements) });
-                }}
-              >
-                {t("buttons.scrollBackToContent")}
-              </button>
+          </Section>
+        ) : appState.openMenu === "shape" &&
+          showSelectedShapeActions(appState, elements) ? (
+          <Section className="App-mobile-menu" heading="selectedShapeActions">
+            <SelectedShapeActions
+              appState={appState}
+              elements={elements}
+              renderAction={actionManager.renderAction}
+              elementType={appState.elementType}
+            />
+          </Section>
+        ) : null}
+        <footer className="App-toolbar">
+          <div className="App-toolbar-content">
+            {actionManager.renderAction("toggleCanvasMenu")}
+            {actionManager.renderAction("toggleEditMenu")}
+            {actionManager.renderAction("undo")}
+            {actionManager.renderAction("redo")}
+            {actionManager.renderAction(
+              appState.multiElement ? "finalize" : "duplicateSelection",
             )}
-          </footer>
-        </Island>
-      </div>
-    </>
-  );
-}
+            {actionManager.renderAction("deleteSelectedElements")}
+          </div>
+          {appState.scrolledOutside && (
+            <button
+              className="scroll-back-to-content"
+              onClick={() => {
+                setAppState({ ...calculateScrollCenter(elements) });
+              }}
+            >
+              {t("buttons.scrollBackToContent")}
+            </button>
+          )}
+        </footer>
+      </Island>
+    </div>
+  </>
+);

+ 7 - 7
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: {
     </div>,
     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;
-}
+};

+ 3 - 3
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<HTMLDivElement>(null);
 
   // ensure the popover doesn't overflow the viewport
@@ -53,4 +53,4 @@ export function Popover({
       {children}
     </div>
   );
-}
+};

+ 10 - 10
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<HTMLInputElement>(null);
 
-  function copyRoomLink() {
+  const copyRoomLink = () => {
     copyTextToSystemClipboard(activeRoomLink);
     if (roomLinkInput.current) {
       roomLinkInput.current.select();
     }
-  }
-  function selectInput(event: React.MouseEvent<HTMLInputElement>) {
+  };
+  const selectInput = (event: React.MouseEvent<HTMLInputElement>) => {
     if (event.target !== document.activeElement) {
       event.preventDefault();
       (event.target as HTMLInputElement).select();
     }
-  }
+  };
 
   return (
     <div className="RoomDialog-modal">
@@ -113,9 +113,9 @@ function RoomModal({
       )}
     </div>
   );
-}
+};
 
-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({
       )}
     </>
   );
-}
+};

+ 2 - 2
src/components/Section.tsx

@@ -6,7 +6,7 @@ interface SectionProps extends React.HTMLProps<HTMLElement> {
   children: React.ReactNode | ((header: React.ReactNode) => React.ReactNode);
 }
 
-export function Section({ heading, children, ...props }: SectionProps) {
+export const Section = ({ heading, children, ...props }: SectionProps) => {
   const header = (
     <h2 className="visually-hidden" id={`${heading}-title`}>
       {t(`headings.${heading}`)}
@@ -24,4 +24,4 @@ export function Section({ heading, children, ...props }: SectionProps) {
       )}
     </section>
   );
-}
+};

+ 6 - 6
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 (
     <div
       className={`Stack Stack_horizontal ${className || ""}`}
@@ -31,15 +31,15 @@ function RowStack({
       {children}
     </div>
   );
-}
+};
 
-function ColStack({
+const ColStack = ({
   children,
   gap,
   align,
   justifyContent,
   className,
-}: StackProps) {
+}: StackProps) => {
   return (
     <div
       className={`Stack Stack_vertical ${className || ""}`}
@@ -54,7 +54,7 @@ function ColStack({
       {children}
     </div>
   );
-}
+};
 
 export default {
   Row: RowStack,

+ 1 - 4
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}`;

+ 2 - 2
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 });
-}
+};

+ 30 - 33
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<EncryptedData> {
+): Promise<EncryptedData> => {
   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<SocketUpdateDataIncoming> {
+): Promise<SocketUpdateDataIncoming> => {
   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,
   };
-}
+};

+ 8 - 9
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);
-}
+};

+ 9 - 9
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);
-}
+};

+ 3 - 3
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<ExcalidrawElement>[],
   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,
   };
-}
+};

+ 10 - 10
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,

+ 6 - 6
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,

+ 9 - 9
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;
-}
+};

+ 12 - 17
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<T extends ExcalidrawElement>(
+export const isNonDeletedElement = <T extends ExcalidrawElement>(
   element: T,
-): element is NonDeleted<T> {
-  return !element.isDeleted;
-}
+): element is NonDeleted<T> => !element.isDeleted;

+ 10 - 12
src/element/mutateElement.ts

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

+ 4 - 4
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({

+ 35 - 38
src/element/newElement.ts

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

+ 21 - 25
src/element/resizeTest.ts

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

+ 13 - 11
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;
-}
+};

+ 11 - 11
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);
-}
+};

+ 8 - 8
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"
   );
-}
+};

+ 6 - 8
src/gesture.ts

@@ -1,18 +1,16 @@
 import { PointerCoords } from "./types";
 import { normalizeScroll } from "./scene";
 
-export function getCenter(pointers: Map<number, PointerCoords>) {
+export const getCenter = (pointers: Map<number, PointerCoords>) => {
   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<T>(array: readonly T[], mapper: (item: T) => number): number {
-  return array.reduce((acc, item) => acc + mapper(item), 0);
-}
+const sum = <T>(array: readonly T[], mapper: (item: T) => number): number =>
+  array.reduce((acc, item) => acc + mapper(item), 0);

+ 3 - 4
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<typeof elements>),
     });
-  }
 
   pushEntry(appState: AppState, elements: readonly ExcalidrawElement[]) {
     const newEntry = this.generateEntry(appState, elements);

+ 8 - 12
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,

+ 1 - 1
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();

+ 6 - 2
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<MediaQueryList>();
   if (!query.current) {
     query.current = window.matchMedia
@@ -24,7 +28,7 @@ export function IsMobileProvider({ children }: { children: React.ReactNode }) {
   }, []);
 
   return <context.Provider value={isMobile}>{children}</context.Provider>;
-}
+};
 
 export default function useIsMobile() {
   return useContext(context);

+ 6 - 8
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;

+ 19 - 20
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;
-}
+};

+ 5 - 5
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;
-}
+};

+ 5 - 8
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();

+ 23 - 25
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(
       }
     }
   }
-}
+};

+ 17 - 17
src/renderer/renderScene.ts

@@ -30,7 +30,7 @@ import colors from "../colors";
 
 type HandlerRectanglesRet = keyof ReturnType<typeof handlerRectangles>;
 
-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(
       );
     }
   });
-}
+};

+ 3 - 3
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();
-}
+};

+ 6 - 6
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;
-}
+};

+ 9 - 12
src/scene/export.ts

@@ -11,7 +11,7 @@ import { t } from "../i18n";
 
 export const SVG_EXPORT_TAG = `<!-- svg-source:excalidraw -->`;
 
-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,
   });
-}
+};

+ 5 - 6
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),
   };
-}
+};

+ 9 - 5
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,
   };
-}
+};

+ 18 - 18
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<T>(
+export const getCommonAttributeOfSelectedElements = <T>(
   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<T>(
     ),
   );
   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);
-}
+};

+ 7 - 4
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;
-}
+};

+ 8 - 8
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);
       });
   }
-}
+};

+ 4 - 7
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";

+ 40 - 40
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<typeof handlerRectangles>;
-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

+ 6 - 6
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", () => {

+ 44 - 57
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<T extends any[]>(
+export const debounce = <T extends any[]>(
   fn: (...args: T) => void,
   timeout: number,
-) {
+) => {
   let handle = 0;
   let lastArgs: T;
   const ret = (...args: T) => {
@@ -111,9 +103,9 @@ export function debounce<T extends any[]>(
     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}`);

+ 3 - 3
src/zindex.test.ts

@@ -1,14 +1,14 @@
 import { moveOneLeft, moveOneRight, moveAllLeft, moveAllRight } from "./zindex";
 
-function expectMove<T>(
+const expectMove = <T>(
   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"]);

+ 10 - 10
src/zindex.ts

@@ -1,10 +1,10 @@
-function swap<T>(elements: T[], indexA: number, indexB: number) {
+const swap = <T>(elements: T[], indexA: number, indexB: number) => {
   const element = elements[indexA];
   elements[indexA] = elements[indexB];
   elements[indexB] = element;
-}
+};
 
-export function moveOneLeft<T>(elements: T[], indicesToMove: number[]) {
+export const moveOneLeft = <T>(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<T>(elements: T[], indicesToMove: number[]) {
   });
 
   return elements;
-}
+};
 
-export function moveOneRight<T>(elements: T[], indicesToMove: number[]) {
+export const moveOneRight = <T>(elements: T[], indicesToMove: number[]) => {
   const reversedIndicesToMove = indicesToMove.sort(
     (a: number, b: number) => b - a,
   );
@@ -38,7 +38,7 @@ export function moveOneRight<T>(elements: T[], indicesToMove: number[]) {
     swap(elements, index + 1, index);
   });
   return elements;
-}
+};
 
 // Let's go through an example
 //        |        |
@@ -86,7 +86,7 @@ export function moveOneRight<T>(elements: T[], indicesToMove: number[]) {
 // [c, f, a, b, d, e, g]
 //
 // And we are done!
-export function moveAllLeft<T>(elements: T[], indicesToMove: number[]) {
+export const moveAllLeft = <T>(elements: T[], indicesToMove: number[]) => {
   indicesToMove.sort((a: number, b: number) => a - b);
 
   // Copy the elements to move
@@ -117,7 +117,7 @@ export function moveAllLeft<T>(elements: T[], indicesToMove: number[]) {
   });
 
   return elements;
-}
+};
 
 // Let's go through an example
 //        |        |
@@ -164,7 +164,7 @@ export function moveAllLeft<T>(elements: T[], indicesToMove: number[]) {
 // [a, b, d, e, g, c, f]
 //
 // And we are done!
-export function moveAllRight<T>(elements: T[], indicesToMove: number[]) {
+export const moveAllRight = <T>(elements: T[], indicesToMove: number[]) => {
   const reversedIndicesToMove = indicesToMove.sort(
     (a: number, b: number) => b - a,
   );
@@ -199,4 +199,4 @@ export function moveAllRight<T>(elements: T[], indicesToMove: number[]) {
   });
 
   return elements;
-}
+};