From 4f627a034208273a4c0ac76dadd2e627deecf220 Mon Sep 17 00:00:00 2001 From: Mike Sawka Date: Wed, 26 Jun 2024 09:31:43 -0700 Subject: [PATCH] react.memo (#79) --- frontend/app/block/block.tsx | 132 +++++++------- frontend/app/element/button.tsx | 4 +- frontend/app/tab/tab.tsx | 221 ++++++++++++------------ frontend/app/tab/tabbar.tsx | 4 +- frontend/app/tab/tabcontent.tsx | 53 +++--- frontend/app/view/directorypreview.less | 7 + frontend/app/view/directorypreview.tsx | 17 +- frontend/app/workspace/workspace.tsx | 12 +- frontend/faraday/lib/TileLayout.tsx | 134 ++++++++------ 9 files changed, 322 insertions(+), 262 deletions(-) diff --git a/frontend/app/block/block.tsx b/frontend/app/block/block.tsx index 510b73dcf..d54fb54e3 100644 --- a/frontend/app/block/block.tsx +++ b/frontend/app/block/block.tsx @@ -174,7 +174,7 @@ interface FramelessBlockHeaderProps { dragHandleRef?: React.RefObject; } -const FramelessBlockHeader = ({ blockId, onClose, dragHandleRef }: FramelessBlockHeaderProps) => { +const FramelessBlockHeader = React.memo(({ blockId, onClose, dragHandleRef }: FramelessBlockHeaderProps) => { const [blockData] = WOS.useWaveObjectValue(WOS.makeORef("block", blockId)); const settingsConfig = jotai.useAtomValue(atoms.settingsConfigAtom); @@ -193,7 +193,7 @@ const FramelessBlockHeader = ({ blockId, onClose, dragHandleRef }: FramelessBloc )} ); -}; +}); const hoverStateOff = "off"; const hoverStatePending = "pending"; @@ -209,62 +209,56 @@ interface BlockFrameProps { dragHandleRef?: React.RefObject; } -const BlockFrame_Tech = ({ - blockId, - onClose, - onClick, - preview, - blockRef, - dragHandleRef, - children, -}: BlockFrameProps) => { - const [blockData] = WOS.useWaveObjectValue(WOS.makeORef("block", blockId)); - const settingsConfig = jotai.useAtomValue(atoms.settingsConfigAtom); - const isFocusedAtom = useBlockAtom(blockId, "isFocused", () => { - return jotai.atom((get) => { - const winData = get(atoms.waveWindow); - return winData.activeblockid === blockId; +const BlockFrame_Tech = React.memo( + ({ blockId, onClose, onClick, preview, blockRef, dragHandleRef, children }: BlockFrameProps) => { + const [blockData] = WOS.useWaveObjectValue(WOS.makeORef("block", blockId)); + const settingsConfig = jotai.useAtomValue(atoms.settingsConfigAtom); + const isFocusedAtom = useBlockAtom(blockId, "isFocused", () => { + return jotai.atom((get) => { + const winData = get(atoms.waveWindow); + return winData.activeblockid === blockId; + }); }); - }); - let isFocused = jotai.useAtomValue(isFocusedAtom); - const blockIcon = useBlockIcon(blockId); + let isFocused = jotai.useAtomValue(isFocusedAtom); + const blockIcon = useBlockIcon(blockId); - if (preview) { - isFocused = true; - } - let style: React.CSSProperties = {}; - if (!isFocused && blockData?.meta?.["frame:bordercolor"]) { - style.borderColor = blockData.meta["frame:bordercolor"]; - } - if (isFocused && blockData?.meta?.["frame:bordercolor:focused"]) { - style.borderColor = blockData.meta["frame:bordercolor:focused"]; - } - return ( -
+ if (preview) { + isFocused = true; + } + let style: React.CSSProperties = {}; + if (!isFocused && blockData?.meta?.["frame:bordercolor"]) { + style.borderColor = blockData.meta["frame:bordercolor"]; + } + if (isFocused && blockData?.meta?.["frame:bordercolor:focused"]) { + style.borderColor = blockData.meta["frame:bordercolor:focused"]; + } + return (
handleHeaderContextMenu(e, blockData, onClose)} + className={clsx( + "block", + "block-frame-tech", + isFocused ? "block-focused" : null, + preview ? "block-preview" : null + )} + onClick={onClick} + ref={blockRef} + style={style} > - {getBlockHeaderText(blockIcon, blockData, settingsConfig)} +
handleHeaderContextMenu(e, blockData, onClose)} + > + {getBlockHeaderText(blockIcon, blockData, settingsConfig)} +
+
+ +
+ {preview ?
: children}
-
- -
- {preview ?
: children} -
- ); -}; + ); + } +); const BlockFrame_Frameless = ({ blockId, @@ -340,7 +334,7 @@ const BlockFrame_Frameless = ({ ); }; -const BlockFrame = (props: BlockFrameProps) => { +const BlockFrame = React.memo((props: BlockFrameProps) => { const blockId = props.blockId; const [blockData, blockDataLoading] = WOS.useWaveObjectValue(WOS.makeORef("block", blockId)); const tabData = jotai.useAtomValue(atoms.tabAtom); @@ -360,7 +354,7 @@ const BlockFrame = (props: BlockFrameProps) => { FrameElem = BlockFrame_Frameless; } return ; -}; +}); function blockViewToIcon(view: string): string { console.log("blockViewToIcon", view); @@ -398,7 +392,16 @@ function useBlockIcon(blockId: string): string { return blockIcon; } -const Block = ({ blockId, onClose, dragHandleRef }: BlockProps) => { +const wm = new WeakMap(); +let wmCounter = 0; +function getObjectId(obj: any): number { + if (!wm.has(obj)) { + wm.set(obj, wmCounter++); + } + return wm.get(obj); +} + +const Block = React.memo(({ blockId, onClose, dragHandleRef }: BlockProps) => { let blockElem: JSX.Element = null; const focusElemRef = React.useRef(null); const blockRef = React.useRef(null); @@ -417,24 +420,29 @@ const Block = ({ blockId, onClose, dragHandleRef }: BlockProps) => { setBlockFocus(blockId); }, [blockClicked]); + const setBlockClickedTrue = React.useCallback(() => { + setBlockClicked(true); + }, []); + if (!blockId || !blockData) return null; if (blockDataLoading) { blockElem = Loading...; } else if (blockData.view === "term") { - blockElem = ; + blockElem = ; } else if (blockData.view === "preview") { - blockElem = ; + blockElem = ; } else if (blockData.view === "plot") { - blockElem = ; + blockElem = ; } else if (blockData.view === "codeedit") { - blockElem = ; + blockElem = ; } return ( setBlockClicked(true)} + onClick={setBlockClickedTrue} blockRef={blockRef} dragHandleRef={dragHandleRef} > @@ -448,6 +456,6 @@ const Block = ({ blockId, onClose, dragHandleRef }: BlockProps) => {
); -}; +}); export { Block, BlockFrame }; diff --git a/frontend/app/element/button.tsx b/frontend/app/element/button.tsx index 9153c6fe7..1cec10795 100644 --- a/frontend/app/element/button.tsx +++ b/frontend/app/element/button.tsx @@ -6,7 +6,7 @@ interface ButtonProps extends React.ButtonHTMLAttributes { className?: string; } -const Button: React.FC = ({ className = "primary", children, disabled, ...props }) => { +const Button: React.FC = React.memo(({ className = "primary", children, disabled, ...props }) => { const hasIcon = React.Children.toArray(children).some( (child) => React.isValidElement(child) && (child as React.ReactElement).type === "svg" ); @@ -23,6 +23,6 @@ const Button: React.FC = ({ className = "primary", children, disabl {children} ); -}; +}); export { Button }; diff --git a/frontend/app/tab/tab.tsx b/frontend/app/tab/tab.tsx index 0f243c426..b9a3a9bb5 100644 --- a/frontend/app/tab/tab.tsx +++ b/frontend/app/tab/tab.tsx @@ -6,6 +6,7 @@ import { ContextMenuModel } from "@/store/contextmenu"; import * as services from "@/store/services"; import * as WOS from "@/store/wos"; import { clsx } from "clsx"; +import * as React from "react"; import { forwardRef, useEffect, useRef, useState } from "react"; import "./tab.less"; @@ -22,129 +23,131 @@ interface TabProps { onLoaded: () => void; } -const Tab = forwardRef( - ({ id, active, isFirst, isBeforeActive, isDragging, onLoaded, onSelect, onClose, onDragStart }, ref) => { - const [tabData, tabLoading] = WOS.useWaveObjectValue(WOS.makeORef("tab", id)); - const [originalName, setOriginalName] = useState(""); - const [isEditable, setIsEditable] = useState(false); +const Tab = React.memo( + forwardRef( + ({ id, active, isFirst, isBeforeActive, isDragging, onLoaded, onSelect, onClose, onDragStart }, ref) => { + const [tabData, tabLoading] = WOS.useWaveObjectValue(WOS.makeORef("tab", id)); + const [originalName, setOriginalName] = useState(""); + const [isEditable, setIsEditable] = useState(false); - const editableRef = useRef(null); - const editableTimeoutRef = useRef(); - const loadedRef = useRef(false); + const editableRef = useRef(null); + const editableTimeoutRef = useRef(); + const loadedRef = useRef(false); - useEffect(() => { - if (tabData?.name) { - setOriginalName(tabData.name); - } - }, [tabData]); + useEffect(() => { + if (tabData?.name) { + setOriginalName(tabData.name); + } + }, [tabData]); - useEffect(() => { - return () => { - if (editableTimeoutRef.current) { - clearTimeout(editableTimeoutRef.current); + useEffect(() => { + return () => { + if (editableTimeoutRef.current) { + clearTimeout(editableTimeoutRef.current); + } + }; + }, []); + + const handleDoubleClick = (event) => { + event.stopPropagation(); + setIsEditable(true); + editableTimeoutRef.current = setTimeout(() => { + if (editableRef.current) { + editableRef.current.focus(); + document.execCommand("selectAll", false); + } + }, 0); + }; + + const handleBlur = () => { + let newText = editableRef.current.innerText.trim(); + newText = newText || originalName; + editableRef.current.innerText = newText; + setIsEditable(false); + services.ObjectService.UpdateTabName(id, newText); + }; + + const handleKeyDown = (event) => { + if ((event.metaKey || event.ctrlKey) && event.key === "a") { + event.preventDefault(); + if (editableRef.current) { + const range = document.createRange(); + const selection = window.getSelection(); + range.selectNodeContents(editableRef.current); + selection.removeAllRanges(); + selection.addRange(range); + } + return; + } + + if (event.key === "Enter") { + event.preventDefault(); + if (editableRef.current.innerText.trim() === "") { + editableRef.current.innerText = originalName; + } + editableRef.current.blur(); + } else if (event.key === "Escape") { + editableRef.current.innerText = originalName; + editableRef.current.blur(); + } else if ( + editableRef.current.innerText.length >= 8 && + !["Backspace", "Delete", "ArrowLeft", "ArrowRight"].includes(event.key) + ) { + event.preventDefault(); } }; - }, []); - const handleDoubleClick = (event) => { - event.stopPropagation(); - setIsEditable(true); - editableTimeoutRef.current = setTimeout(() => { - if (editableRef.current) { - editableRef.current.focus(); - document.execCommand("selectAll", false); + useEffect(() => { + if (!loadedRef.current) { + onLoaded(); + loadedRef.current = true; } - }, 0); - }; + }, [onLoaded]); - const handleBlur = () => { - let newText = editableRef.current.innerText.trim(); - newText = newText || originalName; - editableRef.current.innerText = newText; - setIsEditable(false); - services.ObjectService.UpdateTabName(id, newText); - }; + // Prevent drag from being triggered on mousedown + const handleMouseDownOnClose = (event: React.MouseEvent) => { + event.stopPropagation(); + }; - const handleKeyDown = (event) => { - if ((event.metaKey || event.ctrlKey) && event.key === "a") { - event.preventDefault(); - if (editableRef.current) { - const range = document.createRange(); - const selection = window.getSelection(); - range.selectNodeContents(editableRef.current); - selection.removeAllRanges(); - selection.addRange(range); - } - return; + function handleContextMenu(e: React.MouseEvent) { + e.preventDefault(); + let menu: ContextMenuItem[] = []; + menu.push({ label: "Copy TabId", click: () => navigator.clipboard.writeText(id) }); + menu.push({ type: "separator" }); + menu.push({ label: "Close Tab", click: () => onClose(null) }); + ContextMenuModel.showContextMenu(menu, e); } - if (event.key === "Enter") { - event.preventDefault(); - if (editableRef.current.innerText.trim() === "") { - editableRef.current.innerText = originalName; - } - editableRef.current.blur(); - } else if (event.key === "Escape") { - editableRef.current.innerText = originalName; - editableRef.current.blur(); - } else if ( - editableRef.current.innerText.length >= 8 && - !["Backspace", "Delete", "ArrowLeft", "ArrowRight"].includes(event.key) - ) { - event.preventDefault(); - } - }; - - useEffect(() => { - if (!loadedRef.current) { - onLoaded(); - loadedRef.current = true; - } - }, [onLoaded]); - - // Prevent drag from being triggered on mousedown - const handleMouseDownOnClose = (event: React.MouseEvent) => { - event.stopPropagation(); - }; - - function handleContextMenu(e: React.MouseEvent) { - e.preventDefault(); - let menu: ContextMenuItem[] = []; - menu.push({ label: "Copy TabId", click: () => navigator.clipboard.writeText(id) }); - menu.push({ type: "separator" }); - menu.push({ label: "Close Tab", click: () => onClose(null) }); - ContextMenuModel.showContextMenu(menu, e); - } - - return ( -
- {isFirst &&
} + return (
- {tabData?.name} + {isFirst &&
} +
+ {tabData?.name} +
+ {!isDragging &&
} + {active &&
} +
- {!isDragging &&
} - {active &&
} - -
- ); - } + ); + } + ) ); export { Tab }; diff --git a/frontend/app/tab/tabbar.tsx b/frontend/app/tab/tabbar.tsx index 6d419ee69..50905bdaf 100644 --- a/frontend/app/tab/tabbar.tsx +++ b/frontend/app/tab/tabbar.tsx @@ -37,7 +37,7 @@ interface TabBarProps { workspace: Workspace; } -const TabBar = ({ workspace }: TabBarProps) => { +const TabBar = React.memo(({ workspace }: TabBarProps) => { const [tabIds, setTabIds] = useState([]); const [dragStartPositions, setDragStartPositions] = useState([]); const [draggingTab, setDraggingTab] = useState(); @@ -497,6 +497,6 @@ const TabBar = ({ workspace }: TabBarProps) => {
); -}; +}); export { TabBar }; diff --git a/frontend/app/tab/tabcontent.tsx b/frontend/app/tab/tabcontent.tsx index 79090ad64..64452aca1 100644 --- a/frontend/app/tab/tabcontent.tsx +++ b/frontend/app/tab/tabcontent.tsx @@ -4,16 +4,17 @@ import { Block, BlockFrame } from "@/app/block/block"; import * as services from "@/store/services"; import * as WOS from "@/store/wos"; +import * as React from "react"; import { CenteredDiv, CenteredLoadingDiv } from "@/element/quickelems"; import { TileLayout } from "@/faraday/index"; import { getLayoutStateAtomForTab } from "@/faraday/lib/layoutAtom"; import { useAtomValue } from "jotai"; -import { useCallback, useMemo } from "react"; +import { useMemo } from "react"; import { getApi } from "../store/global"; import "./tabcontent.less"; -const TabContent = ({ tabId }: { tabId: string }) => { +const TabContent = React.memo(({ tabId }: { tabId: string }) => { const oref = useMemo(() => WOS.makeORef("tab", tabId), [tabId]); const loadingAtom = useMemo(() => WOS.getWaveObjectLoadingAtom(oref), [oref]); const tabLoading = useAtomValue(loadingAtom); @@ -21,31 +22,40 @@ const TabContent = ({ tabId }: { tabId: string }) => { const layoutStateAtom = useMemo(() => getLayoutStateAtomForTab(tabId, tabAtom), [tabAtom, tabId]); const tabData = useAtomValue(tabAtom); - const renderBlock = useCallback( - ( + const tileLayoutContents = useMemo(() => { + function renderBlock( tabData: TabLayoutData, ready: boolean, onClose: () => void, dragHandleRef: React.RefObject - ) => { + ) { if (!tabData.blockId || !ready) { return null; } - return ; - }, - [] - ); + return ( + + ); + } - const renderPreview = useCallback((tabData: TabLayoutData) => { - return ; - }, []); + function renderPreview(tabData: TabLayoutData) { + return ; + } - const onNodeDelete = useCallback((data: TabLayoutData) => { - return services.ObjectService.DeleteBlock(data.blockId); - }, []); + function onNodeDelete(data: TabLayoutData) { + return services.ObjectService.DeleteBlock(data.blockId); + } - const getCursorPoint = useCallback(() => { - return getApi().getCursorPoint(); + return { + renderContent: renderBlock, + renderPreview: renderPreview, + tabId: tabId, + onNodeDelete: onNodeDelete, + }; }, []); if (tabLoading) { @@ -68,15 +78,12 @@ const TabContent = ({ tabId }: { tabId: string }) => {
); -}; +}); export { TabContent }; diff --git a/frontend/app/view/directorypreview.less b/frontend/app/view/directorypreview.less index 2400a5bad..31f5afa09 100644 --- a/frontend/app/view/directorypreview.less +++ b/frontend/app/view/directorypreview.less @@ -67,6 +67,13 @@ &.col-size { text-align: right; } + + .dir-table-lastmod, + .dir-table-modestr, + .dir-table-size, + .dir-table-type { + color: var(--secondary-text-color); + } } } } diff --git a/frontend/app/view/directorypreview.tsx b/frontend/app/view/directorypreview.tsx index ddc1e7f5d..9e3115959 100644 --- a/frontend/app/view/directorypreview.tsx +++ b/frontend/app/view/directorypreview.tsx @@ -158,37 +158,40 @@ function DirectoryTable({ data, cwd, setFileName }: DirectoryTableProps) { () => [ columnHelper.accessor("mimetype", { cell: (info) => , - header: () => , + header: () => Type, id: "logo", size: 25, enableSorting: false, }), columnHelper.accessor("path", { - cell: (info) => info.getValue(), + cell: (info) => {info.getValue()}, header: () => Name, sortingFn: "alphanumeric", }), columnHelper.accessor("modestr", { - cell: (info) => info.getValue(), + cell: (info) => {info.getValue()}, header: () => Permissions, size: 91, sortingFn: "alphanumeric", }), columnHelper.accessor("modtime", { - cell: (info) => - getLastModifiedTime(info.getValue(), settings.datetime.locale, settings.datetime.format), + cell: (info) => ( + + {getLastModifiedTime(info.getValue(), settings.datetime.locale, settings.datetime.format)} + + ), header: () => Last Modified, size: 185, sortingFn: "datetime", }), columnHelper.accessor("size", { - cell: (info) => getBestUnit(info.getValue()), + cell: (info) => {getBestUnit(info.getValue())}, header: () => Size, size: 55, sortingFn: "auto", }), columnHelper.accessor("mimetype", { - cell: (info) => info.getValue(), + cell: (info) => {info.getValue()}, header: () => Type, sortingFn: "alphanumeric", }), diff --git a/frontend/app/workspace/workspace.tsx b/frontend/app/workspace/workspace.tsx index 6f2109b0d..b1008f9cc 100644 --- a/frontend/app/workspace/workspace.tsx +++ b/frontend/app/workspace/workspace.tsx @@ -14,7 +14,7 @@ import "./workspace.less"; const iconRegex = /^[a-z0-9-]+$/; -function Widgets() { +const Widgets = React.memo(() => { const settingsConfig = jotai.useAtomValue(atoms.settingsConfigAtom); const newWidgetModalVisible = React.useState(false); async function clickTerminal() { @@ -92,27 +92,27 @@ function Widgets() {
); -} +}); -function WorkspaceElem() { +const WorkspaceElem = React.memo(() => { const windowData = jotai.useAtomValue(atoms.waveWindow); const activeTabId = windowData?.activetabid; const ws = jotai.useAtomValue(atoms.workspace); return (
- +
{activeTabId == "" ? ( No Active Tab ) : ( <> - + )}
); -} +}); export { WorkspaceElem as Workspace }; diff --git a/frontend/faraday/lib/TileLayout.tsx b/frontend/faraday/lib/TileLayout.tsx index ef707da30..ec99aff73 100644 --- a/frontend/faraday/lib/TileLayout.tsx +++ b/frontend/faraday/lib/TileLayout.tsx @@ -37,11 +37,11 @@ import { import "./tilelayout.less"; import { Dimensions, FlexDirection, setTransform as createTransform, determineDropDirection } from "./utils"; -export interface TileLayoutProps { - /** - * The atom containing the layout tree state. - */ - layoutTreeStateAtom: WritableLayoutTreeStateAtom; +/** + * contains callbacks and information about the contents (or styling) of of the TileLayout + * nothing in here is specific to the TileLayout itself + */ +export interface TileLayoutContents { /** * A callback that accepts the data from the leaf node and displays the leaf contents to the user. */ @@ -64,6 +64,18 @@ export interface TileLayoutProps { * tabId this TileLayout is associated with */ tabId: string; +} + +export interface TileLayoutProps { + /** + * The atom containing the layout tree state. + */ + layoutTreeStateAtom: WritableLayoutTreeStateAtom; + + /** + * callbacks and information about the contents (or styling) of the TileLayout or contents + */ + contents: TileLayoutContents; /** * A callback for getting the cursor point in reference to the current window. This removes Electron as a runtime dependency, allowing for better integration with Storybook. @@ -75,15 +87,7 @@ export interface TileLayoutProps { const DragPreviewWidth = 300; const DragPreviewHeight = 300; -export const TileLayout = ({ - layoutTreeStateAtom, - tabId, - className, - renderContent, - renderPreview, - onNodeDelete, - getCursorPoint, -}: TileLayoutProps) => { +export const TileLayout = React.memo(({ layoutTreeStateAtom, contents, getCursorPoint }: TileLayoutProps) => { const overlayContainerRef = useRef(null); const displayContainerRef = useRef(null); @@ -126,7 +130,7 @@ export const TileLayout = ({ const [layoutLeafTransforms, setLayoutLeafTransformsRaw] = useState>({}); const setLayoutLeafTransforms = (transforms: Record) => { - globalLayoutTransformsMap.set(tabId, transforms); + globalLayoutTransformsMap.set(contents.tabId, transforms); setLayoutLeafTransformsRaw(transforms); }; @@ -247,30 +251,23 @@ export const TileLayout = ({ // console.log("calling dispatch", deleteAction); dispatch(deleteAction); // console.log("calling onNodeDelete", node); - await onNodeDelete?.(node.data); + await contents.onNodeDelete?.(node.data); // console.log("node deleted"); }, - [onNodeDelete, dispatch] + [contents.onNodeDelete, dispatch] ); return ( -
+
- {layoutLeafTransforms && - layoutTreeState.leafs.map((leaf) => { - return ( - - ); - })} +
({
); -}; +}); + +interface DisplayNodesWrapperProps { + /** + * The layout tree state. + */ + layoutTreeState: LayoutTreeState; + /** + * contains callbacks and information about the contents (or styling) of of the TileLayout + */ + contents: TileLayoutContents; + /** + * A callback that is called when a leaf node gets closed. + * @param node The node that is closed. + */ + onLeafClose: (node: LayoutNode) => void; + /** + * A series of CSS properties used to display a leaf node with the correct dimensions and position, as determined from its corresponding OverlayNode. + */ + layoutLeafTransforms: Record; + /** + * Determines whether the leaf nodes are ready to be displayed to the user. + */ + ready: boolean; +} + +const DisplayNodesWrapper = React.memo( + ({ layoutTreeState, contents, onLeafClose, layoutLeafTransforms, ready }: DisplayNodesWrapperProps) => { + if (!layoutLeafTransforms) { + return null; + } + return layoutTreeState.leafs.map((leaf) => { + return ( + + ); + }); + } +); interface DisplayNodeProps { /** * The leaf node object, containing the data needed to display the leaf contents to the user. */ layoutNode: LayoutNode; + /** - * A callback that accepts the data from the leaf node and displays the leaf contents to the user. + * contains callbacks and information about the contents (or styling) of of the TileLayout */ - renderContent: ContentRenderer; - /** - * A callback that accepts the data from the leaf node and returns a preview that can be shown when the user drags a node. - */ - renderPreview?: PreviewRenderer; + contents: TileLayoutContents; + /** * A callback that is called when a leaf node gets closed. * @param node The node that is closed. @@ -331,14 +370,7 @@ const dragItemType = "TILE_ITEM"; /** * The draggable and displayable portion of a leaf node in a layout tree. */ -const DisplayNode = ({ - layoutNode, - renderContent, - renderPreview, - transform, - onLeafClose, - ready, -}: DisplayNodeProps) => { +const DisplayNode = React.memo(({ layoutNode, contents, transform, onLeafClose, ready }: DisplayNodeProps) => { const tileNodeRef = useRef(null); const dragHandleRef = useRef(null); const previewRef = useRef(null); @@ -370,11 +402,11 @@ const DisplayNode = ({ transform: `scale(${1 / devicePixelRatio})`, }} > - {renderPreview?.(layoutNode.data)} + {contents.renderPreview?.(layoutNode.data)}
); - }, [renderPreview, devicePixelRatio]); + }, [contents.renderPreview, devicePixelRatio]); const [previewImage, setPreviewImage] = useState(null); const [previewImageGeneration, setPreviewImageGeneration] = useState(0); @@ -414,7 +446,7 @@ const DisplayNode = ({ return ( layoutNode.data && (
- {renderContent(layoutNode.data, ready, onClose, dragHandleRef)} + {contents.renderContent(layoutNode.data, ready, onClose, dragHandleRef)}
) ); @@ -436,7 +468,7 @@ const DisplayNode = ({ {previewElement}
); -}; +}); interface OverlayNodeProps { /**