From 8ed0fc2c87ec308fc3960b4543e198f2eb95aefe Mon Sep 17 00:00:00 2001 From: David Luzar Date: Sat, 19 Nov 2022 18:27:54 +0100 Subject: [PATCH] fix: remove legacy React.render() from the editor (#5893) --- src/components/App.tsx | 1 + src/components/ContextMenu.tsx | 55 +++++++++++++--------------- src/components/ImageExportDialog.tsx | 25 ++++--------- 3 files changed, 33 insertions(+), 48 deletions(-) diff --git a/src/components/App.tsx b/src/components/App.tsx index abcc04bc..3f664865 100644 --- a/src/components/App.tsx +++ b/src/components/App.tsx @@ -5917,6 +5917,7 @@ class App extends React.Component { }, type: "canvas" | "element", ) => { + trackEvent("contextMenu", "openContextMenu", type); if (this.state.showHyperlinkPopup) { this.setState({ showHyperlinkPopup: false }); } diff --git a/src/components/ContextMenu.tsx b/src/components/ContextMenu.tsx index d172334f..4b4a5f2f 100644 --- a/src/components/ContextMenu.tsx +++ b/src/components/ContextMenu.tsx @@ -1,4 +1,4 @@ -import { render, unmountComponentAtNode } from "react-dom"; +import { createRoot, Root } from "react-dom/client"; import clsx from "clsx"; import { Popover } from "./Popover"; import { t } from "../i18n"; @@ -89,42 +89,38 @@ const ContextMenu = ({ ); }; -const contextMenuNodeByContainer = new WeakMap(); +const contextMenuRoots = new WeakMap(); -const getContextMenuNode = (container: HTMLElement): HTMLDivElement => { - let contextMenuNode = contextMenuNodeByContainer.get(container); - if (contextMenuNode) { - return contextMenuNode; +const getContextMenuRoot = (container: HTMLElement): Root => { + let contextMenuRoot = contextMenuRoots.get(container); + if (contextMenuRoot) { + return contextMenuRoot; } - contextMenuNode = document.createElement("div"); - container - .querySelector(".excalidraw-contextMenuContainer")! - .appendChild(contextMenuNode); - contextMenuNodeByContainer.set(container, contextMenuNode); - return contextMenuNode; -}; - -type ContextMenuParams = { - options: (ContextMenuOption | false | null | undefined)[]; - top: ContextMenuProps["top"]; - left: ContextMenuProps["left"]; - actionManager: ContextMenuProps["actionManager"]; - appState: Readonly; - container: HTMLElement; - elements: readonly NonDeletedExcalidrawElement[]; + contextMenuRoot = createRoot( + container.querySelector(".excalidraw-contextMenuContainer")!, + ); + contextMenuRoots.set(container, contextMenuRoot); + return contextMenuRoot; }; const handleClose = (container: HTMLElement) => { - const contextMenuNode = contextMenuNodeByContainer.get(container); - if (contextMenuNode) { - unmountComponentAtNode(contextMenuNode); - contextMenuNode.remove(); - contextMenuNodeByContainer.delete(container); + const contextMenuRoot = contextMenuRoots.get(container); + if (contextMenuRoot) { + contextMenuRoot.unmount(); + contextMenuRoots.delete(container); } }; export default { - push(params: ContextMenuParams) { + push(params: { + options: (ContextMenuOption | false | null | undefined)[]; + top: ContextMenuProps["top"]; + left: ContextMenuProps["left"]; + actionManager: ContextMenuProps["actionManager"]; + appState: Readonly; + container: HTMLElement; + elements: readonly NonDeletedExcalidrawElement[]; + }) { const options = Array.of(); params.options.forEach((option) => { if (option) { @@ -132,7 +128,7 @@ export default { } }); if (options.length) { - render( + getContextMenuRoot(params.container).render( , - getContextMenuNode(params.container), ); } }, diff --git a/src/components/ImageExportDialog.tsx b/src/components/ImageExportDialog.tsx index 2a1e0592..40031bfc 100644 --- a/src/components/ImageExportDialog.tsx +++ b/src/components/ImageExportDialog.tsx @@ -1,9 +1,7 @@ import React, { useEffect, useRef, useState } from "react"; -import { render, unmountComponentAtNode } from "react-dom"; import { probablySupportsClipboardBlob } from "../clipboard"; import { canvasToBlob } from "../data/blob"; import { NonDeletedExcalidrawElement } from "../element/types"; -import { CanvasError } from "../errors"; import { t } from "../i18n"; import { getSelectedElements, isSomeElementSelected } from "../scene"; import { exportToCanvas } from "../scene/export"; @@ -33,19 +31,6 @@ export const ErrorCanvasPreview = () => { ); }; -const renderPreview = ( - content: HTMLCanvasElement | Error, - previewNode: HTMLDivElement, -) => { - unmountComponentAtNode(previewNode); - previewNode.innerHTML = ""; - if (content instanceof HTMLCanvasElement) { - previewNode.appendChild(content); - } else { - render(, previewNode); - } -}; - export type ExportCB = ( elements: readonly NonDeletedExcalidrawElement[], scale?: number, @@ -99,6 +84,7 @@ const ImageExportModal = ({ const [exportSelected, setExportSelected] = useState(someElementIsSelected); const previewRef = useRef(null); const { exportBackground, viewBackgroundColor } = appState; + const [renderError, setRenderError] = useState(null); const exportedElements = exportSelected ? getSelectedElements(elements, appState, true) @@ -119,15 +105,16 @@ const ImageExportModal = ({ exportPadding, }) .then((canvas) => { + setRenderError(null); // if converting to blob fails, there's some problem that will // likely prevent preview and export (e.g. canvas too big) return canvasToBlob(canvas).then(() => { - renderPreview(canvas, previewNode); + previewNode.replaceChildren(canvas); }); }) .catch((error) => { console.error(error); - renderPreview(new CanvasError(), previewNode); + setRenderError(error); }); }, [ appState, @@ -140,7 +127,9 @@ const ImageExportModal = ({ return (
-
+
+ {renderError && } +
{supportsContextFilters && actionManager.renderAction("exportWithDarkMode")}