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

DataViews: Optimize the patterns dataviews by extracting the fields definition #63927

Merged
merged 2 commits into from
Jul 25, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
253 changes: 253 additions & 0 deletions packages/edit-site/src/components/page-patterns/fields.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,253 @@
/**
* External dependencies
*/
import clsx from 'clsx';

/**
* WordPress dependencies
*/
import {
__experimentalHStack as HStack,
Button,
Tooltip,
Flex,
} from '@wordpress/components';
import { __, _x } from '@wordpress/i18n';
import { useState, useMemo, useId } from '@wordpress/element';
import {
BlockPreview,
privateApis as blockEditorPrivateApis,
} from '@wordpress/block-editor';
import { Icon, lockSmall } from '@wordpress/icons';
import { parse } from '@wordpress/blocks';
import { decodeEntities } from '@wordpress/html-entities';

/**
* Internal dependencies
*/
import { Async } from '../async';
import {
PATTERN_TYPES,
TEMPLATE_PART_POST_TYPE,
PATTERN_SYNC_TYPES,
OPERATOR_IS,
} from '../../utils/constants';
import { unlock } from '../../lock-unlock';
import { useLink } from '../routes/link';
import { useAddedBy } from '../page-templates/hooks';
import { defaultGetTitle } from './search-items';

const { useGlobalStyle } = unlock( blockEditorPrivateApis );

function PreviewWrapper( { item, onClick, ariaDescribedBy, children } ) {
return (
<button
className="page-patterns-preview-field__button"
type="button"
onClick={ item.type !== PATTERN_TYPES.theme ? onClick : undefined }
aria-label={ item.title }
aria-describedby={ ariaDescribedBy }
aria-disabled={ item.type === PATTERN_TYPES.theme }
>
{ children }
</button>
);
}

function Preview( { item } ) {
const descriptionId = useId();
const description = item.description || item?.excerpt?.raw;
const isUserPattern = item.type === PATTERN_TYPES.user;
const isTemplatePart = item.type === TEMPLATE_PART_POST_TYPE;
const [ backgroundColor ] = useGlobalStyle( 'color.background' );
const { onClick } = useLink( {
postType: item.type,
postId: isUserPattern || isTemplatePart ? item.id : item.name,
canvas: 'edit',
} );
const blocks = useMemo( () => {
return (
item.blocks ??
parse( item.content.raw, {
__unstableSkipMigrationLogs: true,
} )
);
}, [ item?.content?.raw, item.blocks ] );
const isEmpty = ! blocks?.length;

return (
<div
className="page-patterns-preview-field"
style={ { backgroundColor } }
>
<PreviewWrapper
item={ item }
onClick={ onClick }
ariaDescribedBy={ !! description ? descriptionId : undefined }
>
{ isEmpty && isTemplatePart && __( 'Empty template part' ) }
{ isEmpty && ! isTemplatePart && __( 'Empty pattern' ) }
{ ! isEmpty && (
<Async>
<BlockPreview
blocks={ blocks }
viewportWidth={ item.viewportWidth }
/>
</Async>
) }
</PreviewWrapper>
{ !! description && (
<div hidden id={ descriptionId }>
{ description }
</div>
) }
</div>
);
}

export const previewField = {
label: __( 'Preview' ),
id: 'preview',
render: ( { item } ) => <Preview item={ item } />,
enableSorting: false,
};

function Title( { item } ) {
const isUserPattern = item.type === PATTERN_TYPES.user;
const isTemplatePart = item.type === TEMPLATE_PART_POST_TYPE;
const { onClick } = useLink( {
postType: item.type,
postId: isUserPattern || isTemplatePart ? item.id : item.name,
canvas: 'edit',
} );
const title = decodeEntities( defaultGetTitle( item ) );
return (
<HStack alignment="center" justify="flex-start" spacing={ 2 }>
<Flex
as="div"
gap={ 0 }
justify="left"
className="edit-site-patterns__pattern-title"
>
{ item.type === PATTERN_TYPES.theme ? (
title
) : (
<Button
variant="link"
onClick={ onClick }
// Required for the grid's roving tab index system.
// See https://github.com/WordPress/gutenberg/pull/51898#discussion_r1243399243.
tabIndex="-1"
>
{ title }
</Button>
) }
</Flex>
{ item.type === PATTERN_TYPES.theme && (
<Tooltip
placement="top"
text={ __( 'This pattern cannot be edited.' ) }
>
<Icon
className="edit-site-patterns__pattern-lock-icon"
icon={ lockSmall }
size={ 24 }
/>
</Tooltip>
) }
</HStack>
);
}

export const titleField = {
label: __( 'Title' ),
id: 'title',
getValue: ( { item } ) => item.title?.raw || item.title,
render: ( { item } ) => <Title item={ item } />,
youknowriad marked this conversation as resolved.
Show resolved Hide resolved
enableHiding: false,
};

const SYNC_FILTERS = [
{
value: PATTERN_SYNC_TYPES.full,
label: _x( 'Synced', 'pattern (singular)' ),
description: __( 'Patterns that are kept in sync across the site.' ),
},
{
value: PATTERN_SYNC_TYPES.unsynced,
label: _x( 'Not synced', 'pattern (singular)' ),
description: __(
'Patterns that can be changed freely without affecting the site.'
),
},
];

export const patternStatusField = {
label: __( 'Sync status' ),
id: 'sync-status',
render: ( { item } ) => {
const syncStatus =
'wp_pattern_sync_status' in item
? item.wp_pattern_sync_status || PATTERN_SYNC_TYPES.full
: PATTERN_SYNC_TYPES.unsynced;
// User patterns can have their sync statuses checked directly.
// Non-user patterns are all unsynced for the time being.
return (
<span
className={ `edit-site-patterns__field-sync-status-${ syncStatus }` }
>
{
SYNC_FILTERS.find( ( { value } ) => value === syncStatus )
.label
}
</span>
);
},
elements: SYNC_FILTERS,
filterBy: {
operators: [ OPERATOR_IS ],
isPrimary: true,
},
enableSorting: false,
};

function Author( { item } ) {
const [ isImageLoaded, setIsImageLoaded ] = useState( false );
const { text, icon, imageUrl } = useAddedBy( item.type, item.id );

return (
<HStack alignment="left" spacing={ 0 }>
{ imageUrl && (
<div
className={ clsx( 'page-templates-author-field__avatar', {
'is-loaded': isImageLoaded,
} ) }
>
<img
onLoad={ () => setIsImageLoaded( true ) }
alt=""
src={ imageUrl }
/>
</div>
) }
{ ! imageUrl && (
<div className="page-templates-author-field__icon">
<Icon icon={ icon } />
</div>
) }
<span className="page-templates-author-field__name">{ text }</span>
</HStack>
);
}

export const templatePartAuthorField = {
label: __( 'Author' ),
id: 'author',
getValue: ( { item } ) => item.author_text,
render: ( { item } ) => {
return <Author item={ item } />;
},
filterBy: {
isPrimary: true,
},
};
Loading
Loading