diff --git a/lib/compat/wordpress-6.3/blocks.php b/lib/compat/wordpress-6.3/blocks.php index ccc68786dc6adb..903d82f77b423a 100644 --- a/lib/compat/wordpress-6.3/blocks.php +++ b/lib/compat/wordpress-6.3/blocks.php @@ -120,3 +120,50 @@ function gutenberg_wp_block_register_post_meta() { ); } add_action( 'init', 'gutenberg_wp_block_register_post_meta' ); + +/** + * Allow querying blocks by sync_status. + * + * Note: This should be removed when the minimum required WP version is >= 6.3. + * + * @param array $args Array of arguments for WP_Query. + * @param WP_REST_Request $request The REST API request. + * + * @return array Updated array of arguments for WP_Query. + */ +function gutenberg_rest_wp_block_query( $args, $request ) { + if ( isset( $request['sync_status'] ) ) { + if ( 'fully' === $request['sync_status'] ) { + $sync_status_query = array( + 'relation' => 'OR', + array( + 'key' => 'sync_status', + 'value' => '', + 'compare' => 'NOT EXISTS', + ), + array( + 'key' => 'sync_status', + 'value' => 'fully', + 'compare' => '=', + ), + ); + } else { + $sync_status_query = array( + array( + 'key' => 'sync_status', + 'value' => sanitize_text_field( $request['sync_status'] ), + 'compare' => '=', + ), + ); + } + + if ( isset( $args['meta_query'] ) && is_array( $args['meta_query'] ) ) { + array_push( $args['meta_query'], $sync_status_query ); + } else { + $args['meta_query'] = $sync_status_query; + } + } + + return $args; +} +add_filter( 'rest_wp_block_query', 'gutenberg_rest_wp_block_query', 10, 2 ); diff --git a/packages/edit-site/src/components/page-patterns/grid-item.js b/packages/edit-site/src/components/page-patterns/grid-item.js index 7f40fbce9035cf..d8e3b2fe16d532 100644 --- a/packages/edit-site/src/components/page-patterns/grid-item.js +++ b/packages/edit-site/src/components/page-patterns/grid-item.js @@ -18,13 +18,14 @@ import { Flex, } from '@wordpress/components'; import { useDispatch } from '@wordpress/data'; -import { useState, useId } from '@wordpress/element'; +import { useState, useId, memo } from '@wordpress/element'; import { __, sprintf } from '@wordpress/i18n'; import { Icon, header, footer, symbolFilled as uncategorized, + symbol, moreHorizontal, lockSmall, } from '@wordpress/icons'; @@ -37,13 +38,13 @@ import { DELETE, BACKSPACE } from '@wordpress/keycodes'; */ import RenameMenuItem from './rename-menu-item'; import DuplicateMenuItem from './duplicate-menu-item'; -import { PATTERNS, TEMPLATE_PARTS, USER_PATTERNS } from './utils'; +import { PATTERNS, TEMPLATE_PARTS, USER_PATTERNS, SYNC_TYPES } from './utils'; import { store as editSiteStore } from '../../store'; import { useLink } from '../routes/link'; const templatePartIcons = { header, footer, uncategorized }; -export default function GridItem( { categoryId, composite, icon, item } ) { +function GridItem( { categoryId, item, ...props } ) { const descriptionId = useId(); const [ isDeleteDialogOpen, setIsDeleteDialogOpen ] = useState( false ); @@ -122,9 +123,9 @@ export default function GridItem( { categoryId, composite, icon, item } ) { ariaDescriptions.push( __( 'Theme patterns cannot be edited.' ) ); } - const itemIcon = templatePartIcons[ categoryId ] - ? templatePartIcons[ categoryId ] - : icon; + const itemIcon = + templatePartIcons[ categoryId ] || + ( item.syncStatus === SYNC_TYPES.full ? symbol : undefined ); const confirmButtonText = hasThemeFile ? __( 'Clear' ) : __( 'Delete' ); const confirmPrompt = hasThemeFile @@ -142,7 +143,10 @@ export default function GridItem( { categoryId, composite, icon, item } ) { className={ previewClassNames } role="option" as="div" - { ...composite } + // Even though still incomplete, passing ids helps performance. + // @see https://reakit.io/docs/composite/#performance. + id={ `edit-site-patterns-${ item.name }` } + { ...props } onClick={ item.type !== PATTERNS ? onClick : undefined } onKeyDown={ isCustomPattern ? onKeyDown : undefined } aria-label={ item.title } @@ -180,7 +184,7 @@ export default function GridItem( { categoryId, composite, icon, item } ) { spacing={ 3 } className="edit-site-patterns__pattern-title" > - { icon && ( + { itemIcon && ( ); } + +export default memo( GridItem ); diff --git a/packages/edit-site/src/components/page-patterns/grid.js b/packages/edit-site/src/components/page-patterns/grid.js index 3f6e5fd01f72fe..ee9cb030514b70 100644 --- a/packages/edit-site/src/components/page-patterns/grid.js +++ b/packages/edit-site/src/components/page-patterns/grid.js @@ -4,36 +4,56 @@ import { __unstableComposite as Composite, __unstableUseCompositeState as useCompositeState, + __experimentalText as Text, } from '@wordpress/components'; +import { useRef } from '@wordpress/element'; +import { __, sprintf } from '@wordpress/i18n'; /** * Internal dependencies */ import GridItem from './grid-item'; -export default function Grid( { categoryId, label, icon, items } ) { - const composite = useCompositeState( { orientation: 'vertical' } ); +const PAGE_SIZE = 100; + +export default function Grid( { categoryId, items, ...props } ) { + const composite = useCompositeState( { wrap: true } ); + const gridRef = useRef(); if ( ! items?.length ) { return null; } + const list = items.slice( 0, PAGE_SIZE ); + const restLength = items.length - PAGE_SIZE; + return ( - - { items.map( ( item ) => ( - - ) ) } - + <> + + { list.map( ( item ) => ( + + ) ) } + + { restLength > 0 && ( + + { sprintf( + /* translators: %d: number of patterns */ + __( '+ %d more patterns' ), + restLength + ) } + + ) } + ); } diff --git a/packages/edit-site/src/components/page-patterns/header.js b/packages/edit-site/src/components/page-patterns/header.js new file mode 100644 index 00000000000000..6d493fd866349b --- /dev/null +++ b/packages/edit-site/src/components/page-patterns/header.js @@ -0,0 +1,69 @@ +/** + * WordPress dependencies + */ +import { + __experimentalVStack as VStack, + __experimentalHeading as Heading, + __experimentalText as Text, +} from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; +import { store as editorStore } from '@wordpress/editor'; +import { useSelect } from '@wordpress/data'; + +/** + * Internal dependencies + */ +import usePatternCategories from '../sidebar-navigation-screen-patterns/use-pattern-categories'; +import { + USER_PATTERN_CATEGORY, + USER_PATTERNS, + TEMPLATE_PARTS, + PATTERNS, +} from './utils'; + +export default function PatternsHeader( { + categoryId, + type, + titleId, + descriptionId, +} ) { + const { patternCategories } = usePatternCategories(); + const templatePartAreas = useSelect( + ( select ) => + select( editorStore ).__experimentalGetDefaultTemplatePartAreas(), + [] + ); + + let title, description; + if ( categoryId === USER_PATTERN_CATEGORY && type === USER_PATTERNS ) { + title = __( 'My Patterns' ); + description = __( 'Patterns that are kept in sync across your site.' ); // TODO + } else if ( type === TEMPLATE_PARTS ) { + const templatePartArea = templatePartAreas.find( + ( area ) => area.area === categoryId + ); + title = templatePartArea?.label; + description = templatePartArea?.description; + } else if ( type === PATTERNS ) { + const patternCategory = patternCategories.find( + ( category ) => category.name === categoryId + ); + title = patternCategory?.label; + description = patternCategory?.description; + } + + if ( ! title ) return null; + + return ( + + + { title } + + { description ? ( + + { description } + + ) : null } + + ); +} diff --git a/packages/edit-site/src/components/page-patterns/index.js b/packages/edit-site/src/components/page-patterns/index.js index 961ed51f39e5d6..d90fc748442444 100644 --- a/packages/edit-site/src/components/page-patterns/index.js +++ b/packages/edit-site/src/components/page-patterns/index.js @@ -32,7 +32,12 @@ export default function PagePatterns() { title={ __( 'Patterns content' ) } hideTitleFromUI > - + ); diff --git a/packages/edit-site/src/components/page-patterns/pagination.js b/packages/edit-site/src/components/page-patterns/pagination.js new file mode 100644 index 00000000000000..7f52fd911a9d2b --- /dev/null +++ b/packages/edit-site/src/components/page-patterns/pagination.js @@ -0,0 +1,68 @@ +/** + * WordPress dependencies + */ +import { + useLayoutEffect, + useEffect, + useRef, + useState, + startTransition, +} from '@wordpress/element'; +import { __experimentalHStack as HStack, Button } from '@wordpress/components'; + +export default function PatternsPagination( { + patterns, + page, + setPage, + getTotalPages, +} ) { + const [ totalPages, setTotalPages ] = useState( page ); + const getTotalPagesRef = useRef( getTotalPages ); + useLayoutEffect( () => { + getTotalPagesRef.current = getTotalPages; + } ); + + // Refetch total pages when `patterns` changes. + // This is not a good indicator of when to refetch the total pages, + // but the only one we have for now. + useEffect( () => { + const abortController = new AbortController(); + const signal = abortController.signal; + getTotalPagesRef + .current( { signal } ) + .then( ( pages ) => setTotalPages( pages ) ) + .catch( () => setTotalPages( 1 ) ); + return () => { + abortController.abort(); + }; + }, [ patterns ] ); + + const pages = Array.from( + { length: totalPages }, + ( _, index ) => index + 1 + ); + + return ( + + { pages.map( ( p ) => + p === page ? ( + + { p } + + ) : ( + + ) + ) } + + ); +} diff --git a/packages/edit-site/src/components/page-patterns/patterns-list.js b/packages/edit-site/src/components/page-patterns/patterns-list.js index d59596f20e7952..c4f2ce0b4ccc8a 100644 --- a/packages/edit-site/src/components/page-patterns/patterns-list.js +++ b/packages/edit-site/src/components/page-patterns/patterns-list.js @@ -1,51 +1,81 @@ /** * WordPress dependencies */ - +import { useState, useDeferredValue, useId } from '@wordpress/element'; import { SearchControl, - __experimentalHeading as Heading, - __experimentalText as Text, __experimentalVStack as VStack, Flex, FlexBlock, + __experimentalToggleGroupControl as ToggleGroupControl, + __experimentalToggleGroupControlOption as ToggleGroupControlOption, } from '@wordpress/components'; import { __, isRTL } from '@wordpress/i18n'; -import { symbol, chevronLeft, chevronRight } from '@wordpress/icons'; +import { chevronLeft, chevronRight } from '@wordpress/icons'; import { privateApis as routerPrivateApis } from '@wordpress/router'; import { useViewportMatch } from '@wordpress/compose'; /** * Internal dependencies */ +import PatternsHeader from './header'; import Grid from './grid'; import NoPatterns from './no-patterns'; import usePatterns from './use-patterns'; +import PatternsPagination from './pagination'; import SidebarButton from '../sidebar-button'; import useDebouncedInput from '../../utils/use-debounced-input'; import { unlock } from '../../lock-unlock'; +import { SYNC_TYPES, USER_PATTERN_CATEGORY } from './utils'; const { useLocation, useHistory } = unlock( routerPrivateApis ); +const SYNC_FILTERS = { + all: __( 'All' ), + [ SYNC_TYPES.full ]: __( 'Synced' ), + [ SYNC_TYPES.unsynced ]: __( 'Standard' ), +}; + export default function PatternsList( { categoryId, type } ) { const location = useLocation(); const history = useHistory(); const isMobileViewport = useViewportMatch( 'medium', '<' ); + const [ page, setPage ] = useState( 1 ); const [ filterValue, setFilterValue, delayedFilterValue ] = useDebouncedInput( '' ); + const deferredFilterValue = useDeferredValue( delayedFilterValue ); + const [ syncFilter, setSyncFilter ] = useState( 'all' ); + const deferredSyncedFilter = useDeferredValue( syncFilter ); - const [ patterns, isResolving ] = usePatterns( + const { patterns, isResolving, getTotalPages } = usePatterns( type, categoryId, - delayedFilterValue + { + search: deferredFilterValue, + page, + syncStatus: + deferredSyncedFilter === 'all' + ? undefined + : deferredSyncedFilter, + } ); - const { syncedPatterns, unsyncedPatterns } = patterns; - const hasPatterns = !! syncedPatterns.length || !! unsyncedPatterns.length; + const id = useId(); + const titleId = `${ id }-title`; + const descriptionId = `${ id }-description`; + + const hasPatterns = patterns.length; return ( - + + + { isMobileViewport && ( ) } - + setFilterValue( value ) } + onChange={ ( value ) => { + setFilterValue( value ); + setPage( 1 ); + } } placeholder={ __( 'Search patterns' ) } label={ __( 'Search patterns' ) } value={ filterValue } __nextHasNoMarginBottom /> + { categoryId === USER_PATTERN_CATEGORY && ( + { + setSyncFilter( value ); + setPage( 1 ); + } } + __nextHasNoMarginBottom + > + { Object.entries( SYNC_FILTERS ).map( + ( [ key, label ] ) => ( + + ) + ) } + + ) } - { isResolving && __( 'Loading' ) } - { ! isResolving && !! syncedPatterns.length && ( + + { hasPatterns && ( <> - - - { __( 'Synced' ) } - - - { __( - 'Patterns that are kept in sync across the site' - ) } - - - - ) } - { ! isResolving && !! unsyncedPatterns.length && ( - <> - - - { __( 'Standard' ) } - - - { __( - 'Patterns that can be changed freely without affecting the site' - ) } - - - ) } diff --git a/packages/edit-site/src/components/page-patterns/style.scss b/packages/edit-site/src/components/page-patterns/style.scss index 7a7bf026b9c625..64dcb61ac1a74d 100644 --- a/packages/edit-site/src/components/page-patterns/style.scss +++ b/packages/edit-site/src/components/page-patterns/style.scss @@ -1,6 +1,13 @@ .edit-site-patterns { - background: rgba(0, 0, 0, 0.05); + background: rgba(0, 0, 0, 0.15); margin: $header-height 0 0; + .components-base-control { + width: 100%; + @include break-medium { + width: auto; + } + } + .components-text { color: $gray-600; } @@ -12,25 +19,63 @@ @include break-medium { margin: 0; } -} -.edit-site-patterns__grid { - column-gap: $grid-unit-30; - @include break-large() { - column-count: 2; + .edit-site-patterns__search-block { + min-width: fit-content; + flex-grow: 1; } + // The increased specificity here is to overcome component styles + // without relying on internal component class names. + .edit-site-patterns__search { + input[type="search"] { + height: $button-size-next-default-40px; + background: $gray-800; + color: $gray-200; + + &:focus { + background: $gray-800; + } + } + + svg { + fill: $gray-600; + } + } + + .edit-site-patterns__sync-status-filter { + background: $gray-800; + border: none; + height: $button-size-next-default-40px; + min-width: max-content; + width: 100%; + max-width: 100%; + + @include break-medium { + width: 300px; + } + } + .edit-site-patterns__sync-status-filter-option:active { + background: $gray-700; + color: $gray-100; + } +} + +.edit-site-patterns__grid { + display: grid; + grid-template-columns: 1fr; + gap: $grid-unit-40; // Small top padding required to avoid cutting off the visible outline // when hovering items. padding-top: $border-width-focus-fallback; margin-bottom: $grid-unit-40; - + @include break-large { + grid-template-columns: 1fr 1fr; + } .edit-site-patterns__pattern { break-inside: avoid-column; display: flex; flex-direction: column; - margin-bottom: $grid-unit-60; - .edit-site-patterns__preview { border-radius: $radius-block-ui; cursor: pointer; @@ -68,26 +113,13 @@ } .edit-site-patterns__preview { - flex: 1; + flex: 0 1 auto; margin-bottom: $grid-unit-20; } } -// The increased specificity here is to overcome component styles -// without relying on internal component class names. -.edit-site-patterns__search { - &#{&} input[type="search"] { - background: $gray-800; - color: $gray-200; - - &:focus { - background: $gray-800; - } - } - - svg { - fill: $gray-600; - } +.edit-site-patterns__load-more { + align-self: center; } .edit-site-patterns__pattern-title { diff --git a/packages/edit-site/src/components/page-patterns/use-patterns.js b/packages/edit-site/src/components/page-patterns/use-patterns.js index 295d1eee8e410f..9d3045547402ff 100644 --- a/packages/edit-site/src/components/page-patterns/use-patterns.js +++ b/packages/edit-site/src/components/page-patterns/use-patterns.js @@ -4,7 +4,8 @@ import { parse } from '@wordpress/blocks'; import { useSelect } from '@wordpress/data'; import { store as coreStore } from '@wordpress/core-data'; -import { useMemo } from '@wordpress/element'; +import apiFetch from '@wordpress/api-fetch'; +import { addQueryArgs } from '@wordpress/url'; /** * Internal dependencies @@ -15,7 +16,6 @@ import { SYNC_TYPES, TEMPLATE_PARTS, USER_PATTERNS, - USER_PATTERN_CATEGORY, filterOutDuplicatesByName, } from './utils'; import { unlock } from '../../lock-unlock'; @@ -40,109 +40,94 @@ const templatePartToPattern = ( templatePart ) => ( { templatePart, } ); -const templatePartHasCategory = ( item, category ) => - item.templatePart.area === category; - -const useTemplatePartsAsPatterns = ( - categoryId, - postType = TEMPLATE_PARTS, - filterValue = '' +const selectTemplatePartsAsPatterns = ( + select, + { categoryId, search = '', page = 1 } = {} ) => { - const { templateParts, isResolving } = useSelect( - ( select ) => { - if ( postType !== TEMPLATE_PARTS ) { - return { - templateParts: EMPTY_PATTERN_LIST, - isResolving: false, - }; - } - - const { getEntityRecords, isResolving: _isResolving } = - select( coreStore ); - const query = { per_page: -1 }; - const rawTemplateParts = getEntityRecords( - 'postType', - postType, - query - ); - const partsAsPatterns = rawTemplateParts?.map( ( templatePart ) => - templatePartToPattern( templatePart ) - ); - - return { - templateParts: partsAsPatterns, - isResolving: _isResolving( 'getEntityRecords', [ - 'postType', - 'wp_template_part', - query, - ] ), - }; - }, - [ postType ] + const { getEntityRecords, getIsResolving, getEntityConfig } = + select( coreStore ); + const query = { + per_page: 20, + page, + area: categoryId, + // TODO: The template parts REST API doesn't support searching yet. + search, + search_columns: 'post_title', + }; + const templateParts = + getEntityRecords( 'postType', TEMPLATE_PARTS, query ) ?? + EMPTY_PATTERN_LIST; + const patterns = templateParts.map( ( templatePart ) => + templatePartToPattern( templatePart ) ); - const filteredTemplateParts = useMemo( () => { - if ( ! templateParts ) { - return EMPTY_PATTERN_LIST; - } - - return searchItems( templateParts, filterValue, { - categoryId, - hasCategory: templatePartHasCategory, + const isResolving = getIsResolving( 'getEntityRecords', [ + 'postType', + TEMPLATE_PARTS, + query, + ] ); + + const getTotalPages = async ( { signal } = {} ) => { + const entityConfig = getEntityConfig( 'postType', TEMPLATE_PARTS ); + const response = await apiFetch( { + path: addQueryArgs( entityConfig.baseURL, { + ...entityConfig.baseURLParams, + ...query, + } ), + method: 'HEAD', + parse: false, + signal, } ); - }, [ templateParts, filterValue, categoryId ] ); + return parseInt( response.headers.get( 'X-WP-Totalpages' ), 10 ); + }; - return { templateParts: filteredTemplateParts, isResolving }; + return { + patterns, + isResolving, + getTotalPages, + }; }; -const useThemePatterns = ( - categoryId, - postType = PATTERNS, - filterValue = '' +const selectThemePatterns = ( + select, + { categoryId, search = '', page = 1 } = {} ) => { - const blockPatterns = useSelect( ( select ) => { - const { getSettings } = unlock( select( editSiteStore ) ); - const settings = getSettings(); - return ( - settings.__experimentalAdditionalBlockPatterns ?? - settings.__experimentalBlockPatterns - ); + const { getSettings } = unlock( select( editSiteStore ) ); + const settings = getSettings(); + const blockPatterns = + settings.__experimentalAdditionalBlockPatterns ?? + settings.__experimentalBlockPatterns; + + const restBlockPatterns = select( coreStore ).getBlockPatterns(); + + let patterns = [ + ...( blockPatterns || [] ), + ...( restBlockPatterns || [] ), + ] + .filter( + ( pattern ) => ! CORE_PATTERN_SOURCES.includes( pattern.source ) + ) + .filter( filterOutDuplicatesByName ) + .map( ( pattern ) => ( { + ...pattern, + keywords: pattern.keywords || [], + type: 'pattern', + blocks: parse( pattern.content ), + } ) ); + + patterns = searchItems( patterns, search, { + categoryId, + hasCategory: ( item, currentCategory ) => + item.categories?.includes( currentCategory ), } ); - const restBlockPatterns = useSelect( ( select ) => - select( coreStore ).getBlockPatterns() - ); - - const patterns = useMemo( - () => - [ ...( blockPatterns || [] ), ...( restBlockPatterns || [] ) ] - .filter( - ( pattern ) => - ! CORE_PATTERN_SOURCES.includes( pattern.source ) - ) - .filter( filterOutDuplicatesByName ) - .map( ( pattern ) => ( { - ...pattern, - keywords: pattern.keywords || [], - type: 'pattern', - blocks: parse( pattern.content ), - } ) ), - [ blockPatterns, restBlockPatterns ] - ); - - const filteredPatterns = useMemo( () => { - if ( postType !== PATTERNS ) { - return EMPTY_PATTERN_LIST; - } - - return searchItems( patterns, filterValue, { - categoryId, - hasCategory: ( item, currentCategory ) => - item.categories?.includes( currentCategory ), - } ); - }, [ patterns, filterValue, categoryId, postType ] ); + patterns = patterns.slice( ( page - 1 ) * 20, page * 20 ); - return filteredPatterns; + return { + patterns, + isResolving: false, + getTotalPages: async () => Math.ceil( patterns.length / 20 ), + }; }; const reusableBlockToPattern = ( reusableBlock ) => ( { @@ -156,88 +141,83 @@ const reusableBlockToPattern = ( reusableBlock ) => ( { reusableBlock, } ); -const useUserPatterns = ( - categoryId, - categoryType = PATTERNS, - filterValue = '' +const selectUserPatterns = ( + select, + { search = '', syncStatus, page = 1 } = {} ) => { - const postType = categoryType === PATTERNS ? USER_PATTERNS : categoryType; - const unfilteredPatterns = useSelect( - ( select ) => { - if ( - postType !== USER_PATTERNS || - categoryId !== USER_PATTERN_CATEGORY - ) { - return EMPTY_PATTERN_LIST; - } - - const { getEntityRecords } = select( coreStore ); - const records = getEntityRecords( 'postType', postType, { - per_page: -1, - } ); - - if ( ! records ) { - return EMPTY_PATTERN_LIST; - } - - return records.map( ( record ) => - reusableBlockToPattern( record ) - ); - }, - [ postType, categoryId ] - ); - - const filteredPatterns = useMemo( () => { - if ( ! unfilteredPatterns.length ) { - return EMPTY_PATTERN_LIST; - } - - return searchItems( unfilteredPatterns, filterValue, { - // We exit user pattern retrieval early if we aren't in the - // catch-all category for user created patterns, so it has - // to be in the category. - hasCategory: () => true, + const { getEntityRecords, getIsResolving, getEntityConfig } = + select( coreStore ); + + const query = { + per_page: 20, + page, + search, + search_columns: 'post_title', + sync_status: syncStatus, + }; + const records = getEntityRecords( 'postType', USER_PATTERNS, query ); + + const isResolving = getIsResolving( 'getEntityRecords', [ + 'postType', + USER_PATTERNS, + query, + ] ); + + const patterns = records + ? records.map( ( record ) => reusableBlockToPattern( record ) ) + : EMPTY_PATTERN_LIST; + + const getTotalPages = async ( { signal } = {} ) => { + const entityConfig = getEntityConfig( 'postType', USER_PATTERNS ); + const response = await apiFetch( { + path: addQueryArgs( entityConfig.baseURL, { + ...entityConfig.baseURLParams, + ...query, + } ), + method: 'HEAD', + parse: false, + signal, } ); - }, [ unfilteredPatterns, filterValue ] ); - - const patterns = { syncedPatterns: [], unsyncedPatterns: [] }; - - filteredPatterns.forEach( ( pattern ) => { - if ( pattern.syncStatus === SYNC_TYPES.full ) { - patterns.syncedPatterns.push( pattern ); - } else { - patterns.unsyncedPatterns.push( pattern ); - } - } ); + return parseInt( response.headers.get( 'X-Wp-Totalpages' ), 10 ); + }; - return patterns; + return { patterns, isResolving, getTotalPages }; }; -export const usePatterns = ( categoryType, categoryId, filterValue ) => { - const blockPatterns = useThemePatterns( - categoryId, - categoryType, - filterValue - ); - - const { syncedPatterns = [], unsyncedPatterns = [] } = useUserPatterns( - categoryId, - categoryType, - filterValue - ); - - const { templateParts, isResolving } = useTemplatePartsAsPatterns( - categoryId, - categoryType, - filterValue +export const usePatterns = ( + categoryType, + categoryId, + { search = '', page = 1, syncStatus } +) => { + return useSelect( + ( select ) => { + if ( categoryType === TEMPLATE_PARTS ) { + return selectTemplatePartsAsPatterns( select, { + categoryId, + search, + page, + } ); + } else if ( categoryType === PATTERNS ) { + return selectThemePatterns( select, { + categoryId, + search, + page, + } ); + } else if ( categoryType === USER_PATTERNS ) { + return selectUserPatterns( select, { + search, + syncStatus, + page, + } ); + } + return { + patterns: EMPTY_PATTERN_LIST, + isResolving: false, + getTotalPages: async () => 1, + }; + }, + [ categoryType, categoryId, search, page, syncStatus ] ); - - const patterns = { - syncedPatterns: [ ...templateParts, ...syncedPatterns ], - unsyncedPatterns: [ ...blockPatterns, ...unsyncedPatterns ], - }; - - return [ patterns, isResolving ]; }; export default usePatterns; diff --git a/packages/edit-site/src/components/sidebar-navigation-screen-patterns/use-my-patterns.js b/packages/edit-site/src/components/sidebar-navigation-screen-patterns/use-my-patterns.js index e3d5cc297164a2..37f0b0f8a4e063 100644 --- a/packages/edit-site/src/components/sidebar-navigation-screen-patterns/use-my-patterns.js +++ b/packages/edit-site/src/components/sidebar-navigation-screen-patterns/use-my-patterns.js @@ -6,18 +6,19 @@ import { useSelect } from '@wordpress/data'; import { __ } from '@wordpress/i18n'; export default function useMyPatterns() { - const myPatterns = useSelect( ( select ) => - select( coreStore ).getEntityRecords( 'postType', 'wp_block', { - per_page: -1, - } ) + const myPatternsCount = useSelect( + ( select ) => + select( coreStore ).getEntityRecords( 'postType', 'wp_block', { + per_page: -1, + } )?.length ?? 0 ); return { myPatterns: { - count: myPatterns?.length || 0, + count: myPatternsCount, name: 'my-patterns', label: __( 'My patterns' ), }, - hasPatterns: !! myPatterns?.length, + hasPatterns: myPatternsCount > 0, }; } diff --git a/packages/router/src/router.js b/packages/router/src/router.js index e5449cef54c6a0..d88f7a4522616f 100644 --- a/packages/router/src/router.js +++ b/packages/router/src/router.js @@ -6,6 +6,7 @@ import { useState, useEffect, useContext, + startTransition, } from '@wordpress/element'; /** @@ -39,7 +40,9 @@ export function RouterProvider( { children } ) { useEffect( () => { return history.listen( ( { location: updatedLocation } ) => { - setLocation( getLocationWithParams( updatedLocation ) ); + startTransition( () => { + setLocation( getLocationWithParams( updatedLocation ) ); + } ); } ); }, [] );