159 lines
4.4 KiB
JavaScript
159 lines
4.4 KiB
JavaScript
|
/**
|
||
|
* External dependencies
|
||
|
*/
|
||
|
import { sprintf } from '@wordpress/i18n';
|
||
|
import { createRegistrySelector } from '@wordpress/data';
|
||
|
|
||
|
/**
|
||
|
* Internal dependencies
|
||
|
*/
|
||
|
import { STORE_KEY } from './constants';
|
||
|
|
||
|
/**
|
||
|
* Returns the requested route for the given arguments.
|
||
|
*
|
||
|
* @param {Object} state The original state.
|
||
|
* @param {string} namespace The namespace for the route.
|
||
|
* @param {string} resourceName The resource being requested
|
||
|
* (eg. products/attributes)
|
||
|
* @param {Array} [ids] This is for any ids that might be implemented in
|
||
|
* the route request. It is not for any query
|
||
|
* parameters.
|
||
|
*
|
||
|
* Ids example:
|
||
|
* If you are looking for the route for a single product on the `wc/blocks`
|
||
|
* namespace, then you'd have `[ 20 ]` as the ids. This would produce something
|
||
|
* like `/wc/blocks/products/20`
|
||
|
*
|
||
|
*
|
||
|
* @throws {Error} If there is no route for the given arguments, then this will
|
||
|
* throw
|
||
|
*
|
||
|
* @return {string} The route if it is available.
|
||
|
*/
|
||
|
export const getRoute = createRegistrySelector(
|
||
|
( select ) => ( state, namespace, resourceName, ids = [] ) => {
|
||
|
const hasResolved = select(
|
||
|
STORE_KEY
|
||
|
).hasFinishedResolution( 'getRoutes', [ namespace ] );
|
||
|
state = state.routes;
|
||
|
let error = '';
|
||
|
if ( ! state[ namespace ] ) {
|
||
|
error = sprintf(
|
||
|
'There is no route for the given namespace (%s) in the store',
|
||
|
namespace
|
||
|
);
|
||
|
} else if ( ! state[ namespace ][ resourceName ] ) {
|
||
|
error = sprintf(
|
||
|
'There is no route for the given resource name (%s) in the store',
|
||
|
resourceName
|
||
|
);
|
||
|
}
|
||
|
if ( error !== '' ) {
|
||
|
if ( hasResolved ) {
|
||
|
throw new Error( error );
|
||
|
}
|
||
|
return '';
|
||
|
}
|
||
|
const route = getRouteFromResourceEntries(
|
||
|
state[ namespace ][ resourceName ],
|
||
|
ids
|
||
|
);
|
||
|
if ( route === '' ) {
|
||
|
if ( hasResolved ) {
|
||
|
throw new Error(
|
||
|
sprintf(
|
||
|
'While there is a route for the given namespace (%1$s) and resource name (%2$s), there is no route utilizing the number of ids you included in the select arguments. The available routes are: (%3$s)',
|
||
|
namespace,
|
||
|
resourceName,
|
||
|
JSON.stringify( state[ namespace ][ resourceName ] )
|
||
|
)
|
||
|
);
|
||
|
}
|
||
|
}
|
||
|
return route;
|
||
|
}
|
||
|
);
|
||
|
|
||
|
/**
|
||
|
* Return all the routes for a given namespace.
|
||
|
*
|
||
|
* @param {Object} state The current state.
|
||
|
* @param {string} namespace The namespace to return routes for.
|
||
|
*
|
||
|
* @return {Array} An array of all routes for the given namespace.
|
||
|
*/
|
||
|
export const getRoutes = createRegistrySelector(
|
||
|
( select ) => ( state, namespace ) => {
|
||
|
const hasResolved = select(
|
||
|
STORE_KEY
|
||
|
).hasFinishedResolution( 'getRoutes', [ namespace ] );
|
||
|
const routes = state.routes[ namespace ];
|
||
|
if ( ! routes ) {
|
||
|
if ( hasResolved ) {
|
||
|
throw new Error(
|
||
|
sprintf(
|
||
|
'There is no route for the given namespace (%s) in the store',
|
||
|
namespace
|
||
|
)
|
||
|
);
|
||
|
}
|
||
|
return [];
|
||
|
}
|
||
|
let namespaceRoutes = [];
|
||
|
for ( const resourceName in routes ) {
|
||
|
namespaceRoutes = [
|
||
|
...namespaceRoutes,
|
||
|
...Object.keys( routes[ resourceName ] ),
|
||
|
];
|
||
|
}
|
||
|
return namespaceRoutes;
|
||
|
}
|
||
|
);
|
||
|
|
||
|
/**
|
||
|
* Returns the route from the given slice of the route state.
|
||
|
*
|
||
|
* @param {Object} stateSlice This will be a slice of the route state from a
|
||
|
* given namespace and resource name.
|
||
|
* @param {Array} [ids=[]] Any id references that are to be replaced in
|
||
|
* route placeholders.
|
||
|
*
|
||
|
* @return {string} The route or an empty string if nothing found.
|
||
|
*/
|
||
|
const getRouteFromResourceEntries = ( stateSlice, ids = [] ) => {
|
||
|
// convert to array for easier discovery
|
||
|
stateSlice = Object.entries( stateSlice );
|
||
|
const match = stateSlice.find( ( [ , idNames ] ) => {
|
||
|
return ids.length === idNames.length;
|
||
|
} );
|
||
|
const [ matchingRoute, routePlaceholders ] = match || [];
|
||
|
// if we have a matching route, let's return it.
|
||
|
if ( matchingRoute ) {
|
||
|
return ids.length === 0
|
||
|
? matchingRoute
|
||
|
: assembleRouteWithPlaceholders(
|
||
|
matchingRoute,
|
||
|
routePlaceholders,
|
||
|
ids
|
||
|
);
|
||
|
}
|
||
|
return '';
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* For a given route, route parts and ids,
|
||
|
*
|
||
|
* @param {string} route
|
||
|
* @param {Array} routePlaceholders
|
||
|
* @param {Array} ids
|
||
|
*
|
||
|
* @return {string} Assembled route.
|
||
|
*/
|
||
|
const assembleRouteWithPlaceholders = ( route, routePlaceholders, ids ) => {
|
||
|
routePlaceholders.forEach( ( part, index ) => {
|
||
|
route = route.replace( `{${ part }}`, ids[ index ] );
|
||
|
} );
|
||
|
return route;
|
||
|
};
|