diff --git a/packages/core-data/src/private-selectors.ts b/packages/core-data/src/private-selectors.ts index 0d4a28ad174a19..31c06f4f2073de 100644 --- a/packages/core-data/src/private-selectors.ts +++ b/packages/core-data/src/private-selectors.ts @@ -6,9 +6,16 @@ import { createSelector, createRegistrySelector } from '@wordpress/data'; /** * Internal dependencies */ -import { getDefaultTemplateId, getEntityRecord, type State } from './selectors'; +import { + getDefaultTemplateId, + getEditedEntityRecord, + getEntityRecord, + type State, + type GetRecordsHttpQuery, +} from './selectors'; import { STORE_NAME } from './name'; import { unlock } from './lock-unlock'; +import type * as ET from './entity-types'; type EntityRecordKey = string | number; @@ -257,3 +264,54 @@ export const getTemplateId = createRegistrySelector( } ); } ); + +/** + * Returns a list of entity records, merged with their edits. + * + * @param state State tree. + * @param kind Entity kind. + * @param name Entity name. + * @param recordIds Record IDs. + * + * @return The list of entity records, merged with their edits. + */ +export const getEditedEntityRecords = createSelector( + < EntityRecord extends ET.EntityRecord< any > >( + state: State, + kind: string, + name: string, + recordIds: EntityRecordKey[] + ): Array< ET.Updatable< EntityRecord > | false > => { + return recordIds.map( ( recordId ) => + getEditedEntityRecord( state, kind, name, recordId ) + ); + }, + ( + state: State, + kind: string, + name: string, + recordIds: EntityRecordKey[], + query?: GetRecordsHttpQuery + ) => { + const context = query?.context ?? 'default'; + return [ + state.entities.config, + ...recordIds.map( + ( recordId ) => + state.entities.records?.[ kind ]?.[ name ]?.queriedData + .items[ context ]?.[ recordId ] + ), + ...recordIds.map( + ( recordId ) => + state.entities.records?.[ kind ]?.[ name ]?.queriedData + .itemIsComplete[ context ]?.[ recordId ] + ), + ...recordIds.map( + ( recordId ) => + state.entities.records?.[ kind ]?.[ name ]?.edits?.[ + recordId + ] + ), + ]; + } +); diff --git a/packages/core-data/src/selectors.ts b/packages/core-data/src/selectors.ts index 7f4b0d38846468..c31ebc04254640 100644 --- a/packages/core-data/src/selectors.ts +++ b/packages/core-data/src/selectors.ts @@ -113,7 +113,7 @@ type Optional< T > = T | undefined; /** * HTTP Query parameters sent with the API request to fetch the entity records. */ -type GetRecordsHttpQuery = Record< string, any >; +export type GetRecordsHttpQuery = Record< string, any >; /** * Arguments for EntityRecord selectors. diff --git a/packages/editor/src/components/post-actions/index.js b/packages/editor/src/components/post-actions/index.js index 4b541d52d429cc..deb621c9d058e1 100644 --- a/packages/editor/src/components/post-actions/index.js +++ b/packages/editor/src/components/post-actions/index.js @@ -21,27 +21,48 @@ import { usePostActions } from './actions'; const { Menu, kebabCase } = unlock( componentsPrivateApis ); function useEditedEntityRecordsWithPermissions( postType, postIds ) { - const { items, permissions } = useSelect( + const { items, entityConfig } = useSelect( ( select ) => { - const { getEditedEntityRecord, getEntityRecordPermissions } = - unlock( select( coreStore ) ); + const { getEntityConfig } = select( coreStore ); + const { getEditedEntityRecords } = unlock( select( coreStore ) ); + const entityRecords = getEditedEntityRecords( + 'postType', + postType, + postIds + ); + return { - items: postIds.map( ( postId ) => - getEditedEntityRecord( 'postType', postType, postId ) - ), - permissions: postIds.map( ( postId ) => - getEntityRecordPermissions( 'postType', postType, postId ) - ), + items: entityRecords, + entityConfig: getEntityConfig( 'postType', postType ), }; }, [ postIds, postType ] ); + const ids = useMemo( + () => + items?.map( ( record ) => record[ entityConfig?.key ?? 'id' ] ) ?? + [], + [ items, entityConfig?.key ] + ); + + const permissions = useSelect( + ( select ) => { + const { getEntityRecordsPermissions } = unlock( + select( coreStore ) + ); + return getEntityRecordsPermissions( 'postType', postType, ids ); + }, + [ ids, postType ] + ); + return useMemo( () => { - return items.map( ( item, index ) => ( { - ...item, - permissions: permissions[ index ], - } ) ); + return ( + items?.map( ( item, index ) => ( { + ...item, + permissions: permissions ? permissions[ index ] : undefined, + } ) ) ?? [] + ); }, [ items, permissions ] ); } diff --git a/packages/fields/src/fields/template/template-edit.tsx b/packages/fields/src/fields/template/template-edit.tsx index c17364568a4578..20fc05c0a4a10a 100644 --- a/packages/fields/src/fields/template/template-edit.tsx +++ b/packages/fields/src/fields/template/template-edit.tsx @@ -39,52 +39,50 @@ export const TemplateEdit = ( { typeof data.id === 'number' ? data.id : parseInt( data.id, 10 ); const slug = data.slug; - const { availableTemplates, templates } = useSelect( + const { templates, allowSwitchingTemplate } = useSelect( ( select ) => { - const allTemplates = - select( coreStore ).getEntityRecords< WpTemplate >( - 'postType', - 'wp_template', - { - per_page: -1, - post_type: postType, - } - ) ?? []; + const allTemplates = select( + coreStore + ).getEntityRecords< WpTemplate >( 'postType', 'wp_template', { + per_page: -1, + post_type: postType, + } ); const { getHomePage, getPostsPageId } = unlock( select( coreStore ) ); - const isPostsPage = getPostsPageId() === +postId; + const isPostsPage = getPostsPageId() === postId.toString(); const isFrontPage = - postType === 'page' && getHomePage()?.postId === +postId; - - const allowSwitchingTemplate = ! isPostsPage && ! isFrontPage; + postType === 'page' && + getHomePage()?.postId === postId.toString(); return { templates: allTemplates, - availableTemplates: allowSwitchingTemplate - ? allTemplates.filter( - ( template ) => - template.is_custom && - template.slug !== data.template && - !! template.content.raw // Skip empty templates. - ) - : [], + allowSwitchingTemplate: ! isPostsPage && ! isFrontPage, }; }, - [ data.template, postId, postType ] + [ postId, postType ] ); const templatesAsPatterns = useMemo( () => - availableTemplates.map( ( template ) => ( { - name: template.slug, - blocks: parse( template.content.raw ), - title: decodeEntities( template.title.rendered ), - id: template.id, - } ) ), - [ availableTemplates ] + allowSwitchingTemplate + ? ( templates ?? [] ) + .filter( + ( template ) => + template.is_custom && + template.slug !== data.template && + !! template.content.raw // Skip empty templates. + ) + .map( ( template ) => ( { + name: template.slug, + blocks: parse( template.content.raw ), + title: decodeEntities( template.title.rendered ), + id: template.id, + } ) ) + : [], + [ templates, allowSwitchingTemplate, data.template ] ); const shownTemplates = useAsyncList( templatesAsPatterns ); @@ -151,6 +149,10 @@ export const TemplateEdit = ( { variant="tertiary" size="compact" onClick={ onToggle } + accessibleWhenDisabled + disabled={ + value === '' && ! templatesAsPatterns.length + } > { currentTemplate ? getItemTitle( currentTemplate ) @@ -159,14 +161,16 @@ export const TemplateEdit = ( { ) } renderContent={ ( { onToggle } ) => ( - { - setShowModal( true ); - onToggle(); - } } - > - { __( 'Swap template' ) } - + { templatesAsPatterns.length > 0 && ( + { + setShowModal( true ); + onToggle(); + } } + > + { __( 'Swap template' ) } + + ) } { // The default template in a post is indicated by an empty string value !== '' && (