From 8119ad4f30b57c39ddbae14e2acb54bbbf1611a1 Mon Sep 17 00:00:00 2001 From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> Date: Wed, 4 Oct 2023 18:45:19 +1000 Subject: [PATCH 01/18] Patterns: Add ability to rename pattern category --- .../src/components/page-patterns/header.js | 43 +++++- .../rename-category-menu-item.js | 39 +++++ .../src/components/page-patterns/style.scss | 4 + .../rename-pattern-category-modal.js | 139 ++++++++++++++++++ packages/patterns/src/private-apis.js | 2 + 5 files changed, 221 insertions(+), 6 deletions(-) create mode 100644 packages/edit-site/src/components/page-patterns/rename-category-menu-item.js create mode 100644 packages/patterns/src/components/rename-pattern-category-modal.js diff --git a/packages/edit-site/src/components/page-patterns/header.js b/packages/edit-site/src/components/page-patterns/header.js index d692f523e8482..7887f5fef6e0a 100644 --- a/packages/edit-site/src/components/page-patterns/header.js +++ b/packages/edit-site/src/components/page-patterns/header.js @@ -2,16 +2,22 @@ * WordPress dependencies */ import { - __experimentalVStack as VStack, + DropdownMenu, + MenuGroup, + __experimentalHStack as HStack, __experimentalHeading as Heading, __experimentalText as Text, + __experimentalVStack as VStack, } from '@wordpress/components'; import { store as editorStore } from '@wordpress/editor'; import { useSelect } from '@wordpress/data'; +import { __, sprintf } from '@wordpress/i18n'; +import { moreVertical } from '@wordpress/icons'; /** * Internal dependencies */ +import RenameCategoryMenuItem from './rename-category-menu-item'; import usePatternCategories from '../sidebar-navigation-screen-patterns/use-pattern-categories'; import { TEMPLATE_PART_POST_TYPE, PATTERN_TYPES } from '../../utils/constants'; @@ -28,7 +34,7 @@ export default function PatternsHeader( { [] ); - let title, description; + let title, description, patternCategory; if ( type === TEMPLATE_PART_POST_TYPE ) { const templatePartArea = templatePartAreas.find( ( area ) => area.area === categoryId @@ -36,7 +42,7 @@ export default function PatternsHeader( { title = templatePartArea?.label; description = templatePartArea?.description; } else if ( type === PATTERN_TYPES.theme ) { - const patternCategory = patternCategories.find( + patternCategory = patternCategories.find( ( category ) => category.name === categoryId ); title = patternCategory?.label; @@ -47,9 +53,34 @@ export default function PatternsHeader( { return ( - - { title } - + + + { title } + + { !! patternCategory?.id && ( + + { ( { onClose } ) => ( + + + + ) } + + ) } + { description ? ( { description } diff --git a/packages/edit-site/src/components/page-patterns/rename-category-menu-item.js b/packages/edit-site/src/components/page-patterns/rename-category-menu-item.js new file mode 100644 index 0000000000000..fcb46a09fbccd --- /dev/null +++ b/packages/edit-site/src/components/page-patterns/rename-category-menu-item.js @@ -0,0 +1,39 @@ +/** + * WordPress dependencies + */ +import { MenuItem } from '@wordpress/components'; +import { useState } from '@wordpress/element'; +import { __ } from '@wordpress/i18n'; +import { privateApis as patternsPrivateApis } from '@wordpress/patterns'; + +/** + * Internal dependencies + */ +import { unlock } from '../../lock-unlock'; + +const { RenamePatternCategoryModal } = unlock( patternsPrivateApis ); + +export default function RenameCategoryMenuItem( { category, onClose } ) { + const [ isModalOpen, setIsModalOpen ] = useState( false ); + + if ( ! category?.id ) { + return null; + } + + return ( + <> + setIsModalOpen( true ) }> + { __( 'Rename' ) } + + { isModalOpen && ( + { + setIsModalOpen( false ); + onClose(); + } } + /> + ) } + + ); +} diff --git a/packages/edit-site/src/components/page-patterns/style.scss b/packages/edit-site/src/components/page-patterns/style.scss index 4b4d7bb9059d4..7a70550d29d6f 100644 --- a/packages/edit-site/src/components/page-patterns/style.scss +++ b/packages/edit-site/src/components/page-patterns/style.scss @@ -101,6 +101,10 @@ background: $gray-900; padding: $grid-unit-40 $grid-unit-40 $grid-unit-20; z-index: z-index(".edit-site-patterns__header"); + + .edit-site-patterns__button { + color: $gray-600; + } } .edit-site-patterns__section { diff --git a/packages/patterns/src/components/rename-pattern-category-modal.js b/packages/patterns/src/components/rename-pattern-category-modal.js new file mode 100644 index 0000000000000..71e6b19a3f329 --- /dev/null +++ b/packages/patterns/src/components/rename-pattern-category-modal.js @@ -0,0 +1,139 @@ +/** + * WordPress dependencies + */ +import { + Modal, + Button, + TextControl, + __experimentalHStack as HStack, + __experimentalVStack as VStack, +} from '@wordpress/components'; +import { store as coreStore } from '@wordpress/core-data'; +import { useDispatch, useSelect } from '@wordpress/data'; +import { useEffect, useState } from '@wordpress/element'; +import { decodeEntities } from '@wordpress/html-entities'; +import { __ } from '@wordpress/i18n'; +import { store as noticesStore } from '@wordpress/notices'; + +/** + * Internal dependencies + */ +import { CATEGORY_SLUG } from './category-selector'; + +const usePatternCategory = ( categoryId ) => { + return useSelect( + ( select ) => { + const record = select( coreStore ).getEditedEntityRecord( + 'taxonomy', + CATEGORY_SLUG, + categoryId + ); + const hasResolvedRecord = select( coreStore ).hasFinishedResolution( + 'getEditedEntityRecord', + [ 'taxonomy', CATEGORY_SLUG, categoryId ] + ); + + return { + category: record, + hasResolved: hasResolvedRecord, + }; + }, + [ categoryId ] + ); +}; + +export default function RenamePatternCategoryModal( { categoryId, onClose } ) { + const { category, hasResolved } = usePatternCategory( categoryId ); + const [ name, setName ] = useState( '' ); + const [ isSaving, setIsSaving ] = useState( false ); + + useEffect( () => { + if ( hasResolved && category?.name ) { + setName( decodeEntities( category.name ) ); + } + }, [ hasResolved, category?.name ] ); + + const { + editEntityRecord, + invalidateResolution, + __experimentalSaveSpecifiedEntityEdits: saveSpecifiedEntityEdits, + } = useDispatch( coreStore ); + + const { createErrorNotice, createSuccessNotice } = + useDispatch( noticesStore ); + + async function onRename( event ) { + event.preventDefault(); + + if ( ! name || isSaving ) { + return; + } + try { + setIsSaving( true ); + + await editEntityRecord( 'taxonomy', CATEGORY_SLUG, categoryId, { + name, + } ); + + setName( '' ); + onClose?.(); + + await saveSpecifiedEntityEdits( + 'taxonomy', + CATEGORY_SLUG, + categoryId, + [ 'name' ], + { throwOnError: true } + ); + + invalidateResolution( 'getUserPatternCategories' ); + + createSuccessNotice( __( 'Pattern category renamed.' ), { + type: 'snackbar', + id: 'pattern-category-update', + } ); + } catch ( error ) { + createErrorNotice( error.message, { + type: 'snackbar', + id: 'pattern-category-update', + } ); + } finally { + setIsSaving( false ); + setName( '' ); + } + } + + const onRequestClose = () => { + onClose(); + setName( '' ); + }; + + return ( + +
+ + + + + + + +
+
+ ); +} diff --git a/packages/patterns/src/private-apis.js b/packages/patterns/src/private-apis.js index cdbf1a6ba2940..52d0f05fb2287 100644 --- a/packages/patterns/src/private-apis.js +++ b/packages/patterns/src/private-apis.js @@ -4,6 +4,7 @@ import { lock } from './lock-unlock'; import CreatePatternModal from './components/create-pattern-modal'; import PatternsMenuItems from './components'; +import RenamePatternCategoryModal from './components/rename-pattern-category-modal'; import { PATTERN_TYPES, PATTERN_DEFAULT_CATEGORY, @@ -16,6 +17,7 @@ export const privateApis = {}; lock( privateApis, { CreatePatternModal, PatternsMenuItems, + RenamePatternCategoryModal, PATTERN_TYPES, PATTERN_DEFAULT_CATEGORY, PATTERN_USER_CATEGORY, From b9fc306b75714ab49b7d3d5e38ce3d515c608061 Mon Sep 17 00:00:00 2001 From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> Date: Wed, 4 Oct 2023 19:12:59 +1000 Subject: [PATCH 02/18] Add option to delete pattern category --- .../delete-category-menu-item.js | 95 +++++++++++++++++++ .../src/components/page-patterns/header.js | 5 + 2 files changed, 100 insertions(+) create mode 100644 packages/edit-site/src/components/page-patterns/delete-category-menu-item.js diff --git a/packages/edit-site/src/components/page-patterns/delete-category-menu-item.js b/packages/edit-site/src/components/page-patterns/delete-category-menu-item.js new file mode 100644 index 0000000000000..46bbf450cd40d --- /dev/null +++ b/packages/edit-site/src/components/page-patterns/delete-category-menu-item.js @@ -0,0 +1,95 @@ +/** + * WordPress dependencies + */ +import { + MenuItem, + __experimentalConfirmDialog as ConfirmDialog, +} from '@wordpress/components'; +import { store as coreStore } from '@wordpress/core-data'; +import { useDispatch } from '@wordpress/data'; +import { useState } from '@wordpress/element'; +import { decodeEntities } from '@wordpress/html-entities'; +import { __, sprintf } from '@wordpress/i18n'; +import { store as noticesStore } from '@wordpress/notices'; +import { privateApis as routerPrivateApis } from '@wordpress/router'; + +/** + * Internal dependencies + */ +import { unlock } from '../../lock-unlock'; +import { PATTERN_TYPES, PATTERN_DEFAULT_CATEGORY } from '../../utils/constants'; + +const { useHistory } = unlock( routerPrivateApis ); + +export default function DeleteCategoryMenuItem( { category, onClose } ) { + const [ isModalOpen, setIsModalOpen ] = useState( false ); + const history = useHistory(); + + const { createSuccessNotice, createErrorNotice } = + useDispatch( noticesStore ); + const { deleteEntityRecord, invalidateResolution } = + useDispatch( coreStore ); + + if ( ! category?.id ) { + return null; + } + + const onDelete = async () => { + try { + await deleteEntityRecord( + 'taxonomy', + 'wp_pattern_category', + category.id, + { force: true }, + { throwOnError: true } + ); + + invalidateResolution( 'getUserPatternCategories' ); + + createSuccessNotice( + sprintf( + /* translators: The pattern category's name */ + __( '"%s" deleted.' ), + category.label + ), + { type: 'snackbar', id: 'pattern-category-delete' } + ); + + onClose?.(); + history.push( { + path: `/patterns`, + categoryType: PATTERN_TYPES.theme, + categoryId: PATTERN_DEFAULT_CATEGORY, + } ); + } catch ( error ) { + const errorMessage = + error.message && error.code !== 'unknown_error' + ? error.message + : __( 'An error occurred while deleting the pattern.' ); + + createErrorNotice( errorMessage, { + type: 'snackbar', + id: 'pattern-category-delete', + } ); + } + }; + + return ( + <> + setIsModalOpen( true ) }> + { __( 'Delete' ) } + + setIsModalOpen( false ) } + > + { sprintf( + // translators: %s: The pattern category's name. + __( 'Are you sure you want to delete "%s"?' ), + decodeEntities( category.label ) + ) } + + + ); +} diff --git a/packages/edit-site/src/components/page-patterns/header.js b/packages/edit-site/src/components/page-patterns/header.js index 7887f5fef6e0a..a5b19e4e3a6b9 100644 --- a/packages/edit-site/src/components/page-patterns/header.js +++ b/packages/edit-site/src/components/page-patterns/header.js @@ -18,6 +18,7 @@ import { moreVertical } from '@wordpress/icons'; * Internal dependencies */ import RenameCategoryMenuItem from './rename-category-menu-item'; +import DeleteCategoryMenuItem from './delete-category-menu-item'; import usePatternCategories from '../sidebar-navigation-screen-patterns/use-pattern-categories'; import { TEMPLATE_PART_POST_TYPE, PATTERN_TYPES } from '../../utils/constants'; @@ -76,6 +77,10 @@ export default function PatternsHeader( { category={ patternCategory } onClose={ onClose } /> + ) } From 153b60740a6c500f044d69f02e99ece0adf88193 Mon Sep 17 00:00:00 2001 From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> Date: Thu, 5 Oct 2023 17:15:04 +1000 Subject: [PATCH 03/18] Prevent stale categorization of patterns after category deletion --- .../components/page-patterns/delete-category-menu-item.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/packages/edit-site/src/components/page-patterns/delete-category-menu-item.js b/packages/edit-site/src/components/page-patterns/delete-category-menu-item.js index 46bbf450cd40d..f6297a8556684 100644 --- a/packages/edit-site/src/components/page-patterns/delete-category-menu-item.js +++ b/packages/edit-site/src/components/page-patterns/delete-category-menu-item.js @@ -44,7 +44,14 @@ export default function DeleteCategoryMenuItem( { category, onClose } ) { { throwOnError: true } ); + // Prevent the need to refresh the page to get up-to-date categories + // and pattern categorization. invalidateResolution( 'getUserPatternCategories' ); + invalidateResolution( 'getEntityRecords', [ + 'postType', + PATTERN_TYPES.user, + { per_page: -1 }, + ] ); createSuccessNotice( sprintf( From 5186f878af3ed52462dddbb6a743fb57ecfdf585 Mon Sep 17 00:00:00 2001 From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> Date: Mon, 9 Oct 2023 13:33:00 +1000 Subject: [PATCH 04/18] Disable button when name hasn't changed --- .../src/components/rename-pattern-category-modal.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/patterns/src/components/rename-pattern-category-modal.js b/packages/patterns/src/components/rename-pattern-category-modal.js index 71e6b19a3f329..03280910d6aa8 100644 --- a/packages/patterns/src/components/rename-pattern-category-modal.js +++ b/packages/patterns/src/components/rename-pattern-category-modal.js @@ -65,7 +65,7 @@ export default function RenamePatternCategoryModal( { categoryId, onClose } ) { async function onRename( event ) { event.preventDefault(); - if ( ! name || isSaving ) { + if ( ! name || name === category.name || isSaving ) { return; } try { @@ -126,7 +126,9 @@ export default function RenamePatternCategoryModal( { categoryId, onClose } ) {