diff --git a/packages/edit-site/src/components/add-new-pattern/index.js b/packages/edit-site/src/components/add-new-pattern/index.js index 014ac6165aaef..0d25ff67bf448 100644 --- a/packages/edit-site/src/components/add-new-pattern/index.js +++ b/packages/edit-site/src/components/add-new-pattern/index.js @@ -25,10 +25,11 @@ import { PATTERN_DEFAULT_CATEGORY, TEMPLATE_PART_POST_TYPE, } from '../../utils/constants'; -import usePatternCategories from '../sidebar-navigation-screen-patterns/use-pattern-categories'; const { useHistory, useLocation } = unlock( routerPrivateApis ); -const { CreatePatternModal } = unlock( editPatternsPrivateApis ); +const { CreatePatternModal, useAddPatternCategory } = unlock( + editPatternsPrivateApis +); export default function AddNewPattern() { const history = useHistory(); @@ -43,7 +44,6 @@ export default function AddNewPattern() { const { createSuccessNotice, createErrorNotice } = useDispatch( noticesStore ); const patternUploadInputRef = useRef(); - const { patternCategories } = usePatternCategories(); function handleCreatePattern( { pattern, categoryId } ) { setShowPatternModal( false ); @@ -97,6 +97,7 @@ export default function AddNewPattern() { title: __( 'Import pattern from JSON' ), } ); + const { categoryMap, findOrCreateTerm } = useAddPatternCategory(); return ( <> - category.name === params.categoryId - )?.id; + let currentCategoryId; + // When we're not handling template parts, we should + // add or create the proper pattern category. + if ( params.categoryType !== TEMPLATE_PART_POST_TYPE ) { + const currentCategory = categoryMap + .values() + .find( + ( term ) => term.name === params.categoryId + ); + if ( !! currentCategory ) { + currentCategoryId = + currentCategory.id || + ( await findOrCreateTerm( + currentCategory.label + ) ); + } + } const pattern = await createPatternFromFile( file, currentCategoryId @@ -146,8 +158,12 @@ export default function AddNewPattern() { ); // Navigate to the All patterns category for the newly created pattern - // if we're not on that page already. - if ( ! currentCategoryId ) { + // if we're not on that page already and if we're not in the `my-patterns` + // category. + if ( + ! currentCategoryId && + params.categoryId !== 'my-patterns' + ) { history.push( { path: `/patterns`, categoryType: PATTERN_TYPES.theme, diff --git a/packages/patterns/src/components/create-pattern-modal.js b/packages/patterns/src/components/create-pattern-modal.js index 137c14222ced3..9576e50309e23 100644 --- a/packages/patterns/src/components/create-pattern-modal.js +++ b/packages/patterns/src/components/create-pattern-modal.js @@ -10,21 +10,17 @@ import { ToggleControl, } from '@wordpress/components'; import { __, _x } from '@wordpress/i18n'; -import { useState, useMemo } from '@wordpress/element'; -import { useDispatch, useSelect } from '@wordpress/data'; +import { useState } from '@wordpress/element'; +import { useDispatch } from '@wordpress/data'; import { store as noticesStore } from '@wordpress/notices'; -import { store as coreStore } from '@wordpress/core-data'; /** * Internal dependencies */ import { PATTERN_DEFAULT_CATEGORY, PATTERN_SYNC_TYPES } from '../constants'; - -/** - * Internal dependencies - */ import { store as patternsStore } from '../store'; -import CategorySelector, { CATEGORY_SLUG } from './category-selector'; +import CategorySelector from './category-selector'; +import { useAddPatternCategory } from '../private-hooks'; import { unlock } from '../lock-unlock'; export default function CreatePatternModal( { @@ -59,47 +55,9 @@ export function CreatePatternModalContents( { const [ isSaving, setIsSaving ] = useState( false ); const { createPattern } = unlock( useDispatch( patternsStore ) ); - const { saveEntityRecord, invalidateResolution } = useDispatch( coreStore ); const { createErrorNotice } = useDispatch( noticesStore ); - const { corePatternCategories, userPatternCategories } = useSelect( - ( select ) => { - const { getUserPatternCategories, getBlockPatternCategories } = - select( coreStore ); - - return { - corePatternCategories: getBlockPatternCategories(), - userPatternCategories: getUserPatternCategories(), - }; - } - ); - - const categoryMap = useMemo( () => { - // Merge the user and core pattern categories and remove any duplicates. - const uniqueCategories = new Map(); - userPatternCategories.forEach( ( category ) => { - uniqueCategories.set( category.label.toLowerCase(), { - label: category.label, - name: category.name, - id: category.id, - } ); - } ); - - corePatternCategories.forEach( ( category ) => { - if ( - ! uniqueCategories.has( category.label.toLowerCase() ) && - // There are two core categories with `Post` label so explicitly remove the one with - // the `query` slug to avoid any confusion. - category.name !== 'query' - ) { - uniqueCategories.set( category.label.toLowerCase(), { - label: category.label, - name: category.name, - } ); - } - } ); - return uniqueCategories; - }, [ userPatternCategories, corePatternCategories ] ); + const { categoryMap, findOrCreateTerm } = useAddPatternCategory(); async function onCreate( patternTitle, sync ) { if ( ! title || isSaving ) { @@ -137,38 +95,6 @@ export function CreatePatternModalContents( { } } - /** - * @param {string} term - * @return {Promise} The pattern category id. - */ - async function findOrCreateTerm( term ) { - try { - const existingTerm = categoryMap.get( term.toLowerCase() ); - if ( existingTerm && existingTerm.id ) { - return existingTerm.id; - } - // If we have an existing core category we need to match the new user category to the - // correct slug rather than autogenerating it to prevent duplicates, eg. the core `Headers` - // category uses the singular `header` as the slug. - const termData = existingTerm - ? { name: existingTerm.label, slug: existingTerm.name } - : { name: term }; - const newTerm = await saveEntityRecord( - 'taxonomy', - CATEGORY_SLUG, - termData, - { throwOnError: true } - ); - invalidateResolution( 'getUserPatternCategories' ); - return newTerm.id; - } catch ( error ) { - if ( error.code !== 'term_exists' ) { - throw error; - } - - return error.data.term_id; - } - } return (
{ diff --git a/packages/patterns/src/private-apis.js b/packages/patterns/src/private-apis.js index 099e4ae8ffed4..a5fbddb62fd62 100644 --- a/packages/patterns/src/private-apis.js +++ b/packages/patterns/src/private-apis.js @@ -15,6 +15,7 @@ import PatternsMenuItems from './components'; import RenamePatternCategoryModal from './components/rename-pattern-category-modal'; import PartialSyncingControls from './components/partial-syncing-controls'; import ResetOverridesControl from './components/reset-overrides-control'; +import { useAddPatternCategory } from './private-hooks'; import { PATTERN_TYPES, PATTERN_DEFAULT_CATEGORY, @@ -35,6 +36,7 @@ lock( privateApis, { RenamePatternCategoryModal, PartialSyncingControls, ResetOverridesControl, + useAddPatternCategory, PATTERN_TYPES, PATTERN_DEFAULT_CATEGORY, PATTERN_USER_CATEGORY, diff --git a/packages/patterns/src/private-hooks.js b/packages/patterns/src/private-hooks.js new file mode 100644 index 0000000000000..7dee37222fbbd --- /dev/null +++ b/packages/patterns/src/private-hooks.js @@ -0,0 +1,91 @@ +/** + * WordPress dependencies + */ +import { useSelect, useDispatch } from '@wordpress/data'; +import { store as coreStore } from '@wordpress/core-data'; +import { useMemo } from '@wordpress/element'; + +/** + * Internal dependencies + */ +import { CATEGORY_SLUG } from './components/category-selector'; + +/** + * Helper hook that creates a Map with the core and user patterns categories + * and removes any duplicates. It's used when we need to create new user + * categories when creating or importing patterns. + * This hook also provides a function to find or create a pattern category. + * + * @return {Object} The merged categories map and the callback function to find or create a category. + */ +export function useAddPatternCategory() { + const { saveEntityRecord, invalidateResolution } = useDispatch( coreStore ); + const { corePatternCategories, userPatternCategories } = useSelect( + ( select ) => { + const { getUserPatternCategories, getBlockPatternCategories } = + select( coreStore ); + + return { + corePatternCategories: getBlockPatternCategories(), + userPatternCategories: getUserPatternCategories(), + }; + }, + [] + ); + const categoryMap = useMemo( () => { + // Merge the user and core pattern categories and remove any duplicates. + const uniqueCategories = new Map(); + userPatternCategories.forEach( ( category ) => { + uniqueCategories.set( category.label.toLowerCase(), { + label: category.label, + name: category.name, + id: category.id, + } ); + } ); + + corePatternCategories.forEach( ( category ) => { + if ( + ! uniqueCategories.has( category.label.toLowerCase() ) && + // There are two core categories with `Post` label so explicitly remove the one with + // the `query` slug to avoid any confusion. + category.name !== 'query' + ) { + uniqueCategories.set( category.label.toLowerCase(), { + label: category.label, + name: category.name, + } ); + } + } ); + return uniqueCategories; + }, [ userPatternCategories, corePatternCategories ] ); + + async function findOrCreateTerm( term ) { + try { + const existingTerm = categoryMap.get( term.toLowerCase() ); + if ( existingTerm?.id ) { + return existingTerm.id; + } + // If we have an existing core category we need to match the new user category to the + // correct slug rather than autogenerating it to prevent duplicates, eg. the core `Headers` + // category uses the singular `header` as the slug. + const termData = existingTerm + ? { name: existingTerm.label, slug: existingTerm.name } + : { name: term }; + const newTerm = await saveEntityRecord( + 'taxonomy', + CATEGORY_SLUG, + termData, + { throwOnError: true } + ); + invalidateResolution( 'getUserPatternCategories' ); + return newTerm.id; + } catch ( error ) { + if ( error.code !== 'term_exists' ) { + throw error; + } + return error.data.term_id; + } + } + + return { categoryMap, findOrCreateTerm }; +}