Skip to content

Commit

Permalink
Refactor starter patterns to editor package
Browse files Browse the repository at this point in the history
Add start page options modal to site editor

Move component to block editor

Avoid bug where context is not defined until the store is populated

Find the content block in the page and use that as the basis for finding and inserting patterns

Refactor modal closed state out of base component

Update classnames to correct convention

Remove double useSelect

Remove unnecessary comments

Select inserted pattern
  • Loading branch information
talldan committed Nov 13, 2023
1 parent e1c30f8 commit 283489e
Show file tree
Hide file tree
Showing 8 changed files with 205 additions and 86 deletions.
4 changes: 4 additions & 0 deletions packages/block-editor/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -763,6 +763,10 @@ _Related_

- <https://github.com/WordPress/gutenberg/blob/HEAD/packages/block-editor/src/components/skip-to-selected-block/README.md>

### StarterPatternsModal

Undocumented declaration.

### store

Store definition for the block editor namespace.
Expand Down
1 change: 1 addition & 0 deletions packages/block-editor/src/components/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ export {
useMouseMoveTypingReset as __unstableUseMouseMoveTypingReset,
} from './observe-typing';
export { default as SkipToSelectedBlock } from './skip-to-selected-block';
export { default as StarterPatternsModal } from './starter-patterns-modal';
export {
default as Typewriter,
useTypewriter as __unstableUseTypewriter,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/**
* WordPress dependencies
*/
import { Modal } from '@wordpress/components';
import { __ } from '@wordpress/i18n';
import { useMemo } from '@wordpress/element';
import { useSelect } from '@wordpress/data';
import { useAsyncList } from '@wordpress/compose';

/**
* Internal dependencies
*/
import { store as blockEditorStore } from '../../store';
import { __experimentalBlockPatternsList as BlockPatternsList } from '../';

function useStarterPatterns( postType, rootClientId ) {
// A pattern is a start pattern if it includes 'core/post-content' in its blockTypes,
// and it has no postTypes declared and the current post type is page or if
// the current post type is part of the postTypes declared.
const blockPatternsWithPostContentBlockType = useSelect(
( select ) =>
select( blockEditorStore ).getPatternsByBlockTypes(
'core/post-content',
rootClientId
),
[ rootClientId ]
);

return useMemo( () => {
// filter patterns without postTypes declared if the current postType is page
// or patterns that declare the current postType in its post type array.
return blockPatternsWithPostContentBlockType.filter( ( pattern ) => {
return (
( postType === 'page' && ! pattern.postTypes ) ||
( Array.isArray( pattern.postTypes ) &&
pattern.postTypes.includes( postType ) )
);
} );
}, [ postType, blockPatternsWithPostContentBlockType ] );
}

export default function StarterPatternsModal( {
onChoosePattern,
onRequestClose,
postType,
rootClientId,
} ) {
const starterPatterns = useStarterPatterns( postType, rootClientId );
const shownStarterPatterns = useAsyncList( starterPatterns );

if ( starterPatterns.length === 0 ) {
return null;
}

return (
<Modal
title={ __( 'Choose a pattern' ) }
isFullScreen
onRequestClose={ onRequestClose }
>
<div className="block-editor-starter-patterns-modal__content">
<BlockPatternsList
blockPatterns={ starterPatterns }
shownPatterns={ shownStarterPatterns }
onClickPattern={ onChoosePattern }
/>
</div>
</Modal>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// 2 column masonry layout.
.block-editor-starter-patterns-modal__content .block-editor-block-patterns-list {
column-count: 2;
column-gap: $grid-unit-30;

@include break-medium() {
column-count: 3;
}

@include break-wide() {
column-count: 4;
}

.block-editor-block-patterns-list__list-item {
break-inside: avoid-column;
margin-bottom: $grid-unit-30;

.block-editor-block-preview__container {
min-height: 100px;
}

.block-editor-block-preview__content {
width: 100%;
}
}
}
1 change: 1 addition & 0 deletions packages/block-editor/src/style.scss
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
@import "./components/responsive-block-control/style.scss";
@import "./components/rich-text/style.scss";
@import "./components/skip-to-selected-block/style.scss";
@import "./components/starter-patterns-modal/style.scss";
@import "./components/text-decoration-control/style.scss";
@import "./components/text-transform-control/style.scss";
@import "./components/tool-selector/style.scss";
Expand Down
108 changes: 22 additions & 86 deletions packages/edit-post/src/components/start-page-options/index.js
Original file line number Diff line number Diff line change
@@ -1,108 +1,44 @@
/**
* WordPress dependencies
*/
import { Modal } from '@wordpress/components';
import { __ } from '@wordpress/i18n';
import { useState, useMemo } from '@wordpress/element';
import {
store as blockEditorStore,
__experimentalBlockPatternsList as BlockPatternsList,
} from '@wordpress/block-editor';
import { useSelect, useDispatch } from '@wordpress/data';
import { useAsyncList } from '@wordpress/compose';
import { store as editorStore } from '@wordpress/editor';
import { useState } from '@wordpress/element';
import { StarterPatternsModal } from '@wordpress/block-editor';

/**
* Internal dependencies
*/
import { store as editPostStore } from '../../store';

function useStartPatterns() {
// A pattern is a start pattern if it includes 'core/post-content' in its blockTypes,
// and it has no postTypes declared and the current post type is page or if
// the current post type is part of the postTypes declared.
const { blockPatternsWithPostContentBlockType, postType } = useSelect(
( select ) => {
const { getPatternsByBlockTypes } = select( blockEditorStore );
const { getCurrentPostType } = select( editorStore );
return {
blockPatternsWithPostContentBlockType:
getPatternsByBlockTypes( 'core/post-content' ),
postType: getCurrentPostType(),
};
},
[]
);

return useMemo( () => {
// filter patterns without postTypes declared if the current postType is page
// or patterns that declare the current postType in its post type array.
return blockPatternsWithPostContentBlockType.filter( ( pattern ) => {
return (
( postType === 'page' && ! pattern.postTypes ) ||
( Array.isArray( pattern.postTypes ) &&
pattern.postTypes.includes( postType ) )
);
} );
}, [ postType, blockPatternsWithPostContentBlockType ] );
}

function PatternSelection( { blockPatterns, onChoosePattern } ) {
const shownBlockPatterns = useAsyncList( blockPatterns );
const { resetEditorBlocks } = useDispatch( editorStore );
return (
<BlockPatternsList
blockPatterns={ blockPatterns }
shownPatterns={ shownBlockPatterns }
onClickPattern={ ( _pattern, blocks ) => {
resetEditorBlocks( blocks );
onChoosePattern();
} }
/>
);
}

function StartPageOptionsModal( { onClose } ) {
const startPatterns = useStartPatterns();
const hasStartPattern = startPatterns.length > 0;

if ( ! hasStartPattern ) {
return null;
}

return (
<Modal
className="edit-post-start-page-options__modal"
title={ __( 'Choose a pattern' ) }
isFullScreen
onRequestClose={ onClose }
>
<div className="edit-post-start-page-options__modal-content">
<PatternSelection
blockPatterns={ startPatterns }
onChoosePattern={ onClose }
/>
</div>
</Modal>
);
}

export default function StartPageOptions() {
const [ isClosed, setIsClosed ] = useState( false );
const shouldEnableModal = useSelect( ( select ) => {
const { isCleanNewPost } = select( editorStore );
const { resetEditorBlocks } = useDispatch( editorStore );
const { shouldEnableModal, postType } = useSelect( ( select ) => {
const { isCleanNewPost, getCurrentPostType } = select( editorStore );
const { isEditingTemplate, isFeatureActive } = select( editPostStore );

return (
! isEditingTemplate() &&
! isFeatureActive( 'welcomeGuide' ) &&
isCleanNewPost()
);
return {
shouldEnableModal:
! isEditingTemplate() &&
! isFeatureActive( 'welcomeGuide' ) &&
isCleanNewPost(),
postType: getCurrentPostType(),
};
}, [] );

if ( ! shouldEnableModal || isClosed ) {
return null;
}

return <StartPageOptionsModal onClose={ () => setIsClosed( true ) } />;
return (
<StarterPatternsModal
postType={ postType }
onChoosePattern={ ( pattern, blocks ) => {
resetEditorBlocks( blocks );
setIsClosed( true );
} }
onRequestClose={ () => setIsClosed( true ) }
/>
);
}
2 changes: 2 additions & 0 deletions packages/edit-site/src/components/editor/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import InserterSidebar from '../secondary-sidebar/inserter-sidebar';
import ListViewSidebar from '../secondary-sidebar/list-view-sidebar';
import WelcomeGuide from '../welcome-guide';
import StartTemplateOptions from '../start-template-options';
import StartPageOptions from '../start-page-options';
import { store as editSiteStore } from '../../store';
import { GlobalStylesRenderer } from '../global-styles-renderer';
import useTitle from '../routes/use-title';
Expand Down Expand Up @@ -194,6 +195,7 @@ export default function Editor( { listViewToggleElement, isLoading } ) {
<BlockContextProvider value={ blockContext }>
<SidebarComplementaryAreaFills />
{ isEditMode && <StartTemplateOptions /> }
{ isEditMode && <StartPageOptions /> }
<InterfaceSkeleton
isDistractionFree={ true }
enableRegionNavigation={ false }
Expand Down
79 changes: 79 additions & 0 deletions packages/edit-site/src/components/start-page-options/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/**
* WordPress dependencies
*/
import {
store as blockEditorStore,
StarterPatternsModal,
} from '@wordpress/block-editor';
import { useDispatch, useSelect } from '@wordpress/data';
import { useState } from '@wordpress/element';
import { store as preferencesStore } from '@wordpress/preferences';

/**
* Internal dependencies
*/
import { store as editSiteStore } from '../../store';

export default function StartPageOptions() {
const { replaceInnerBlocks } = useDispatch( blockEditorStore );
const [ isClosed, setIsClosed ] = useState( false );
const { shouldOpenModal, postType, rootClientId } = useSelect(
( select ) => {
const { hasPageContentFocus, getEditedPostContext } =
select( editSiteStore );
const context = getEditedPostContext();
const isEditingPage =
context?.postType === 'page' &&
context?.postId &&
hasPageContentFocus();
const isWelcomeGuideOpen = select( preferencesStore ).get(
'core/edit-site',
'welcomeGuide'
);
if ( ! isEditingPage || isWelcomeGuideOpen ) {
return { shouldOpenModal: false };
}

const { __experimentalGetGlobalBlocksByName, getBlock } =
select( blockEditorStore );
const [ contentBlockClientId ] =
__experimentalGetGlobalBlocksByName( 'core/post-content' );
if ( ! contentBlockClientId ) {
return { shouldOpenModal: false };
}

const contentBlock = getBlock( contentBlockClientId );
if ( contentBlock?.innerBlocks?.length ) {
return { shouldOpenModal: false };
}

return {
shouldOpenModal: true,
postType: context.postType,
rootClientId: contentBlockClientId,
};
},
[]
);

if ( isClosed || ! shouldOpenModal ) {
return null;
}

return (
<StarterPatternsModal
postType={ postType }
rootClientId={ rootClientId }
onChoosePattern={ ( pattern, blocks ) => {
setIsClosed( true );
const selectInsertedBlocks = true;
replaceInnerBlocks(
rootClientId,
blocks,
selectInsertedBlocks
);
} }
onRequestClose={ () => setIsClosed( true ) }
/>
);
}

0 comments on commit 283489e

Please sign in to comment.