@wordpress/data 6.0.1-next.5df0cd52b7.0 → 6.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +8 -0
- package/README.md +31 -2
- package/build/components/registry-provider/use-registry.js +1 -1
- package/build/components/registry-provider/use-registry.js.map +1 -1
- package/build/controls.js +23 -15
- package/build/controls.js.map +1 -1
- package/build/plugins/persistence/index.js +43 -50
- package/build/plugins/persistence/index.js.map +1 -1
- package/build/registry.js +40 -7
- package/build/registry.js.map +1 -1
- package/build/utils/emitter.js +57 -0
- package/build/utils/emitter.js.map +1 -0
- package/build-module/components/registry-provider/use-registry.js +1 -1
- package/build-module/components/registry-provider/use-registry.js.map +1 -1
- package/build-module/controls.js +22 -15
- package/build-module/controls.js.map +1 -1
- package/build-module/plugins/persistence/index.js +42 -51
- package/build-module/plugins/persistence/index.js.map +1 -1
- package/build-module/registry.js +40 -8
- package/build-module/registry.js.map +1 -1
- package/build-module/utils/emitter.js +50 -0
- package/build-module/utils/emitter.js.map +1 -0
- package/build-types/redux-store/metadata/selectors.d.ts +1 -3
- package/build-types/redux-store/metadata/selectors.d.ts.map +1 -1
- package/build-types/utils/emitter.d.ts +7 -0
- package/build-types/utils/emitter.d.ts.map +1 -0
- package/package.json +8 -8
- package/src/components/registry-provider/use-registry.js +1 -1
- package/src/controls.js +43 -15
- package/src/plugins/persistence/index.js +56 -62
- package/src/plugins/persistence/test/index.js +117 -1
- package/src/registry.js +38 -8
- package/src/test/registry.js +34 -0
- package/src/types.d.ts +8 -0
- package/src/utils/emitter.js +46 -0
- package/tsconfig.json +1 -0
- package/tsconfig.tsbuildinfo +1 -826
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["@wordpress/data/src/utils/emitter.js"],"names":["createEmitter","isPaused","isPending","listeners","Set","notifyListeners","Array","from","forEach","listener","subscribe","add","delete","pause","resume","emit"],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA,OAAO,SAASA,aAAT,GAAyB;AAC/B,MAAIC,QAAQ,GAAG,KAAf;AACA,MAAIC,SAAS,GAAG,KAAhB;AACA,QAAMC,SAAS,GAAG,IAAIC,GAAJ,EAAlB;;AACA,QAAMC,eAAe,GAAG,MACvB;AACA;AACA;AACAC,EAAAA,KAAK,CAACC,IAAN,CAAYJ,SAAZ,EAAwBK,OAAxB,CAAmCC,QAAF,IAAgBA,QAAQ,EAAzD,CAJD;;AAMA,SAAO;AACN,QAAIR,QAAJ,GAAe;AACd,aAAOA,QAAP;AACA,KAHK;;AAKNS,IAAAA,SAAS,CAAED,QAAF,EAAa;AACrBN,MAAAA,SAAS,CAACQ,GAAV,CAAeF,QAAf;AACA,aAAO,MAAMN,SAAS,CAACS,MAAV,CAAkBH,QAAlB,CAAb;AACA,KARK;;AAUNI,IAAAA,KAAK,GAAG;AACPZ,MAAAA,QAAQ,GAAG,IAAX;AACA,KAZK;;AAcNa,IAAAA,MAAM,GAAG;AACRb,MAAAA,QAAQ,GAAG,KAAX;;AACA,UAAKC,SAAL,EAAiB;AAChBA,QAAAA,SAAS,GAAG,KAAZ;AACAG,QAAAA,eAAe;AACf;AACD,KApBK;;AAsBNU,IAAAA,IAAI,GAAG;AACN,UAAKd,QAAL,EAAgB;AACfC,QAAAA,SAAS,GAAG,IAAZ;AACA;AACA;;AACDG,MAAAA,eAAe;AACf;;AA5BK,GAAP;AA8BA","sourcesContent":["/**\n * Create an event emitter.\n *\n * @return {import(\"../types\").WPDataEmitter} Emitter.\n */\nexport function createEmitter() {\n\tlet isPaused = false;\n\tlet isPending = false;\n\tconst listeners = new Set();\n\tconst notifyListeners = () =>\n\t\t// We use Array.from to clone the listeners Set\n\t\t// This ensures that we don't run a listener\n\t\t// that was added as a response to another listener.\n\t\tArray.from( listeners ).forEach( ( listener ) => listener() );\n\n\treturn {\n\t\tget isPaused() {\n\t\t\treturn isPaused;\n\t\t},\n\n\t\tsubscribe( listener ) {\n\t\t\tlisteners.add( listener );\n\t\t\treturn () => listeners.delete( listener );\n\t\t},\n\n\t\tpause() {\n\t\t\tisPaused = true;\n\t\t},\n\n\t\tresume() {\n\t\t\tisPaused = false;\n\t\t\tif ( isPending ) {\n\t\t\t\tisPending = false;\n\t\t\t\tnotifyListeners();\n\t\t\t}\n\t\t},\n\n\t\temit() {\n\t\t\tif ( isPaused ) {\n\t\t\t\tisPending = true;\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tnotifyListeners();\n\t\t},\n\t};\n}\n"]}
|
|
@@ -53,7 +53,5 @@ export function isResolving(state: State, selectorName: string, args?: unknown[]
|
|
|
53
53
|
* @return {State} Resolvers mapped by args and selectorName.
|
|
54
54
|
*/
|
|
55
55
|
export function getCachedResolvers(state: State): State;
|
|
56
|
-
export type State =
|
|
57
|
-
[x: string]: import("./reducer").State;
|
|
58
|
-
};
|
|
56
|
+
export type State = Record<string, import('./reducer').State>;
|
|
59
57
|
//# sourceMappingURL=selectors.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"selectors.d.ts","sourceRoot":"","sources":["../../../src/redux-store/metadata/selectors.js"],"names":[],"mappings":"AAKA,iEAAiE;AAEjE;;;;;;;;;;;GAWG;AACH,sCANW,KAAK,gBACL,MAAM,QACN,OAAO,EAAE,GAER,OAAO,GAAG,SAAS,CAS9B;AAED;;;;;;;;;GASG;AACH,4CANW,KAAK,gBACL,MAAM,iCAGL,OAAO,CAIlB;AAED;;;;;;;;;GASG;AACH,6CANW,KAAK,gBACL,MAAM,iCAGL,OAAO,CAIlB;AAED;;;;;;;;;GASG;AACH,mCANW,KAAK,gBACL,MAAM,iCAGL,OAAO,CAIlB;AAED;;;;;;GAMG;AACH,0CAJW,KAAK,GAEJ,KAAK,CAIhB"}
|
|
1
|
+
{"version":3,"file":"selectors.d.ts","sourceRoot":"","sources":["../../../src/redux-store/metadata/selectors.js"],"names":[],"mappings":"AAKA,iEAAiE;AAEjE;;;;;;;;;;;GAWG;AACH,sCANW,KAAK,gBACL,MAAM,QACN,OAAO,EAAE,GAER,OAAO,GAAG,SAAS,CAS9B;AAED;;;;;;;;;GASG;AACH,4CANW,KAAK,gBACL,MAAM,iCAGL,OAAO,CAIlB;AAED;;;;;;;;;GASG;AACH,6CANW,KAAK,gBACL,MAAM,iCAGL,OAAO,CAIlB;AAED;;;;;;;;;GASG;AACH,mCANW,KAAK,gBACL,MAAM,iCAGL,OAAO,CAIlB;AAED;;;;;;GAMG;AACH,0CAJW,KAAK,GAEJ,KAAK,CAIhB;oBA1Ea,OAAO,MAAM,EAAE,OAAO,WAAW,EAAE,KAAK,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"emitter.d.ts","sourceRoot":"","sources":["../../src/utils/emitter.js"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,iCAFY,OAAO,UAAU,EAAE,aAAa,CA0C3C"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wordpress/data",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.1.2",
|
|
4
4
|
"description": "Data module for WordPress.",
|
|
5
5
|
"author": "The WordPress Contributors",
|
|
6
6
|
"license": "GPL-2.0-or-later",
|
|
@@ -28,12 +28,12 @@
|
|
|
28
28
|
"sideEffects": false,
|
|
29
29
|
"dependencies": {
|
|
30
30
|
"@babel/runtime": "^7.13.10",
|
|
31
|
-
"@wordpress/compose": "^5.0.
|
|
32
|
-
"@wordpress/deprecated": "^3.2.2
|
|
33
|
-
"@wordpress/element": "^4.0.
|
|
34
|
-
"@wordpress/is-shallow-equal": "^4.2.
|
|
35
|
-
"@wordpress/priority-queue": "^2.2.2
|
|
36
|
-
"@wordpress/redux-routine": "^4.2.
|
|
31
|
+
"@wordpress/compose": "^5.0.4",
|
|
32
|
+
"@wordpress/deprecated": "^3.2.2",
|
|
33
|
+
"@wordpress/element": "^4.0.3",
|
|
34
|
+
"@wordpress/is-shallow-equal": "^4.2.0",
|
|
35
|
+
"@wordpress/priority-queue": "^2.2.2",
|
|
36
|
+
"@wordpress/redux-routine": "^4.2.1",
|
|
37
37
|
"equivalent-key-map": "^0.2.2",
|
|
38
38
|
"is-promise": "^4.0.0",
|
|
39
39
|
"lodash": "^4.17.21",
|
|
@@ -47,5 +47,5 @@
|
|
|
47
47
|
"publishConfig": {
|
|
48
48
|
"access": "public"
|
|
49
49
|
},
|
|
50
|
-
"gitHead": "
|
|
50
|
+
"gitHead": "157f4ae53ab98e574af01f72213ae5a9613159ff"
|
|
51
51
|
}
|
|
@@ -33,7 +33,7 @@ import { Context } from './context';
|
|
|
33
33
|
* const registry = createRegistry( {} );
|
|
34
34
|
*
|
|
35
35
|
* const SomeChildUsingRegistry = ( props ) => {
|
|
36
|
-
* const registry = useRegistry(
|
|
36
|
+
* const registry = useRegistry();
|
|
37
37
|
* // ...logic implementing the registry in other react hooks.
|
|
38
38
|
* };
|
|
39
39
|
*
|
package/src/controls.js
CHANGED
|
@@ -1,8 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* External dependencies
|
|
3
|
+
*/
|
|
4
|
+
import { isObject } from 'lodash';
|
|
5
|
+
|
|
1
6
|
/**
|
|
2
7
|
* Internal dependencies
|
|
3
8
|
*/
|
|
4
9
|
import { createRegistryControl } from './factory';
|
|
5
10
|
|
|
11
|
+
/** @typedef {import('./types').WPDataStore} WPDataStore */
|
|
12
|
+
|
|
6
13
|
const SELECT = '@@data/SELECT';
|
|
7
14
|
const RESOLVE_SELECT = '@@data/RESOLVE_SELECT';
|
|
8
15
|
const DISPATCH = '@@data/DISPATCH';
|
|
@@ -13,9 +20,9 @@ const DISPATCH = '@@data/DISPATCH';
|
|
|
13
20
|
* Note: This control synchronously returns the current selector value, triggering the
|
|
14
21
|
* resolution, but not waiting for it.
|
|
15
22
|
*
|
|
16
|
-
* @param {string}
|
|
17
|
-
* @param {string}
|
|
18
|
-
* @param {Array}
|
|
23
|
+
* @param {string|WPDataStore} storeNameOrDefinition Unique namespace identifier for the store
|
|
24
|
+
* @param {string} selectorName The name of the selector.
|
|
25
|
+
* @param {Array} args Arguments for the selector.
|
|
19
26
|
*
|
|
20
27
|
* @example
|
|
21
28
|
* ```js
|
|
@@ -30,8 +37,15 @@ const DISPATCH = '@@data/DISPATCH';
|
|
|
30
37
|
*
|
|
31
38
|
* @return {Object} The control descriptor.
|
|
32
39
|
*/
|
|
33
|
-
function select(
|
|
34
|
-
return {
|
|
40
|
+
function select( storeNameOrDefinition, selectorName, ...args ) {
|
|
41
|
+
return {
|
|
42
|
+
type: SELECT,
|
|
43
|
+
storeKey: isObject( storeNameOrDefinition )
|
|
44
|
+
? storeNameOrDefinition.name
|
|
45
|
+
: storeNameOrDefinition,
|
|
46
|
+
selectorName,
|
|
47
|
+
args,
|
|
48
|
+
};
|
|
35
49
|
}
|
|
36
50
|
|
|
37
51
|
/**
|
|
@@ -41,9 +55,9 @@ function select( storeKey, selectorName, ...args ) {
|
|
|
41
55
|
* selectors that may have a resolver. In such case, it will return a `Promise` that resolves
|
|
42
56
|
* after the selector finishes resolving, with the final result value.
|
|
43
57
|
*
|
|
44
|
-
* @param {string}
|
|
45
|
-
* @param {string}
|
|
46
|
-
* @param {Array}
|
|
58
|
+
* @param {string|WPDataStore} storeNameOrDefinition Unique namespace identifier for the store
|
|
59
|
+
* @param {string} selectorName The name of the selector
|
|
60
|
+
* @param {Array} args Arguments for the selector.
|
|
47
61
|
*
|
|
48
62
|
* @example
|
|
49
63
|
* ```js
|
|
@@ -58,16 +72,23 @@ function select( storeKey, selectorName, ...args ) {
|
|
|
58
72
|
*
|
|
59
73
|
* @return {Object} The control descriptor.
|
|
60
74
|
*/
|
|
61
|
-
function resolveSelect(
|
|
62
|
-
return {
|
|
75
|
+
function resolveSelect( storeNameOrDefinition, selectorName, ...args ) {
|
|
76
|
+
return {
|
|
77
|
+
type: RESOLVE_SELECT,
|
|
78
|
+
storeKey: isObject( storeNameOrDefinition )
|
|
79
|
+
? storeNameOrDefinition.name
|
|
80
|
+
: storeNameOrDefinition,
|
|
81
|
+
selectorName,
|
|
82
|
+
args,
|
|
83
|
+
};
|
|
63
84
|
}
|
|
64
85
|
|
|
65
86
|
/**
|
|
66
87
|
* Dispatches a control action for triggering a registry dispatch.
|
|
67
88
|
*
|
|
68
|
-
* @param {string}
|
|
69
|
-
* @param {string}
|
|
70
|
-
* @param {Array}
|
|
89
|
+
* @param {string|WPDataStore} storeNameOrDefinition Unique namespace identifier for the store
|
|
90
|
+
* @param {string} actionName The name of the action to dispatch
|
|
91
|
+
* @param {Array} args Arguments for the dispatch action.
|
|
71
92
|
*
|
|
72
93
|
* @example
|
|
73
94
|
* ```js
|
|
@@ -82,8 +103,15 @@ function resolveSelect( storeKey, selectorName, ...args ) {
|
|
|
82
103
|
*
|
|
83
104
|
* @return {Object} The control descriptor.
|
|
84
105
|
*/
|
|
85
|
-
function dispatch(
|
|
86
|
-
return {
|
|
106
|
+
function dispatch( storeNameOrDefinition, actionName, ...args ) {
|
|
107
|
+
return {
|
|
108
|
+
type: DISPATCH,
|
|
109
|
+
storeKey: isObject( storeNameOrDefinition )
|
|
110
|
+
? storeNameOrDefinition.name
|
|
111
|
+
: storeNameOrDefinition,
|
|
112
|
+
actionName,
|
|
113
|
+
args,
|
|
114
|
+
};
|
|
87
115
|
}
|
|
88
116
|
|
|
89
117
|
export const controls = { select, resolveSelect, dispatch };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* External dependencies
|
|
3
3
|
*/
|
|
4
|
-
import { merge, isPlainObject
|
|
4
|
+
import { merge, isPlainObject } from 'lodash';
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Internal dependencies
|
|
@@ -223,75 +223,69 @@ function persistencePlugin( registry, pluginOptions ) {
|
|
|
223
223
|
}
|
|
224
224
|
|
|
225
225
|
/**
|
|
226
|
-
*
|
|
227
|
-
*
|
|
226
|
+
* Move the 'features' object in local storage from the sourceStoreName to the
|
|
227
|
+
* interface store.
|
|
228
|
+
*
|
|
229
|
+
* @param {Object} persistence The persistence interface.
|
|
230
|
+
* @param {string} sourceStoreName The name of the store that has persisted
|
|
231
|
+
* preferences to migrate to the interface
|
|
232
|
+
* package.
|
|
228
233
|
*/
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
234
|
+
export function migrateFeaturePreferencesToInterfaceStore(
|
|
235
|
+
persistence,
|
|
236
|
+
sourceStoreName
|
|
237
|
+
) {
|
|
238
|
+
const interfaceStoreName = 'core/interface';
|
|
233
239
|
const state = persistence.get();
|
|
240
|
+
const sourcePreferences = state[ sourceStoreName ]?.preferences;
|
|
241
|
+
const sourceFeatures = sourcePreferences?.features;
|
|
242
|
+
|
|
243
|
+
if ( sourceFeatures ) {
|
|
244
|
+
const targetFeatures =
|
|
245
|
+
state[ interfaceStoreName ]?.preferences?.features;
|
|
246
|
+
|
|
247
|
+
// Avoid migrating features again if they've previously been migrated.
|
|
248
|
+
if ( ! targetFeatures?.[ sourceStoreName ] ) {
|
|
249
|
+
// Set the feature values in the interface store, the features
|
|
250
|
+
// object is keyed by 'scope', which matches the store name for
|
|
251
|
+
// the source.
|
|
252
|
+
persistence.set( interfaceStoreName, {
|
|
253
|
+
preferences: {
|
|
254
|
+
features: {
|
|
255
|
+
...targetFeatures,
|
|
256
|
+
[ sourceStoreName ]: sourceFeatures,
|
|
257
|
+
},
|
|
258
|
+
},
|
|
259
|
+
} );
|
|
234
260
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
persistence.set( 'core/block-editor', {
|
|
241
|
-
preferences: {
|
|
242
|
-
insertUsage: {
|
|
243
|
-
...editorInsertUsage,
|
|
244
|
-
...blockEditorInsertUsage,
|
|
261
|
+
// Remove feature preferences from the source.
|
|
262
|
+
persistence.set( sourceStoreName, {
|
|
263
|
+
preferences: {
|
|
264
|
+
...sourcePreferences,
|
|
265
|
+
features: undefined,
|
|
245
266
|
},
|
|
246
|
-
}
|
|
247
|
-
}
|
|
267
|
+
} );
|
|
268
|
+
}
|
|
248
269
|
}
|
|
270
|
+
}
|
|
249
271
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
// is needed since `fullscreenMode` previously did not have a default value
|
|
255
|
-
// and was implicitly false by its absence. It is now `true` by default, but
|
|
256
|
-
// this change is not intended to affect upgrades from earlier versions.
|
|
257
|
-
const hadPersistedState = Object.keys( state ).length > 0;
|
|
258
|
-
const hadFullscreenModePreference = has( state, [
|
|
259
|
-
'core/edit-post',
|
|
260
|
-
'preferences',
|
|
261
|
-
'features',
|
|
262
|
-
'fullscreenMode',
|
|
263
|
-
] );
|
|
264
|
-
if ( hadPersistedState && ! hadFullscreenModePreference ) {
|
|
265
|
-
editPostState = merge( {}, editPostState, {
|
|
266
|
-
preferences: { features: { fullscreenMode: false } },
|
|
267
|
-
} );
|
|
268
|
-
}
|
|
272
|
+
/**
|
|
273
|
+
* Deprecated: Remove this function and the code in WordPress Core that calls
|
|
274
|
+
* it once WordPress 6.0 is released.
|
|
275
|
+
*/
|
|
269
276
|
|
|
270
|
-
|
|
271
|
-
const
|
|
272
|
-
'core/nux',
|
|
273
|
-
'preferences',
|
|
274
|
-
'areTipsEnabled',
|
|
275
|
-
] );
|
|
276
|
-
const hasWelcomeGuide = has( state, [
|
|
277
|
-
'core/edit-post',
|
|
278
|
-
'preferences',
|
|
279
|
-
'features',
|
|
280
|
-
'welcomeGuide',
|
|
281
|
-
] );
|
|
282
|
-
if ( areTipsEnabled !== undefined && ! hasWelcomeGuide ) {
|
|
283
|
-
editPostState = merge( {}, editPostState, {
|
|
284
|
-
preferences: {
|
|
285
|
-
features: {
|
|
286
|
-
welcomeGuide: areTipsEnabled,
|
|
287
|
-
},
|
|
288
|
-
},
|
|
289
|
-
} );
|
|
290
|
-
}
|
|
277
|
+
persistencePlugin.__unstableMigrate = ( pluginOptions ) => {
|
|
278
|
+
const persistence = createPersistenceInterface( pluginOptions );
|
|
291
279
|
|
|
292
|
-
|
|
293
|
-
persistence
|
|
294
|
-
|
|
280
|
+
migrateFeaturePreferencesToInterfaceStore(
|
|
281
|
+
persistence,
|
|
282
|
+
'core/edit-widgets'
|
|
283
|
+
);
|
|
284
|
+
migrateFeaturePreferencesToInterfaceStore(
|
|
285
|
+
persistence,
|
|
286
|
+
'core/customize-widgets'
|
|
287
|
+
);
|
|
288
|
+
migrateFeaturePreferencesToInterfaceStore( persistence, 'core/edit-post' );
|
|
295
289
|
};
|
|
296
290
|
|
|
297
291
|
export default persistencePlugin;
|
|
@@ -6,7 +6,11 @@ import deepFreeze from 'deep-freeze';
|
|
|
6
6
|
/**
|
|
7
7
|
* Internal dependencies
|
|
8
8
|
*/
|
|
9
|
-
import plugin, {
|
|
9
|
+
import plugin, {
|
|
10
|
+
createPersistenceInterface,
|
|
11
|
+
withLazySameState,
|
|
12
|
+
migrateFeaturePreferencesToInterfaceStore,
|
|
13
|
+
} from '../';
|
|
10
14
|
import objectStorage from '../storage/object';
|
|
11
15
|
import { createRegistry } from '../../../';
|
|
12
16
|
|
|
@@ -377,3 +381,115 @@ describe( 'persistence', () => {
|
|
|
377
381
|
} );
|
|
378
382
|
} );
|
|
379
383
|
} );
|
|
384
|
+
|
|
385
|
+
describe( 'migrateFeaturePreferencesToInterfaceStore', () => {
|
|
386
|
+
it( 'migrates preferences from the source to the interface store', () => {
|
|
387
|
+
const persistenceInterface = createPersistenceInterface( {
|
|
388
|
+
storageKey: 'test-username',
|
|
389
|
+
} );
|
|
390
|
+
|
|
391
|
+
const initialState = {
|
|
392
|
+
preferences: {
|
|
393
|
+
features: {
|
|
394
|
+
featureA: true,
|
|
395
|
+
featureB: false,
|
|
396
|
+
featureC: true,
|
|
397
|
+
},
|
|
398
|
+
},
|
|
399
|
+
};
|
|
400
|
+
|
|
401
|
+
persistenceInterface.set( 'core/test', initialState );
|
|
402
|
+
|
|
403
|
+
migrateFeaturePreferencesToInterfaceStore(
|
|
404
|
+
persistenceInterface,
|
|
405
|
+
'core/test'
|
|
406
|
+
);
|
|
407
|
+
|
|
408
|
+
expect( persistenceInterface.get() ).toEqual( {
|
|
409
|
+
'core/interface': {
|
|
410
|
+
preferences: {
|
|
411
|
+
features: {
|
|
412
|
+
'core/test': {
|
|
413
|
+
featureA: true,
|
|
414
|
+
featureB: false,
|
|
415
|
+
featureC: true,
|
|
416
|
+
},
|
|
417
|
+
},
|
|
418
|
+
},
|
|
419
|
+
},
|
|
420
|
+
'core/test': {
|
|
421
|
+
preferences: {
|
|
422
|
+
features: undefined,
|
|
423
|
+
},
|
|
424
|
+
},
|
|
425
|
+
} );
|
|
426
|
+
} );
|
|
427
|
+
|
|
428
|
+
it( 'handles multiple preferences from different stores to be migrated', () => {
|
|
429
|
+
const persistenceInterface = createPersistenceInterface( {
|
|
430
|
+
storageKey: 'test-username',
|
|
431
|
+
} );
|
|
432
|
+
|
|
433
|
+
const initialStateA = {
|
|
434
|
+
preferences: {
|
|
435
|
+
features: {
|
|
436
|
+
featureA: true,
|
|
437
|
+
featureB: false,
|
|
438
|
+
featureC: true,
|
|
439
|
+
},
|
|
440
|
+
},
|
|
441
|
+
};
|
|
442
|
+
|
|
443
|
+
const initialStateB = {
|
|
444
|
+
preferences: {
|
|
445
|
+
features: {
|
|
446
|
+
featureD: true,
|
|
447
|
+
featureE: false,
|
|
448
|
+
featureF: true,
|
|
449
|
+
},
|
|
450
|
+
},
|
|
451
|
+
};
|
|
452
|
+
|
|
453
|
+
persistenceInterface.set( 'core/test-a', initialStateA );
|
|
454
|
+
persistenceInterface.set( 'core/test-b', initialStateB );
|
|
455
|
+
|
|
456
|
+
migrateFeaturePreferencesToInterfaceStore(
|
|
457
|
+
persistenceInterface,
|
|
458
|
+
'core/test-a'
|
|
459
|
+
);
|
|
460
|
+
|
|
461
|
+
migrateFeaturePreferencesToInterfaceStore(
|
|
462
|
+
persistenceInterface,
|
|
463
|
+
'core/test-b'
|
|
464
|
+
);
|
|
465
|
+
|
|
466
|
+
expect( persistenceInterface.get() ).toEqual( {
|
|
467
|
+
'core/interface': {
|
|
468
|
+
preferences: {
|
|
469
|
+
features: {
|
|
470
|
+
'core/test-a': {
|
|
471
|
+
featureA: true,
|
|
472
|
+
featureB: false,
|
|
473
|
+
featureC: true,
|
|
474
|
+
},
|
|
475
|
+
'core/test-b': {
|
|
476
|
+
featureD: true,
|
|
477
|
+
featureE: false,
|
|
478
|
+
featureF: true,
|
|
479
|
+
},
|
|
480
|
+
},
|
|
481
|
+
},
|
|
482
|
+
},
|
|
483
|
+
'core/test-a': {
|
|
484
|
+
preferences: {
|
|
485
|
+
features: undefined,
|
|
486
|
+
},
|
|
487
|
+
},
|
|
488
|
+
'core/test-b': {
|
|
489
|
+
preferences: {
|
|
490
|
+
features: undefined,
|
|
491
|
+
},
|
|
492
|
+
},
|
|
493
|
+
} );
|
|
494
|
+
} );
|
|
495
|
+
} );
|
package/src/registry.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* External dependencies
|
|
3
3
|
*/
|
|
4
|
-
import {
|
|
4
|
+
import { mapValues, isObject, forEach } from 'lodash';
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Internal dependencies
|
|
@@ -9,6 +9,7 @@ import { without, mapValues, isObject } from 'lodash';
|
|
|
9
9
|
import createReduxStore from './redux-store';
|
|
10
10
|
import createCoreDataStore from './store';
|
|
11
11
|
import { STORE_NAME } from './store/name';
|
|
12
|
+
import { createEmitter } from './utils/emitter';
|
|
12
13
|
|
|
13
14
|
/** @typedef {import('./types').WPDataStore} WPDataStore */
|
|
14
15
|
|
|
@@ -49,14 +50,14 @@ import { STORE_NAME } from './store/name';
|
|
|
49
50
|
*/
|
|
50
51
|
export function createRegistry( storeConfigs = {}, parent = null ) {
|
|
51
52
|
const stores = {};
|
|
52
|
-
|
|
53
|
+
const emitter = createEmitter();
|
|
53
54
|
const __experimentalListeningStores = new Set();
|
|
54
55
|
|
|
55
56
|
/**
|
|
56
57
|
* Global listener called for each store's update.
|
|
57
58
|
*/
|
|
58
59
|
function globalListener() {
|
|
59
|
-
|
|
60
|
+
emitter.emit();
|
|
60
61
|
}
|
|
61
62
|
|
|
62
63
|
/**
|
|
@@ -67,11 +68,7 @@ export function createRegistry( storeConfigs = {}, parent = null ) {
|
|
|
67
68
|
* @return {Function} Unsubscribe function.
|
|
68
69
|
*/
|
|
69
70
|
const subscribe = ( listener ) => {
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
return () => {
|
|
73
|
-
listeners = without( listeners, listener );
|
|
74
|
-
};
|
|
71
|
+
return emitter.subscribe( listener );
|
|
75
72
|
};
|
|
76
73
|
|
|
77
74
|
/**
|
|
@@ -177,6 +174,30 @@ export function createRegistry( storeConfigs = {}, parent = null ) {
|
|
|
177
174
|
if ( typeof config.subscribe !== 'function' ) {
|
|
178
175
|
throw new TypeError( 'config.subscribe must be a function' );
|
|
179
176
|
}
|
|
177
|
+
// Thi emitter is used to keep track of active listeners when the registry
|
|
178
|
+
// get paused, that way, when resumed we should be able to call all these
|
|
179
|
+
// pending listeners.
|
|
180
|
+
config.emitter = createEmitter();
|
|
181
|
+
const currentSubscribe = config.subscribe;
|
|
182
|
+
config.subscribe = ( listener ) => {
|
|
183
|
+
const unsubscribeFromStoreEmitter = config.emitter.subscribe(
|
|
184
|
+
listener
|
|
185
|
+
);
|
|
186
|
+
const unsubscribeFromRootStore = currentSubscribe( () => {
|
|
187
|
+
if ( config.emitter.isPaused ) {
|
|
188
|
+
config.emitter.emit();
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
listener();
|
|
192
|
+
} );
|
|
193
|
+
|
|
194
|
+
return () => {
|
|
195
|
+
if ( unsubscribeFromRootStore ) {
|
|
196
|
+
unsubscribeFromRootStore();
|
|
197
|
+
}
|
|
198
|
+
unsubscribeFromStoreEmitter();
|
|
199
|
+
};
|
|
200
|
+
};
|
|
180
201
|
stores[ key ] = config;
|
|
181
202
|
config.subscribe( globalListener );
|
|
182
203
|
}
|
|
@@ -213,7 +234,16 @@ export function createRegistry( storeConfigs = {}, parent = null ) {
|
|
|
213
234
|
return parent.__experimentalSubscribeStore( storeName, handler );
|
|
214
235
|
}
|
|
215
236
|
|
|
237
|
+
function batch( callback ) {
|
|
238
|
+
emitter.pause();
|
|
239
|
+
forEach( stores, ( store ) => store.emitter.pause() );
|
|
240
|
+
callback();
|
|
241
|
+
emitter.resume();
|
|
242
|
+
forEach( stores, ( store ) => store.emitter.resume() );
|
|
243
|
+
}
|
|
244
|
+
|
|
216
245
|
let registry = {
|
|
246
|
+
batch,
|
|
217
247
|
registerGenericStore,
|
|
218
248
|
stores,
|
|
219
249
|
namespaces: stores, // TODO: Deprecate/remove this.
|
package/src/test/registry.js
CHANGED
|
@@ -722,6 +722,40 @@ describe( 'createRegistry', () => {
|
|
|
722
722
|
} );
|
|
723
723
|
} );
|
|
724
724
|
|
|
725
|
+
describe( 'batch', () => {
|
|
726
|
+
it( 'should batch callbacks and only run the subscriber once', () => {
|
|
727
|
+
const store = registry.registerStore( 'myAwesomeReducer', {
|
|
728
|
+
reducer: ( state = 0 ) => state + 1,
|
|
729
|
+
} );
|
|
730
|
+
const listener = jest.fn();
|
|
731
|
+
subscribeWithUnsubscribe( listener );
|
|
732
|
+
|
|
733
|
+
registry.batch( () => {} );
|
|
734
|
+
expect( listener ).not.toHaveBeenCalled();
|
|
735
|
+
|
|
736
|
+
registry.batch( () => {
|
|
737
|
+
store.dispatch( { type: 'dummy' } );
|
|
738
|
+
store.dispatch( { type: 'dummy' } );
|
|
739
|
+
} );
|
|
740
|
+
expect( listener ).toHaveBeenCalledTimes( 1 );
|
|
741
|
+
|
|
742
|
+
const listener2 = jest.fn();
|
|
743
|
+
// useSelect subscribes to the stores differently,
|
|
744
|
+
// This test ensures batching works in this case as well.
|
|
745
|
+
const unsubscribe = registry.__experimentalSubscribeStore(
|
|
746
|
+
'myAwesomeReducer',
|
|
747
|
+
listener2
|
|
748
|
+
);
|
|
749
|
+
registry.batch( () => {
|
|
750
|
+
store.dispatch( { type: 'dummy' } );
|
|
751
|
+
store.dispatch( { type: 'dummy' } );
|
|
752
|
+
} );
|
|
753
|
+
unsubscribe();
|
|
754
|
+
|
|
755
|
+
expect( listener2 ).toHaveBeenCalledTimes( 1 );
|
|
756
|
+
} );
|
|
757
|
+
} );
|
|
758
|
+
|
|
725
759
|
describe( 'use', () => {
|
|
726
760
|
it( 'should pass through options object to plugin', () => {
|
|
727
761
|
const expectedOptions = {};
|
package/src/types.d.ts
CHANGED
|
@@ -30,3 +30,11 @@ export interface WPDataReduxStoreConfig {
|
|
|
30
30
|
export interface WPDataRegistry {
|
|
31
31
|
register: ( store: WPDataStore ) => void;
|
|
32
32
|
}
|
|
33
|
+
|
|
34
|
+
export interface WPDataEmitter {
|
|
35
|
+
emit: () => void;
|
|
36
|
+
subscribe: ( listener: () => void ) => () => void;
|
|
37
|
+
pause: () => void;
|
|
38
|
+
resume: () => void;
|
|
39
|
+
isPaused: boolean;
|
|
40
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Create an event emitter.
|
|
3
|
+
*
|
|
4
|
+
* @return {import("../types").WPDataEmitter} Emitter.
|
|
5
|
+
*/
|
|
6
|
+
export function createEmitter() {
|
|
7
|
+
let isPaused = false;
|
|
8
|
+
let isPending = false;
|
|
9
|
+
const listeners = new Set();
|
|
10
|
+
const notifyListeners = () =>
|
|
11
|
+
// We use Array.from to clone the listeners Set
|
|
12
|
+
// This ensures that we don't run a listener
|
|
13
|
+
// that was added as a response to another listener.
|
|
14
|
+
Array.from( listeners ).forEach( ( listener ) => listener() );
|
|
15
|
+
|
|
16
|
+
return {
|
|
17
|
+
get isPaused() {
|
|
18
|
+
return isPaused;
|
|
19
|
+
},
|
|
20
|
+
|
|
21
|
+
subscribe( listener ) {
|
|
22
|
+
listeners.add( listener );
|
|
23
|
+
return () => listeners.delete( listener );
|
|
24
|
+
},
|
|
25
|
+
|
|
26
|
+
pause() {
|
|
27
|
+
isPaused = true;
|
|
28
|
+
},
|
|
29
|
+
|
|
30
|
+
resume() {
|
|
31
|
+
isPaused = false;
|
|
32
|
+
if ( isPending ) {
|
|
33
|
+
isPending = false;
|
|
34
|
+
notifyListeners();
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
|
|
38
|
+
emit() {
|
|
39
|
+
if ( isPaused ) {
|
|
40
|
+
isPending = true;
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
notifyListeners();
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
}
|