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,
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
Loading