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: Use modal for pattern duplication flow as workaround for changing sync status #54764

Merged
merged 7 commits into from
Oct 12, 2023
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,
area: areaProp = TEMPLATE_PART_AREA_DEFAULT_CATEGORY,
blocks = [],
buttonLabel = __( 'Create' ),
aaronrobertshaw marked this conversation as resolved.
Show resolved Hide resolved
closeModal,
modalTitle = __( 'Create template part' ),
onCreate,
onError,
title: titleProp = '',
aaronrobertshaw marked this conversation as resolved.
Show resolved Hide resolved
} ) {
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( titleProp );
const [ area, setArea ] = useState( areaProp );
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' ) }
{ buttonLabel }
</Button>
</HStack>
</VStack>
Expand Down
276 changes: 100 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,141 @@
* 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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This shows the infamous [object object] in the snackbar as shown in the video too.

Copy link
Contributor Author

@aaronrobertshaw aaronrobertshaw Oct 11, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, I completely missed that but just ran into it while adding the duplicate pattern command. I'll fix it up tomorrow (f3c2b9e)

),
{
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,
area: item.templatePart.area,
title: sprintf(
/* translators: %s: Existing template part title */
__( '%s (Copy)' ),
item.title
),
}
: {
categories: isThemePattern ? item.categories : item.termLabels,
content: isThemePattern
? item.content
: item.patternBlock.content,
syncType: isThemePattern
? PATTERN_SYNC_TYPES.unsynced
: item.syncStatus,
title: 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
buttonLabel={ __( 'Duplicate' ) }
modalTitle={ __( 'Duplicate pattern' ) }
onClose={ closeModal }
onError={ closeModal }
onSuccess={ onPatternSuccess }
{ ...duplicatedProps }
/>
) }
{ isModalOpen && isTemplatePart && (
<CreateTemplatePartModal
buttonLabel={ __( '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
Loading