Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Patterns: Add a core selector to get user patterns #55985

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions docs/reference-guides/data/data-core.md
Original file line number Diff line number Diff line change
Expand Up @@ -458,6 +458,18 @@ _Returns_

- `Array< UserPatternCategory >`: User patterns category array.

### getUserPatterns

Retrieve the list of registered user patterns.

_Parameters_

- _state_ `State`: Data state.

_Returns_

- `Array< UserPattern >`: User pattern list.

### getUserQueryResults

Returns all the users returned by a query ID.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,16 +105,16 @@ function BlockPattern( {
blocks={ blocks }
viewportWidth={ viewportWidth }
/>

<HStack className="block-editor-patterns__pattern-details">
{ pattern.id && ! pattern.syncStatus && (
<div className="block-editor-patterns__pattern-icon-wrapper">
<Icon
className="block-editor-patterns__pattern-icon"
icon={ symbol }
/>
</div>
) }
{ pattern.id &&
pattern.syncStatus === 'fully' && (
<div className="block-editor-patterns__pattern-icon-wrapper">
<Icon
className="block-editor-patterns__pattern-icon"
icon={ symbol }
/>
</div>
) }
{ ( ! showTooltip || pattern.id ) && (
<div className="block-editor-block-patterns-list__item-title">
{ pattern.title }
Expand Down
42 changes: 10 additions & 32 deletions packages/block-editor/src/store/selectors.js
Original file line number Diff line number Diff line change
Expand Up @@ -2274,31 +2274,6 @@ const checkAllowListRecursive = ( blocks, allowedBlockTypes ) => {
return true;
};

function getUserPatterns( state ) {
const userPatterns =
state?.settings?.__experimentalReusableBlocks ?? EMPTY_ARRAY;
const userPatternCategories =
state?.settings?.__experimentalUserPatternCategories ?? [];
const categories = new Map();
userPatternCategories.forEach( ( userCategory ) =>
categories.set( userCategory.id, userCategory )
);
return userPatterns.map( ( userPattern ) => {
return {
name: `core/block/${ userPattern.id }`,
id: userPattern.id,
title: userPattern.title.raw,
categories: userPattern.wp_pattern_category.map( ( catId ) =>
categories && categories.get( catId )
? categories.get( catId ).slug
: catId
),
content: userPattern.content.raw,
syncStatus: userPattern.wp_pattern_sync_status,
};
} );
}

export const __experimentalUserPatternCategories = createSelector(
( state ) => {
return state?.settings?.__experimentalUserPatternCategories;
Expand All @@ -2309,32 +2284,35 @@ export const __experimentalUserPatternCategories = createSelector(
export const __experimentalGetParsedPattern = createSelector(
( state, patternName ) => {
const patterns = state.settings.__experimentalBlockPatterns;
const userPatterns = getUserPatterns( state );
const userPatterns = state.settings.__experimentalUserPatterns || [];

const pattern = [ ...patterns, ...userPatterns ].find(
( { name } ) => name === patternName
);
if ( ! pattern ) {
return null;
}

return {
...pattern,
blocks: parse( pattern.content, {
__unstableSkipMigrationLogs: true,
} ),
blocks: pattern.blocks
? pattern.blocks
: parse( pattern.content, {
__unstableSkipMigrationLogs: true,
} ),
};
},
( state ) => [
state.settings.__experimentalBlockPatterns,
state.settings.__experimentalReusableBlocks,
state?.settings?.__experimentalUserPatternCategories,
state?.settings?.__experimentalUserPatterns,
]
);

const getAllAllowedPatterns = createSelector(
( state ) => {
const patterns = state.settings.__experimentalBlockPatterns;
const userPatterns = getUserPatterns( state );
const userPatterns = state.settings.__experimentalUserPatterns || [];

const { allowedBlockTypes } = getSettings( state );

Expand All @@ -2350,7 +2328,7 @@ const getAllAllowedPatterns = createSelector(
},
( state ) => [
state.settings.__experimentalBlockPatterns,
state.settings.__experimentalReusableBlocks,
state.settings.__experimentalUserPatterns,
state.settings.allowedBlockTypes,
state?.settings?.__experimentalUserPatternCategories,
]
Expand Down
12 changes: 12 additions & 0 deletions packages/core-data/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -765,6 +765,18 @@ _Returns_

- `Array< UserPatternCategory >`: User patterns category array.

### getUserPatterns

Retrieve the list of registered user patterns.

_Parameters_

- _state_ `State`: Data state.

_Returns_

- `Array< UserPattern >`: User pattern list.

### getUserQueryResults

Returns all the users returned by a query ID.
Expand Down
10 changes: 10 additions & 0 deletions packages/core-data/src/reducer.js
Original file line number Diff line number Diff line change
Expand Up @@ -535,6 +535,15 @@ export function blockPatternCategories( state = [], action ) {
return state;
}

export function userPatterns( state = [], action ) {
switch ( action.type ) {
case 'RECEIVE_USER_PATTERNS':
return action.userPatterns;
}

return state;
}

export function userPatternCategories( state = [], action ) {
switch ( action.type ) {
case 'RECEIVE_USER_PATTERN_CATEGORIES':
Expand Down Expand Up @@ -610,6 +619,7 @@ export default combineReducers( {
autosaves,
blockPatterns,
blockPatternCategories,
userPatterns,
userPatternCategories,
navigationFallbackId,
defaultTemplates,
Expand Down
17 changes: 17 additions & 0 deletions packages/core-data/src/resolvers.js
Original file line number Diff line number Diff line change
Expand Up @@ -668,6 +668,23 @@ export const getUserPatternCategories =
} );
};

export const getUserPatterns =
() =>
async ( { dispatch, resolveSelect } ) => {
const userPatterns = await resolveSelect.getEntityRecords(
'postType',
'wp_block',
{
per_page: -1,
}
);

dispatch( {
type: 'RECEIVE_USER_PATTERNS',
userPatterns,
} );
};

export const getNavigationFallbackId =
() =>
async ( { dispatch, select } ) => {
Expand Down
51 changes: 50 additions & 1 deletion packages/core-data/src/selectors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import { createRegistrySelector } from '@wordpress/data';
import { addQueryArgs } from '@wordpress/url';
import deprecated from '@wordpress/deprecated';
import { parse } from '@wordpress/blocks';

Check failure on line 12 in packages/core-data/src/selectors.ts

View workflow job for this annotation

GitHub Actions / Puppeteer - 1

Cannot find module '@wordpress/blocks' or its corresponding type declarations.

Check failure on line 12 in packages/core-data/src/selectors.ts

View workflow job for this annotation

GitHub Actions / Playwright - 3

Cannot find module '@wordpress/blocks' or its corresponding type declarations.

Check failure on line 12 in packages/core-data/src/selectors.ts

View workflow job for this annotation

GitHub Actions / Puppeteer - 2

Cannot find module '@wordpress/blocks' or its corresponding type declarations.

Check failure on line 12 in packages/core-data/src/selectors.ts

View workflow job for this annotation

GitHub Actions / Playwright - 2

Cannot find module '@wordpress/blocks' or its corresponding type declarations.

Check failure on line 12 in packages/core-data/src/selectors.ts

View workflow job for this annotation

GitHub Actions / Playwright - 1

Cannot find module '@wordpress/blocks' or its corresponding type declarations.

Check failure on line 12 in packages/core-data/src/selectors.ts

View workflow job for this annotation

GitHub Actions / PHP 8.1 on ubuntu-latest

Cannot find module '@wordpress/blocks' or its corresponding type declarations.

Check failure on line 12 in packages/core-data/src/selectors.ts

View workflow job for this annotation

GitHub Actions / Puppeteer - 3

Cannot find module '@wordpress/blocks' or its corresponding type declarations.

Check failure on line 12 in packages/core-data/src/selectors.ts

View workflow job for this annotation

GitHub Actions / PHP 7.0 on ubuntu-latest

Cannot find module '@wordpress/blocks' or its corresponding type declarations.

Check failure on line 12 in packages/core-data/src/selectors.ts

View workflow job for this annotation

GitHub Actions / PHP 8.1 multisite on ubuntu-latest

Cannot find module '@wordpress/blocks' or its corresponding type declarations.

Check failure on line 12 in packages/core-data/src/selectors.ts

View workflow job for this annotation

GitHub Actions / PHP 7.1 multisite on ubuntu-latest

Cannot find module '@wordpress/blocks' or its corresponding type declarations.

Check failure on line 12 in packages/core-data/src/selectors.ts

View workflow job for this annotation

GitHub Actions / PHP 8.2 multisite on ubuntu-latest

Cannot find module '@wordpress/blocks' or its corresponding type declarations.

Check failure on line 12 in packages/core-data/src/selectors.ts

View workflow job for this annotation

GitHub Actions / PHP 8.0 on ubuntu-latest

Cannot find module '@wordpress/blocks' or its corresponding type declarations.

Check failure on line 12 in packages/core-data/src/selectors.ts

View workflow job for this annotation

GitHub Actions / Playwright - 4

Cannot find module '@wordpress/blocks' or its corresponding type declarations.

Check failure on line 12 in packages/core-data/src/selectors.ts

View workflow job for this annotation

GitHub Actions / PHP 7.4 (WP 6.3.2) on ubuntu-latest

Cannot find module '@wordpress/blocks' or its corresponding type declarations.

Check failure on line 12 in packages/core-data/src/selectors.ts

View workflow job for this annotation

GitHub Actions / Build Release Artifact

Cannot find module '@wordpress/blocks' or its corresponding type declarations.

Check failure on line 12 in packages/core-data/src/selectors.ts

View workflow job for this annotation

GitHub Actions / PHP 7.4 multisite on ubuntu-latest

Cannot find module '@wordpress/blocks' or its corresponding type declarations.

Check failure on line 12 in packages/core-data/src/selectors.ts

View workflow job for this annotation

GitHub Actions / PHP 8.2 on ubuntu-latest

Cannot find module '@wordpress/blocks' or its corresponding type declarations.

Check failure on line 12 in packages/core-data/src/selectors.ts

View workflow job for this annotation

GitHub Actions / PHP 8.0 multisite on ubuntu-latest

Cannot find module '@wordpress/blocks' or its corresponding type declarations.

Check failure on line 12 in packages/core-data/src/selectors.ts

View workflow job for this annotation

GitHub Actions / Check

Cannot find module '@wordpress/blocks' or its corresponding type declarations.

Check failure on line 12 in packages/core-data/src/selectors.ts

View workflow job for this annotation

GitHub Actions / Run performance tests

Cannot find module '@wordpress/blocks' or its corresponding type declarations.

Check failure on line 12 in packages/core-data/src/selectors.ts

View workflow job for this annotation

GitHub Actions / All

Cannot find module '@wordpress/blocks' or its corresponding type declarations.

/**
* Internal dependencies
Expand Down Expand Up @@ -45,6 +46,7 @@
userPermissions: Record< string, boolean >;
users: UserState;
navigationFallbackId: EntityRecordKey;
userPatterns: Array< UserPattern >;
userPatternCategories: Array< UserPatternCategory >;
defaultTemplates: Record< string, string >;
}
Expand Down Expand Up @@ -96,6 +98,17 @@
description: string;
}

export interface UserPattern {
blocks: Array< unknown >;
categories?: Array< string >;
id: string;
name: string;
slug: string;
syncStatus: string;
title: string;
type: string;
}

type Optional< T > = T | undefined;

/**
Expand Down Expand Up @@ -1325,14 +1338,50 @@
return state.blockPatternCategories;
}

/**
* Retrieve the list of registered user patterns.
*
* @param state Data state.
*
* @return User pattern list.
*/
export const getUserPatterns = createSelector(
( state: State ): Array< UserPattern > => {
const categories = new Map();
state.userPatternCategories.forEach( ( userCategory ) =>
categories.set( userCategory.id, userCategory )
);

return state.userPatterns.map( ( patternBlock: any ) => ( {
blocks: parse( patternBlock.content.raw, {
Copy link
Member

@jsnajdr jsnajdr Nov 13, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Calling parse in a selector is potentially a problem because of block lazy loading (see #51778 and #53260). With block lazy loading, parsing content into blocks is an async function because the parser internally lazily loads the blocks it encounters in the content.

And selectors are synchronous, unless you want to also add a getUserPatterns resolver, so they can't really call async functions.

We'll either need to move content parsing out of the selector, or, at least, mark the getUserPatterns selector as experimental/private, so that we're not stuck with it forever.

You can also do a "thought experiment" where the parse function is already async (returns a promise). How does this PR need to change to accomodate that?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, great to know this, thanks for this feedback, will have another think about the best approach.

__unstableSkipMigrationLogs: true,
} ),
...( patternBlock.wp_pattern_category.length > 0 && {
categories: patternBlock.wp_pattern_category.map(
( patternCategoryId: number ) =>
categories && categories.get( patternCategoryId )
? categories.get( patternCategoryId ).slug
: patternCategoryId
),
} ),
id: patternBlock.id,
name: `core/block/${ patternBlock.id }`,
slug: patternBlock.slug,
syncStatus: patternBlock.wp_pattern_sync_status || 'fully',
title: patternBlock.title.raw,
type: 'wp_block',
} ) );
},
( state ) => [ state.userPatternCategories, state.userPatterns ]
);

/**
* Retrieve the registered user pattern categories.
*
* @param state Data state.
*
* @return User patterns category array.
*/

export function getUserPatternCategories(
state: State
): Array< UserPatternCategory > {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,7 @@ export default function DeleteCategoryMenuItem( { category, onClose } ) {
// Prevent the need to refresh the page to get up-to-date categories
// and pattern categorization.
invalidateResolution( 'getUserPatternCategories' );
invalidateResolution( 'getEntityRecords', [
'postType',
PATTERN_TYPES.user,
{ per_page: -1 },
] );
invalidateResolution( 'getUserPatterns' );

createSuccessNotice(
sprintf(
Expand Down
55 changes: 5 additions & 50 deletions packages/edit-site/src/components/page-patterns/use-patterns.js
Original file line number Diff line number Diff line change
Expand Up @@ -184,58 +184,19 @@ const selectPatterns = createSelector(
]
);

const patternBlockToPattern = ( patternBlock, categories ) => ( {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm guessing this is the duplication that we're removing. Can you explain a bit where we're using this?

Copy link
Contributor Author

@glendaviesnz glendaviesnz Nov 13, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It essentially takes a wp_block entity record and maps it to an object that more closely resembles a theme/directory block pattern in order to allow both to be more easily merged into a single list of patterns under the categories in the site editor patterns page and post editor inserter.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't we have the patterns package now for this kind of things?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeh, maybe instead of pushing new settings into the block editor we can abstract the duplication out into a shared hook in the patterns package, or something. Will play around with some alternative approaches. Thanks for the feedback.

blocks: parse( patternBlock.content.raw, {
__unstableSkipMigrationLogs: true,
} ),
...( patternBlock.wp_pattern_category.length > 0 && {
categories: patternBlock.wp_pattern_category.map(
( patternCategoryId ) =>
categories && categories.get( patternCategoryId )
? categories.get( patternCategoryId ).slug
: patternCategoryId
),
} ),
termLabels: patternBlock.wp_pattern_category.map( ( patternCategoryId ) =>
categories?.get( patternCategoryId )
? categories.get( patternCategoryId ).label
: patternCategoryId
),
id: patternBlock.id,
name: patternBlock.slug,
syncStatus: patternBlock.wp_pattern_sync_status || PATTERN_SYNC_TYPES.full,
title: patternBlock.title.raw,
type: PATTERN_TYPES.user,
patternBlock,
} );

const selectUserPatterns = createSelector(
( select, syncStatus, search = '' ) => {
const { getEntityRecords, getIsResolving, getUserPatternCategories } =
const { getIsResolving, getUserPatternCategories, getUserPatterns } =
select( coreStore );

const query = { per_page: -1 };
const records = getEntityRecords(
'postType',
PATTERN_TYPES.user,
query
);
let patterns = getUserPatterns();
const userPatternCategories = getUserPatternCategories();
const categories = new Map();
userPatternCategories.forEach( ( userCategory ) =>
categories.set( userCategory.id, userCategory )
);
let patterns = records
? records.map( ( record ) =>
patternBlockToPattern( record, categories )
)
: EMPTY_PATTERN_LIST;

const isResolving = getIsResolving( 'getEntityRecords', [
'postType',
PATTERN_TYPES.user,
query,
] );
const isResolving = getIsResolving( 'getUserPatterns' );

if ( syncStatus ) {
patterns = patterns.filter(
Expand All @@ -257,14 +218,8 @@ const selectUserPatterns = createSelector(
};
},
( select ) => [
select( coreStore ).getEntityRecords( 'postType', PATTERN_TYPES.user, {
per_page: -1,
} ),
select( coreStore ).getIsResolving( 'getEntityRecords', [
'postType',
PATTERN_TYPES.user,
{ per_page: -1 },
] ),
select( coreStore ).getUserPatterns(),
select( coreStore ).getIsResolving( 'getUserPatterns' ),
select( coreStore ).getUserPatternCategories(),
]
);
Expand Down
Loading
Loading