From 560231d3659520958261778cb0ffee4aa2877b2a Mon Sep 17 00:00:00 2001 From: David Luzar Date: Mon, 8 May 2023 10:14:02 +0200 Subject: [PATCH] perf: use `UIAppState` where possible to reduce UI rerenders (#6560) --- src/components/Actions.tsx | 13 +++--- src/components/HintViewer.tsx | 8 ++-- src/components/ImageExportDialog.tsx | 11 ++--- src/components/JSONExportDialog.tsx | 8 ++-- src/components/LayerUI.tsx | 40 ++++++++++++------- src/components/LibraryMenu.tsx | 11 +++-- src/components/LibraryMenuBrowseButton.tsx | 4 +- src/components/LibraryMenuControlButtons.tsx | 4 +- src/components/LibraryMenuHeaderContent.tsx | 11 ++--- src/components/LibraryMenuItems.tsx | 13 ++++-- src/components/MobileMenu.tsx | 10 ++--- src/components/PasteChartDialog.tsx | 7 ++-- src/components/PublishLibrary.tsx | 6 +-- src/components/Sidebar/Sidebar.tsx | 9 ++--- src/components/Sidebar/SidebarTrigger.tsx | 8 ++-- src/components/Stats.tsx | 6 +-- .../dropdownMenu/DropdownMenuTrigger.tsx | 5 ++- src/components/footer/Footer.tsx | 4 +- src/components/footer/FooterCenter.tsx | 4 +- .../LiveCollaborationTrigger.tsx | 4 +- src/components/main-menu/DefaultItems.tsx | 11 ++--- src/components/main-menu/MainMenu.tsx | 9 ++--- .../welcome-screen/WelcomeScreen.Center.tsx | 9 ++--- src/context/ui-appState.ts | 4 +- src/element/Hyperlink.tsx | 5 ++- src/element/showSelectedShapeActions.ts | 4 +- src/excalidraw-app/CustomStats.tsx | 5 ++- .../components/ExportToExcalidrawPlus.tsx | 4 +- src/excalidraw-app/data/index.ts | 2 +- src/excalidraw-app/index.tsx | 5 ++- src/scene/selection.ts | 8 ++-- src/types.ts | 19 ++++++--- src/utils.ts | 9 ++++- 33 files changed, 155 insertions(+), 125 deletions(-) diff --git a/src/components/Actions.tsx b/src/components/Actions.tsx index 3bbc0ff1..875d8447 100644 --- a/src/components/Actions.tsx +++ b/src/components/Actions.tsx @@ -14,7 +14,7 @@ import { hasText, } from "../scene"; import { SHAPES } from "../shapes"; -import { AppState, Zoom } from "../types"; +import { UIAppState, Zoom } from "../types"; import { capitalizeString, isTransparent, @@ -28,19 +28,20 @@ import { trackEvent } from "../analytics"; import { hasBoundTextElement } from "../element/typeChecks"; import clsx from "clsx"; import { actionToggleZenMode } from "../actions"; -import "./Actions.scss"; import { Tooltip } from "./Tooltip"; import { shouldAllowVerticalAlign, suppportsHorizontalAlign, } from "../element/textElement"; +import "./Actions.scss"; + export const SelectedShapeActions = ({ appState, elements, renderAction, }: { - appState: AppState; + appState: UIAppState; elements: readonly ExcalidrawElement[]; renderAction: ActionManager["renderAction"]; }) => { @@ -215,10 +216,10 @@ export const ShapesSwitcher = ({ appState, }: { canvas: HTMLCanvasElement | null; - activeTool: AppState["activeTool"]; - setAppState: React.Component["setState"]; + activeTool: UIAppState["activeTool"]; + setAppState: React.Component["setState"]; onImageAction: (data: { pointerType: PointerType | null }) => void; - appState: AppState; + appState: UIAppState; }) => ( <> {SHAPES.map(({ value, icon, key, numericKey, fillable }, index) => { diff --git a/src/components/HintViewer.tsx b/src/components/HintViewer.tsx index a193754d..a1b6308f 100644 --- a/src/components/HintViewer.tsx +++ b/src/components/HintViewer.tsx @@ -1,9 +1,7 @@ import { t } from "../i18n"; import { NonDeletedExcalidrawElement } from "../element/types"; import { getSelectedElements } from "../scene"; - -import "./HintViewer.scss"; -import { AppState, Device } from "../types"; +import { Device, UIAppState } from "../types"; import { isImageElement, isLinearElement, @@ -13,8 +11,10 @@ import { import { getShortcutKey } from "../utils"; import { isEraserActive } from "../appState"; +import "./HintViewer.scss"; + interface HintViewerProps { - appState: AppState; + appState: UIAppState; elements: readonly NonDeletedExcalidrawElement[]; isMobile: boolean; device: Device; diff --git a/src/components/ImageExportDialog.tsx b/src/components/ImageExportDialog.tsx index 0e4eff36..3f8a9679 100644 --- a/src/components/ImageExportDialog.tsx +++ b/src/components/ImageExportDialog.tsx @@ -4,11 +4,10 @@ import { canvasToBlob } from "../data/blob"; import { NonDeletedExcalidrawElement } from "../element/types"; import { t } from "../i18n"; import { getSelectedElements, isSomeElementSelected } from "../scene"; -import { AppState, BinaryFiles } from "../types"; +import { BinaryFiles, UIAppState } from "../types"; import { Dialog } from "./Dialog"; import { clipboard } from "./icons"; import Stack from "./Stack"; -import "./ExportDialog.scss"; import OpenColor from "open-color"; import { CheckboxItem } from "./CheckboxItem"; import { DEFAULT_EXPORT_PADDING, isFirefox } from "../constants"; @@ -16,6 +15,8 @@ import { nativeFileSystemSupported } from "../data/filesystem"; import { ActionManager } from "../actions/manager"; import { exportToCanvas } from "../packages/utils"; +import "./ExportDialog.scss"; + const supportsContextFilters = "filter" in document.createElement("canvas").getContext("2d")!; @@ -70,7 +71,7 @@ const ImageExportModal = ({ onExportToSvg, onExportToClipboard, }: { - appState: AppState; + appState: UIAppState; elements: readonly NonDeletedExcalidrawElement[]; files: BinaryFiles; exportPadding?: number; @@ -216,8 +217,8 @@ export const ImageExportDialog = ({ onExportToSvg, onExportToClipboard, }: { - appState: AppState; - setAppState: React.Component["setState"]; + appState: UIAppState; + setAppState: React.Component["setState"]; elements: readonly NonDeletedExcalidrawElement[]; files: BinaryFiles; exportPadding?: number; diff --git a/src/components/JSONExportDialog.tsx b/src/components/JSONExportDialog.tsx index 8f89ebcb..f40ebe45 100644 --- a/src/components/JSONExportDialog.tsx +++ b/src/components/JSONExportDialog.tsx @@ -2,7 +2,7 @@ import React from "react"; import { NonDeletedExcalidrawElement } from "../element/types"; import { t } from "../i18n"; -import { AppState, ExportOpts, BinaryFiles } from "../types"; +import { ExportOpts, BinaryFiles, UIAppState } from "../types"; import { Dialog } from "./Dialog"; import { exportToFileIcon, LinkIcon } from "./icons"; import { ToolButton } from "./ToolButton"; @@ -28,7 +28,7 @@ const JSONExportModal = ({ exportOpts, canvas, }: { - appState: AppState; + appState: UIAppState; files: BinaryFiles; elements: readonly NonDeletedExcalidrawElement[]; actionManager: ActionManager; @@ -96,12 +96,12 @@ export const JSONExportDialog = ({ setAppState, }: { elements: readonly NonDeletedExcalidrawElement[]; - appState: AppState; + appState: UIAppState; files: BinaryFiles; actionManager: ActionManager; exportOpts: ExportOpts; canvas: HTMLCanvasElement | null; - setAppState: React.Component["setState"]; + setAppState: React.Component["setState"]; }) => { const handleClose = React.useCallback(() => { setAppState({ openDialog: null }); diff --git a/src/components/LayerUI.tsx b/src/components/LayerUI.tsx index 6ae01d6a..0e08eafa 100644 --- a/src/components/LayerUI.tsx +++ b/src/components/LayerUI.tsx @@ -8,7 +8,13 @@ import { NonDeletedExcalidrawElement } from "../element/types"; import { Language, t } from "../i18n"; import { calculateScrollCenter } from "../scene"; import { ExportType } from "../scene/types"; -import { AppProps, AppState, ExcalidrawProps, BinaryFiles } from "../types"; +import { + AppProps, + AppState, + ExcalidrawProps, + BinaryFiles, + UIAppState, +} from "../types"; import { capitalizeString, isShallowEqual, muteFSAbortError } from "../utils"; import { SelectedShapeActions, ShapesSwitcher } from "./Actions"; import { ErrorDialog } from "./ErrorDialog"; @@ -49,7 +55,7 @@ import "./Toolbar.scss"; interface LayerUIProps { actionManager: ActionManager; - appState: AppState; + appState: UIAppState; files: BinaryFiles; canvas: HTMLCanvasElement | null; setAppState: React.Component["setState"]; @@ -144,7 +150,8 @@ const LayerUI = ({ const fileHandle = await exportCanvas( type, exportedElements, - appState, + // FIXME once we split UI canvas from element canvas + appState as AppState, files, { exportBackground: appState.exportBackground, @@ -458,9 +465,9 @@ const LayerUI = ({