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 Extensibility: Allow unregistering the reorder-page action #64199

Merged
merged 3 commits into from
Aug 8, 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
5 changes: 3 additions & 2 deletions packages/dataviews/src/normalize-fields.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
* Internal dependencies
*/
import getFieldTypeDefinition from './field-types';
import type { Field, NormalizedField } from './types';
import type { Field, NormalizedField, ItemRecord } from './types';

/**
* Apply default values and normalize the fields config.
Expand All @@ -18,7 +18,8 @@ export function normalizeFields< Item >(

const getValue =
field.getValue ||
( ( { item }: { item: Item } ) => item[ field.id as keyof Item ] );
// @ts-ignore
( ( { item }: { item: ItemRecord } ) => item[ field.id ] );

const sort =
field.sort ??
Expand Down
14 changes: 8 additions & 6 deletions packages/dataviews/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ export type Operator =
| 'isAll'
| 'isNotAll';

export type ItemRecord = Record< string, unknown >;
export type ItemRecord = Object;

export type FieldType = 'text' | 'integer';

Expand Down Expand Up @@ -423,6 +423,12 @@ interface ActionBase< Item > {
supportsBulk?: boolean;
}

export interface RenderModalProps< Item > {
items: Item[];
closeModal?: () => void;
onActionPerformed?: ( items: Item[] ) => void;
}

export interface ActionModal< Item > extends ActionBase< Item > {
/**
* Modal to render when the action is triggered.
Expand All @@ -431,11 +437,7 @@ export interface ActionModal< Item > extends ActionBase< Item > {
items,
closeModal,
onActionPerformed,
}: {
items: Item[];
closeModal?: () => void;
onActionPerformed?: ( items: Item[] ) => void;
} ) => ReactElement;
}: RenderModalProps< Item > ) => ReactElement;

/**
* Whether to hide the modal header.
Expand Down
113 changes: 1 addition & 112 deletions packages/editor/src/components/post-actions/actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { store as noticesStore } from '@wordpress/notices';
import { useMemo, useState, useEffect } from '@wordpress/element';
import { privateApis as patternsPrivateApis } from '@wordpress/patterns';
import { parse } from '@wordpress/blocks';
import { DataForm, isItemValid } from '@wordpress/dataviews';
import { DataForm } from '@wordpress/dataviews';
import {
Button,
TextControl,
Expand Down Expand Up @@ -58,10 +58,6 @@ const formDuplicateAction = {
fields: [ 'title' ],
};

const formOrderAction = {
fields: [ 'menu_order' ],
};

/**
* Check if a template is removable.
*
Expand Down Expand Up @@ -238,110 +234,6 @@ const renamePostAction = {
},
};

function ReorderModal( { items, closeModal, onActionPerformed } ) {
const [ item, setItem ] = useState( items[ 0 ] );
const orderInput = item.menu_order;
const { editEntityRecord, saveEditedEntityRecord } =
useDispatch( coreStore );
const { createSuccessNotice, createErrorNotice } =
useDispatch( noticesStore );

async function onOrder( event ) {
event.preventDefault();

if ( ! isItemValid( item, fields, formOrderAction ) ) {
return;
}

try {
await editEntityRecord( 'postType', item.type, item.id, {
menu_order: orderInput,
} );
closeModal();
// Persist edited entity.
await saveEditedEntityRecord( 'postType', item.type, item.id, {
throwOnError: true,
} );
createSuccessNotice( __( 'Order updated' ), {
type: 'snackbar',
} );
onActionPerformed?.( items );
} catch ( error ) {
const errorMessage =
error.message && error.code !== 'unknown_error'
? error.message
: __( 'An error occurred while updating the order' );
createErrorNotice( errorMessage, {
type: 'snackbar',
} );
}
}
const isSaveDisabled = ! isItemValid( item, fields, formOrderAction );
return (
<form onSubmit={ onOrder }>
<VStack spacing="5">
<div>
{ __(
'Determines the order of pages. Pages with the same order value are sorted alphabetically. Negative order values are supported.'
) }
</div>
<DataForm
data={ item }
fields={ fields }
form={ formOrderAction }
onChange={ setItem }
/>
<HStack justify="right">
<Button
__next40pxDefaultSize
variant="tertiary"
onClick={ () => {
closeModal();
} }
>
{ __( 'Cancel' ) }
</Button>
<Button
__next40pxDefaultSize
variant="primary"
type="submit"
accessibleWhenDisabled
disabled={ isSaveDisabled }
__experimentalIsFocusable
>
{ __( 'Save' ) }
</Button>
</HStack>
</VStack>
</form>
);
}

function useReorderPagesAction( postType ) {
const supportsPageAttributes = useSelect(
( select ) => {
const { getPostType } = select( coreStore );
const postTypeObject = getPostType( postType );

return !! postTypeObject?.supports?.[ 'page-attributes' ];
},
[ postType ]
);

return useMemo(
() =>
supportsPageAttributes && {
id: 'order-pages',
label: __( 'Order' ),
isEligible( { status } ) {
return status !== 'trash';
},
RenderModal: ReorderModal,
},
[ supportsPageAttributes ]
);
}

const useDuplicatePostAction = ( postType ) => {
const userCanCreatePost = useSelect(
( select ) => {
Expand Down Expand Up @@ -595,7 +487,6 @@ export function usePostActions( { postType, onActionPerformed, context } ) {
}, [ registerPostTypeActions, postType ] );

const duplicatePostAction = useDuplicatePostAction( postType );
const reorderPagesAction = useReorderPagesAction( postType );
const isTemplateOrTemplatePart = [
TEMPLATE_POST_TYPE,
TEMPLATE_PART_POST_TYPE,
Expand All @@ -622,7 +513,6 @@ export function usePostActions( { postType, onActionPerformed, context } ) {
duplicateTemplatePartAction,
isPattern && userCanCreatePostType && duplicatePatternAction,
supportsTitle && renamePostAction,
reorderPagesAction,
...defaultActions,
].filter( Boolean );
// Filter actions based on provided context. If not provided
Expand Down Expand Up @@ -693,7 +583,6 @@ export function usePostActions( { postType, onActionPerformed, context } ) {
isPattern,
postTypeObject?.viewable,
duplicatePostAction,
reorderPagesAction,
onActionPerformed,
isLoaded,
supportsRevisions,
Expand Down
120 changes: 120 additions & 0 deletions packages/editor/src/dataviews/actions/reorder-page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
/**
* WordPress dependencies
*/
import { useDispatch } from '@wordpress/data';
import { store as coreStore } from '@wordpress/core-data';
import { __ } from '@wordpress/i18n';
import { store as noticesStore } from '@wordpress/notices';
import { useState } from '@wordpress/element';
import { DataForm, isItemValid } from '@wordpress/dataviews';
import {
Button,
__experimentalHStack as HStack,
__experimentalVStack as VStack,
} from '@wordpress/components';
import type { Action, RenderModalProps } from '@wordpress/dataviews';

/**
* Internal dependencies
*/
import type { CoreDataError, PostWithPageAttributesSupport } from '../types';
import { orderField } from '../fields';

const fields = [ orderField ];
const formOrderAction = {
fields: [ 'menu_order' ],
};

function ReorderModal( {
items,
closeModal,
onActionPerformed,
}: RenderModalProps< PostWithPageAttributesSupport > ) {
const [ item, setItem ] = useState( items[ 0 ] );
const orderInput = item.menu_order;
const { editEntityRecord, saveEditedEntityRecord } =
useDispatch( coreStore );
const { createSuccessNotice, createErrorNotice } =
useDispatch( noticesStore );

async function onOrder( event: React.FormEvent ) {
event.preventDefault();

if ( ! isItemValid( item, fields, formOrderAction ) ) {
return;
}

try {
await editEntityRecord( 'postType', item.type, item.id, {
menu_order: orderInput,
} );
closeModal?.();
// Persist edited entity.
await saveEditedEntityRecord( 'postType', item.type, item.id, {
throwOnError: true,
} );
createSuccessNotice( __( 'Order updated' ), {
youknowriad marked this conversation as resolved.
Show resolved Hide resolved
type: 'snackbar',
} );
onActionPerformed?.( items );
} catch ( error ) {
const typedError = error as CoreDataError;
const errorMessage =
typedError.message && typedError.code !== 'unknown_error'
? typedError.message
: __( 'An error occurred while updating the order' );
createErrorNotice( errorMessage, {
type: 'snackbar',
} );
}
}
const isSaveDisabled = ! isItemValid( item, fields, formOrderAction );
return (
<form onSubmit={ onOrder }>
<VStack spacing="5">
<div>
{ __(
'Determines the order of pages. Pages with the same order value are sorted alphabetically. Negative order values are supported.'
) }
</div>
<DataForm
data={ item }
fields={ fields }
form={ formOrderAction }
onChange={ setItem }
/>
<HStack justify="right">
<Button
__next40pxDefaultSize
variant="tertiary"
onClick={ () => {
closeModal?.();
} }
>
{ __( 'Cancel' ) }
</Button>
<Button
__next40pxDefaultSize
variant="primary"
type="submit"
accessibleWhenDisabled
disabled={ isSaveDisabled }
>
{ __( 'Save' ) }
</Button>
</HStack>
</VStack>
</form>
);
}

const reorderPage: Action< PostWithPageAttributesSupport > = {
id: 'order-pages',
label: __( 'Order' ),
isEligible( { status } ) {
return status !== 'trash';
},
RenderModal: ReorderModal,
};

export default reorderPage;
25 changes: 25 additions & 0 deletions packages/editor/src/dataviews/fields/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/**
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This PR is an opportunity to create a dedicated folder/file for the post type fields. If the fields declaration grows and becomes too big we can separate each field in its own file.

* WordPress dependencies
*/
import { __ } from '@wordpress/i18n';
import type { Field } from '@wordpress/dataviews';

/**
* Internal dependencies
*/
import type { BasePost, PostWithPageAttributesSupport } from '../types';

export const titleField: Field< BasePost > = {
type: 'text',
id: 'title',
label: __( 'Title' ),
placeholder: __( 'No title' ),
getValue: ( { item } ) => item.title,
};

export const orderField: Field< PostWithPageAttributesSupport > = {
type: 'integer',
id: 'menu_order',
label: __( 'Order' ),
description: __( 'Determines the order of pages.' ),
};
4 changes: 4 additions & 0 deletions packages/editor/src/dataviews/store/private-actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import resetPost from '../actions/reset-post';
import trashPost from '../actions/trash-post';
import permanentlyDeletePost from '../actions/permanently-delete-post';
import restorePost from '../actions/restore-post';
import reorderPage from '../actions/reorder-page';
import type { PostType } from '../types';
import { store as editorStore } from '../../store';
import { unlock } from '../../lock-unlock';
Expand Down Expand Up @@ -73,6 +74,9 @@ export const registerPostTypeActions =
.getPostType( postType ) ) as PostType;

const actions = [
postTypeConfig?.supports?.[ 'page-attributes' ]
? reorderPage
: undefined,
postTypeConfig.slug === 'wp_block' ? exportPattern : undefined,
resetPost,
restorePost,
Expand Down
7 changes: 7 additions & 0 deletions packages/editor/src/dataviews/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ export interface Pattern extends BasePost {
wp_pattern_sync_status: string;
}

export interface PostWithPageAttributesSupport extends BasePost {
menu_order: number;
}

export type Post = TemplateOrTemplatePart | Pattern | BasePost;

export type PostWithPermissions = Post & {
Expand All @@ -38,6 +42,9 @@ export type PostWithPermissions = Post & {

export interface PostType {
slug: string;
supports?: {
'page-attributes'?: boolean;
};
}

// Will be unnecessary after typescript 5.0 upgrade.
Expand Down
Loading