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

Template Parts: Show existing template parts and a list of block patterns at creation flow. #38814

Merged
merged 12 commits into from
Feb 18, 2022
1 change: 1 addition & 0 deletions packages/block-editor/src/components/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@ export { default as WritingFlow } from './writing-flow';
export { default as useBlockDisplayInformation } from './use-block-display-information';
export { default as __unstableIframe } from './iframe';
export { default as __experimentalUseNoRecursiveRenders } from './use-no-recursive-renders';
export { default as __experimentalBlockPatternsList } from './block-patterns-list';

/*
* State Related Components
Expand Down
108 changes: 50 additions & 58 deletions packages/block-library/src/template-part/edit/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,19 +15,20 @@ import {
store as blockEditorStore,
} from '@wordpress/block-editor';
import {
Dropdown,
ToolbarGroup,
ToolbarButton,
Spinner,
Modal,
} from '@wordpress/components';
import { __, sprintf } from '@wordpress/i18n';
import { store as coreStore } from '@wordpress/core-data';
import { useState } from '@wordpress/element';

/**
* Internal dependencies
*/
import TemplatePartPlaceholder from './placeholder';
import TemplatePartSelection from './selection';
import TemplatePartSelectionModal from './selection-modal';
import { TemplatePartAdvancedControls } from './advanced-controls';
import TemplatePartInnerBlocks from './inner-blocks';
import { createTemplatePartId } from './utils/create-template-part-id';
Expand All @@ -39,10 +40,13 @@ export default function TemplatePartEdit( {
} ) {
const { slug, theme, tagName, layout = {} } = attributes;
const templatePartId = createTemplatePartId( theme, slug );

const [ hasAlreadyRendered, RecursionProvider ] = useNoRecursiveRenders(
templatePartId
);
const [
isTemplatePartSelectionOpen,
setIsTemplatePartSelectionOpen,
] = useState( false );

// Set the postId block attribute if it did not exist,
// but wait until the inner blocks have loaded to allow
Expand All @@ -53,15 +57,12 @@ export default function TemplatePartEdit( {
isMissing,
defaultWrapper,
area,
enableSelection,
youknowriad marked this conversation as resolved.
Show resolved Hide resolved
hasResolvedReplacements,
areaLabel,
} = useSelect(
( select ) => {
const {
getEditedEntityRecord,
getEntityRecords,
hasFinishedResolution,
} = select( coreStore );
const { getEditedEntityRecord, hasFinishedResolution } = select(
coreStore
);
const { getBlocks } = select( blockEditorStore );

const getEntityArgs = [
Expand All @@ -73,20 +74,6 @@ export default function TemplatePartEdit( {
? getEditedEntityRecord( ...getEntityArgs )
: null;
const _area = entityRecord?.area || attributes.area;

// Check whether other entities exist for switching/selection.
const availableReplacementArgs = [
'postType',
'wp_template_part',
_area && 'uncategorized' !== _area && { area: _area },
];
const matchingReplacements = getEntityRecords(
...availableReplacementArgs
);
const _enableSelection = templatePartId
? matchingReplacements?.length > 1
: matchingReplacements?.length > 0;

const hasResolvedEntity = templatePartId
? hasFinishedResolution(
'getEditedEntityRecord',
Expand All @@ -97,21 +84,17 @@ export default function TemplatePartEdit( {
// FIXME: @wordpress/block-library should not depend on @wordpress/editor.
// Blocks can be loaded into a *non-post* block editor.
// eslint-disable-next-line @wordpress/data-no-store-string-literals
const defaultWrapperElement = select( 'core/editor' )
const areaObject = select( 'core/editor' )
.__experimentalGetDefaultTemplatePartAreas()
.find( ( { area: value } ) => value === _area )?.area_tag;
.find( ( { area: value } ) => value === _area );
ntsekouras marked this conversation as resolved.
Show resolved Hide resolved

return {
innerBlocks: getBlocks( clientId ),
isResolved: hasResolvedEntity,
isMissing: hasResolvedEntity && isEmpty( entityRecord ),
defaultWrapper: defaultWrapperElement || 'div',
defaultWrapper: areaObject?.area_tag ?? 'div',
youknowriad marked this conversation as resolved.
Show resolved Hide resolved
areaLabel: areaObject?.label || __( 'Template Part' ),
youknowriad marked this conversation as resolved.
Show resolved Hide resolved
area: _area,
enableSelection: _enableSelection,
hasResolvedReplacements: hasFinishedResolution(
'getEntityRecords',
availableReplacementArgs
),
};
},
[ templatePartId, clientId ]
Expand Down Expand Up @@ -166,37 +149,22 @@ export default function TemplatePartEdit( {
<TagName { ...blockProps }>
<TemplatePartPlaceholder
area={ attributes.area }
clientId={ clientId }
setAttributes={ setAttributes }
enableSelection={ enableSelection }
hasResolvedReplacements={ hasResolvedReplacements }
onOpenSelectionModal={ () =>
setIsTemplatePartSelectionOpen( true )
}
/>
</TagName>
) }
{ isEntityAvailable && enableSelection && (
{ isEntityAvailable && (
<BlockControls>
<ToolbarGroup className="wp-block-template-part__block-control-group">
<Dropdown
className="wp-block-template-part__preview-dropdown-button"
contentClassName="wp-block-template-part__preview-dropdown-content"
position="bottom right left"
renderToggle={ ( { isOpen, onToggle } ) => (
<ToolbarButton
aria-expanded={ isOpen }
onClick={ onToggle }
>
{ __( 'Replace' ) }
</ToolbarButton>
) }
renderContent={ ( { onClose } ) => (
<TemplatePartSelection
setAttributes={ setAttributes }
onClose={ onClose }
area={ area }
templatePartId={ templatePartId }
/>
) }
/>
<ToolbarButton
onClick={ () =>
setIsTemplatePartSelectionOpen( true )
}
>
{ __( 'Replace' ) }
</ToolbarButton>
</ToolbarGroup>
</BlockControls>
) }
Expand All @@ -215,6 +183,30 @@ export default function TemplatePartEdit( {
<Spinner />
</TagName>
) }
{ isTemplatePartSelectionOpen && (
<Modal
className="block-editor-template-part__selection-modal"
title={ sprintf(
// Translators: %s as template part area title ("Header", "Footer", etc.).
__( 'Choose a %s' ),
areaLabel.toLowerCase()
) }
closeLabel={ __( 'Cancel' ) }
onRequestClose={ () =>
setIsTemplatePartSelectionOpen( false )
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
}
}
isFullScreen

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I tried this but the behavior is a bit weird, the width of the modal changes as the patterns load.

>
<TemplatePartSelectionModal
clientId={ clientId }
area={ area }
areaLabel={ areaLabel }
setAttributes={ setAttributes }
onClose={ () =>
setIsTemplatePartSelectionOpen( false )
}
/>
</Modal>
) }
</RecursionProvider>
);
}
152 changes: 16 additions & 136 deletions packages/block-library/src/template-part/edit/placeholder/index.js
Original file line number Diff line number Diff line change
@@ -1,39 +1,19 @@
/**
* External dependencies
*/
import { find, kebabCase } from 'lodash';
import { find } from 'lodash';

/**
* WordPress dependencies
*/
import { __, sprintf } from '@wordpress/i18n';
import { useCallback, useState } from '@wordpress/element';
import { useDispatch, useSelect } from '@wordpress/data';
import { Placeholder, Dropdown, Button, Spinner } from '@wordpress/components';
import { serialize } from '@wordpress/blocks';
import { store as coreStore } from '@wordpress/core-data';

/**
* Internal dependencies
*/
import TemplatePartSelection from '../selection';
import PatternsSetup from './patterns-setup';

const PLACEHOLDER_STEPS = {
initial: 1,
patterns: 2,
};
import { useSelect } from '@wordpress/data';
import { Placeholder, Button } from '@wordpress/components';

export default function TemplatePartPlaceholder( {
area,
clientId,
setAttributes,
enableSelection,
hasResolvedReplacements,
onOpenSelectionModal,
} ) {
const { saveEntityRecord } = useDispatch( coreStore );
const [ step, setStep ] = useState( PLACEHOLDER_STEPS.initial );

const { areaIcon, areaLabel } = useSelect(
( select ) => {
// FIXME: @wordpress/block-library should not depend on @wordpress/editor.
Expand All @@ -54,119 +34,19 @@ export default function TemplatePartPlaceholder( {
[ area ]
);

const onCreate = useCallback(
async (
startingBlocks = [],
title = __( 'Untitled Template Part' )
) => {
// If we have `area` set from block attributes, means an exposed
// block variation was inserted. So add this prop to the template
// part entity on creation. Afterwards remove `area` value from
// block attributes.
const record = {
title,
slug: kebabCase( title ),
content: serialize( startingBlocks ),
// `area` is filterable on the server and defaults to `UNCATEGORIZED`
// if provided value is not allowed.
area,
};
const templatePart = await saveEntityRecord(
'postType',
'wp_template_part',
record
);
setAttributes( {
slug: templatePart.slug,
theme: templatePart.theme,
area: undefined,
} );
},
[ setAttributes, area ]
);

return (
<>
{ step === PLACEHOLDER_STEPS.initial && (
<Placeholder
icon={ areaIcon }
label={ areaLabel }
instructions={
enableSelection
? sprintf(
// Translators: %s as template part area title ("Header", "Footer", etc.).
__(
'Choose an existing %s or create a new one.'
),
areaLabel.toLowerCase()
)
: sprintf(
// Translators: %s as template part area title ("Header", "Footer", etc.).
__( 'Create a new %s.' ),
areaLabel.toLowerCase()
)
}
>
{ ! hasResolvedReplacements ? (
<Spinner />
) : (
<Dropdown
contentClassName="wp-block-template-part__placeholder-preview-dropdown-content"
position="bottom right left"
renderToggle={ ( { isOpen, onToggle } ) => (
<>
{ enableSelection && (
<Button
variant="primary"
onClick={ onToggle }
aria-expanded={ isOpen }
>
{ __( 'Choose existing' ) }
</Button>
) }
<Button
variant={
enableSelection
? 'tertiary'
: 'primary'
}
onClick={ () =>
setStep(
PLACEHOLDER_STEPS.patterns
)
}
>
{ sprintf(
// Translators: %s as template part area title ("Header", "Footer", etc.).
__( 'New %s' ),
areaLabel.toLowerCase()
) }
</Button>
</>
) }
renderContent={ ( { onClose } ) => (
<TemplatePartSelection
setAttributes={ setAttributes }
onClose={ onClose }
area={ area }
/>
) }
/>
) }
</Placeholder>
) }
{ step === PLACEHOLDER_STEPS.patterns && (
<PatternsSetup
area={ area }
areaLabel={ areaLabel }
areaIcon={ areaIcon }
onCreate={ onCreate }
clientId={ clientId }
resetPlaceholder={ () =>
setStep( PLACEHOLDER_STEPS.initial )
}
/>
<Placeholder
icon={ areaIcon }
label={ areaLabel }
instructions={ sprintf(
// Translators: %s as template part area title ("Header", "Footer", etc.).
__( 'Choose an existing %s or create a new one.' ),
areaLabel.toLowerCase()
) }
</>
>
<Button variant="primary" onClick={ onOpenSelectionModal }>
{ __( 'Choose' ) }
</Button>
</Placeholder>
);
}
Loading