From 1f0325f9b5aca9a2ac15ff47e2c0886e20f0676f Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Wed, 12 Jun 2024 09:16:27 +0200 Subject: [PATCH 1/5] DataViews: Support thunk callbacks in actions --- package-lock.json | 2 + packages/dataviews/package.json | 1 + .../dataviews/src/bulk-actions-toolbar.tsx | 7 +- packages/dataviews/src/bulk-actions.tsx | 7 +- packages/dataviews/src/item-actions.tsx | 17 +- packages/dataviews/src/types.ts | 4 +- packages/dataviews/src/view-list.tsx | 21 +- .../src/components/post-actions/actions.js | 418 ++++++++---------- 8 files changed, 243 insertions(+), 234 deletions(-) diff --git a/package-lock.json b/package-lock.json index 9fceaac246b7a..b2abc2d220b52 100644 --- a/package-lock.json +++ b/package-lock.json @@ -53880,6 +53880,7 @@ "@babel/runtime": "^7.16.0", "@wordpress/components": "file:../components", "@wordpress/compose": "file:../compose", + "@wordpress/data": "file:../data", "@wordpress/element": "file:../element", "@wordpress/i18n": "file:../i18n", "@wordpress/icons": "file:../icons", @@ -69130,6 +69131,7 @@ "@babel/runtime": "^7.16.0", "@wordpress/components": "file:../components", "@wordpress/compose": "file:../compose", + "@wordpress/data": "file:../data", "@wordpress/element": "file:../element", "@wordpress/i18n": "file:../i18n", "@wordpress/icons": "file:../icons", diff --git a/packages/dataviews/package.json b/packages/dataviews/package.json index 500bb8a13090c..5de3498c9f647 100644 --- a/packages/dataviews/package.json +++ b/packages/dataviews/package.json @@ -32,6 +32,7 @@ "@babel/runtime": "^7.16.0", "@wordpress/components": "file:../components", "@wordpress/compose": "file:../compose", + "@wordpress/data": "file:../data", "@wordpress/element": "file:../element", "@wordpress/i18n": "file:../i18n", "@wordpress/icons": "file:../icons", diff --git a/packages/dataviews/src/bulk-actions-toolbar.tsx b/packages/dataviews/src/bulk-actions-toolbar.tsx index 3cce31c513904..d754d79661af1 100644 --- a/packages/dataviews/src/bulk-actions-toolbar.tsx +++ b/packages/dataviews/src/bulk-actions-toolbar.tsx @@ -12,6 +12,7 @@ import { useMemo, useState, useRef } from '@wordpress/element'; import { _n, sprintf, __ } from '@wordpress/i18n'; import { closeSmall } from '@wordpress/icons'; import { useReducedMotion } from '@wordpress/compose'; +import { useRegistry } from '@wordpress/data'; /** * Internal dependencies @@ -92,6 +93,7 @@ function ActionButton< Item extends AnyItem >( { actionInProgress, setActionInProgress, }: ActionButtonProps< Item > ) { + const registry = useRegistry(); const selectedEligibleItems = useMemo( () => { return selectedItems.filter( ( item ) => { return ! action.isEligible || action.isEligible( item ); @@ -113,7 +115,10 @@ function ActionButton< Item extends AnyItem >( { action={ action } onClick={ () => { setActionInProgress( action.id ); - action.callback( selectedItems ); + const maybeThunk = action.callback( selectedItems ); + if ( typeof maybeThunk === 'function' ) { + maybeThunk( { registry } ); + } } } items={ selectedEligibleItems } isBusy={ actionInProgress === action.id } diff --git a/packages/dataviews/src/bulk-actions.tsx b/packages/dataviews/src/bulk-actions.tsx index 9be8f6b6d2d7f..abc0c8bf8cf1b 100644 --- a/packages/dataviews/src/bulk-actions.tsx +++ b/packages/dataviews/src/bulk-actions.tsx @@ -8,6 +8,7 @@ import { } from '@wordpress/components'; import { __, sprintf, _n } from '@wordpress/i18n'; import { useMemo, useState, useCallback, useEffect } from '@wordpress/element'; +import { useRegistry } from '@wordpress/data'; /** * Internal dependencies @@ -119,6 +120,7 @@ function BulkActionItem< Item extends AnyItem >( { selectedItems, setActionWithModal, }: BulkActionsItemProps< Item > ) { + const registry = useRegistry(); const eligibleItems = useMemo( () => { return selectedItems.filter( ( item ) => ! action.isEligible || action.isEligible( item ) @@ -136,7 +138,10 @@ function BulkActionItem< Item extends AnyItem >( { if ( shouldShowModal ) { setActionWithModal( action ); } else { - await action.callback( eligibleItems ); + const maybeThunk = action.callback( eligibleItems ); + if ( typeof maybeThunk === 'function' ) { + maybeThunk( { registry } ); + } } } } suffix={ diff --git a/packages/dataviews/src/item-actions.tsx b/packages/dataviews/src/item-actions.tsx index 90ae74b5f74ea..a2be44facb571 100644 --- a/packages/dataviews/src/item-actions.tsx +++ b/packages/dataviews/src/item-actions.tsx @@ -15,6 +15,7 @@ import { import { __ } from '@wordpress/i18n'; import { useMemo, useState } from '@wordpress/element'; import { moreVertical } from '@wordpress/icons'; +import { useRegistry } from '@wordpress/data'; /** * Internal dependencies @@ -159,6 +160,7 @@ export function ActionsDropdownMenuGroup< Item extends AnyItem >( { actions, item, }: ActionsDropdownMenuGroupProps< Item > ) { + const registry = useRegistry(); return ( { actions.map( ( action ) => { @@ -176,7 +178,12 @@ export function ActionsDropdownMenuGroup< Item extends AnyItem >( { action.callback( [ item ] ) } + onClick={ () => { + const maybeThunk = action.callback( [ item ] ); + if ( typeof maybeThunk === 'function' ) { + maybeThunk( { registry } ); + } + } } items={ [ item ] } /> ); @@ -190,6 +197,7 @@ export default function ItemActions< Item extends AnyItem >( { actions, isCompact, }: ItemActionsProps< Item > ) { + const registry = useRegistry(); const { primaryActions, eligibleActions } = useMemo( () => { // If an action is eligible for all items, doesn't need // to provide the `isEligible` function. @@ -233,7 +241,12 @@ export default function ItemActions< Item extends AnyItem >( { action.callback( [ item ] ) } + onClick={ () => { + const maybeThunk = action.callback( [ item ] ); + if ( typeof maybeThunk === 'function' ) { + maybeThunk( { registry } ); + } + } } items={ [ item ] } /> ); diff --git a/packages/dataviews/src/types.ts b/packages/dataviews/src/types.ts index e3d6d2dd158ec..45c1408818f17 100644 --- a/packages/dataviews/src/types.ts +++ b/packages/dataviews/src/types.ts @@ -368,7 +368,9 @@ export interface ActionButton< Item extends AnyItem > /** * The callback to execute when the action is triggered. */ - callback: ( items: Item[] ) => void; + callback: ( + items: Item[] + ) => void | ( ( { registry }: { registry: any } ) => void ); } export type Action< Item extends AnyItem > = diff --git a/packages/dataviews/src/view-list.tsx b/packages/dataviews/src/view-list.tsx index 0721a9b5d8ffe..e6ed38a87e362 100644 --- a/packages/dataviews/src/view-list.tsx +++ b/packages/dataviews/src/view-list.tsx @@ -27,6 +27,7 @@ import { } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; import { moreVertical } from '@wordpress/icons'; +import { useRegistry } from '@wordpress/data'; /** * Internal dependencies @@ -67,6 +68,7 @@ function ListItem< Item extends AnyItem >( { store, visibleFields, }: ListViewItemProps< Item > ) { + const registry = useRegistry(); const itemRef = useRef< HTMLElement >( null ); const labelId = `${ id }-label`; const descriptionId = `${ id }-description`; @@ -235,11 +237,20 @@ function ListItem< Item extends AnyItem >( { primaryAction.isDestructive } size="compact" - onClick={ () => - primaryAction.callback( [ - item, - ] ) - } + onClick={ () => { + const maybeThunk = + primaryAction.callback( + [ item ] + ); + if ( + typeof maybeThunk === + 'function' + ) { + maybeThunk( { + registry, + } ); + } + } } /> } /> diff --git a/packages/editor/src/components/post-actions/actions.js b/packages/editor/src/components/post-actions/actions.js index b6ee097e85085..76f0e02ee7f08 100644 --- a/packages/editor/src/components/post-actions/actions.js +++ b/packages/editor/src/components/post-actions/actions.js @@ -323,246 +323,220 @@ const trashPostAction = { }, }; -function usePermanentlyDeletePostAction() { - const { createSuccessNotice, createErrorNotice } = - useDispatch( noticesStore ); - const { deleteEntityRecord } = useDispatch( coreStore ); - - return useMemo( - () => ( { - id: 'permanently-delete', - label: __( 'Permanently delete' ), - supportsBulk: true, - isEligible( { status } ) { - return status === 'trash'; - }, - async callback( posts, onActionPerformed ) { - const promiseResult = await Promise.allSettled( - posts.map( ( post ) => { - return deleteEntityRecord( - 'postType', - post.type, - post.id, - { force: true }, - { throwOnError: true } - ); - } ) - ); - // If all the promises were fulfilled with success. - if ( - promiseResult.every( - ( { status } ) => status === 'fulfilled' - ) - ) { - let successMessage; - if ( promiseResult.length === 1 ) { - successMessage = sprintf( - /* translators: The posts's title. */ - __( '"%s" permanently deleted.' ), - getItemTitle( posts[ 0 ] ) - ); +const permanentlyDeletePostAction = { + id: 'permanently-delete', + label: __( 'Permanently delete' ), + supportsBulk: true, + isEligible( { status } ) { + return status === 'trash'; + }, + callback: + ( posts, onActionPerformed ) => + async ( { registry } ) => { + const { createSuccessNotice, createErrorNotice } = + registry.dispatch( noticesStore ); + const { deleteEntityRecord } = registry.dispatch( coreStore ); + const promiseResult = await Promise.allSettled( + posts.map( ( post ) => { + return deleteEntityRecord( + 'postType', + post.type, + post.id, + { force: true }, + { throwOnError: true } + ); + } ) + ); + // If all the promises were fulfilled with success. + if ( + promiseResult.every( ( { status } ) => status === 'fulfilled' ) + ) { + let successMessage; + if ( promiseResult.length === 1 ) { + successMessage = sprintf( + /* translators: The posts's title. */ + __( '"%s" permanently deleted.' ), + getItemTitle( posts[ 0 ] ) + ); + } else { + successMessage = __( + 'The posts were permanently deleted.' + ); + } + createSuccessNotice( successMessage, { + type: 'snackbar', + id: 'permanently-delete-post-action', + } ); + if ( onActionPerformed ) { + onActionPerformed( posts ); + } + } else { + // If there was at lease one failure. + let errorMessage; + // If we were trying to permanently delete a single post. + if ( promiseResult.length === 1 ) { + if ( promiseResult[ 0 ].reason?.message ) { + errorMessage = promiseResult[ 0 ].reason.message; } else { - successMessage = __( - 'The posts were permanently deleted.' + errorMessage = __( + 'An error occurred while permanently deleting the post.' ); } - createSuccessNotice( successMessage, { - type: 'snackbar', - id: 'permanently-delete-post-action', - } ); - if ( onActionPerformed ) { - onActionPerformed( posts ); - } + // If we were trying to permanently delete multiple posts } else { - // If there was at lease one failure. - let errorMessage; - // If we were trying to permanently delete a single post. - if ( promiseResult.length === 1 ) { - if ( promiseResult[ 0 ].reason?.message ) { - errorMessage = promiseResult[ 0 ].reason.message; - } else { - errorMessage = __( - 'An error occurred while permanently deleting the post.' - ); + const errorMessages = new Set(); + const failedPromises = promiseResult.filter( + ( { status } ) => status === 'rejected' + ); + for ( const failedPromise of failedPromises ) { + if ( failedPromise.reason?.message ) { + errorMessages.add( failedPromise.reason.message ); } - // If we were trying to permanently delete multiple posts + } + if ( errorMessages.size === 0 ) { + errorMessage = __( + 'An error occurred while permanently deleting the posts.' + ); + } else if ( errorMessages.size === 1 ) { + errorMessage = sprintf( + /* translators: %s: an error message */ + __( + 'An error occurred while permanently deleting the posts: %s' + ), + [ ...errorMessages ][ 0 ] + ); } else { - const errorMessages = new Set(); - const failedPromises = promiseResult.filter( - ( { status } ) => status === 'rejected' + errorMessage = sprintf( + /* translators: %s: a list of comma separated error messages */ + __( + 'Some errors occurred while permanently deleting the posts: %s' + ), + [ ...errorMessages ].join( ',' ) ); - for ( const failedPromise of failedPromises ) { - if ( failedPromise.reason?.message ) { - errorMessages.add( - failedPromise.reason.message - ); - } - } - if ( errorMessages.size === 0 ) { - errorMessage = __( - 'An error occurred while permanently deleting the posts.' - ); - } else if ( errorMessages.size === 1 ) { - errorMessage = sprintf( - /* translators: %s: an error message */ - __( - 'An error occurred while permanently deleting the posts: %s' - ), - [ ...errorMessages ][ 0 ] - ); - } else { - errorMessage = sprintf( - /* translators: %s: a list of comma separated error messages */ - __( - 'Some errors occurred while permanently deleting the posts: %s' - ), - [ ...errorMessages ].join( ',' ) - ); - } } - createErrorNotice( errorMessage, { - type: 'snackbar', - } ); } - }, - } ), - [ createSuccessNotice, createErrorNotice, deleteEntityRecord ] - ); -} - -function useRestorePostAction() { - const { createSuccessNotice, createErrorNotice } = - useDispatch( noticesStore ); - const { editEntityRecord, saveEditedEntityRecord } = - useDispatch( coreStore ); + createErrorNotice( errorMessage, { + type: 'snackbar', + } ); + } + }, +}; - return useMemo( - () => ( { - id: 'restore', - label: __( 'Restore' ), - isPrimary: true, - icon: backup, - supportsBulk: true, - isEligible( { status } ) { - return status === 'trash'; - }, - async callback( posts, onActionPerformed ) { - await Promise.allSettled( - posts.map( ( post ) => { - return editEntityRecord( - 'postType', - post.type, - post.id, - { - status: 'draft', - } - ); - } ) - ); - const promiseResult = await Promise.allSettled( - posts.map( ( post ) => { - return saveEditedEntityRecord( - 'postType', - post.type, - post.id, - { throwOnError: true } - ); - } ) - ); +const restorePostAction = { + id: 'restore', + label: __( 'Restore' ), + isPrimary: true, + icon: backup, + supportsBulk: true, + isEligible( { status } ) { + return status === 'trash'; + }, + callback: + ( posts, onActionPerformed ) => + async ( { registry } ) => { + const { createSuccessNotice, createErrorNotice } = + registry.dispatch( noticesStore ); + const { editEntityRecord, saveEditedEntityRecord } = + registry.dispatch( coreStore ); + await Promise.allSettled( + posts.map( ( post ) => { + return editEntityRecord( 'postType', post.type, post.id, { + status: 'draft', + } ); + } ) + ); + const promiseResult = await Promise.allSettled( + posts.map( ( post ) => { + return saveEditedEntityRecord( + 'postType', + post.type, + post.id, + { throwOnError: true } + ); + } ) + ); - if ( - promiseResult.every( - ( { status } ) => status === 'fulfilled' - ) - ) { - let successMessage; - if ( posts.length === 1 ) { - successMessage = sprintf( - /* translators: The number of posts. */ - __( '"%s" has been restored.' ), - getItemTitle( posts[ 0 ] ) - ); - } else if ( posts[ 0 ].type === 'page' ) { - successMessage = sprintf( - /* translators: The number of posts. */ - __( '%d pages have been restored.' ), - posts.length - ); + if ( + promiseResult.every( ( { status } ) => status === 'fulfilled' ) + ) { + let successMessage; + if ( posts.length === 1 ) { + successMessage = sprintf( + /* translators: The number of posts. */ + __( '"%s" has been restored.' ), + getItemTitle( posts[ 0 ] ) + ); + } else if ( posts[ 0 ].type === 'page' ) { + successMessage = sprintf( + /* translators: The number of posts. */ + __( '%d pages have been restored.' ), + posts.length + ); + } else { + successMessage = sprintf( + /* translators: The number of posts. */ + __( '%d posts have been restored.' ), + posts.length + ); + } + createSuccessNotice( successMessage, { + type: 'snackbar', + id: 'restore-post-action', + } ); + if ( onActionPerformed ) { + onActionPerformed( posts ); + } + } else { + // If there was at lease one failure. + let errorMessage; + // If we were trying to move a single post to the trash. + if ( promiseResult.length === 1 ) { + if ( promiseResult[ 0 ].reason?.message ) { + errorMessage = promiseResult[ 0 ].reason.message; } else { - successMessage = sprintf( - /* translators: The number of posts. */ - __( '%d posts have been restored.' ), - posts.length + errorMessage = __( + 'An error occurred while restoring the post.' ); } - createSuccessNotice( successMessage, { - type: 'snackbar', - id: 'restore-post-action', - } ); - if ( onActionPerformed ) { - onActionPerformed( posts ); - } + // If we were trying to move multiple posts to the trash } else { - // If there was at lease one failure. - let errorMessage; - // If we were trying to move a single post to the trash. - if ( promiseResult.length === 1 ) { - if ( promiseResult[ 0 ].reason?.message ) { - errorMessage = promiseResult[ 0 ].reason.message; - } else { - errorMessage = __( - 'An error occurred while restoring the post.' - ); + const errorMessages = new Set(); + const failedPromises = promiseResult.filter( + ( { status } ) => status === 'rejected' + ); + for ( const failedPromise of failedPromises ) { + if ( failedPromise.reason?.message ) { + errorMessages.add( failedPromise.reason.message ); } - // If we were trying to move multiple posts to the trash + } + if ( errorMessages.size === 0 ) { + errorMessage = __( + 'An error occurred while restoring the posts.' + ); + } else if ( errorMessages.size === 1 ) { + errorMessage = sprintf( + /* translators: %s: an error message */ + __( + 'An error occurred while restoring the posts: %s' + ), + [ ...errorMessages ][ 0 ] + ); } else { - const errorMessages = new Set(); - const failedPromises = promiseResult.filter( - ( { status } ) => status === 'rejected' + errorMessage = sprintf( + /* translators: %s: a list of comma separated error messages */ + __( + 'Some errors occurred while restoring the posts: %s' + ), + [ ...errorMessages ].join( ',' ) ); - for ( const failedPromise of failedPromises ) { - if ( failedPromise.reason?.message ) { - errorMessages.add( - failedPromise.reason.message - ); - } - } - if ( errorMessages.size === 0 ) { - errorMessage = __( - 'An error occurred while restoring the posts.' - ); - } else if ( errorMessages.size === 1 ) { - errorMessage = sprintf( - /* translators: %s: an error message */ - __( - 'An error occurred while restoring the posts: %s' - ), - [ ...errorMessages ][ 0 ] - ); - } else { - errorMessage = sprintf( - /* translators: %s: a list of comma separated error messages */ - __( - 'Some errors occurred while restoring the posts: %s' - ), - [ ...errorMessages ].join( ',' ) - ); - } } - createErrorNotice( errorMessage, { - type: 'snackbar', - } ); } - }, - } ), - [ - createSuccessNotice, - createErrorNotice, - editEntityRecord, - saveEditedEntityRecord, - ] - ); -} + createErrorNotice( errorMessage, { + type: 'snackbar', + } ); + } + }, +}; const viewPostAction = { id: 'view-post', @@ -1087,8 +1061,6 @@ export function usePostActions( { postType, onActionPerformed, context } ) { [ postType ] ); - const permanentlyDeletePostAction = usePermanentlyDeletePostAction(); - const restorePostAction = useRestorePostAction(); const duplicatePostAction = useDuplicatePostAction( postType ); const isTemplateOrTemplatePart = [ TEMPLATE_POST_TYPE, @@ -1182,8 +1154,6 @@ export function usePostActions( { postType, onActionPerformed, context } ) { isTemplateOrTemplatePart, isPattern, postTypeObject?.viewable, - permanentlyDeletePostAction, - restorePostAction, duplicatePostAction, onActionPerformed, isLoaded, From 056a66e3e7430870cfd2c3ac2b25d9d72adc56a9 Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Wed, 12 Jun 2024 09:27:22 +0200 Subject: [PATCH 2/5] Update DataViews changelog --- packages/dataviews/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/dataviews/CHANGELOG.md b/packages/dataviews/CHANGELOG.md index 3e4d617e9eb24..95cc08861fb0c 100644 --- a/packages/dataviews/CHANGELOG.md +++ b/packages/dataviews/CHANGELOG.md @@ -19,6 +19,7 @@ ### Enhancement - `label` prop in Actions API can be either a `string` value or a `function`, in case we want to use information from the selected items. ([#61942](https://github.com/WordPress/gutenberg/pull/61942)). +- Support thunks in the `callback` property of the actions API. ([#62505](https://github.com/WordPress/gutenberg/pull/62505)). ## 1.2.0 (2024-05-16) From 80ed76b046eeec4f65b979a6b9f3a9034b2ada1d Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Wed, 12 Jun 2024 10:50:58 +0200 Subject: [PATCH 3/5] Fix static checks --- packages/dataviews/tsconfig.json | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/dataviews/tsconfig.json b/packages/dataviews/tsconfig.json index 3a944ae6e2d1d..869f9fcfb9b49 100644 --- a/packages/dataviews/tsconfig.json +++ b/packages/dataviews/tsconfig.json @@ -9,6 +9,7 @@ "references": [ { "path": "../components" }, { "path": "../compose" }, + { "path": "../data" }, { "path": "../element" }, { "path": "../i18n" }, { "path": "../icons" }, From bc8a5150739711fe1c7d6e743f98d242af85d343 Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Wed, 12 Jun 2024 13:00:46 +0200 Subject: [PATCH 4/5] Simplify API --- .../dataviews/src/bulk-actions-toolbar.tsx | 7 +- packages/dataviews/src/bulk-actions.tsx | 5 +- packages/dataviews/src/item-actions.tsx | 17 +- packages/dataviews/src/types.ts | 20 +- packages/dataviews/src/view-list.tsx | 16 +- .../src/components/post-actions/actions.js | 370 ++++++++---------- 6 files changed, 181 insertions(+), 254 deletions(-) diff --git a/packages/dataviews/src/bulk-actions-toolbar.tsx b/packages/dataviews/src/bulk-actions-toolbar.tsx index d754d79661af1..56a8aa58e7dc4 100644 --- a/packages/dataviews/src/bulk-actions-toolbar.tsx +++ b/packages/dataviews/src/bulk-actions-toolbar.tsx @@ -115,10 +115,9 @@ function ActionButton< Item extends AnyItem >( { action={ action } onClick={ () => { setActionInProgress( action.id ); - const maybeThunk = action.callback( selectedItems ); - if ( typeof maybeThunk === 'function' ) { - maybeThunk( { registry } ); - } + action.callback( selectedItems, { + registry, + } ); } } items={ selectedEligibleItems } isBusy={ actionInProgress === action.id } diff --git a/packages/dataviews/src/bulk-actions.tsx b/packages/dataviews/src/bulk-actions.tsx index abc0c8bf8cf1b..0382e9d836bc9 100644 --- a/packages/dataviews/src/bulk-actions.tsx +++ b/packages/dataviews/src/bulk-actions.tsx @@ -138,10 +138,7 @@ function BulkActionItem< Item extends AnyItem >( { if ( shouldShowModal ) { setActionWithModal( action ); } else { - const maybeThunk = action.callback( eligibleItems ); - if ( typeof maybeThunk === 'function' ) { - maybeThunk( { registry } ); - } + action.callback( eligibleItems, { registry } ); } } } suffix={ diff --git a/packages/dataviews/src/item-actions.tsx b/packages/dataviews/src/item-actions.tsx index a2be44facb571..370162130f1f2 100644 --- a/packages/dataviews/src/item-actions.tsx +++ b/packages/dataviews/src/item-actions.tsx @@ -117,12 +117,7 @@ export function ActionModal< Item extends AnyItem >( { action.id ) }` } > - + ); } @@ -179,10 +174,7 @@ export function ActionsDropdownMenuGroup< Item extends AnyItem >( { key={ action.id } action={ action } onClick={ () => { - const maybeThunk = action.callback( [ item ] ); - if ( typeof maybeThunk === 'function' ) { - maybeThunk( { registry } ); - } + action.callback( [ item ], { registry } ); } } items={ [ item ] } /> @@ -242,10 +234,7 @@ export default function ItemActions< Item extends AnyItem >( { key={ action.id } action={ action } onClick={ () => { - const maybeThunk = action.callback( [ item ] ); - if ( typeof maybeThunk === 'function' ) { - maybeThunk( { registry } ); - } + action.callback( [ item ], { registry } ); } } items={ [ item ] } /> diff --git a/packages/dataviews/src/types.ts b/packages/dataviews/src/types.ts index 45c1408818f17..8c1819b3a7c67 100644 --- a/packages/dataviews/src/types.ts +++ b/packages/dataviews/src/types.ts @@ -327,28 +327,16 @@ interface ActionBase< Item extends AnyItem > { export interface ActionModal< Item extends AnyItem > extends ActionBase< Item > { - /** - * The callback to execute when the action has finished. - */ - onActionPerformed: ( ( items: Item[] ) => void ) | undefined; - - /** - * The callback to execute when the action is triggered. - */ - onActionStart: ( ( items: Item[] ) => void ) | undefined; - /** * Modal to render when the action is triggered. */ RenderModal: ( { items, closeModal, - onActionStart, onActionPerformed, }: { items: Item[]; closeModal?: () => void; - onActionStart?: ( items: Item[] ) => void; onActionPerformed?: ( items: Item[] ) => void; } ) => ReactElement; @@ -369,8 +357,12 @@ export interface ActionButton< Item extends AnyItem > * The callback to execute when the action is triggered. */ callback: ( - items: Item[] - ) => void | ( ( { registry }: { registry: any } ) => void ); + items: Item[], + context: { + registry: any; + onActionPerformed?: ( items: Item[] ) => void; + } + ) => void; } export type Action< Item extends AnyItem > = diff --git a/packages/dataviews/src/view-list.tsx b/packages/dataviews/src/view-list.tsx index e6ed38a87e362..81dc9127c922a 100644 --- a/packages/dataviews/src/view-list.tsx +++ b/packages/dataviews/src/view-list.tsx @@ -238,18 +238,10 @@ function ListItem< Item extends AnyItem >( { } size="compact" onClick={ () => { - const maybeThunk = - primaryAction.callback( - [ item ] - ); - if ( - typeof maybeThunk === - 'function' - ) { - maybeThunk( { - registry, - } ); - } + primaryAction.callback( + [ item ], + { registry } + ); } } /> } diff --git a/packages/editor/src/components/post-actions/actions.js b/packages/editor/src/components/post-actions/actions.js index 76f0e02ee7f08..5cb208e23e786 100644 --- a/packages/editor/src/components/post-actions/actions.js +++ b/packages/editor/src/components/post-actions/actions.js @@ -93,12 +93,7 @@ const deletePostAction = { }, supportsBulk: true, hideModalHeader: true, - RenderModal: ( { - items, - closeModal, - onActionStart, - onActionPerformed, - } ) => { + RenderModal: ( { items, closeModal, onActionPerformed } ) => { const [ isBusy, setIsBusy ] = useState( false ); const { removeTemplates } = unlock( useDispatch( editorStore ) ); return ( @@ -133,9 +128,6 @@ const deletePostAction = { variant="primary" onClick={ async () => { setIsBusy( true ); - if ( onActionStart ) { - onActionStart( items ); - } await removeTemplates( items, { allowUndo: false, } ); @@ -165,12 +157,7 @@ const trashPostAction = { }, supportsBulk: true, hideModalHeader: true, - RenderModal: ( { - items, - closeModal, - onActionStart, - onActionPerformed, - } ) => { + RenderModal: ( { items, closeModal, onActionPerformed } ) => { const [ isBusy, setIsBusy ] = useState( false ); const { createSuccessNotice, createErrorNotice } = useDispatch( noticesStore ); @@ -209,9 +196,6 @@ const trashPostAction = { variant="primary" onClick={ async () => { setIsBusy( true ); - if ( onActionStart ) { - onActionStart( items ); - } const promiseResult = await Promise.allSettled( items.map( ( item ) => deleteEntityRecord( @@ -330,96 +314,87 @@ const permanentlyDeletePostAction = { isEligible( { status } ) { return status === 'trash'; }, - callback: - ( posts, onActionPerformed ) => - async ( { registry } ) => { - const { createSuccessNotice, createErrorNotice } = - registry.dispatch( noticesStore ); - const { deleteEntityRecord } = registry.dispatch( coreStore ); - const promiseResult = await Promise.allSettled( - posts.map( ( post ) => { - return deleteEntityRecord( - 'postType', - post.type, - post.id, - { force: true }, - { throwOnError: true } - ); - } ) - ); - // If all the promises were fulfilled with success. - if ( - promiseResult.every( ( { status } ) => status === 'fulfilled' ) - ) { - let successMessage; - if ( promiseResult.length === 1 ) { - successMessage = sprintf( - /* translators: The posts's title. */ - __( '"%s" permanently deleted.' ), - getItemTitle( posts[ 0 ] ) - ); + async callback( posts, { registry } ) { + const { createSuccessNotice, createErrorNotice } = + registry.dispatch( noticesStore ); + const { deleteEntityRecord } = registry.dispatch( coreStore ); + const promiseResult = await Promise.allSettled( + posts.map( ( post ) => { + return deleteEntityRecord( + 'postType', + post.type, + post.id, + { force: true }, + { throwOnError: true } + ); + } ) + ); + // If all the promises were fulfilled with success. + if ( promiseResult.every( ( { status } ) => status === 'fulfilled' ) ) { + let successMessage; + if ( promiseResult.length === 1 ) { + successMessage = sprintf( + /* translators: The posts's title. */ + __( '"%s" permanently deleted.' ), + getItemTitle( posts[ 0 ] ) + ); + } else { + successMessage = __( 'The posts were permanently deleted.' ); + } + createSuccessNotice( successMessage, { + type: 'snackbar', + id: 'permanently-delete-post-action', + } ); + } else { + // If there was at lease one failure. + let errorMessage; + // If we were trying to permanently delete a single post. + if ( promiseResult.length === 1 ) { + if ( promiseResult[ 0 ].reason?.message ) { + errorMessage = promiseResult[ 0 ].reason.message; } else { - successMessage = __( - 'The posts were permanently deleted.' + errorMessage = __( + 'An error occurred while permanently deleting the post.' ); } - createSuccessNotice( successMessage, { - type: 'snackbar', - id: 'permanently-delete-post-action', - } ); - if ( onActionPerformed ) { - onActionPerformed( posts ); - } + // If we were trying to permanently delete multiple posts } else { - // If there was at lease one failure. - let errorMessage; - // If we were trying to permanently delete a single post. - if ( promiseResult.length === 1 ) { - if ( promiseResult[ 0 ].reason?.message ) { - errorMessage = promiseResult[ 0 ].reason.message; - } else { - errorMessage = __( - 'An error occurred while permanently deleting the post.' - ); + const errorMessages = new Set(); + const failedPromises = promiseResult.filter( + ( { status } ) => status === 'rejected' + ); + for ( const failedPromise of failedPromises ) { + if ( failedPromise.reason?.message ) { + errorMessages.add( failedPromise.reason.message ); } - // If we were trying to permanently delete multiple posts + } + if ( errorMessages.size === 0 ) { + errorMessage = __( + 'An error occurred while permanently deleting the posts.' + ); + } else if ( errorMessages.size === 1 ) { + errorMessage = sprintf( + /* translators: %s: an error message */ + __( + 'An error occurred while permanently deleting the posts: %s' + ), + [ ...errorMessages ][ 0 ] + ); } else { - const errorMessages = new Set(); - const failedPromises = promiseResult.filter( - ( { status } ) => status === 'rejected' + errorMessage = sprintf( + /* translators: %s: a list of comma separated error messages */ + __( + 'Some errors occurred while permanently deleting the posts: %s' + ), + [ ...errorMessages ].join( ',' ) ); - for ( const failedPromise of failedPromises ) { - if ( failedPromise.reason?.message ) { - errorMessages.add( failedPromise.reason.message ); - } - } - if ( errorMessages.size === 0 ) { - errorMessage = __( - 'An error occurred while permanently deleting the posts.' - ); - } else if ( errorMessages.size === 1 ) { - errorMessage = sprintf( - /* translators: %s: an error message */ - __( - 'An error occurred while permanently deleting the posts: %s' - ), - [ ...errorMessages ][ 0 ] - ); - } else { - errorMessage = sprintf( - /* translators: %s: a list of comma separated error messages */ - __( - 'Some errors occurred while permanently deleting the posts: %s' - ), - [ ...errorMessages ].join( ',' ) - ); - } } - createErrorNotice( errorMessage, { - type: 'snackbar', - } ); } - }, + createErrorNotice( errorMessage, { + type: 'snackbar', + } ); + } + }, }; const restorePostAction = { @@ -431,111 +406,102 @@ const restorePostAction = { isEligible( { status } ) { return status === 'trash'; }, - callback: - ( posts, onActionPerformed ) => - async ( { registry } ) => { - const { createSuccessNotice, createErrorNotice } = - registry.dispatch( noticesStore ); - const { editEntityRecord, saveEditedEntityRecord } = - registry.dispatch( coreStore ); - await Promise.allSettled( - posts.map( ( post ) => { - return editEntityRecord( 'postType', post.type, post.id, { - status: 'draft', - } ); - } ) - ); - const promiseResult = await Promise.allSettled( - posts.map( ( post ) => { - return saveEditedEntityRecord( - 'postType', - post.type, - post.id, - { throwOnError: true } - ); - } ) - ); + async callback( posts, { registry, onActionPerformed } ) { + const { createSuccessNotice, createErrorNotice } = + registry.dispatch( noticesStore ); + const { editEntityRecord, saveEditedEntityRecord } = + registry.dispatch( coreStore ); + await Promise.allSettled( + posts.map( ( post ) => { + return editEntityRecord( 'postType', post.type, post.id, { + status: 'draft', + } ); + } ) + ); + const promiseResult = await Promise.allSettled( + posts.map( ( post ) => { + return saveEditedEntityRecord( 'postType', post.type, post.id, { + throwOnError: true, + } ); + } ) + ); - if ( - promiseResult.every( ( { status } ) => status === 'fulfilled' ) - ) { - let successMessage; - if ( posts.length === 1 ) { - successMessage = sprintf( - /* translators: The number of posts. */ - __( '"%s" has been restored.' ), - getItemTitle( posts[ 0 ] ) - ); - } else if ( posts[ 0 ].type === 'page' ) { - successMessage = sprintf( - /* translators: The number of posts. */ - __( '%d pages have been restored.' ), - posts.length - ); + if ( promiseResult.every( ( { status } ) => status === 'fulfilled' ) ) { + let successMessage; + if ( posts.length === 1 ) { + successMessage = sprintf( + /* translators: The number of posts. */ + __( '"%s" has been restored.' ), + getItemTitle( posts[ 0 ] ) + ); + } else if ( posts[ 0 ].type === 'page' ) { + successMessage = sprintf( + /* translators: The number of posts. */ + __( '%d pages have been restored.' ), + posts.length + ); + } else { + successMessage = sprintf( + /* translators: The number of posts. */ + __( '%d posts have been restored.' ), + posts.length + ); + } + createSuccessNotice( successMessage, { + type: 'snackbar', + id: 'restore-post-action', + } ); + if ( onActionPerformed ) { + onActionPerformed( posts ); + } + } else { + // If there was at lease one failure. + let errorMessage; + // If we were trying to move a single post to the trash. + if ( promiseResult.length === 1 ) { + if ( promiseResult[ 0 ].reason?.message ) { + errorMessage = promiseResult[ 0 ].reason.message; } else { - successMessage = sprintf( - /* translators: The number of posts. */ - __( '%d posts have been restored.' ), - posts.length + errorMessage = __( + 'An error occurred while restoring the post.' ); } - createSuccessNotice( successMessage, { - type: 'snackbar', - id: 'restore-post-action', - } ); - if ( onActionPerformed ) { - onActionPerformed( posts ); - } + // If we were trying to move multiple posts to the trash } else { - // If there was at lease one failure. - let errorMessage; - // If we were trying to move a single post to the trash. - if ( promiseResult.length === 1 ) { - if ( promiseResult[ 0 ].reason?.message ) { - errorMessage = promiseResult[ 0 ].reason.message; - } else { - errorMessage = __( - 'An error occurred while restoring the post.' - ); + const errorMessages = new Set(); + const failedPromises = promiseResult.filter( + ( { status } ) => status === 'rejected' + ); + for ( const failedPromise of failedPromises ) { + if ( failedPromise.reason?.message ) { + errorMessages.add( failedPromise.reason.message ); } - // If we were trying to move multiple posts to the trash + } + if ( errorMessages.size === 0 ) { + errorMessage = __( + 'An error occurred while restoring the posts.' + ); + } else if ( errorMessages.size === 1 ) { + errorMessage = sprintf( + /* translators: %s: an error message */ + __( 'An error occurred while restoring the posts: %s' ), + [ ...errorMessages ][ 0 ] + ); } else { - const errorMessages = new Set(); - const failedPromises = promiseResult.filter( - ( { status } ) => status === 'rejected' + errorMessage = sprintf( + /* translators: %s: a list of comma separated error messages */ + __( + 'Some errors occurred while restoring the posts: %s' + ), + [ ...errorMessages ].join( ',' ) ); - for ( const failedPromise of failedPromises ) { - if ( failedPromise.reason?.message ) { - errorMessages.add( failedPromise.reason.message ); - } - } - if ( errorMessages.size === 0 ) { - errorMessage = __( - 'An error occurred while restoring the posts.' - ); - } else if ( errorMessages.size === 1 ) { - errorMessage = sprintf( - /* translators: %s: an error message */ - __( - 'An error occurred while restoring the posts: %s' - ), - [ ...errorMessages ][ 0 ] - ); - } else { - errorMessage = sprintf( - /* translators: %s: a list of comma separated error messages */ - __( - 'Some errors occurred while restoring the posts: %s' - ), - [ ...errorMessages ].join( ',' ) - ); - } } - createErrorNotice( errorMessage, { - type: 'snackbar', - } ); } - }, + createErrorNotice( errorMessage, { + type: 'snackbar', + } ); + } + }, }; const viewPostAction = { @@ -546,7 +512,7 @@ const viewPostAction = { isEligible( post ) { return post.status !== 'trash'; }, - callback( posts, onActionPerformed ) { + callback( posts, { onActionPerformed } ) { const post = posts[ 0 ]; window.open( post.link, '_blank' ); if ( onActionPerformed ) { @@ -577,7 +543,7 @@ const postRevisionsAction = { post?._links?.[ 'version-history' ]?.[ 0 ]?.count ?? 0; return lastRevisionId && revisionsCount > 1; }, - callback( posts, onActionPerformed ) { + callback( posts, { onActionPerformed } ) { const post = posts[ 0 ]; const href = addQueryArgs( 'revision.php', { revision: post?._links?.[ 'predecessor-version' ]?.[ 0 ]?.id, @@ -874,12 +840,7 @@ const resetTemplateAction = { icon: backup, supportsBulk: true, hideModalHeader: true, - RenderModal: ( { - items, - closeModal, - onActionStart, - onActionPerformed, - } ) => { + RenderModal: ( { items, closeModal, onActionPerformed } ) => { const [ isBusy, setIsBusy ] = useState( false ); const { revertTemplate, removeTemplates } = unlock( useDispatch( editorStore ) @@ -969,9 +930,6 @@ const resetTemplateAction = { variant="primary" onClick={ async () => { setIsBusy( true ); - if ( onActionStart ) { - onActionStart( items ); - } await onConfirm( items ); onActionPerformed?.( items ); setIsBusy( false ); @@ -1113,7 +1071,7 @@ export function usePostActions( { postType, onActionPerformed, context } ) { const existingCallback = actions[ i ].callback; actions[ i ] = { ...actions[ i ], - callback: ( items, _onActionPerformed ) => { + callback: ( items, { _onActionPerformed } ) => { existingCallback( items, ( _items ) => { if ( _onActionPerformed ) { _onActionPerformed( _items ); From b8996d6e0185e659676c74c343175d2ffa3d008d Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Wed, 12 Jun 2024 13:01:29 +0200 Subject: [PATCH 5/5] Clarify changelog --- packages/dataviews/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/dataviews/CHANGELOG.md b/packages/dataviews/CHANGELOG.md index 95cc08861fb0c..800ebd363ca91 100644 --- a/packages/dataviews/CHANGELOG.md +++ b/packages/dataviews/CHANGELOG.md @@ -19,7 +19,7 @@ ### Enhancement - `label` prop in Actions API can be either a `string` value or a `function`, in case we want to use information from the selected items. ([#61942](https://github.com/WordPress/gutenberg/pull/61942)). -- Support thunks in the `callback` property of the actions API. ([#62505](https://github.com/WordPress/gutenberg/pull/62505)). +- Add `registry` argument to the callback of the actions API. ([#62505](https://github.com/WordPress/gutenberg/pull/62505)). ## 1.2.0 (2024-05-16)