Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
96 changes: 64 additions & 32 deletions routes/post-list/stage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@ import {
Link,
useInvalidate,
} from '@wordpress/route';
import { useView } from '@wordpress/views';
import { useView, useViewConfig } from '@wordpress/views';
import { DataViews } from '@wordpress/dataviews';
import { Page } from '@wordpress/admin-ui';
import type { View, Action } from '@wordpress/dataviews';
import type { View, Action, SupportedLayouts } from '@wordpress/dataviews';
import {
store as coreStore,
privateApis as coreDataPrivateApis,
Expand All @@ -25,11 +25,10 @@ import { drawerRight } from '@wordpress/icons';
import type { Post } from '@wordpress/core-data';
import { unlock } from '@wordpress/routes-lock-unlock';
import {
getDefaultView,
getActiveViewOverridesForTab,
DEFAULT_VIEWS,
DEFAULT_LAYOUTS,
getActiveViewOverrides,
viewToQuery,
type ViewListEntry,
type ViewOverrides,
} from './view-utils';
import { QuickEditModal } from './quick-edit-modal';
// Unlock WordPress private APIs
Expand All @@ -52,10 +51,56 @@ function getItemLevel( item: Post ) {
}

function PostList() {
const invalidate = useInvalidate();
const { type: postType, slug = 'all' } = useParams( {
from: '/types/$type/list/$slug',
} );
const {
default_view: defaultView,
default_layouts: defaultLayouts,
view_list: viewList,
} = useViewConfig( {
kind: 'postType',
name: postType,
} );
const activeViewOverrides = useMemo(
() => getActiveViewOverrides( viewList, slug ),
[ viewList, slug ]
);

if ( ! defaultView ) {
// The route canvas resolves the view configuration before the stage
// mounts, so this only guards against the store being reset.
return null;
}

return (
<PostListView
postType={ postType }
slug={ slug }
defaultView={ defaultView }
defaultLayouts={ defaultLayouts }
viewList={ viewList }
activeViewOverrides={ activeViewOverrides }
/>
);
}

function PostListView( {
postType,
slug,
defaultView,
defaultLayouts,
viewList,
activeViewOverrides,
}: {
postType: string;
slug: string;
defaultView: View;
defaultLayouts: SupportedLayouts | undefined;
viewList: ViewListEntry[] | undefined;
activeViewOverrides: ViewOverrides;
} ) {
const invalidate = useInvalidate();
const navigate = useNavigate();
const searchParams = useSearch( { from: '/types/$type/list/$slug' } );
const postTypeObject = useSelect(
Expand All @@ -73,15 +118,6 @@ function PostList() {
[ postType ]
);

const defaultView: View = useMemo( () => {
return getDefaultView( postTypeObject );
}, [ postTypeObject ] );

const activeViewOverrides = useMemo(
() => getActiveViewOverridesForTab( slug ),
[ slug ]
);

// Callback to handle URL query parameter changes
const handleQueryParamsChange = useCallback(
( params: { page?: number; search?: string } ) => {
Expand All @@ -101,6 +137,7 @@ function PostList() {
name: postType,
slug: 'default-new',
defaultView,
defaultLayouts,
activeViewOverrides,
queryParams: searchParams,
onChangeQueryParams: handleQueryParamsChange,
Expand Down Expand Up @@ -333,23 +370,18 @@ function PostList() {
}
hasPadding={ false }
>
{ DEFAULT_VIEWS.length > 1 && (
{ viewList && viewList.length > 1 && (
<div className="routes-post-list__tabs-wrapper">
<Tabs
onSelect={ handleTabChange }
selectedTabId={ slug ?? 'all' }
>
<Tabs onSelect={ handleTabChange } selectedTabId={ slug }>
<Tabs.TabList>
{ DEFAULT_VIEWS.map(
( filter: { slug: string; label: string } ) => (
<Tabs.Tab
tabId={ filter.slug }
key={ filter.slug }
>
{ filter.label }
</Tabs.Tab>
)
) }
{ viewList.map( ( entry ) => (
<Tabs.Tab
tabId={ entry.slug }
key={ entry.slug }
>
{ entry.title }
</Tabs.Tab>
) ) }
</Tabs.TabList>
</Tabs>
</div>
Expand All @@ -365,7 +397,7 @@ function PostList() {
totalItems,
totalPages,
} }
defaultLayouts={ DEFAULT_LAYOUTS }
defaultLayouts={ defaultLayouts }
getItemId={ getItemId }
getItemLevel={ getItemLevel }
selection={ selection }
Expand Down
156 changes: 63 additions & 93 deletions routes/post-list/view-utils.ts
Original file line number Diff line number Diff line change
@@ -1,122 +1,92 @@
import { loadView } from '@wordpress/views';
import { resolveSelect } from '@wordpress/data';
import { store as coreStore } from '@wordpress/core-data';
import type { Type } from '@wordpress/core-data';
import type {
View,
Filter,
SupportedLayouts,
ViewTable,
} from '@wordpress/dataviews';

const DEFAULT_VIEW: View = {

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

All these config come now from the server and are shared with the current site editor. This means there will be some changes with respect to how this experiment worked and it's now aligned with the current site editor. What I've noticed:

  • the default layout was table and it is now list
  • the sort order was "descending date" but it's now "ascending title"
  • visible fields were "author, status, date" and are now "author, status"
  • there's a new tab for "Scheduled" post

type: 'table' as const,
sort: {
field: 'date',
direction: 'desc' as const,
},
fields: [ 'author', 'status', 'date' ],
titleField: 'title',
mediaField: 'featured_media',
descriptionField: 'excerpt',
};

const DEFAULT_TABLE_LAYOUT: Omit< ViewTable, 'type' > = {
layout: {
styles: {
author: {
align: 'start',
},
},
},
};

export const DEFAULT_LAYOUTS: SupportedLayouts = {
table: DEFAULT_TABLE_LAYOUT,
grid: true,
list: true,
import type { View, SupportedLayouts } from '@wordpress/dataviews';
import { unlock } from '@wordpress/routes-lock-unlock';

/**
* A layer merged on top of a view. Mirrors the `ViewOverrides` type of
* `@wordpress/views`, which is not exported.
*/
export type ViewOverrides = Partial< Omit< View, 'type' | 'layout' > > & {
type?: View[ 'type' ];
layout?: Record< string, unknown >;
};

export const DEFAULT_VIEWS: {
export interface ViewListEntry {
title: string;
slug: string;
label: string;
}[] = [
{
slug: 'all',
label: 'All',
},
{
slug: 'publish',
label: 'Published',
},
{
slug: 'draft',
label: 'Draft',
},
{
slug: 'pending',
label: 'Pending',
},
{
slug: 'private',
label: 'Private',
},
{
slug: 'trash',
label: 'Trash',
},
];

type ActiveViewOverrides = {
filters?: Filter[];
sort?: View[ 'sort' ];
layout?: Record< string, unknown >;
};
view?: ViewOverrides;
}

export function getActiveViewOverridesForTab(
slug: string
): ActiveViewOverrides {
if ( slug === 'all' ) {
return {
...DEFAULT_TABLE_LAYOUT,
};
}
return {
...DEFAULT_TABLE_LAYOUT,
filters: [
{
field: 'status',
operator: 'is',
value: slug,
},
],
};
interface EntityViewConfig {
default_view: View | undefined;
default_layouts: SupportedLayouts | undefined;
view_list: ViewListEntry[] | undefined;
}

export function getDefaultView( postType: Type | undefined ): View {
/**
* Resolves the server-provided view configuration for the given post type,
* for use in the route loader that runs outside React (where `useViewConfig`
* is unavailable).
*
* @param postType The post type name.
* @return The entity view configuration.
*/
export async function loadPostTypeViewConfig(
postType: string
): Promise< EntityViewConfig > {
const config = await unlock( resolveSelect( coreStore ) ).getViewConfig(
'postType',
postType
);
return {
...DEFAULT_VIEW,
showLevels: postType?.hierarchical,
default_view: config?.default_view,
default_layouts: config?.default_layouts,
view_list: config?.view_list,
};
}

/**
* Returns the view overrides of the entry in the view list matching the
* given slug, or an empty object when there is none.
*
* @param viewList The `view_list` of an entity view configuration.
* @param slug Slug of the active view.
* @return The view overrides for the active view.
*/
export function getActiveViewOverrides(
viewList: ViewListEntry[] | undefined,
slug: string
): ViewOverrides {
return viewList?.find( ( v ) => v.slug === slug )?.view ?? {};
}

export async function ensureView(
type: string,
slug?: string,
search?: { page?: number; search?: string }
) {
const postTypeObject = await resolveSelect( coreStore ).getPostType( type );
const defaultView = getDefaultView( postTypeObject );
const {
default_view: defaultView,
default_layouts: defaultLayouts,
view_list: viewList,
} = await loadPostTypeViewConfig( type );
if ( ! defaultView ) {
throw new Error(
`Missing view configuration for the ${ type } post type.`
);
}
return loadView( {
kind: 'postType',
name: type,
slug: 'default-new',
defaultView,
activeViewOverrides: getActiveViewOverridesForTab( slug ?? 'all' ),
defaultLayouts,
activeViewOverrides: getActiveViewOverrides( viewList, slug ?? 'all' ),
queryParams: search,
} );
}

export function viewToQuery( view: View, postType: string ) {
const result: Record< string, any > = { _embed: 'author,wp:featuredmedia' };

Expand Down
Loading