From beeef9b8e0c64ce56d72ec6af93f636009a1d778 Mon Sep 17 00:00:00 2001 From: Ashar Fuadi Date: Thu, 22 Jun 2023 16:46:36 +0700 Subject: [PATCH] Site Assembler: ensure only one snackbar notice is shown per unique action (#78421) --- .../pattern-assembler/index.tsx | 33 ++--- .../pattern-assembler/notices/notices.tsx | 128 ++++++++++++------ 2 files changed, 98 insertions(+), 63 deletions(-) diff --git a/client/landing/stepper/declarative-flow/internals/steps-repository/pattern-assembler/index.tsx b/client/landing/stepper/declarative-flow/internals/steps-repository/pattern-assembler/index.tsx index 90ed32ee337e8d..1c2e1563d1eee8 100644 --- a/client/landing/stepper/declarative-flow/internals/steps-repository/pattern-assembler/index.tsx +++ b/client/landing/stepper/declarative-flow/internals/steps-repository/pattern-assembler/index.tsx @@ -6,7 +6,6 @@ import { __experimentalNavigatorProvider as NavigatorProvider, __experimentalNavigatorScreen as NavigatorScreen, __experimentalUseNavigator as useNavigator, - withNotices, } from '@wordpress/components'; import { compose } from '@wordpress/compose'; import { useDispatch, useSelect } from '@wordpress/data'; @@ -33,7 +32,7 @@ import usePatternCategories from './hooks/use-pattern-categories'; import usePatternsMapByCategory from './hooks/use-patterns-map-by-category'; import { usePrefetchImages } from './hooks/use-prefetch-images'; import useRecipe from './hooks/use-recipe'; -import Notices, { getNoticeContent } from './notices/notices'; +import withNotices, { NoticesProps } from './notices/notices'; import PatternAssemblerContainer from './pattern-assembler-container'; import PatternLargePreview from './pattern-large-preview'; import ScreenActivation from './screen-activation'; @@ -61,9 +60,9 @@ const PatternAssembler = ( { navigation, flow, stepName, - noticeList, noticeOperations, -}: StepProps & withNotices.Props ) => { + noticeUI, +}: StepProps & NoticesProps ) => { const translate = useTranslate(); const navigator = useNavigator(); const [ sectionPosition, setSectionPosition ] = useState< number | null >( null ); @@ -212,10 +211,6 @@ const PatternAssembler = ( { } ); }; - const showNotice = ( action: string, pattern: Pattern ) => { - noticeOperations.createNotice( { content: getNoticeContent( action, pattern ) } ); - }; - const getDesign = () => ( { ...selectedDesign, @@ -237,12 +232,12 @@ const PatternAssembler = ( { updateActivePatternPosition( -1 ); if ( pattern ) { if ( header ) { - showNotice( 'replace', pattern ); + noticeOperations.showPatternReplacedNotice( pattern ); } else { - showNotice( 'add', pattern ); + noticeOperations.showPatternInsertedNotice( pattern ); } } else if ( header ) { - showNotice( 'remove', header ); + noticeOperations.showPatternRemovedNotice( header ); } }; @@ -256,12 +251,12 @@ const PatternAssembler = ( { activateFooterPosition( !! pattern ); if ( pattern ) { if ( footer ) { - showNotice( 'replace', pattern ); + noticeOperations.showPatternReplacedNotice( pattern ); } else { - showNotice( 'add', pattern ); + noticeOperations.showPatternInsertedNotice( pattern ); } } else if ( footer ) { - showNotice( 'remove', footer ); + noticeOperations.showPatternRemovedNotice( footer ); } }; @@ -276,7 +271,7 @@ const PatternAssembler = ( { ...sections.slice( sectionPosition + 1 ), ] ); updateActivePatternPosition( sectionPosition ); - showNotice( 'replace', pattern ); + noticeOperations.showPatternReplacedNotice( pattern ); } }; @@ -289,11 +284,11 @@ const PatternAssembler = ( { }, ] ); updateActivePatternPosition( sections.length ); - showNotice( 'add', pattern ); + noticeOperations.showPatternInsertedNotice( pattern ); }; const deleteSection = ( position: number ) => { - showNotice( 'remove', sections[ position ] ); + noticeOperations.showPatternRemovedNotice( sections[ position ] ); setSections( [ ...sections.slice( 0, position ), ...sections.slice( position + 1 ) ] ); updateActivePatternPosition( position ); }; @@ -564,7 +559,7 @@ const PatternAssembler = ( { ref={ wrapperRef } tabIndex={ -1 } > - + { noticeUI }
( +const PatternAssemblerStep = ( props: StepProps & NoticesProps ) => ( diff --git a/client/landing/stepper/declarative-flow/internals/steps-repository/pattern-assembler/notices/notices.tsx b/client/landing/stepper/declarative-flow/internals/steps-repository/pattern-assembler/notices/notices.tsx index f6d3445e08fac9..a3197993942501 100644 --- a/client/landing/stepper/declarative-flow/internals/steps-repository/pattern-assembler/notices/notices.tsx +++ b/client/landing/stepper/declarative-flow/internals/steps-repository/pattern-assembler/notices/notices.tsx @@ -1,6 +1,7 @@ -import { SnackbarList, withNotices, NoticeList } from '@wordpress/components'; +import { NoticeList, SnackbarList } from '@wordpress/components'; +import { createHigherOrderComponent } from '@wordpress/compose'; import i18n from 'i18n-calypso'; -import { useEffect } from 'react'; +import { ComponentType, ReactNode, ReactChild, useState } from 'react'; import type { Pattern } from '../types'; import './notices.scss'; @@ -10,47 +11,86 @@ type Notice = NoticeList.Notice & { timer?: ReturnType< typeof setTimeout >; }; -const Notices = ( { - noticeList, - noticeOperations, -}: Pick< withNotices.Props, 'noticeList' | 'noticeOperations' > ) => { - const onRemoveNotice = ( id: string ) => { - const notice = noticeList.find( ( notice ) => id === notice.id ) as Notice; - if ( notice?.timer ) { - clearTimeout( notice.timer ); - delete notice.timer; - } - noticeOperations.removeNotice( id ); - }; - - useEffect( () => { - const lastNotice = noticeList[ noticeList.length - 1 ] as Notice; - - if ( lastNotice?.id && ! lastNotice?.timer ) { - lastNotice.timer = setTimeout( - () => noticeOperations.removeNotice( lastNotice.id ), - NOTICE_TIMEOUT - ); - } - }, [ noticeList, noticeOperations ] ); - - return ; -}; +interface NoticeOperationsProps { + showPatternInsertedNotice: ( pattern: Pattern ) => void; + showPatternReplacedNotice: ( pattern: Pattern ) => void; + showPatternRemovedNotice: ( pattern: Pattern ) => void; +} -export const getNoticeContent = ( action: string, pattern: Pattern ) => { - const actions: { [ key: string ]: any } = { - add: i18n.translate( 'Block pattern "%(patternName)s" inserted.', { - args: { patternName: pattern.title }, - } ), - replace: i18n.translate( 'Block pattern "%(patternName)s" replaced.', { - args: { patternName: pattern.title }, - } ), - remove: i18n.translate( 'Block pattern "%(patternName)s" removed.', { - args: { patternName: pattern.title }, - } ), - }; - - return actions[ action ]; -}; +export interface NoticesProps { + noticeOperations: NoticeOperationsProps; + noticeUI: ReactNode; +} + +const withNotices = createHigherOrderComponent( + < OuterProps, >( InnerComponent: ComponentType< OuterProps > ) => { + return ( props: OuterProps & NoticesProps ) => { + const [ noticeList, setNoticeList ] = useState< Notice[] >( [] ); + + const removeNotice = ( id: string ) => { + setNoticeList( ( current ) => current.filter( ( notice ) => notice.id !== id ) ); + }; + + const createNotice = ( id: string, content: ReactChild ) => { + const existingNoticeWithSameId = noticeList.find( ( notice ) => notice.id === id ); + if ( existingNoticeWithSameId?.timer ) { + clearTimeout( existingNoticeWithSameId.timer ); + delete existingNoticeWithSameId.timer; + } + + const newNotice = { + id, + content, + timer: setTimeout( () => { + removeNotice( id ); + }, NOTICE_TIMEOUT ), + }; + + setNoticeList( ( current ) => [ + ...current.filter( ( notice ) => notice.id !== id ), + newNotice, + ] ); + }; + + const noticeOperations: NoticeOperationsProps = { + showPatternInsertedNotice: ( pattern: Pattern ) => { + createNotice( + 'pattern-inserted', + i18n.translate( 'Block pattern "%(patternName)s" inserted.', { + args: { patternName: pattern.title }, + } ) + ); + }, + showPatternReplacedNotice: ( pattern: Pattern ) => { + createNotice( + 'pattern-replaced', + i18n.translate( 'Block pattern "%(patternName)s" replaced.', { + args: { patternName: pattern.title }, + } ) + ); + }, + showPatternRemovedNotice: ( pattern: Pattern ) => { + createNotice( + 'pattern-removed', + i18n.translate( 'Block pattern "%(patternName)s" removed.', { + args: { patternName: pattern.title }, + } ) + ); + }, + }; + + const noticeUI = ; + + const propsWithNotices = { + ...props, + noticeOperations, + noticeUI, + }; + + return ; + }; + }, + 'withNotices' +); -export default Notices; +export default withNotices;