Skip to content

Commit

Permalink
Patterns: Use modal for pattern duplication flow as workaround for ch…
Browse files Browse the repository at this point in the history
…anging sync status (WordPress#54764)
  • Loading branch information
aaronrobertshaw authored Oct 12, 2023
1 parent d84da61 commit d2bf136
Show file tree
Hide file tree
Showing 4 changed files with 130 additions and 189 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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 );

Expand Down Expand Up @@ -104,7 +108,7 @@ export default function CreateTemplatePartModal( {

return (
<Modal
title={ __( 'Create template part' ) }
title={ modalTitle }
onRequestClose={ closeModal }
overlayClassName="edit-site-create-template-part-modal"
>
Expand Down Expand Up @@ -181,7 +185,7 @@ export default function CreateTemplatePartModal( {
aria-disabled={ ! title || isSubmitting }
isBusy={ isSubmitting }
>
{ __( 'Create' ) }
{ confirmLabel }
</Button>
</HStack>
</VStack>
Expand Down
278 changes: 102 additions & 176 deletions packages/edit-site/src/components/page-patterns/duplicate-menu-item.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,217 +2,143 @@
* 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';

/**
* Internal dependencies
*/
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 <MenuItem onClick={ createItem }>{ label }</MenuItem>;
};

return (
<>
<MenuItem
onClick={ () => setIsModalOpen( true ) }
aria-expanded={ isModalOpen }
aria-haspopup="dialog"
>
{ label }
</MenuItem>
{ isModalOpen && ! isTemplatePart && (
<CreatePatternModal
confirmLabel={ __( 'Duplicate' ) }
modalTitle={ __( 'Duplicate pattern' ) }
onClose={ closeModal }
onError={ closeModal }
onSuccess={ onPatternSuccess }
{ ...duplicatedProps }
/>
) }
{ isModalOpen && isTemplatePart && (
<CreateTemplatePartModal
confirmLabel={ __( 'Duplicate' ) }
closeModal={ closeModal }
modalTitle={ __( 'Duplicate template part' ) }
onCreate={ onTemplatePartSuccess }
onError={ closeModal }
{ ...duplicatedProps }
/>
) }
</>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Loading

0 comments on commit d2bf136

Please sign in to comment.