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;
};