From d2bf1366d55f8fad5edeebd521eda29b610d19bc Mon Sep 17 00:00:00 2001 From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> Date: Thu, 12 Oct 2023 13:44:33 +1000 Subject: [PATCH] Patterns: Use modal for pattern duplication flow as workaround for changing sync status (#54764) --- .../create-template-part-modal/index.js | 14 +- .../page-patterns/duplicate-menu-item.js | 278 +++++++----------- .../components/page-patterns/use-patterns.js | 5 + .../src/components/create-pattern-modal.js | 22 +- 4 files changed, 130 insertions(+), 189 deletions(-) diff --git a/packages/edit-site/src/components/create-template-part-modal/index.js b/packages/edit-site/src/components/create-template-part-modal/index.js index 0b3a57e0c0744..31f12b6cab56d 100644 --- a/packages/edit-site/src/components/create-template-part-modal/index.js +++ b/packages/edit-site/src/components/create-template-part-modal/index.js @@ -39,17 +39,21 @@ import { } from '../../utils/template-part-create'; export default function CreateTemplatePartModal( { - closeModal, + defaultArea = TEMPLATE_PART_AREA_DEFAULT_CATEGORY, blocks = [], + confirmLabel = __( 'Create' ), + closeModal, + modalTitle = __( 'Create template part' ), onCreate, onError, + defaultTitle = '', } ) { const { createErrorNotice } = useDispatch( noticesStore ); const { saveEntityRecord } = useDispatch( coreStore ); const existingTemplateParts = useExistingTemplateParts(); - const [ title, setTitle ] = useState( '' ); - const [ area, setArea ] = useState( TEMPLATE_PART_AREA_DEFAULT_CATEGORY ); + const [ title, setTitle ] = useState( defaultTitle ); + const [ area, setArea ] = useState( defaultArea ); const [ isSubmitting, setIsSubmitting ] = useState( false ); const instanceId = useInstanceId( CreateTemplatePartModal ); @@ -104,7 +108,7 @@ export default function CreateTemplatePartModal( { return ( @@ -181,7 +185,7 @@ export default function CreateTemplatePartModal( { aria-disabled={ ! title || isSubmitting } isBusy={ isSubmitting } > - { __( 'Create' ) } + { confirmLabel } diff --git a/packages/edit-site/src/components/page-patterns/duplicate-menu-item.js b/packages/edit-site/src/components/page-patterns/duplicate-menu-item.js index a882de95bdaeb..a0842cf9002a0 100644 --- a/packages/edit-site/src/components/page-patterns/duplicate-menu-item.js +++ b/packages/edit-site/src/components/page-patterns/duplicate-menu-item.js @@ -2,10 +2,11 @@ * WordPress dependencies */ import { MenuItem } from '@wordpress/components'; -import { store as coreStore } from '@wordpress/core-data'; import { useDispatch } from '@wordpress/data'; +import { useState } from '@wordpress/element'; import { __, sprintf } from '@wordpress/i18n'; import { store as noticesStore } from '@wordpress/notices'; +import { privateApis as patternsPrivateApis } from '@wordpress/patterns'; import { privateApis as routerPrivateApis } from '@wordpress/router'; /** @@ -13,206 +14,131 @@ import { privateApis as routerPrivateApis } from '@wordpress/router'; */ import { TEMPLATE_PART_POST_TYPE, - PATTERN_TYPES, PATTERN_SYNC_TYPES, + PATTERN_TYPES, } from '../../utils/constants'; -import { - useExistingTemplateParts, - getUniqueTemplatePartTitle, - getCleanTemplatePartSlug, -} from '../../utils/template-part-create'; import { unlock } from '../../lock-unlock'; -import usePatternCategories from '../sidebar-navigation-screen-patterns/use-pattern-categories'; +import CreateTemplatePartModal from '../create-template-part-modal'; +const { CreatePatternModal } = unlock( patternsPrivateApis ); const { useHistory } = unlock( routerPrivateApis ); -function getPatternMeta( item ) { - if ( item.type === PATTERN_TYPES.theme ) { - return { wp_pattern_sync_status: PATTERN_SYNC_TYPES.unsynced }; - } - - const syncStatus = item.patternBlock.wp_pattern_sync_status; - const isUnsynced = syncStatus === PATTERN_SYNC_TYPES.unsynced; - - return { - ...item.patternBlock.meta, - wp_pattern_sync_status: isUnsynced ? syncStatus : undefined, - }; -} - export default function DuplicateMenuItem( { categoryId, item, label = __( 'Duplicate' ), onClose, } ) { - const { saveEntityRecord, invalidateResolution } = useDispatch( coreStore ); - const { createErrorNotice, createSuccessNotice } = - useDispatch( noticesStore ); - + const { createSuccessNotice } = useDispatch( noticesStore ); + const [ isModalOpen, setIsModalOpen ] = useState( false ); const history = useHistory(); - const existingTemplateParts = useExistingTemplateParts(); - const { patternCategories } = usePatternCategories(); - async function createTemplatePart() { - try { - const copiedTitle = sprintf( - /* translators: %s: Existing template part title */ - __( '%s (Copy)' ), - item.title - ); - const title = getUniqueTemplatePartTitle( - copiedTitle, - existingTemplateParts - ); - const slug = getCleanTemplatePartSlug( title ); - const { area, content } = item.templatePart; - - const result = await saveEntityRecord( - 'postType', - TEMPLATE_PART_POST_TYPE, - { slug, title, content, area }, - { throwOnError: true } - ); - - createSuccessNotice( - sprintf( - // translators: %s: The new template part's title e.g. 'Call to action (copy)'. - __( '"%s" duplicated.' ), - item.title - ), - { - type: 'snackbar', - id: 'edit-site-patterns-success', - } - ); - - history.push( { - postType: TEMPLATE_PART_POST_TYPE, - postId: result?.id, - categoryType: TEMPLATE_PART_POST_TYPE, - categoryId, - } ); + const isTemplatePart = item.type === TEMPLATE_PART_POST_TYPE; - onClose(); - } catch ( error ) { - const errorMessage = - error.message && error.code !== 'unknown_error' - ? error.message - : __( - 'An error occurred while creating the template part.' - ); - - createErrorNotice( errorMessage, { + async function onTemplatePartSuccess( templatePart ) { + createSuccessNotice( + sprintf( + // translators: %s: The new template part's title e.g. 'Call to action (copy)'. + __( '"%s" duplicated.' ), + item.title + ), + { type: 'snackbar', - id: 'edit-site-patterns-error', - } ); - onClose(); - } - } - - async function findOrCreateTerm( term ) { - try { - const newTerm = await saveEntityRecord( - 'taxonomy', - 'wp_pattern_category', - { - name: term.label, - slug: term.name, - description: term.description, - }, - { - throwOnError: true, - } - ); - invalidateResolution( 'getUserPatternCategories' ); - return newTerm.id; - } catch ( error ) { - if ( error.code !== 'term_exists' ) { - throw error; + id: 'edit-site-patterns-success', } + ); - return error.data.term_id; - } + history.push( { + postType: TEMPLATE_PART_POST_TYPE, + postId: templatePart?.id, + categoryType: TEMPLATE_PART_POST_TYPE, + categoryId, + } ); + + onClose(); } - async function getCategories( categories ) { - const terms = categories.map( ( category ) => { - const fullCategory = patternCategories.find( - ( cat ) => cat.name === category - ); - if ( fullCategory.id ) { - return fullCategory.id; + function onPatternSuccess( { pattern } ) { + createSuccessNotice( + sprintf( + // translators: %s: The new pattern's title e.g. 'Call to action (copy)'. + __( '"%s" duplicated.' ), + pattern.title.raw + ), + { + type: 'snackbar', + id: 'edit-site-patterns-success', } - return findOrCreateTerm( fullCategory ); + ); + + history.push( { + categoryType: PATTERN_TYPES.theme, + categoryId, + postType: PATTERN_TYPES.user, + postId: pattern.id, } ); - return Promise.all( terms ); + onClose(); } - async function createPattern() { - try { - const isThemePattern = item.type === PATTERN_TYPES.theme; - const title = sprintf( - /* translators: %s: Existing pattern title */ - __( '%s (Copy)' ), - item.title || item.name - ); - const categories = await getCategories( item.categories || [] ); - - const result = await saveEntityRecord( - 'postType', - PATTERN_TYPES.user, - { - content: isThemePattern - ? item.content - : item.patternBlock.content, - meta: getPatternMeta( item ), - status: 'publish', - title, - wp_pattern_category: categories, - }, - { throwOnError: true } - ); - - createSuccessNotice( - sprintf( - // translators: %s: The new pattern's title e.g. 'Call to action (copy)'. - __( '"%s" duplicated.' ), + const isThemePattern = item.type === PATTERN_TYPES.theme; + const closeModal = () => setIsModalOpen( false ); + const duplicatedProps = isTemplatePart + ? { + blocks: item.blocks, + defaultArea: item.templatePart.area, + defaultTitle: sprintf( + /* translators: %s: Existing template part title */ + __( '%s (Copy)' ), + item.title + ), + } + : { + defaultCategories: isThemePattern + ? item.categories + : item.termLabels, + content: isThemePattern + ? item.content + : item.patternBlock.content, + defaultSyncType: isThemePattern + ? PATTERN_SYNC_TYPES.unsynced + : item.syncStatus, + defaultTitle: sprintf( + /* translators: %s: Existing pattern title */ + __( '%s (Copy)' ), item.title || item.name ), - { - type: 'snackbar', - id: 'edit-site-patterns-success', - } - ); - - history.push( { - categoryType: PATTERN_TYPES.theme, - categoryId, - postType: PATTERN_TYPES.user, - postId: result?.id, - } ); - - onClose(); - } catch ( error ) { - const errorMessage = - error.message && error.code !== 'unknown_error' - ? error.message - : __( 'An error occurred while creating the pattern.' ); - - createErrorNotice( errorMessage, { - type: 'snackbar', - id: 'edit-site-patterns-error', - } ); - onClose(); - } - } - - const createItem = - item.type === TEMPLATE_PART_POST_TYPE - ? createTemplatePart - : createPattern; - - return { label }; + }; + + return ( + <> + setIsModalOpen( true ) } + aria-expanded={ isModalOpen } + aria-haspopup="dialog" + > + { label } + + { isModalOpen && ! isTemplatePart && ( + + ) } + { isModalOpen && isTemplatePart && ( + + ) } + + ); } 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 6504b6f59684c..fde4eaadb5dc0 100644 --- a/packages/edit-site/src/components/page-patterns/use-patterns.js +++ b/packages/edit-site/src/components/page-patterns/use-patterns.js @@ -195,6 +195,11 @@ const patternBlockToPattern = ( patternBlock, categories ) => ( { : patternCategoryId ), } ), + termLabels: patternBlock.wp_pattern_category.map( ( patternCategoryId ) => + categories?.get( patternCategoryId ) + ? categories.get( patternCategoryId ).label + : patternCategoryId + ), id: patternBlock.id, name: patternBlock.slug, syncStatus: patternBlock.wp_pattern_sync_status || PATTERN_SYNC_TYPES.full, diff --git a/packages/patterns/src/components/create-pattern-modal.js b/packages/patterns/src/components/create-pattern-modal.js index 37dd725ef9226..6dd162605506e 100644 --- a/packages/patterns/src/components/create-pattern-modal.js +++ b/packages/patterns/src/components/create-pattern-modal.js @@ -28,15 +28,21 @@ import CategorySelector, { CATEGORY_SLUG } from './category-selector'; import { unlock } from '../lock-unlock'; export default function CreatePatternModal( { - onSuccess, - onError, + confirmLabel = __( 'Create' ), + defaultCategories = [], + className = 'patterns-menu-items__convert-modal', content, + modalTitle = __( 'Create pattern' ), onClose, - className = 'patterns-menu-items__convert-modal', + onError, + onSuccess, + defaultSyncType = PATTERN_SYNC_TYPES.full, + defaultTitle = '', } ) { - const [ syncType, setSyncType ] = useState( PATTERN_SYNC_TYPES.full ); - const [ categoryTerms, setCategoryTerms ] = useState( [] ); - const [ title, setTitle ] = useState( '' ); + const [ syncType, setSyncType ] = useState( defaultSyncType ); + const [ categoryTerms, setCategoryTerms ] = useState( defaultCategories ); + const [ title, setTitle ] = useState( defaultTitle ); + const [ isSaving, setIsSaving ] = useState( false ); const { createPattern } = unlock( useDispatch( patternsStore ) ); const { saveEntityRecord, invalidateResolution } = useDispatch( coreStore ); @@ -145,7 +151,7 @@ export default function CreatePatternModal( { return ( { onClose(); setTitle( '' ); @@ -203,7 +209,7 @@ export default function CreatePatternModal( { aria-disabled={ ! title || isSaving } isBusy={ isSaving } > - { __( 'Create' ) } + { confirmLabel }