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

Patterns: Fix pattern categories on import #58926

Merged
merged 2 commits into from
Feb 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
38 changes: 27 additions & 11 deletions packages/edit-site/src/components/add-new-pattern/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -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 );
Expand Down Expand Up @@ -97,6 +97,7 @@ export default function AddNewPattern() {
title: __( 'Import pattern from JSON' ),
} );

const { categoryMap, findOrCreateTerm } = useAddPatternCategory();
return (
<>
<DropdownMenu
Expand Down Expand Up @@ -132,12 +133,23 @@ export default function AddNewPattern() {
const file = event.target.files?.[ 0 ];
if ( ! file ) return;
try {
const currentCategoryId =
params.categoryType !== TEMPLATE_PART_POST_TYPE &&
patternCategories.find(
( category ) =>
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
Expand All @@ -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,
Expand Down
84 changes: 5 additions & 79 deletions packages/patterns/src/components/create-pattern-modal.js
Original file line number Diff line number Diff line change
Expand Up @@ -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( {
Expand Down Expand Up @@ -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 ) {
Expand Down Expand Up @@ -137,38 +95,6 @@ export function CreatePatternModalContents( {
}
}

/**
* @param {string} term
* @return {Promise<number>} 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 (
<form
onSubmit={ ( event ) => {
Expand Down
2 changes: 2 additions & 0 deletions packages/patterns/src/private-apis.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -35,6 +36,7 @@ lock( privateApis, {
RenamePatternCategoryModal,
PartialSyncingControls,
ResetOverridesControl,
useAddPatternCategory,
PATTERN_TYPES,
PATTERN_DEFAULT_CATEGORY,
PATTERN_USER_CATEGORY,
Expand Down
91 changes: 91 additions & 0 deletions packages/patterns/src/private-hooks.js
Original file line number Diff line number Diff line change
@@ -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 };
}