Skip to content

Commit

Permalink
DataViews: Use in patterns page
Browse files Browse the repository at this point in the history
  • Loading branch information
ntsekouras committed Dec 22, 2023
1 parent 51b81ef commit 58a7684
Show file tree
Hide file tree
Showing 3 changed files with 374 additions and 1 deletion.
7 changes: 6 additions & 1 deletion packages/edit-site/src/components/page-main/index.js
Expand Up @@ -7,6 +7,7 @@ import { privateApis as routerPrivateApis } from '@wordpress/router';
* Internal dependencies
*/
import PagePatterns from '../page-patterns';
import DataviewsPatterns from '../page-patterns/dataviews-patterns';
import PageTemplateParts from '../page-template-parts';
import PageTemplates from '../page-templates';
import PagePages from '../page-pages';
Expand All @@ -24,7 +25,11 @@ export default function PageMain() {
} else if ( path === '/wp_template_part/all' ) {
return <PageTemplateParts />;
} else if ( path === '/patterns' ) {
return <PagePatterns />;
return window?.__experimentalAdminViews ? (
<DataviewsPatterns />
) : (
<PagePatterns />
);
} else if ( window?.__experimentalAdminViews && path === '/pages' ) {
return <PagePages />;
}
Expand Down
341 changes: 341 additions & 0 deletions packages/edit-site/src/components/page-patterns/dataviews-patterns.js
@@ -0,0 +1,341 @@
/**
* WordPress dependencies
*/
import {
__experimentalHStack as HStack,
Button,
__experimentalHeading as Heading,
Tooltip,
Flex,
} from '@wordpress/components';
import { getQueryArgs } from '@wordpress/url';
import { __ } from '@wordpress/i18n';
import { useState, useMemo, useCallback, useId } from '@wordpress/element';
import {
BlockPreview,
privateApis as blockEditorPrivateApis,
} from '@wordpress/block-editor';
import { DataViews } from '@wordpress/dataviews';
import {
Icon,
header,
footer,
symbolFilled as uncategorized,
symbol,
lockSmall,
} from '@wordpress/icons';

/**
* Internal dependencies
*/
import Page from '../page';
import {
LAYOUT_GRID,
PATTERN_TYPES,
TEMPLATE_PART_POST_TYPE,
PATTERN_SYNC_TYPES,
PATTERN_DEFAULT_CATEGORY,
} from '../../utils/constants';
// import { duplicatePatternAction } from './dataviews-pattern-actions';
import usePatternSettings from './use-pattern-settings';
import { unlock } from '../../lock-unlock';
import usePatterns from './use-patterns';
import PatternsHeader from './header';

const { ExperimentalBlockEditorProvider, useGlobalStyle } = unlock(
blockEditorPrivateApis
);

const templatePartIcons = { header, footer, uncategorized };
const EMPTY_ARRAY = [];
const defaultConfigPerViewType = {
[ LAYOUT_GRID ]: {
mediaField: 'preview',
primaryField: 'title',
},
};
const DEFAULT_VIEW = {
type: LAYOUT_GRID,
search: '',
page: 1,
perPage: 20,
hiddenFields: [],
layout: {
...defaultConfigPerViewType[ LAYOUT_GRID ],
},
filters: [],
};

function Preview( { item, viewType } ) {
const descriptionId = useId();
const isUserPattern = item.type === PATTERN_TYPES.user;
const isNonUserPattern = item.type === PATTERN_TYPES.theme;
const isTemplatePart = item.type === TEMPLATE_PART_POST_TYPE;
const isEmpty = ! item.blocks?.length;
// Only custom patterns or custom template parts can be renamed or deleted.
const isCustomPattern =
isUserPattern || ( isTemplatePart && item.isCustom );
const ariaDescriptions = [];
if ( isCustomPattern ) {
// User patterns don't have descriptions, but can be edited and deleted, so include some help text.
ariaDescriptions.push(
__( 'Press Enter to edit, or Delete to delete the pattern.' )
);
} else if ( item.description ) {
ariaDescriptions.push( item.description );
}

if ( isNonUserPattern ) {
ariaDescriptions.push(
__( 'Theme & plugin patterns cannot be edited.' )
);
}
const [ backgroundColor ] = useGlobalStyle( 'color.background' );
return (
<>
<div
className={ `page-patterns-preview-field is-viewtype-${ viewType }` }
style={ { backgroundColor } }
>
{ isEmpty && isTemplatePart && __( 'Empty template part' ) }
{ isEmpty && ! isTemplatePart && __( 'Empty pattern' ) }
{ ! isEmpty && <BlockPreview blocks={ item.blocks } /> }
</div>
{ ariaDescriptions.map( ( ariaDescription, index ) => (
<div
key={ index }
hidden
id={ `${ descriptionId }-${ index }` }
>
{ ariaDescription }
</div>
) ) }
</>
);
}

function Title( { item, onClick, categoryId } ) {
const isUserPattern = item.type === PATTERN_TYPES.user;
const isNonUserPattern = item.type === PATTERN_TYPES.theme;
let itemIcon;
if ( ! isUserPattern && templatePartIcons[ categoryId ] ) {
itemIcon = templatePartIcons[ categoryId ];
} else {
itemIcon =
item.syncStatus === PATTERN_SYNC_TYPES.full ? symbol : undefined;
}
return (
<HStack alignment="center" justify="flex-start" spacing={ 3 }>
{ itemIcon && ! isNonUserPattern && (
<Tooltip
placement="top"
text={ __(
'Editing this pattern will also update anywhere it is used'
) }
>
<Icon
className="edit-site-patterns__pattern-icon"
icon={ itemIcon }
/>
</Tooltip>
) }
<Flex as="span" gap={ 0 } justify="left">
{ item.type === PATTERN_TYPES.theme ? (
item.title
) : (
<Heading level={ 5 }>
<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"
>
{ item.title || item.name }
</Button>
</Heading>
) }
{ 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>
) }
</Flex>
</HStack>
);
}

export default function DataviewsPatterns() {
const { categoryType, categoryId = PATTERN_DEFAULT_CATEGORY } =
getQueryArgs( window.location.href );
const type = categoryType || PATTERN_TYPES.theme;
const [ view, setView ] = useState( DEFAULT_VIEW );
const isUncategorizedThemePatterns =
type === PATTERN_TYPES.theme && categoryId === 'uncategorized';
const { patterns, isResolving } = usePatterns(
type,
isUncategorizedThemePatterns ? '' : categoryId,
{
search: view.search,
// syncStatus:
// deferredSyncedFilter === 'all'
// ? undefined
// : deferredSyncedFilter,
}
);
const fields = useMemo(
() => [
{
header: __( 'Preview' ),
id: 'preview',
render: ( { item } ) => (
<Preview item={ item } viewType={ view.type } />
),
minWidth: 120,
maxWidth: 120,
enableSorting: false,
enableHiding: false,
},
{
header: __( 'Title' ),
id: 'title',
getValue: ( { item } ) => item.title,
render: ( { item } ) => (
<Title
item={ item }
onClick={ () => {} }
categoryId={ categoryId }
/>
),
maxWidth: 400,
enableHiding: false,
},
],
[ view.type, categoryId ]
);

const { data, paginationInfo } = useMemo( () => {
if ( ! patterns ) {
return {
data: EMPTY_ARRAY,
paginationInfo: { totalItems: 0, totalPages: 0 },
};
}
let filteredData = [ ...patterns ];
// Handle filters.
if ( view.filters.length > 0 ) {
// view.filters.forEach( ( filter ) => {
// if (
// filter.field === 'author' &&
// filter.operator === OPERATOR_IN &&
// !! filter.value
// ) {
// filteredData = filteredData.filter( ( item ) => {
// return item.author_text === filter.value;
// } );
// } else if (
// filter.field === 'author' &&
// filter.operator === OPERATOR_NOT_IN &&
// !! filter.value
// ) {
// filteredData = filteredData.filter( ( item ) => {
// return item.author_text !== filter.value;
// } );
// }
// } );
}

// Handle sorting.
if ( view.sort ) {
const stringSortingFields = [ 'title' ];
const fieldId = view.sort.field;
if ( stringSortingFields.includes( fieldId ) ) {
const fieldToSort = fields.find( ( field ) => {
return field.id === fieldId;
} );
filteredData.sort( ( a, b ) => {
const valueA = fieldToSort.getValue( { item: a } ) ?? '';
const valueB = fieldToSort.getValue( { item: b } ) ?? '';
return view.sort.direction === 'asc'
? valueA.localeCompare( valueB )
: valueB.localeCompare( valueA );
} );
}
}

// Handle pagination.
const start = ( view.page - 1 ) * view.perPage;
const totalItems = filteredData?.length || 0;
filteredData = filteredData?.slice( start, start + view.perPage );
return {
data: filteredData,
paginationInfo: {
totalItems,
totalPages: Math.ceil( totalItems / view.perPage ),
},
};
}, [ patterns, view, fields ] );

// const actions = useMemo( () => [ duplicatePatternAction ], [] );
const onChangeView = useCallback(
( viewUpdater ) => {
let updatedView =
typeof viewUpdater === 'function'
? viewUpdater( view )
: viewUpdater;
if ( updatedView.type !== view.type ) {
updatedView = {
...updatedView,
layout: {
...defaultConfigPerViewType[ updatedView.type ],
},
};
}
setView( updatedView );
},
[ view, setView ]
);
const id = useId();
const titleId = `${ id }-title`;
const descriptionId = `${ id }-description`;
const settings = usePatternSettings();
// Wrap everything in a block editor provider.
// This ensures 'styles' that are needed for the previews are synced
// from the site editor store to the block editor store.
// TODO: check if I add the provider in every preview like in templates...
return (
<ExperimentalBlockEditorProvider settings={ settings }>
<Page
title={ __( 'Patterns content' ) }
className="edit-site-page-patterns-dataviews"
hideTitleFromUI
>
<PatternsHeader
categoryId={ categoryId }
type={ type }
titleId={ titleId }
descriptionId={ descriptionId }
/>
<DataViews
paginationInfo={ paginationInfo }
fields={ fields }
// actions={ actions }
data={ data || EMPTY_ARRAY }
getItemId={ ( item ) => item.name }
isLoading={ isResolving }
view={ view }
onChangeView={ onChangeView }
deferredRendering={ true }
supportedLayouts={ [ LAYOUT_GRID ] }
/>
</Page>
</ExperimentalBlockEditorProvider>
);
}
27 changes: 27 additions & 0 deletions packages/edit-site/src/components/page-patterns/style.scss
Expand Up @@ -223,3 +223,30 @@
}
}
}

/**
* DataViews patterns styles
* TODO: when this becomes stable, consolidate styles with the above.
*/
.edit-site-page-patterns-dataviews {
.page-patterns-preview-field {
&.is-viewtype-grid {
.block-editor-block-preview__container {
height: auto;
}
}
}

.edit-site-patterns__pattern-lock-icon {
min-width: min-content;
}

.edit-site-patterns__section-header {
border-bottom: 1px solid #f0f0f0;
min-height: 72px;
padding: $grid-unit-20 $grid-unit-40;
position: sticky;
top: 0;
z-index: 2;
}
}

0 comments on commit 58a7684

Please sign in to comment.