From 9786a18eb0e2322e67510bcfcc405913af0e4bab Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Thu, 5 Dec 2024 18:55:56 +0100 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20Replace=20isBusy=20with=20new=20act?= =?UTF-8?q?ivity-state=20manager?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../resources/js/data/common/action-types.js | 4 ++ .../resources/js/data/common/actions.js | 37 +++++++++++++------ .../resources/js/data/common/hooks.js | 30 +++++++++++++-- .../resources/js/data/common/reducer.js | 17 ++++++++- .../resources/js/data/common/selectors.js | 5 +++ 5 files changed, 77 insertions(+), 16 deletions(-) diff --git a/modules/ppcp-settings/resources/js/data/common/action-types.js b/modules/ppcp-settings/resources/js/data/common/action-types.js index 0cfe2e758..ac2c6db37 100644 --- a/modules/ppcp-settings/resources/js/data/common/action-types.js +++ b/modules/ppcp-settings/resources/js/data/common/action-types.js @@ -13,6 +13,10 @@ export default { RESET: 'COMMON:RESET', HYDRATE: 'COMMON:HYDRATE', + // Activity management (advanced solution that replaces the isBusy state). + START_ACTIVITY: 'COMMON:START_ACTIVITY', + STOP_ACTIVITY: 'COMMON:STOP_ACTIVITY', + // Controls - always start with "DO_". DO_PERSIST_DATA: 'COMMON:DO_PERSIST_DATA', DO_MANUAL_CONNECTION: 'COMMON:DO_MANUAL_CONNECTION', diff --git a/modules/ppcp-settings/resources/js/data/common/actions.js b/modules/ppcp-settings/resources/js/data/common/actions.js index 6aea05024..c6906546a 100644 --- a/modules/ppcp-settings/resources/js/data/common/actions.js +++ b/modules/ppcp-settings/resources/js/data/common/actions.js @@ -59,14 +59,35 @@ export const setIsSaving = ( isSaving ) => ( { } ); /** - * Transient. Changes the "manual connection is busy" flag. + * Transient (Activity): Marks the start of an async activity + * Think of it as "setIsBusy(true)" * - * @param {boolean} isBusy + * @param {string} id Internal ID/key of the action, used to stop it again. + * @param {?string} description Optional, description for logging/debugging + * @return {?Action} The action. + */ +export const startActivity = ( id, description = null ) => { + if ( ! id || 'string' !== typeof id ) { + console.warn( 'Activity ID must be a non-empty string' ); + return null; + } + + return { + type: ACTION_TYPES.START_ACTIVITY, + payload: { id, description }, + }; +}; + +/** + * Transient (Activity): Marks the end of an async activity. + * Think of it as "setIsBusy(false)" + * + * @param {string} id Internal ID/key of the action, used to stop it again. * @return {Action} The action. */ -export const setIsBusy = ( isBusy ) => ( { - type: ACTION_TYPES.SET_TRANSIENT, - payload: { isBusy }, +export const stopActivity = ( id ) => ( { + type: ACTION_TYPES.STOP_ACTIVITY, + payload: { id }, } ); /** @@ -130,10 +151,8 @@ export const persist = function* () { * @return {Action} The action. */ export const connectToSandbox = function* () { - yield setIsBusy( true ); const result = yield { type: ACTION_TYPES.DO_SANDBOX_LOGIN }; - yield setIsBusy( false ); return result; }; @@ -145,13 +164,11 @@ export const connectToSandbox = function* () { * @return {Action} The action. */ export const connectToProduction = function* ( products = [] ) { - yield setIsBusy( true ); const result = yield { type: ACTION_TYPES.DO_PRODUCTION_LOGIN, products, }; - yield setIsBusy( false ); return result; }; @@ -165,7 +182,6 @@ export const connectViaIdAndSecret = function* () { const { clientId, clientSecret, useSandbox } = yield select( STORE_NAME ).persistentData(); - yield setIsBusy( true ); const result = yield { type: ACTION_TYPES.DO_MANUAL_CONNECTION, @@ -173,7 +189,6 @@ export const connectViaIdAndSecret = function* () { clientSecret, useSandbox, }; - yield setIsBusy( false ); return result; }; diff --git a/modules/ppcp-settings/resources/js/data/common/hooks.js b/modules/ppcp-settings/resources/js/data/common/hooks.js index c1fa859d9..e4442e50f 100644 --- a/modules/ppcp-settings/resources/js/data/common/hooks.js +++ b/modules/ppcp-settings/resources/js/data/common/hooks.js @@ -81,12 +81,34 @@ const useHooks = () => { }; export const useBusyState = () => { - const { setIsBusy } = useDispatch( STORE_NAME ); - const isBusy = useTransient( 'isBusy' ); + const { startActivity, stopActivity } = useDispatch( STORE_NAME ); + + // Resolved value (object), contains a list of all running actions. + const activities = useSelect( + ( select ) => select( STORE_NAME ).getActivityList(), + [] + ); + + // Derive isBusy state from activities + const isBusy = Object.keys( activities ).length > 0; + + // HOC that starts and stops an activity while the callback is executed. + const withActivity = useCallback( + async ( id, description, asyncFn ) => { + startActivity( id, description ); + try { + return await asyncFn(); + } finally { + stopActivity( id ); + } + }, + [ startActivity, stopActivity ] + ); return { - isBusy, - setIsBusy: useCallback( ( busy ) => setIsBusy( busy ), [ setIsBusy ] ), + withActivity, // HOC + isBusy, // Boolean. + activities, // Object. }; }; diff --git a/modules/ppcp-settings/resources/js/data/common/reducer.js b/modules/ppcp-settings/resources/js/data/common/reducer.js index 814f4d6b3..63c231f85 100644 --- a/modules/ppcp-settings/resources/js/data/common/reducer.js +++ b/modules/ppcp-settings/resources/js/data/common/reducer.js @@ -14,7 +14,7 @@ import ACTION_TYPES from './action-types'; const defaultTransient = { isReady: false, - isBusy: false, + activities: new Map(), // Read only values, provided by the server via hydrate. wooSettings: { @@ -57,6 +57,21 @@ const commonReducer = createReducer( defaultTransient, defaultPersistent, { return cleanState; }, + [ ACTION_TYPES.START_ACTIVITY ]: ( state, payload ) => { + return setTransient( state, { + activities: new Map( state.activities ).set( + payload.id, + payload.description + ), + } ); + }, + + [ ACTION_TYPES.STOP_ACTIVITY ]: ( state, payload ) => { + const newActivities = new Map( state.activities ); + newActivities.delete( payload.id ); + return setTransient( state, { activities: newActivities } ); + }, + [ ACTION_TYPES.HYDRATE ]: ( state, payload ) => { const newState = setPersistent( state, payload.data ); diff --git a/modules/ppcp-settings/resources/js/data/common/selectors.js b/modules/ppcp-settings/resources/js/data/common/selectors.js index 7f0b3ee20..17e422b7a 100644 --- a/modules/ppcp-settings/resources/js/data/common/selectors.js +++ b/modules/ppcp-settings/resources/js/data/common/selectors.js @@ -20,6 +20,11 @@ export const transientData = ( state ) => { return transientState || EMPTY_OBJ; }; +export const getActivityList = ( state ) => { + const { activities = new Map() } = state; + return Object.fromEntries( activities ); +}; + export const wooSettings = ( state ) => { return getState( state ).wooSettings || EMPTY_OBJ; };