Skip to content
Merged
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
132 changes: 61 additions & 71 deletions packages/block-library/src/template-part/edit/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,22 +15,28 @@ 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';
import {
useAlternativeBlockPatterns,
useAlternativeTemplateParts,
useTemplatePartArea,
} from './utils/hooks';

export default function TemplatePartEdit( {
attributes,
Expand All @@ -39,29 +45,22 @@ 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
// new edits to trigger this.
const {
isResolved,
innerBlocks,
isMissing,
defaultWrapper,
area,
enableSelection,
hasResolvedReplacements,
} = useSelect(
const { isResolved, innerBlocks, isMissing, area } = useSelect(
( select ) => {
const {
getEditedEntityRecord,
getEntityRecords,
hasFinishedResolution,
} = select( coreStore );
const { getEditedEntityRecord, hasFinishedResolution } = select(
coreStore
);
const { getBlocks } = select( blockEditorStore );

const getEntityArgs = [
Expand All @@ -73,54 +72,33 @@ 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',
getEntityArgs
)
: false;

// 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' )
.__experimentalGetDefaultTemplatePartAreas()
.find( ( { area: value } ) => value === _area )?.area_tag;

return {
innerBlocks: getBlocks( clientId ),
isResolved: hasResolvedEntity,
isMissing: hasResolvedEntity && isEmpty( entityRecord ),
defaultWrapper: defaultWrapperElement || 'div',
area: _area,
enableSelection: _enableSelection,
hasResolvedReplacements: hasFinishedResolution(
'getEntityRecords',
availableReplacementArgs
),
};
},
[ templatePartId, clientId ]
);

const { templateParts } = useAlternativeTemplateParts(
area,
templatePartId
);
const blockPatterns = useAlternativeBlockPatterns( area, clientId );
const hasReplacements = !! templateParts.length || !! blockPatterns.length;
const areaObject = useTemplatePartArea( area );
const blockProps = useBlockProps();
const isPlaceholder = ! slug;
const isEntityAvailable = ! isPlaceholder && ! isMissing && isResolved;
const TagName = tagName || defaultWrapper;
const TagName = tagName || areaObject.tagName;

// We don't want to render a missing state if we have any inner blocks.
// A new template part is automatically created if we have any inner blocks but no entity.
Expand Down Expand Up @@ -160,43 +138,31 @@ export default function TemplatePartEdit( {
setAttributes={ setAttributes }
isEntityAvailable={ isEntityAvailable }
templatePartId={ templatePartId }
defaultWrapper={ defaultWrapper }
defaultWrapper={ areaObject.tagName }
/>
{ isPlaceholder && (
<TagName { ...blockProps }>
<TemplatePartPlaceholder
area={ attributes.area }
templatePartId={ templatePartId }
clientId={ clientId }
setAttributes={ setAttributes }
enableSelection={ enableSelection }
hasResolvedReplacements={ hasResolvedReplacements }
onOpenSelectionModal={ () =>
setIsTemplatePartSelectionOpen( true )
}
/>
</TagName>
) }
{ isEntityAvailable && enableSelection && (
{ isEntityAvailable && hasReplacements && (
<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 +181,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' ),
areaObject.label.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
templatePartId={ templatePartId }
clientId={ clientId }
area={ area }
setAttributes={ setAttributes }
onClose={ () =>
setIsTemplatePartSelectionOpen( false )
}
/>
</Modal>
) }
</RecursionProvider>
);
}
78 changes: 78 additions & 0 deletions packages/block-library/src/template-part/edit/placeholder.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/**
* WordPress dependencies
*/
import { __, sprintf } from '@wordpress/i18n';
import { Placeholder, Button, Spinner } from '@wordpress/components';
import { useState } from '@wordpress/element';

/**
* Internal dependencies
*/
import {
useAlternativeBlockPatterns,
useAlternativeTemplateParts,
useCreateTemplatePartFromBlocks,
useTemplatePartArea,
} from './utils/hooks';
import TitleModal from './title-modal';

export default function TemplatePartPlaceholder( {
area,
clientId,
templatePartId,
onOpenSelectionModal,
setAttributes,
} ) {
const { templateParts, isResolving } = useAlternativeTemplateParts(
area,
templatePartId
);
const blockPatterns = useAlternativeBlockPatterns( area, clientId );
const [ showTitleModal, setShowTitleModal ] = useState( false );
const areaObject = useTemplatePartArea( area );
const createFromBlocks = useCreateTemplatePartFromBlocks(
area,
setAttributes
);

return (
<Placeholder
icon={ areaObject.icon }
label={ areaObject.label }
instructions={ sprintf(
// Translators: %s as template part area title ("Header", "Footer", etc.).
__( 'Choose an existing %s or create a new one.' ),
areaObject.label.toLowerCase()
) }
>
{ isResolving && <Spinner /> }

{ ! isResolving &&
!! ( templateParts.length || blockPatterns.length ) && (
<Button variant="primary" onClick={ onOpenSelectionModal }>
{ __( 'Choose' ) }
</Button>
) }

{ ! isResolving && (
<Button
variant="secondary"
onClick={ () => {
setShowTitleModal( true );
} }
>
{ __( 'Start blank' ) }
</Button>
) }
{ showTitleModal && (
<TitleModal
areaLabel={ areaObject.label }
onClose={ () => setShowTitleModal( false ) }
onSubmit={ ( title ) => {
createFromBlocks( [], title );
} }
/>
) }
</Placeholder>
);
}
Loading