diff --git a/packages/patterns/src/components/category-selector.js b/packages/patterns/src/components/category-selector.js index c9305806c7e13..397d851d3886b 100644 --- a/packages/patterns/src/components/category-selector.js +++ b/packages/patterns/src/components/category-selector.js @@ -4,7 +4,7 @@ import { __ } from '@wordpress/i18n'; import { useMemo, useState } from '@wordpress/element'; import { FormTokenField } from '@wordpress/components'; -import { useSelect, useDispatch } from '@wordpress/data'; +import { useSelect } from '@wordpress/data'; import { store as coreStore } from '@wordpress/core-data'; import { useDebounce } from '@wordpress/compose'; import { decodeEntities } from '@wordpress/html-entities'; @@ -13,13 +13,6 @@ const unescapeString = ( arg ) => { return decodeEntities( arg ); }; -const unescapeTerm = ( term ) => { - return { - ...term, - name: unescapeString( term.name ), - }; -}; - const EMPTY_ARRAY = []; const MAX_TERMS_SUGGESTIONS = 20; const DEFAULT_QUERY = { @@ -27,13 +20,11 @@ const DEFAULT_QUERY = { _fields: 'id,name', context: 'view', }; -const slug = 'wp_pattern_category'; +export const CATEGORY_SLUG = 'wp_pattern_category'; -export default function CategorySelector( { onCategorySelection } ) { - const [ values, setValues ] = useState( [] ); +export default function CategorySelector( { values, onChange } ) { const [ search, setSearch ] = useState( '' ); const debouncedSearch = useDebounce( setSearch, 500 ); - const { invalidateResolution } = useDispatch( coreStore ); const { searchResults } = useSelect( ( select ) => { @@ -41,7 +32,7 @@ export default function CategorySelector( { onCategorySelection } ) { return { searchResults: !! search - ? getEntityRecords( 'taxonomy', slug, { + ? getEntityRecords( 'taxonomy', CATEGORY_SLUG, { ...DEFAULT_QUERY, search, } ) @@ -57,28 +48,7 @@ export default function CategorySelector( { onCategorySelection } ) { ); }, [ searchResults ] ); - const { saveEntityRecord } = useDispatch( coreStore ); - - async function findOrCreateTerm( term ) { - try { - const newTerm = await saveEntityRecord( 'taxonomy', slug, term, { - throwOnError: true, - } ); - invalidateResolution( 'getUserPatternCategories' ); - return unescapeTerm( newTerm ); - } catch ( error ) { - if ( error.code !== 'term_exists' ) { - throw error; - } - - return { - id: error.data.term_id, - name: term.name, - }; - } - } - - function onChange( termNames ) { + function handleChange( termNames ) { const uniqueTerms = termNames.reduce( ( terms, newTerm ) => { if ( ! terms.some( @@ -90,15 +60,7 @@ export default function CategorySelector( { onCategorySelection } ) { return terms; }, [] ); - setValues( uniqueTerms ); - - Promise.all( - uniqueTerms.map( ( termName ) => - findOrCreateTerm( { name: termName } ) - ) - ).then( ( newTerms ) => { - onCategorySelection( newTerms ); - } ); + onChange( uniqueTerms ); } return ( @@ -107,7 +69,7 @@ export default function CategorySelector( { onCategorySelection } ) { className="patterns-menu-items__convert-modal-categories" value={ values } suggestions={ suggestions } - onChange={ onChange } + onChange={ handleChange } onInputChange={ debouncedSearch } maxSuggestions={ MAX_TERMS_SUGGESTIONS } label={ __( 'Categories' ) } diff --git a/packages/patterns/src/components/create-pattern-modal.js b/packages/patterns/src/components/create-pattern-modal.js index 189004b6a046b..8dad206f3f1e6 100644 --- a/packages/patterns/src/components/create-pattern-modal.js +++ b/packages/patterns/src/components/create-pattern-modal.js @@ -13,6 +13,7 @@ import { __ } from '@wordpress/i18n'; 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 @@ -23,7 +24,7 @@ import { PATTERN_DEFAULT_CATEGORY, PATTERN_SYNC_TYPES } from '../constants'; * Internal dependencies */ import { store as patternsStore } from '../store'; -import CategorySelector from './category-selector'; +import CategorySelector, { CATEGORY_SLUG } from './category-selector'; import { unlock } from '../lock-unlock'; export default function CreatePatternModal( { @@ -34,13 +35,24 @@ export default function CreatePatternModal( { className = 'patterns-menu-items__convert-modal', } ) { const [ syncType, setSyncType ] = useState( PATTERN_SYNC_TYPES.full ); - const [ categories, setCategories ] = useState( [] ); + const [ categoryTerms, setCategoryTerms ] = useState( [] ); const [ title, setTitle ] = useState( '' ); + const [ isSaving, setIsSaving ] = useState( false ); const { createPattern } = unlock( useDispatch( patternsStore ) ); - + const { saveEntityRecord, invalidateResolution } = useDispatch( coreStore ); const { createErrorNotice } = useDispatch( noticesStore ); + async function onCreate( patternTitle, sync ) { + if ( isSaving ) return; + try { + setIsSaving( true ); + const categories = await Promise.all( + categoryTerms.map( ( termName ) => + findOrCreateTerm( termName ) + ) + ); + const newPattern = await createPattern( patternTitle, sync, @@ -57,12 +69,35 @@ export default function CreatePatternModal( { id: 'convert-to-pattern-error', } ); onError(); + } finally { + setIsSaving( false ); + setCategoryTerms( [] ); + setTitle( '' ); } } - const handleCategorySelection = ( selectedCategories ) => { - setCategories( selectedCategories.map( ( cat ) => cat.id ) ); - }; + /** + * @param {string} term + * @return {Promise} The pattern category id. + */ + async function findOrCreateTerm( term ) { + try { + const newTerm = await saveEntityRecord( + 'taxonomy', + CATEGORY_SLUG, + { name: term }, + { throwOnError: true } + ); + invalidateResolution( 'getUserPatternCategories' ); + return newTerm.id; + } catch ( error ) { + if ( error.code !== 'term_exists' ) { + throw error; + } + + return error.data.term_id; + } + } return ( { event.preventDefault(); onCreate( title, syncType ); - setTitle( '' ); } } > @@ -90,7 +124,8 @@ export default function CreatePatternModal( { className="patterns-create-modal__name-input" /> -