mirror of
https://github.com/strapi/strapi.git
synced 2025-09-02 13:23:12 +00:00
add redux-thunk to admin
Signed-off-by: HichamELBSI <elabbassih@gmail.com>
This commit is contained in:
parent
58a7665c63
commit
95ed2ee412
52
examples/getstarted/api/newcollection/config/routes.json
Normal file
52
examples/getstarted/api/newcollection/config/routes.json
Normal file
@ -0,0 +1,52 @@
|
||||
{
|
||||
"routes": [
|
||||
{
|
||||
"method": "GET",
|
||||
"path": "/newcollections",
|
||||
"handler": "newcollection.find",
|
||||
"config": {
|
||||
"policies": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"path": "/newcollections/count",
|
||||
"handler": "newcollection.count",
|
||||
"config": {
|
||||
"policies": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"path": "/newcollections/:id",
|
||||
"handler": "newcollection.findOne",
|
||||
"config": {
|
||||
"policies": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"method": "POST",
|
||||
"path": "/newcollections",
|
||||
"handler": "newcollection.create",
|
||||
"config": {
|
||||
"policies": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"method": "PUT",
|
||||
"path": "/newcollections/:id",
|
||||
"handler": "newcollection.update",
|
||||
"config": {
|
||||
"policies": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"method": "DELETE",
|
||||
"path": "/newcollections/:id",
|
||||
"handler": "newcollection.delete",
|
||||
"config": {
|
||||
"policies": []
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Read the documentation (https://strapi.io/documentation/developer-docs/latest/concepts/controllers.html#core-controllers)
|
||||
* to customize this controller
|
||||
*/
|
||||
|
||||
module.exports = {};
|
@ -0,0 +1,8 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Read the documentation (https://strapi.io/documentation/developer-docs/latest/concepts/models.html#lifecycle-hooks)
|
||||
* to customize this model
|
||||
*/
|
||||
|
||||
module.exports = {};
|
@ -0,0 +1,24 @@
|
||||
{
|
||||
"kind": "collectionType",
|
||||
"collectionName": "newcollections",
|
||||
"info": {
|
||||
"name": "newcollection"
|
||||
},
|
||||
"options": {
|
||||
"increments": true,
|
||||
"timestamps": true,
|
||||
"draftAndPublish": false
|
||||
},
|
||||
"attributes": {
|
||||
"title": {
|
||||
"default": "dazdazdzad",
|
||||
"private": true,
|
||||
"regex": "dzadaz",
|
||||
"maxLength": 120,
|
||||
"unique": true,
|
||||
"minLength": 10,
|
||||
"type": "text",
|
||||
"required": true
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Read the documentation (https://strapi.io/documentation/developer-docs/latest/concepts/services.html#core-services)
|
||||
* to customize this service
|
||||
*/
|
||||
|
||||
module.exports = {};
|
@ -60,10 +60,11 @@ import plugins from './plugins';
|
||||
const strapi = Strapi();
|
||||
|
||||
const initialState = {};
|
||||
const store = configureStore(initialState, history);
|
||||
const { dispatch } = store;
|
||||
const MOUNT_NODE = document.getElementById('app') || document.createElement('div');
|
||||
|
||||
const store = configureStore(initialState, strapi);
|
||||
const { dispatch } = store;
|
||||
|
||||
Object.keys(plugins).forEach(current => {
|
||||
const registerPlugin = plugin => {
|
||||
return plugin;
|
||||
@ -78,6 +79,7 @@ Object.keys(plugins).forEach(current => {
|
||||
registerField: strapi.fieldApi.registerField,
|
||||
registerPlugin,
|
||||
settingsBaseURL: SETTINGS_BASE_URL || '/settings',
|
||||
middlewares: strapi.middlewares,
|
||||
});
|
||||
|
||||
const pluginTradsPrefixed = languages.reduce((acc, lang) => {
|
||||
@ -97,7 +99,6 @@ Object.keys(plugins).forEach(current => {
|
||||
}, {});
|
||||
|
||||
// Inject plugins reducers
|
||||
console.log(plugin.reducers);
|
||||
const pluginReducers = plugin.reducers || {};
|
||||
|
||||
Object.keys(pluginReducers).forEach(reducerName => {
|
||||
@ -106,7 +107,7 @@ Object.keys(plugins).forEach(current => {
|
||||
|
||||
try {
|
||||
merge(translationMessages, pluginTradsPrefixed);
|
||||
dispatch(pluginLoaded(plugin));
|
||||
// dispatch(pluginLoaded(plugin));
|
||||
} catch (err) {
|
||||
console.log({ err });
|
||||
}
|
||||
|
@ -6,17 +6,26 @@ import { createStore, applyMiddleware, compose } from 'redux';
|
||||
import { fromJS } from 'immutable';
|
||||
// import { routerMiddleware } from 'react-router-redux';
|
||||
import createSagaMiddleware from 'redux-saga';
|
||||
import thunkMiddleware from 'redux-thunk';
|
||||
import createReducer from './reducers';
|
||||
|
||||
const sagaMiddleware = createSagaMiddleware();
|
||||
|
||||
export default function configureStore(initialState = {}) {
|
||||
export default function configureStore(initialState = {}, strapi) {
|
||||
// Create the store with two middlewares
|
||||
// 1. sagaMiddleware: Makes redux-sagas work
|
||||
// 2. routerMiddleware: Syncs the location/URL path to the state
|
||||
const middlewares = [sagaMiddleware];
|
||||
const thunkMiddlewares = [thunkMiddleware.withExtraArgument(initialState)];
|
||||
|
||||
const enhancers = [applyMiddleware(...middlewares)];
|
||||
// Add the plugins middlewares
|
||||
console.log('loop')
|
||||
for (let i in strapi.middlewares.middlewares) {
|
||||
console.log(i);
|
||||
// thunkMiddlewares.push(app.middlewares[i]());
|
||||
}
|
||||
|
||||
const enhancers = [applyMiddleware(...middlewares, ...thunkMiddlewares)];
|
||||
|
||||
// If Redux DevTools Extension is installed use it, otherwise use Redux compose
|
||||
/* eslint-disable no-underscore-dangle */
|
||||
@ -25,19 +34,15 @@ export default function configureStore(initialState = {}) {
|
||||
typeof window === 'object' &&
|
||||
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__
|
||||
? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({
|
||||
// TODO Try to remove when `react-router-redux` is out of beta, LOCATION_CHANGE should not be fired more than once after hot reloading
|
||||
// Prevent recomputing reducers for `replaceReducer`
|
||||
shouldHotReload: false,
|
||||
name: 'Strapi - Dashboard',
|
||||
})
|
||||
// TODO Try to remove when `react-router-redux` is out of beta, LOCATION_CHANGE should not be fired more than once after hot reloading
|
||||
// Prevent recomputing reducers for `replaceReducer`
|
||||
shouldHotReload: false,
|
||||
name: 'Strapi - Dashboard',
|
||||
})
|
||||
: compose;
|
||||
/* eslint-enable */
|
||||
|
||||
const store = createStore(
|
||||
createReducer(),
|
||||
fromJS(initialState),
|
||||
composeEnhancers(...enhancers),
|
||||
);
|
||||
const store = createStore(createReducer(), fromJS(initialState), composeEnhancers(...enhancers));
|
||||
|
||||
// Extensions
|
||||
store.runSaga = sagaMiddleware.run;
|
||||
|
@ -249,6 +249,8 @@ export class Admin extends React.Component {
|
||||
updatePlugin,
|
||||
} = this.props;
|
||||
|
||||
console.log(plugins);
|
||||
|
||||
// We need the admin data in order to make the initializers work
|
||||
if (this.showLoader()) {
|
||||
return (
|
||||
|
16
packages/strapi-admin/admin/src/utils/MiddleWares.js
Normal file
16
packages/strapi-admin/admin/src/utils/MiddleWares.js
Normal file
@ -0,0 +1,16 @@
|
||||
import { cloneDeep } from 'lodash';
|
||||
|
||||
class MiddleWares {
|
||||
middlewares = [];
|
||||
|
||||
add(middleware) {
|
||||
console.log('add: ', middleware);
|
||||
this.middlewares.push(middleware);
|
||||
}
|
||||
|
||||
get middlewares() {
|
||||
return cloneDeep(this.middlewares);
|
||||
}
|
||||
}
|
||||
|
||||
export default () => new MiddleWares();
|
@ -1,9 +1,12 @@
|
||||
import ComponentApi from './ComponentApi';
|
||||
import FieldApi from './FieldApi';
|
||||
import MiddleWares from './MiddleWares';
|
||||
|
||||
class Strapi {
|
||||
componentApi = ComponentApi();
|
||||
|
||||
middlewares = MiddleWares();
|
||||
|
||||
fieldApi = FieldApi();
|
||||
}
|
||||
|
||||
|
@ -10,7 +10,6 @@ import checkStore from './checkStore';
|
||||
|
||||
export function injectReducerFactory(store, isValid) {
|
||||
return function injectReducer(key, reducer) {
|
||||
// console.log(key, reducer);
|
||||
if (!isValid) checkStore(store);
|
||||
|
||||
invariant(
|
||||
|
@ -93,6 +93,7 @@
|
||||
"redux": "^4.0.1",
|
||||
"redux-immutable": "^4.0.0",
|
||||
"redux-saga": "^0.16.0",
|
||||
"redux-thunk": "2.3.0",
|
||||
"reselect": "^4.0.0",
|
||||
"sanitize.css": "^4.1.0",
|
||||
"semver": "7.3.4",
|
||||
|
@ -0,0 +1,15 @@
|
||||
export const ADD_ATTRIBUTE = 'ContentTypeBuilder/DataManagerProvider/ADD_ATTRIBUTE';
|
||||
export const ADD_CREATED_COMPONENT_TO_DYNAMIC_ZONE = 'ContentTypeBuilder/DataManagerProvider/ADD_CREATED_COMPONENT_TO_DYNAMIC_ZONE';
|
||||
export const CANCEL_CHANGES = 'ContentTypeBuilder/DataManagerProvider/CANCEL_CHANGES';
|
||||
export const CHANGE_DYNAMIC_ZONE_COMPONENTS = 'ContentTypeBuilder/DataManagerProvider/CHANGE_DYNAMIC_ZONE_COMPONENTS';
|
||||
export const CREATE_SCHEMA = 'ContentTypeBuilder/DataManagerProvider/CREATE_SCHEMA';
|
||||
export const CREATE_COMPONENT_SCHEMA = 'ContentTypeBuilder/DataManagerProvider/CREATE_COMPONENT_SCHEMA';
|
||||
export const DELETE_NOT_SAVED_TYPE = 'ContentTypeBuilder/DataManagerProvider/DELETE_NOT_SAVED_TYPE';
|
||||
export const EDIT_ATTRIBUTE = 'ContentTypeBuilder/DataManagerProvider/EDIT_ATTRIBUTE';
|
||||
export const GET_DATA_SUCCEEDED = 'ContentTypeBuilder/DataManagerProvider/GET_DATA_SUCCEEDED';
|
||||
export const RELOAD_PLUGIN = 'ContentTypeBuilder/DataManagerProvider/RELOAD_PLUGIN';
|
||||
export const REMOVE_FIELD_FROM_DISPLAYED_COMPONENT = 'ContentTypeBuilder/DataManagerProvider/REMOVE_FIELD_FROM_DISPLAYED_COMPONENT';
|
||||
export const REMOVE_COMPONENT_FROM_DYNAMIC_ZONE = 'ContentTypeBuilder/DataManagerProvider/REMOVE_COMPONENT_FROM_DYNAMIC_ZONE';
|
||||
export const REMOVE_FIELD = 'ContentTypeBuilder/DataManagerProvider/REMOVE_FIELD';
|
||||
export const SET_MODIFIED_DATA = 'ContentTypeBuilder/DataManagerProvider/SET_MODIFIED_DATA';
|
||||
export const UPDATE_SCHEMA = 'ContentTypeBuilder/DataManagerProvider/UPDATE_SCHEMA';
|
@ -1,4 +1,4 @@
|
||||
import React, { memo, useEffect, useMemo, useReducer, useState, useRef } from 'react';
|
||||
import React, { memo, useEffect, useMemo, useState, useRef } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { get, groupBy, set, size } from 'lodash';
|
||||
import {
|
||||
@ -8,13 +8,12 @@ import {
|
||||
PopUpWarning,
|
||||
} from 'strapi-helper-plugin';
|
||||
import { useHistory, useLocation, useRouteMatch, Redirect } from 'react-router-dom';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import DataManagerContext from '../../contexts/DataManagerContext';
|
||||
import getTrad from '../../utils/getTrad';
|
||||
import makeUnique from '../../utils/makeUnique';
|
||||
import pluginId from '../../pluginId';
|
||||
import FormModal from '../FormModal';
|
||||
import init from './init';
|
||||
import reducer, { initialState } from './reducer';
|
||||
import createDataObject from './utils/createDataObject';
|
||||
import createModifiedDataSchema, {
|
||||
orderAllDataAttributesWithImmutable,
|
||||
@ -30,8 +29,29 @@ import {
|
||||
sortContentType,
|
||||
} from './utils/cleanData';
|
||||
|
||||
import {
|
||||
ADD_ATTRIBUTE,
|
||||
ADD_CREATED_COMPONENT_TO_DYNAMIC_ZONE,
|
||||
CANCEL_CHANGES,
|
||||
CHANGE_DYNAMIC_ZONE_COMPONENTS,
|
||||
CREATE_SCHEMA,
|
||||
CREATE_COMPONENT_SCHEMA,
|
||||
DELETE_NOT_SAVED_TYPE,
|
||||
EDIT_ATTRIBUTE,
|
||||
GET_DATA_SUCCEEDED,
|
||||
RELOAD_PLUGIN,
|
||||
REMOVE_FIELD_FROM_DISPLAYED_COMPONENT,
|
||||
REMOVE_COMPONENT_FROM_DYNAMIC_ZONE,
|
||||
REMOVE_FIELD,
|
||||
SET_MODIFIED_DATA,
|
||||
UPDATE_SCHEMA,
|
||||
} from './constants';
|
||||
import makeSelectDataManagerProvider from './selectors';
|
||||
|
||||
const DataManagerProvider = ({ allIcons, children }) => {
|
||||
const [reducerState, dispatch] = useReducer(reducer, initialState, init);
|
||||
const dataManagerProviderSelector = useMemo(makeSelectDataManagerProvider, []);
|
||||
const dispatch = useDispatch();
|
||||
const reducerState = useSelector(state => dataManagerProviderSelector(state), []);
|
||||
const [infoModals, toggleInfoModal] = useState({ cancel: false });
|
||||
const {
|
||||
autoReload,
|
||||
@ -49,7 +69,7 @@ const DataManagerProvider = ({ allIcons, children }) => {
|
||||
initialData,
|
||||
modifiedData,
|
||||
reservedNames,
|
||||
} = reducerState.toJS();
|
||||
} = reducerState;
|
||||
const { pathname } = useLocation();
|
||||
const { push } = useHistory();
|
||||
const contentTypeMatch = useRouteMatch(`/plugins/${pluginId}/content-types/:uid`);
|
||||
@ -96,7 +116,7 @@ const DataManagerProvider = ({ allIcons, children }) => {
|
||||
});
|
||||
|
||||
dispatch({
|
||||
type: 'GET_DATA_SUCCEEDED',
|
||||
type: GET_DATA_SUCCEEDED,
|
||||
components: orderedComponents.get('components'),
|
||||
contentTypes: orderedContenTypes.get('components'),
|
||||
reservedNames,
|
||||
@ -143,7 +163,7 @@ const DataManagerProvider = ({ allIcons, children }) => {
|
||||
initialAttribute,
|
||||
shouldAddComponentToData = false
|
||||
) => {
|
||||
const actionType = isEditing ? 'EDIT_ATTRIBUTE' : 'ADD_ATTRIBUTE';
|
||||
const actionType = isEditing ? EDIT_ATTRIBUTE : ADD_ATTRIBUTE;
|
||||
|
||||
dispatch({
|
||||
type: actionType,
|
||||
@ -157,7 +177,7 @@ const DataManagerProvider = ({ allIcons, children }) => {
|
||||
|
||||
const addCreatedComponentToDynamicZone = (dynamicZoneTarget, componentsToAdd) => {
|
||||
dispatch({
|
||||
type: 'ADD_CREATED_COMPONENT_TO_DYNAMIC_ZONE',
|
||||
type: ADD_CREATED_COMPONENT_TO_DYNAMIC_ZONE,
|
||||
dynamicZoneTarget,
|
||||
componentsToAdd,
|
||||
});
|
||||
@ -165,7 +185,7 @@ const DataManagerProvider = ({ allIcons, children }) => {
|
||||
|
||||
const cancelChanges = () => {
|
||||
toggleModalCancel();
|
||||
dispatch({ type: 'CANCEL_CHANGES' });
|
||||
dispatch({ type: CANCEL_CHANGES });
|
||||
};
|
||||
|
||||
const createSchema = (
|
||||
@ -175,7 +195,7 @@ const DataManagerProvider = ({ allIcons, children }) => {
|
||||
componentCategory,
|
||||
shouldAddComponentToData = false
|
||||
) => {
|
||||
const type = schemaType === 'contentType' ? 'CREATE_SCHEMA' : 'CREATE_COMPONENT_SCHEMA';
|
||||
const type = schemaType === 'contentType' ? CREATE_SCHEMA : CREATE_COMPONENT_SCHEMA;
|
||||
|
||||
dispatch({
|
||||
type,
|
||||
@ -189,7 +209,7 @@ const DataManagerProvider = ({ allIcons, children }) => {
|
||||
|
||||
const changeDynamicZoneComponents = (dynamicZoneTarget, newComponents) => {
|
||||
dispatch({
|
||||
type: 'CHANGE_DYNAMIC_ZONE_COMPONENTS',
|
||||
type: CHANGE_DYNAMIC_ZONE_COMPONENTS,
|
||||
dynamicZoneTarget,
|
||||
newComponents,
|
||||
});
|
||||
@ -197,7 +217,7 @@ const DataManagerProvider = ({ allIcons, children }) => {
|
||||
|
||||
const removeAttribute = (mainDataKey, attributeToRemoveName, componentUid = '') => {
|
||||
const type =
|
||||
mainDataKey === 'components' ? 'REMOVE_FIELD_FROM_DISPLAYED_COMPONENT' : 'REMOVE_FIELD';
|
||||
mainDataKey === 'components' ? REMOVE_FIELD_FROM_DISPLAYED_COMPONENT : REMOVE_FIELD;
|
||||
|
||||
if (mainDataKey === 'contentType') {
|
||||
emitEvent('willDeleteFieldOfContentType');
|
||||
@ -231,7 +251,7 @@ const DataManagerProvider = ({ allIcons, children }) => {
|
||||
await updatePermissions();
|
||||
|
||||
// Reload the plugin so the cycle is new again
|
||||
dispatch({ type: 'RELOAD_PLUGIN' });
|
||||
dispatch({ type: RELOAD_PLUGIN });
|
||||
// Refetch all the data
|
||||
getDataRef.current();
|
||||
}
|
||||
@ -268,7 +288,7 @@ const DataManagerProvider = ({ allIcons, children }) => {
|
||||
// Here we just need to reset the components to the initial ones and also the content types
|
||||
// Doing so will trigging a url change since the type doesn't exist in either the contentTypes or the components
|
||||
// so the modified and the initial data will also be reset in the useEffect...
|
||||
dispatch({ type: 'DELETE_NOT_SAVED_TYPE' });
|
||||
dispatch({ type: DELETE_NOT_SAVED_TYPE });
|
||||
|
||||
return;
|
||||
}
|
||||
@ -278,7 +298,7 @@ const DataManagerProvider = ({ allIcons, children }) => {
|
||||
await request(requestURL, { method: 'DELETE' }, true);
|
||||
|
||||
// Reload the plugin so the cycle is new again
|
||||
dispatch({ type: 'RELOAD_PLUGIN' });
|
||||
dispatch({ type: RELOAD_PLUGIN });
|
||||
|
||||
// Refetch the permissions
|
||||
await updatePermissions();
|
||||
@ -315,7 +335,7 @@ const DataManagerProvider = ({ allIcons, children }) => {
|
||||
await updatePermissions();
|
||||
|
||||
// Reload the plugin so the cycle is new again
|
||||
dispatch({ type: 'RELOAD_PLUGIN' });
|
||||
dispatch({ type: RELOAD_PLUGIN });
|
||||
// Refetch all the data
|
||||
getDataRef.current();
|
||||
} catch (err) {
|
||||
@ -356,7 +376,7 @@ const DataManagerProvider = ({ allIcons, children }) => {
|
||||
|
||||
const removeComponentFromDynamicZone = (dzName, componentToRemoveIndex) => {
|
||||
dispatch({
|
||||
type: 'REMOVE_COMPONENT_FROM_DYNAMIC_ZONE',
|
||||
type: REMOVE_COMPONENT_FROM_DYNAMIC_ZONE,
|
||||
dzName,
|
||||
componentToRemoveIndex,
|
||||
});
|
||||
@ -387,7 +407,7 @@ const DataManagerProvider = ({ allIcons, children }) => {
|
||||
size(get(schemaToSet, 'schema.attributes', {})) === 0;
|
||||
|
||||
dispatch({
|
||||
type: 'SET_MODIFIED_DATA',
|
||||
type: SET_MODIFIED_DATA,
|
||||
schemaToSet: dataShape,
|
||||
hasJustCreatedSchema,
|
||||
});
|
||||
@ -466,7 +486,7 @@ const DataManagerProvider = ({ allIcons, children }) => {
|
||||
}
|
||||
|
||||
// Reload the plugin so the cycle is new again
|
||||
dispatch({ type: 'RELOAD_PLUGIN' });
|
||||
dispatch({ type: RELOAD_PLUGIN });
|
||||
// Refetch all the data
|
||||
getDataRef.current();
|
||||
} catch (err) {
|
||||
@ -502,7 +522,7 @@ const DataManagerProvider = ({ allIcons, children }) => {
|
||||
|
||||
const updateSchema = (data, schemaType, componentUID) => {
|
||||
dispatch({
|
||||
type: 'UPDATE_SCHEMA',
|
||||
type: UPDATE_SCHEMA,
|
||||
data,
|
||||
schemaType,
|
||||
uid: componentUID,
|
||||
|
@ -1,5 +0,0 @@
|
||||
function init(initialState) {
|
||||
return initialState;
|
||||
}
|
||||
|
||||
export default init;
|
@ -3,7 +3,25 @@ import { get, has } from 'lodash';
|
||||
import makeUnique from '../../utils/makeUnique';
|
||||
import retrieveComponentsFromSchema from './utils/retrieveComponentsFromSchema';
|
||||
|
||||
const initialState = fromJS({
|
||||
import {
|
||||
ADD_ATTRIBUTE,
|
||||
ADD_CREATED_COMPONENT_TO_DYNAMIC_ZONE,
|
||||
CANCEL_CHANGES,
|
||||
CHANGE_DYNAMIC_ZONE_COMPONENTS,
|
||||
CREATE_SCHEMA,
|
||||
CREATE_COMPONENT_SCHEMA,
|
||||
DELETE_NOT_SAVED_TYPE,
|
||||
EDIT_ATTRIBUTE,
|
||||
GET_DATA_SUCCEEDED,
|
||||
RELOAD_PLUGIN,
|
||||
REMOVE_FIELD_FROM_DISPLAYED_COMPONENT,
|
||||
REMOVE_COMPONENT_FROM_DYNAMIC_ZONE,
|
||||
REMOVE_FIELD,
|
||||
SET_MODIFIED_DATA,
|
||||
UPDATE_SCHEMA,
|
||||
} from './constants';
|
||||
|
||||
const initialState = {
|
||||
components: {},
|
||||
contentTypes: {},
|
||||
initialComponents: {},
|
||||
@ -13,7 +31,7 @@ const initialState = fromJS({
|
||||
reservedNames: {},
|
||||
isLoading: true,
|
||||
isLoadingForDataToBeSet: true,
|
||||
});
|
||||
};
|
||||
|
||||
const ONE_SIDE_RELATIONS = ['oneWay', 'manyWay'];
|
||||
|
||||
@ -65,9 +83,11 @@ const addComponentsToState = (state, componentToAddUid, objToUpdate) => {
|
||||
return newObj;
|
||||
};
|
||||
|
||||
const reducer = (state, action) => {
|
||||
const reducer = (jsonState = initialState, action) => {
|
||||
const state = fromJS(jsonState);
|
||||
|
||||
switch (action.type) {
|
||||
case 'ADD_ATTRIBUTE': {
|
||||
case ADD_ATTRIBUTE: {
|
||||
const {
|
||||
attributeToSet: { name, ...rest },
|
||||
forTarget,
|
||||
@ -122,24 +142,28 @@ const reducer = (state, action) => {
|
||||
}
|
||||
|
||||
return existingCompos;
|
||||
});
|
||||
})
|
||||
.toJS();
|
||||
}
|
||||
case 'ADD_CREATED_COMPONENT_TO_DYNAMIC_ZONE': {
|
||||
case ADD_CREATED_COMPONENT_TO_DYNAMIC_ZONE: {
|
||||
const { dynamicZoneTarget, componentsToAdd } = action;
|
||||
|
||||
return state.updateIn(
|
||||
['modifiedData', 'contentType', 'schema', 'attributes', dynamicZoneTarget, 'components'],
|
||||
list => {
|
||||
return list.concat(componentsToAdd);
|
||||
}
|
||||
);
|
||||
return state
|
||||
.updateIn(
|
||||
['modifiedData', 'contentType', 'schema', 'attributes', dynamicZoneTarget, 'components'],
|
||||
list => {
|
||||
return list.concat(componentsToAdd);
|
||||
}
|
||||
)
|
||||
.toJS();
|
||||
}
|
||||
case 'CANCEL_CHANGES': {
|
||||
case CANCEL_CHANGES: {
|
||||
return state
|
||||
.update('modifiedData', () => state.get('initialData'))
|
||||
.update('components', () => state.get('initialComponents'));
|
||||
.update('components', () => state.get('initialComponents'))
|
||||
.toJS();
|
||||
}
|
||||
case 'CHANGE_DYNAMIC_ZONE_COMPONENTS': {
|
||||
case CHANGE_DYNAMIC_ZONE_COMPONENTS: {
|
||||
const { dynamicZoneTarget, newComponents } = action;
|
||||
|
||||
return state
|
||||
@ -155,10 +179,11 @@ const reducer = (state, action) => {
|
||||
}, old);
|
||||
|
||||
return componentsSchema;
|
||||
});
|
||||
})
|
||||
.toJS();
|
||||
}
|
||||
|
||||
case 'CREATE_SCHEMA': {
|
||||
case CREATE_SCHEMA: {
|
||||
const newSchema = {
|
||||
uid: action.uid,
|
||||
isTemporary: true,
|
||||
@ -168,9 +193,9 @@ const reducer = (state, action) => {
|
||||
},
|
||||
};
|
||||
|
||||
return state.updateIn(['contentTypes', action.uid], () => fromJS(newSchema));
|
||||
return state.updateIn(['contentTypes', action.uid], () => fromJS(newSchema)).toJS();
|
||||
}
|
||||
case 'CREATE_COMPONENT_SCHEMA': {
|
||||
case CREATE_COMPONENT_SCHEMA: {
|
||||
const newSchema = {
|
||||
uid: action.uid,
|
||||
isTemporary: true,
|
||||
@ -184,18 +209,20 @@ const reducer = (state, action) => {
|
||||
if (action.shouldAddComponentToData) {
|
||||
return state
|
||||
.updateIn(['components', action.uid], () => fromJS(newSchema))
|
||||
.updateIn(['modifiedData', 'components', action.uid], () => fromJS(newSchema));
|
||||
.updateIn(['modifiedData', 'components', action.uid], () => fromJS(newSchema))
|
||||
.toJS();
|
||||
}
|
||||
|
||||
return state.updateIn(['components', action.uid], () => fromJS(newSchema));
|
||||
return state.updateIn(['components', action.uid], () => fromJS(newSchema)).toJS();
|
||||
}
|
||||
case 'DELETE_NOT_SAVED_TYPE': {
|
||||
case DELETE_NOT_SAVED_TYPE: {
|
||||
// Doing so will also reset the modified and the initial data
|
||||
return state
|
||||
.update('contentTypes', () => state.get('initialContentTypes'))
|
||||
.update('components', () => state.get('initialComponents'));
|
||||
.update('components', () => state.get('initialComponents'))
|
||||
.toJS();
|
||||
}
|
||||
case 'EDIT_ATTRIBUTE': {
|
||||
case EDIT_ATTRIBUTE: {
|
||||
const {
|
||||
attributeToSet: { name, ...rest },
|
||||
forTarget,
|
||||
@ -209,132 +236,134 @@ const reducer = (state, action) => {
|
||||
? [forTarget]
|
||||
: [forTarget, targetUid];
|
||||
|
||||
return newState.updateIn(['modifiedData', ...pathToDataToEdit, 'schema'], obj => {
|
||||
let oppositeAttributeNameToRemove = null;
|
||||
let oppositeAttributeNameToUpdate = null;
|
||||
let oppositeAttributeNameToCreateBecauseOfNatureChange = null;
|
||||
let oppositeAttributeToCreate = null;
|
||||
return newState
|
||||
.updateIn(['modifiedData', ...pathToDataToEdit, 'schema'], obj => {
|
||||
let oppositeAttributeNameToRemove = null;
|
||||
let oppositeAttributeNameToUpdate = null;
|
||||
let oppositeAttributeNameToCreateBecauseOfNatureChange = null;
|
||||
let oppositeAttributeToCreate = null;
|
||||
|
||||
const newObj = OrderedMap(
|
||||
obj
|
||||
.get('attributes')
|
||||
.keySeq()
|
||||
.reduce((acc, current) => {
|
||||
const isEditingCurrentAttribute = current === initialAttributeName;
|
||||
const newObj = OrderedMap(
|
||||
obj
|
||||
.get('attributes')
|
||||
.keySeq()
|
||||
.reduce((acc, current) => {
|
||||
const isEditingCurrentAttribute = current === initialAttributeName;
|
||||
|
||||
if (isEditingCurrentAttribute) {
|
||||
const currentUid = state.getIn(['modifiedData', ...pathToDataToEdit, 'uid']);
|
||||
const isEditingRelation = has(initialAttribute, 'nature');
|
||||
const didChangeTargetRelation = initialAttribute.target !== rest.target;
|
||||
const didCreateInternalRelation = rest.target === currentUid;
|
||||
const nature = rest.nature;
|
||||
const initialNature = initialAttribute.nature;
|
||||
const hadInternalRelation = initialAttribute.target === currentUid;
|
||||
const didChangeRelationNature = initialAttribute.nature !== nature;
|
||||
const shouldRemoveOppositeAttributeBecauseOfTargetChange =
|
||||
didChangeTargetRelation &&
|
||||
!didCreateInternalRelation &&
|
||||
hadInternalRelation &&
|
||||
isEditingRelation;
|
||||
const shouldRemoveOppositeAttributeBecauseOfNatureChange =
|
||||
didChangeRelationNature &&
|
||||
hadInternalRelation &&
|
||||
['oneWay', 'manyWay'].includes(nature) &&
|
||||
isEditingRelation;
|
||||
const shouldUpdateOppositeAttributeBecauseOfNatureChange =
|
||||
!ONE_SIDE_RELATIONS.includes(initialNature) &&
|
||||
!ONE_SIDE_RELATIONS.includes(nature) &&
|
||||
hadInternalRelation &&
|
||||
didCreateInternalRelation &&
|
||||
isEditingRelation;
|
||||
const shouldCreateOppositeAttributeBecauseOfNatureChange =
|
||||
ONE_SIDE_RELATIONS.includes(initialNature) &&
|
||||
!ONE_SIDE_RELATIONS.includes(nature) &&
|
||||
hadInternalRelation &&
|
||||
didCreateInternalRelation &&
|
||||
isEditingRelation;
|
||||
const shouldCreateOppositeAttributeBecauseOfTargetChange =
|
||||
didChangeTargetRelation &&
|
||||
didCreateInternalRelation &&
|
||||
!ONE_SIDE_RELATIONS.includes(nature);
|
||||
if (isEditingCurrentAttribute) {
|
||||
const currentUid = state.getIn(['modifiedData', ...pathToDataToEdit, 'uid']);
|
||||
const isEditingRelation = has(initialAttribute, 'nature');
|
||||
const didChangeTargetRelation = initialAttribute.target !== rest.target;
|
||||
const didCreateInternalRelation = rest.target === currentUid;
|
||||
const nature = rest.nature;
|
||||
const initialNature = initialAttribute.nature;
|
||||
const hadInternalRelation = initialAttribute.target === currentUid;
|
||||
const didChangeRelationNature = initialAttribute.nature !== nature;
|
||||
const shouldRemoveOppositeAttributeBecauseOfTargetChange =
|
||||
didChangeTargetRelation &&
|
||||
!didCreateInternalRelation &&
|
||||
hadInternalRelation &&
|
||||
isEditingRelation;
|
||||
const shouldRemoveOppositeAttributeBecauseOfNatureChange =
|
||||
didChangeRelationNature &&
|
||||
hadInternalRelation &&
|
||||
['oneWay', 'manyWay'].includes(nature) &&
|
||||
isEditingRelation;
|
||||
const shouldUpdateOppositeAttributeBecauseOfNatureChange =
|
||||
!ONE_SIDE_RELATIONS.includes(initialNature) &&
|
||||
!ONE_SIDE_RELATIONS.includes(nature) &&
|
||||
hadInternalRelation &&
|
||||
didCreateInternalRelation &&
|
||||
isEditingRelation;
|
||||
const shouldCreateOppositeAttributeBecauseOfNatureChange =
|
||||
ONE_SIDE_RELATIONS.includes(initialNature) &&
|
||||
!ONE_SIDE_RELATIONS.includes(nature) &&
|
||||
hadInternalRelation &&
|
||||
didCreateInternalRelation &&
|
||||
isEditingRelation;
|
||||
const shouldCreateOppositeAttributeBecauseOfTargetChange =
|
||||
didChangeTargetRelation &&
|
||||
didCreateInternalRelation &&
|
||||
!ONE_SIDE_RELATIONS.includes(nature);
|
||||
|
||||
// Update the opposite attribute name so it is removed at the end of the loop
|
||||
if (
|
||||
shouldRemoveOppositeAttributeBecauseOfTargetChange ||
|
||||
shouldRemoveOppositeAttributeBecauseOfNatureChange
|
||||
) {
|
||||
oppositeAttributeNameToRemove = initialAttribute.targetAttribute;
|
||||
}
|
||||
|
||||
// Set the opposite attribute that will be updated when the loop attribute matches the name
|
||||
if (
|
||||
shouldUpdateOppositeAttributeBecauseOfNatureChange ||
|
||||
shouldCreateOppositeAttributeBecauseOfNatureChange ||
|
||||
shouldCreateOppositeAttributeBecauseOfTargetChange
|
||||
) {
|
||||
oppositeAttributeNameToUpdate = initialAttribute.targetAttribute;
|
||||
oppositeAttributeNameToCreateBecauseOfNatureChange = rest.targetAttribute;
|
||||
|
||||
oppositeAttributeToCreate = {
|
||||
nature: getOppositeNature(rest.nature),
|
||||
target: rest.target,
|
||||
unique: rest.unique,
|
||||
// Leave this if we allow the required on the relation
|
||||
// required: rest.required,
|
||||
dominant: rest.nature === 'manyToMany' ? !rest.dominant : null,
|
||||
targetAttribute: name,
|
||||
columnName: rest.targetColumnName,
|
||||
targetColumnName: rest.columnName,
|
||||
};
|
||||
|
||||
// First update the current attribute with the value
|
||||
acc[name] = fromJS(rest);
|
||||
|
||||
// Then (if needed) create the opposite attribute the case is changing the relation from
|
||||
// We do it here so keep the order of the attributes
|
||||
// oneWay || manyWay to something another relation
|
||||
// Update the opposite attribute name so it is removed at the end of the loop
|
||||
if (
|
||||
shouldRemoveOppositeAttributeBecauseOfTargetChange ||
|
||||
shouldRemoveOppositeAttributeBecauseOfNatureChange
|
||||
) {
|
||||
oppositeAttributeNameToRemove = initialAttribute.targetAttribute;
|
||||
}
|
||||
|
||||
// Set the opposite attribute that will be updated when the loop attribute matches the name
|
||||
if (
|
||||
shouldUpdateOppositeAttributeBecauseOfNatureChange ||
|
||||
shouldCreateOppositeAttributeBecauseOfNatureChange ||
|
||||
shouldCreateOppositeAttributeBecauseOfTargetChange
|
||||
) {
|
||||
acc[oppositeAttributeNameToCreateBecauseOfNatureChange] = fromJS(
|
||||
oppositeAttributeToCreate
|
||||
);
|
||||
oppositeAttributeNameToUpdate = initialAttribute.targetAttribute;
|
||||
oppositeAttributeNameToCreateBecauseOfNatureChange = rest.targetAttribute;
|
||||
|
||||
oppositeAttributeToCreate = null;
|
||||
oppositeAttributeNameToCreateBecauseOfNatureChange = null;
|
||||
oppositeAttributeToCreate = {
|
||||
nature: getOppositeNature(rest.nature),
|
||||
target: rest.target,
|
||||
unique: rest.unique,
|
||||
// Leave this if we allow the required on the relation
|
||||
// required: rest.required,
|
||||
dominant: rest.nature === 'manyToMany' ? !rest.dominant : null,
|
||||
targetAttribute: name,
|
||||
columnName: rest.targetColumnName,
|
||||
targetColumnName: rest.columnName,
|
||||
};
|
||||
|
||||
// First update the current attribute with the value
|
||||
acc[name] = fromJS(rest);
|
||||
|
||||
// Then (if needed) create the opposite attribute the case is changing the relation from
|
||||
// We do it here so keep the order of the attributes
|
||||
// oneWay || manyWay to something another relation
|
||||
if (
|
||||
shouldCreateOppositeAttributeBecauseOfNatureChange ||
|
||||
shouldCreateOppositeAttributeBecauseOfTargetChange
|
||||
) {
|
||||
acc[oppositeAttributeNameToCreateBecauseOfNatureChange] = fromJS(
|
||||
oppositeAttributeToCreate
|
||||
);
|
||||
|
||||
oppositeAttributeToCreate = null;
|
||||
oppositeAttributeNameToCreateBecauseOfNatureChange = null;
|
||||
}
|
||||
|
||||
return acc;
|
||||
}
|
||||
|
||||
return acc;
|
||||
acc[name] = fromJS(rest);
|
||||
} else if (current === oppositeAttributeNameToUpdate) {
|
||||
acc[oppositeAttributeNameToCreateBecauseOfNatureChange] = fromJS(
|
||||
oppositeAttributeToCreate
|
||||
);
|
||||
} else {
|
||||
acc[current] = obj.getIn(['attributes', current]);
|
||||
}
|
||||
|
||||
acc[name] = fromJS(rest);
|
||||
} else if (current === oppositeAttributeNameToUpdate) {
|
||||
acc[oppositeAttributeNameToCreateBecauseOfNatureChange] = fromJS(
|
||||
oppositeAttributeToCreate
|
||||
);
|
||||
} else {
|
||||
acc[current] = obj.getIn(['attributes', current]);
|
||||
}
|
||||
return acc;
|
||||
}, {})
|
||||
);
|
||||
|
||||
return acc;
|
||||
}, {})
|
||||
);
|
||||
let updatedObj;
|
||||
|
||||
let updatedObj;
|
||||
// Remove the opposite attribute
|
||||
if (oppositeAttributeNameToRemove !== null) {
|
||||
updatedObj = newObj.remove(oppositeAttributeNameToRemove);
|
||||
} else {
|
||||
updatedObj = newObj;
|
||||
}
|
||||
|
||||
// Remove the opposite attribute
|
||||
if (oppositeAttributeNameToRemove !== null) {
|
||||
updatedObj = newObj.remove(oppositeAttributeNameToRemove);
|
||||
} else {
|
||||
updatedObj = newObj;
|
||||
}
|
||||
|
||||
return obj.set('attributes', updatedObj);
|
||||
});
|
||||
return obj.set('attributes', updatedObj);
|
||||
})
|
||||
.toJS();
|
||||
}
|
||||
|
||||
case 'GET_DATA_SUCCEEDED': {
|
||||
case GET_DATA_SUCCEEDED: {
|
||||
return state
|
||||
.update('components', () => fromJS(action.components))
|
||||
.update('initialComponents', () => fromJS(action.components))
|
||||
@ -342,33 +371,38 @@ const reducer = (state, action) => {
|
||||
.update('contentTypes', () => fromJS(action.contentTypes))
|
||||
.update('reservedNames', () => fromJS(action.reservedNames))
|
||||
|
||||
.update('isLoading', () => false);
|
||||
.update('isLoading', () => false)
|
||||
.toJS();
|
||||
}
|
||||
case 'RELOAD_PLUGIN':
|
||||
case RELOAD_PLUGIN:
|
||||
return initialState;
|
||||
case 'REMOVE_FIELD_FROM_DISPLAYED_COMPONENT': {
|
||||
case REMOVE_FIELD_FROM_DISPLAYED_COMPONENT: {
|
||||
const { attributeToRemoveName, componentUid } = action;
|
||||
|
||||
return state.removeIn([
|
||||
'modifiedData',
|
||||
'components',
|
||||
componentUid,
|
||||
'schema',
|
||||
'attributes',
|
||||
attributeToRemoveName,
|
||||
]);
|
||||
return state
|
||||
.removeIn([
|
||||
'modifiedData',
|
||||
'components',
|
||||
componentUid,
|
||||
'schema',
|
||||
'attributes',
|
||||
attributeToRemoveName,
|
||||
])
|
||||
.toJS();
|
||||
}
|
||||
case 'REMOVE_COMPONENT_FROM_DYNAMIC_ZONE':
|
||||
return state.removeIn([
|
||||
'modifiedData',
|
||||
'contentType',
|
||||
'schema',
|
||||
'attributes',
|
||||
action.dzName,
|
||||
'components',
|
||||
action.componentToRemoveIndex,
|
||||
]);
|
||||
case 'REMOVE_FIELD': {
|
||||
case REMOVE_COMPONENT_FROM_DYNAMIC_ZONE:
|
||||
return state
|
||||
.removeIn([
|
||||
'modifiedData',
|
||||
'contentType',
|
||||
'schema',
|
||||
'attributes',
|
||||
action.dzName,
|
||||
'components',
|
||||
action.componentToRemoveIndex,
|
||||
])
|
||||
.toJS();
|
||||
case REMOVE_FIELD: {
|
||||
const { mainDataKey, attributeToRemoveName } = action;
|
||||
const pathToAttributes = ['modifiedData', mainDataKey, 'schema', 'attributes'];
|
||||
const pathToAttributeToRemove = [...pathToAttributes, attributeToRemoveName];
|
||||
@ -389,21 +423,25 @@ const reducer = (state, action) => {
|
||||
if (shouldRemoveOppositeAttribute) {
|
||||
return state
|
||||
.removeIn(pathToAttributeToRemove)
|
||||
.removeIn([...pathToAttributes, targetAttribute]);
|
||||
.removeIn([...pathToAttributes, targetAttribute])
|
||||
.toJS();
|
||||
}
|
||||
}
|
||||
|
||||
return state.removeIn(pathToAttributeToRemove).updateIn([...pathToAttributes], attributes => {
|
||||
return attributes.keySeq().reduce((acc, current) => {
|
||||
if (acc.getIn([current, 'targetField']) === attributeToRemoveName) {
|
||||
return acc.removeIn([current, 'targetField']);
|
||||
}
|
||||
return state
|
||||
.removeIn(pathToAttributeToRemove)
|
||||
.updateIn([...pathToAttributes], attributes => {
|
||||
return attributes.keySeq().reduce((acc, current) => {
|
||||
if (acc.getIn([current, 'targetField']) === attributeToRemoveName) {
|
||||
return acc.removeIn([current, 'targetField']);
|
||||
}
|
||||
|
||||
return acc;
|
||||
}, attributes);
|
||||
});
|
||||
return acc;
|
||||
}, attributes);
|
||||
})
|
||||
.toJS();
|
||||
}
|
||||
case 'SET_MODIFIED_DATA': {
|
||||
case SET_MODIFIED_DATA: {
|
||||
let newState = state
|
||||
.update('isLoadingForDataToBeSet', () => false)
|
||||
.update('initialData', () => fromJS(action.schemaToSet))
|
||||
@ -417,9 +455,9 @@ const reducer = (state, action) => {
|
||||
.update('contentTypes', () => state.get('initialContentTypes'));
|
||||
}
|
||||
|
||||
return newState;
|
||||
return newState.toJS();
|
||||
}
|
||||
case 'UPDATE_SCHEMA': {
|
||||
case UPDATE_SCHEMA: {
|
||||
const {
|
||||
data: { name, collectionName, category, icon, kind },
|
||||
schemaType,
|
||||
@ -449,10 +487,10 @@ const reducer = (state, action) => {
|
||||
});
|
||||
}
|
||||
|
||||
return newState;
|
||||
return newState.toJS();
|
||||
}
|
||||
default:
|
||||
return state;
|
||||
return state.toJS();
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -0,0 +1,24 @@
|
||||
import { createSelector } from 'reselect';
|
||||
import pluginId from '../../pluginId';
|
||||
import { initialState } from './reducer';
|
||||
|
||||
/**
|
||||
* Direct selector to the dataManagerProvider state domain
|
||||
*/
|
||||
const dataManagerProviderDomain = () => state => state.get(`${pluginId}_dataManagerProvider`) || initialState;
|
||||
|
||||
/**
|
||||
* Other specific selectors
|
||||
*/
|
||||
|
||||
/**
|
||||
* Default selector used by dataManagerProvider
|
||||
*/
|
||||
|
||||
const makeSelectDataManagerProvider = () =>
|
||||
createSelector(dataManagerProviderDomain(), substate => {
|
||||
return substate;
|
||||
});
|
||||
|
||||
export default makeSelectDataManagerProvider;
|
||||
export { dataManagerProviderDomain };
|
@ -28,8 +28,6 @@ describe('CTB | containers | FormModal | reducer | actions', () => {
|
||||
})
|
||||
);
|
||||
|
||||
console.log(expected.toJS());
|
||||
|
||||
expect(reducer(state, action)).toEqual(expected);
|
||||
});
|
||||
|
||||
|
@ -74,5 +74,16 @@ export default strapi => {
|
||||
},
|
||||
};
|
||||
|
||||
const modalOnChangeMiddleware = () => {
|
||||
return ({ dispatch, getState }) => next => action => {
|
||||
if (action.type === 'ContentTypeBuilder/FormModal/ON_CHANGE') {
|
||||
console.log(action);
|
||||
}
|
||||
return next(action);
|
||||
};
|
||||
};
|
||||
|
||||
strapi.middlewares.add(modalOnChangeMiddleware);
|
||||
|
||||
return strapi.registerPlugin(plugin);
|
||||
};
|
||||
|
@ -1,8 +1,10 @@
|
||||
import formModalReducer from './containers/FormModal/reducer';
|
||||
import dataManagerProvider from './containers/DataManagerProvider/reducer';
|
||||
import pluginId from './pluginId';
|
||||
|
||||
const reducers = {
|
||||
[`${pluginId}_formModal`]: formModalReducer,
|
||||
[`${pluginId}_dataManagerProvider`]: dataManagerProvider,
|
||||
};
|
||||
|
||||
export default reducers;
|
||||
|
@ -16202,6 +16202,11 @@ redux-saga@^0.16.0:
|
||||
resolved "https://registry.yarnpkg.com/redux-saga/-/redux-saga-0.16.2.tgz#993662e86bc945d8509ac2b8daba3a8c615cc971"
|
||||
integrity sha512-iIjKnRThI5sKPEASpUvySemjzwqwI13e3qP7oLub+FycCRDysLSAOwt958niZW6LhxfmS6Qm1BzbU70w/Koc4w==
|
||||
|
||||
redux-thunk@2.3.0:
|
||||
version "2.3.0"
|
||||
resolved "https://registry.yarnpkg.com/redux-thunk/-/redux-thunk-2.3.0.tgz#51c2c19a185ed5187aaa9a2d08b666d0d6467622"
|
||||
integrity sha512-km6dclyFnmcvxhAcrQV2AkZmPQjzPDjgVlQtR0EQjxZPyJ0BnMf3in1ryuR8A2qU0HldVRfxYXbFSKlI3N7Slw==
|
||||
|
||||
redux@^4.0.1, redux@^4.0.4:
|
||||
version "4.0.5"
|
||||
resolved "https://registry.yarnpkg.com/redux/-/redux-4.0.5.tgz#4db5de5816e17891de8a80c424232d06f051d93f"
|
||||
|
Loading…
x
Reference in New Issue
Block a user