Skip to content

Commit

Permalink
Site Assembler: ensure only one snackbar notice is shown per unique a…
Browse files Browse the repository at this point in the history
…ction (#78421)
  • Loading branch information
fushar authored Jun 22, 2023
1 parent cfd1c23 commit beeef9b
Show file tree
Hide file tree
Showing 2 changed files with 98 additions and 63 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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';
Expand Down Expand Up @@ -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 );
Expand Down Expand Up @@ -212,10 +211,6 @@ const PatternAssembler = ( {
} );
};

const showNotice = ( action: string, pattern: Pattern ) => {
noticeOperations.createNotice( { content: getNoticeContent( action, pattern ) } );
};

const getDesign = () =>
( {
...selectedDesign,
Expand All @@ -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 );
}
};

Expand All @@ -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 );
}
};

Expand All @@ -276,7 +271,7 @@ const PatternAssembler = ( {
...sections.slice( sectionPosition + 1 ),
] );
updateActivePatternPosition( sectionPosition );
showNotice( 'replace', pattern );
noticeOperations.showPatternReplacedNotice( pattern );
}
};

Expand All @@ -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 );
};
Expand Down Expand Up @@ -564,7 +559,7 @@ const PatternAssembler = ( {
ref={ wrapperRef }
tabIndex={ -1 }
>
<Notices noticeList={ noticeList } noticeOperations={ noticeOperations } />
{ noticeUI }
<div className="pattern-assembler__sidebar">
<NavigatorScreen path={ NAVIGATOR_PATHS.MAIN }>
<ScreenMain
Expand Down Expand Up @@ -703,7 +698,7 @@ const PatternAssembler = ( {
);
};

const PatternAssemblerStep = ( props: StepProps & withNotices.Props ) => (
const PatternAssemblerStep = ( props: StepProps & NoticesProps ) => (
<NavigatorProvider initialPath={ NAVIGATOR_PATHS.MAIN } tabIndex={ -1 }>
<PatternAssembler { ...props } />
</NavigatorProvider>
Expand Down
Original file line number Diff line number Diff line change
@@ -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';

Expand All @@ -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 <SnackbarList notices={ noticeList } onRemove={ onRemoveNotice } />;
};
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 = <SnackbarList notices={ noticeList } onRemove={ removeNotice } />;

const propsWithNotices = {
...props,
noticeOperations,
noticeUI,
};

return <InnerComponent { ...propsWithNotices } />;
};
},
'withNotices'
);

export default Notices;
export default withNotices;

0 comments on commit beeef9b

Please sign in to comment.