From b3fb34818301cd2c7fa801ed443355abcc24680a Mon Sep 17 00:00:00 2001 From: Daniel Fulop Date: Wed, 13 Mar 2024 11:49:39 +0100 Subject: [PATCH] feat(DoDontBlock): performance improvement (#781) * feat(DoDontBlock): performance improvement * fix tests * conditionally request assets * fix review comments * fix review comments --- package.json | 3 + .../dos-donts-block/src/AssetsProvider.tsx | 26 + packages/dos-donts-block/src/DoDontItem.tsx | 605 +++++++++--------- .../dos-donts-block/src/DosDontsBlock.tsx | 160 +++-- .../src/components/ImageComponent.tsx | 53 +- packages/dos-donts-block/src/const.ts | 5 + packages/dos-donts-block/src/index.ts | 4 +- packages/dos-donts-block/src/settings.ts | 36 +- packages/dos-donts-block/src/types.ts | 9 +- packages/image-block/src/components/Image.tsx | 9 +- .../ResponsiveImage/ResponsiveImage.spec.tsx | 37 +- .../ResponsiveImage/ResponsiveImage.tsx | 17 +- patches/@udecode__plate-emoji@30.7.0.patch | 17 + pnpm-lock.yaml | 10 +- 14 files changed, 563 insertions(+), 428 deletions(-) create mode 100644 packages/dos-donts-block/src/AssetsProvider.tsx create mode 100644 packages/dos-donts-block/src/const.ts create mode 100644 patches/@udecode__plate-emoji@30.7.0.patch diff --git a/package.json b/package.json index 75f093010..c291cbb3c 100644 --- a/package.json +++ b/package.json @@ -48,6 +48,9 @@ "pnpm": { "overrides": { "@codesandbox/sandpack-react@^2.12.1>@codesandbox/sandpack-client": "2.10.0" + }, + "patchedDependencies": { + "@udecode/plate-emoji@30.7.0": "patches/@udecode__plate-emoji@30.7.0.patch" } } } diff --git a/packages/dos-donts-block/src/AssetsProvider.tsx b/packages/dos-donts-block/src/AssetsProvider.tsx new file mode 100644 index 000000000..7417cbfb9 --- /dev/null +++ b/packages/dos-donts-block/src/AssetsProvider.tsx @@ -0,0 +1,26 @@ +/* (c) Copyright Frontify Ltd., all rights reserved. */ + +import { AppBridgeBlock, Asset, useBlockAssets } from '@frontify/app-bridge'; +import { ReactNode, createContext } from 'react'; + +type AssetsProviderProps = { + appBridge: AppBridgeBlock; + children: ReactNode; +}; + +type AssetsContext = { + blockAssets?: Record; + addAssetIdsToKey?: (key: string, assetIds: number[]) => Promise; + deleteAssetIdsFromKey?: (key: string, assetIds: number[]) => Promise; +}; + +export const AssetsContext = createContext({} as AssetsContext); + +export const AssetsProvider = ({ appBridge, children }: AssetsProviderProps) => { + const { blockAssets, addAssetIdsToKey, deleteAssetIdsFromKey } = useBlockAssets(appBridge); + return ( + + {children} + + ); +}; diff --git a/packages/dos-donts-block/src/DoDontItem.tsx b/packages/dos-donts-block/src/DoDontItem.tsx index 8bbfb7163..71cc200cf 100644 --- a/packages/dos-donts-block/src/DoDontItem.tsx +++ b/packages/dos-donts-block/src/DoDontItem.tsx @@ -1,7 +1,7 @@ /* (c) Copyright Frontify Ltd., all rights reserved. */ import { useSortable } from '@dnd-kit/sortable'; -import { Asset, useAssetChooser, useAssetUpload, useBlockAssets, useFileInput } from '@frontify/app-bridge'; +import { Asset, useAssetChooser, useAssetUpload, useFileInput } from '@frontify/app-bridge'; import { IconArrowCircleUp20, IconArrowMove16, @@ -20,349 +20,350 @@ import { toRgbaString, } from '@frontify/guideline-blocks-settings'; import autosize from 'autosize'; -import React, { CSSProperties, useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react'; +import { CSSProperties, memo, useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react'; import IconComponent from './components/IconComponent'; import ImageComponent from './components/ImageComponent'; import { BlockMode, DoDontItemProps, DoDontStyle, DoDontType, SortableDoDontItemProps } from './types'; +import { IMAGES_ASSET_KEY } from './const'; -export const DoDontItem = React.forwardRef( - ( - { - id, - type, - style, - doColor, - dontColor, - onChangeItem, - onChangeLocalItem, - title = '', - body = '', - editing = false, - onRemoveSelf, - hasCustomDoIcon, - hasCustomDontIcon, - dontIconAsset, - doIconAsset, - dontIconChoice, - doIconChoice, - hasStrikethrough, - isDragging = false, - columns, - replaceWithPlaceholder = false, - transformStyle = {}, - draggableProps = {}, - appBridge, - linkedImage, - mode, - customImageHeightValue, - imageDisplay, - imageHeightChoice, - isCustomImageHeight, - backgroundColor, - borderColor, - borderStyle, - borderWidth, - hasBackground, - hasBorder, - hasRadius, - radiusChoice, - radiusValue, - setActivatorNodeRef, - }, - ref - ) => { - const doColorString = toRgbaString(doColor); - const dontColorString = toRgbaString(dontColor); - const { blockAssets, updateAssetIdsFromKey } = useBlockAssets(appBridge); - const { openAssetChooser, closeAssetChooser } = useAssetChooser(appBridge); - const { itemImages } = blockAssets; - const titleRef = useRef(null); +export const DoDontItem = memo((props: DoDontItemProps) => { + const { + id, + type, + style, + doColor, + dontColor, + onChangeItem, + onChangeLocalItem, + title = '', + body = '', + editing = false, + onRemoveSelf, + hasCustomDoIcon, + hasCustomDontIcon, + dontIconAsset, + doIconAsset, + dontIconChoice, + doIconChoice, + hasStrikethrough, + isDragging = false, + replaceWithPlaceholder = false, + draggableProps = {}, + appBridge, + linkedImage, + mode, + customImageHeightValue, + imageDisplay, + imageHeightChoice, + isCustomImageHeight, + backgroundColor, + borderColor, + borderStyle, + borderWidth, + hasBackground, + hasBorder, + hasRadius, + radiusChoice, + radiusValue, + addAssetIdsToKey, + setActivatorNodeRef, + } = props; - const [isUploadLoading, setIsUploadLoading] = useState(false); - const [openFileDialog, { selectedFiles }] = useFileInput({ multiple: false }); - const [uploadFile, { results: uploadResults, doneAll }] = useAssetUpload({ - onUploadProgress: () => !isUploadLoading && setIsUploadLoading(true), - }); + const doColorString = toRgbaString(doColor); + const dontColorString = toRgbaString(dontColor); + const { openAssetChooser, closeAssetChooser } = useAssetChooser(appBridge); + const titleRef = useRef(null); - const onBodyTextChange = useCallback( - (value: string) => value !== body && onChangeItem(id, value, 'body'), - [onChangeItem, body, id] - ); + const [isUploadLoading, setIsUploadLoading] = useState(false); + const [openFileDialog, { selectedFiles }] = useFileInput({ multiple: false }); + const [uploadFile, { results: uploadResults, doneAll }] = useAssetUpload({ + onUploadProgress: () => !isUploadLoading && setIsUploadLoading(true), + }); + + const onBodyTextChange = useCallback( + (value: string) => value !== body && onChangeItem(id, value, 'body'), + [onChangeItem, body, id] + ); - const headingColor = type === DoDontType.Do ? doColorString : dontColorString; + const headingColor = type === DoDontType.Do ? doColorString : dontColorString; - const dividerStyles: Record = { - [DoDontType.Do]: { backgroundColor: doColorString }, - [DoDontType.Dont]: { backgroundColor: dontColorString }, - }; + const dividerStyles: Record = { + [DoDontType.Do]: { backgroundColor: doColorString }, + [DoDontType.Dont]: { backgroundColor: dontColorString }, + }; - const onOpenAssetChooser = () => { - openAssetChooser( - (result: Asset[]) => { - setIsUploadLoading(true); - const imageId = result[0]?.id; - const existingIds = itemImages?.map((x) => x.id) || []; - const newIds = [...new Set([...existingIds, imageId])]; - updateAssetIdsFromKey('itemImages', newIds).then(() => { + const onOpenAssetChooser = () => { + openAssetChooser( + (result: Asset[]) => { + setIsUploadLoading(true); + const imageId = result[0]?.id; + if (addAssetIdsToKey) { + addAssetIdsToKey(IMAGES_ASSET_KEY, [imageId]).then(() => { onChangeItem(id, imageId, 'imageId'); setIsUploadLoading(false); }); - closeAssetChooser(); - }, - { - multiSelection: false, } - ); - }; - - const onUploadClick = () => { - openFileDialog(); - }; - useEffect(() => { - if (selectedFiles) { - setIsUploadLoading(true); - uploadFile(selectedFiles); + closeAssetChooser(); + }, + { + multiSelection: false, } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [selectedFiles]); + ); + }; - useLayoutEffect(() => { - if (titleRef.current) { - autosize(titleRef.current); - autosize.update(titleRef.current); - } - }); + const onUploadClick = () => { + openFileDialog(); + }; + + useEffect(() => { + if (selectedFiles) { + setIsUploadLoading(true); + uploadFile(selectedFiles); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [selectedFiles]); + + useLayoutEffect(() => { + if (titleRef.current) { + autosize(titleRef.current); + autosize.update(titleRef.current); + } + }); - useEffect(() => { - if (doneAll) { - (async (uploadResults) => { - const imageId = uploadResults?.[0]?.id; - const existingIds = itemImages?.map((x) => x.id) || []; - const newIds = [...new Set([...existingIds, imageId])]; - updateAssetIdsFromKey('itemImages', newIds).then(() => { + useEffect(() => { + if (doneAll) { + (async (uploadResults) => { + const imageId = uploadResults?.[0]?.id; + if (addAssetIdsToKey) { + addAssetIdsToKey(IMAGES_ASSET_KEY, [imageId]).then(() => { setIsUploadLoading(false); onChangeItem(id, imageId, 'imageId'); }); - })(uploadResults); - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [doneAll, uploadResults]); + } + })(uploadResults); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [doneAll, uploadResults]); - const shouldRerenderDependency = hasRichTextValue(body) && onBodyTextChange; + const shouldRerenderDependency = hasRichTextValue(body) && onBodyTextChange; - const plugins = useMemo( - () => getDefaultPluginsWithLinkChooser(appBridge), - // eslint-disable-next-line react-hooks/exhaustive-deps - [] - ); + const plugins = useMemo( + () => getDefaultPluginsWithLinkChooser(appBridge), + // eslint-disable-next-line react-hooks/exhaustive-deps + [] + ); - const memoizedRichTextEditor = useMemo( - () => ( - - ), - // eslint-disable-next-line react-hooks/exhaustive-deps - [body, shouldRerenderDependency, editing, appBridge, id] - ); + const memoizedRichTextEditor = useMemo( + () => ( + + ), + // eslint-disable-next-line react-hooks/exhaustive-deps + [body, shouldRerenderDependency, editing, appBridge, id] + ); - return ( -
- , draggableProps, setActivatorNodeRef }, - { type: 'button', icon: , tooltip: 'Delete Item', onClick: onRemoveSelf }, - { - type: 'menu', - items: [ - [ - ...(!!linkedImage - ? [ - { - title: 'Replace with upload', - icon: , - onClick: onUploadClick, - }, - { - title: 'Replace with asset', - icon: , - onClick: onOpenAssetChooser, - }, - ] - : []), - { - title: type === DoDontType.Do ? 'Change to "don\'t"' : 'Change to "do"', - icon: , - onClick: () => - onChangeItem( - id, - type === DoDontType.Do ? DoDontType.Dont : DoDontType.Do, - 'type' - ), - }, - ], - [ - { - title: 'Delete', - icon: , - onClick: onRemoveSelf, - }, - ], + return ( +
+ , draggableProps, setActivatorNodeRef }, + { + type: 'button', + icon: , + tooltip: 'Delete Item', + onClick: () => onRemoveSelf(id), + }, + { + type: 'menu', + items: [ + [ + ...(!!linkedImage + ? [ + { + title: 'Replace with upload', + icon: , + onClick: onUploadClick, + }, + { + title: 'Replace with asset', + icon: , + onClick: onOpenAssetChooser, + }, + ] + : []), + { + title: type === DoDontType.Do ? 'Change to "don\'t"' : 'Change to "do"', + icon: , + onClick: () => + onChangeItem( + id, + type === DoDontType.Do ? DoDontType.Dont : DoDontType.Do, + 'type' + ), + }, + ], + [ + { + title: 'Delete', + icon: , + onClick: () => onRemoveSelf(id), + }, ], - }, - ]} + ], + }, + ]} + > + {mode === BlockMode.TEXT_AND_IMAGE && ( + + )} +
- {mode === BlockMode.TEXT_AND_IMAGE && ( - - )} -
- {style === DoDontStyle.Icons && (title || body || editing) && ( -
- -
- )} -
- -