From 95ed2ee4123d4984472f16626aabb96a1a7f36ab Mon Sep 17 00:00:00 2001 From: HichamELBSI Date: Thu, 21 Jan 2021 20:00:45 +0100 Subject: [PATCH] add redux-thunk to admin Signed-off-by: HichamELBSI --- .../api/newcollection/config/routes.json | 52 +++ .../controllers/newcollection.js | 8 + .../api/newcollection/models/newcollection.js | 8 + .../models/newcollection.settings.json | 24 ++ .../newcollection/services/newcollection.js | 8 + packages/strapi-admin/admin/src/app.js | 9 +- .../strapi-admin/admin/src/configureStore.js | 29 +- .../admin/src/containers/Admin/index.js | 2 + .../admin/src/utils/MiddleWares.js | 16 + .../strapi-admin/admin/src/utils/Strapi.js | 3 + .../admin/src/utils/reducerInjectors.js | 1 - packages/strapi-admin/package.json | 1 + .../DataManagerProvider/constants.js | 15 + .../containers/DataManagerProvider/index.js | 60 ++- .../containers/DataManagerProvider/init.js | 5 - .../containers/DataManagerProvider/reducer.js | 376 ++++++++++-------- .../DataManagerProvider/selectors.js | 24 ++ .../FormModal/tests/reducer.test.js | 2 - .../admin/src/index.js | 11 + .../admin/src/reducers.js | 2 + yarn.lock | 5 + 21 files changed, 448 insertions(+), 213 deletions(-) create mode 100644 examples/getstarted/api/newcollection/config/routes.json create mode 100644 examples/getstarted/api/newcollection/controllers/newcollection.js create mode 100644 examples/getstarted/api/newcollection/models/newcollection.js create mode 100644 examples/getstarted/api/newcollection/models/newcollection.settings.json create mode 100644 examples/getstarted/api/newcollection/services/newcollection.js create mode 100644 packages/strapi-admin/admin/src/utils/MiddleWares.js create mode 100644 packages/strapi-plugin-content-type-builder/admin/src/containers/DataManagerProvider/constants.js delete mode 100644 packages/strapi-plugin-content-type-builder/admin/src/containers/DataManagerProvider/init.js create mode 100644 packages/strapi-plugin-content-type-builder/admin/src/containers/DataManagerProvider/selectors.js diff --git a/examples/getstarted/api/newcollection/config/routes.json b/examples/getstarted/api/newcollection/config/routes.json new file mode 100644 index 0000000000..a0a8dc6aa4 --- /dev/null +++ b/examples/getstarted/api/newcollection/config/routes.json @@ -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": [] + } + } + ] +} diff --git a/examples/getstarted/api/newcollection/controllers/newcollection.js b/examples/getstarted/api/newcollection/controllers/newcollection.js new file mode 100644 index 0000000000..556f4fcee3 --- /dev/null +++ b/examples/getstarted/api/newcollection/controllers/newcollection.js @@ -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 = {}; diff --git a/examples/getstarted/api/newcollection/models/newcollection.js b/examples/getstarted/api/newcollection/models/newcollection.js new file mode 100644 index 0000000000..dd847efdda --- /dev/null +++ b/examples/getstarted/api/newcollection/models/newcollection.js @@ -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 = {}; diff --git a/examples/getstarted/api/newcollection/models/newcollection.settings.json b/examples/getstarted/api/newcollection/models/newcollection.settings.json new file mode 100644 index 0000000000..081dbb8c2a --- /dev/null +++ b/examples/getstarted/api/newcollection/models/newcollection.settings.json @@ -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 + } + } +} diff --git a/examples/getstarted/api/newcollection/services/newcollection.js b/examples/getstarted/api/newcollection/services/newcollection.js new file mode 100644 index 0000000000..6bc4168d35 --- /dev/null +++ b/examples/getstarted/api/newcollection/services/newcollection.js @@ -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 = {}; diff --git a/packages/strapi-admin/admin/src/app.js b/packages/strapi-admin/admin/src/app.js index a227173b59..b3a8a4c6d8 100644 --- a/packages/strapi-admin/admin/src/app.js +++ b/packages/strapi-admin/admin/src/app.js @@ -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 }); } diff --git a/packages/strapi-admin/admin/src/configureStore.js b/packages/strapi-admin/admin/src/configureStore.js index b409e4a8c6..bdbd315330 100644 --- a/packages/strapi-admin/admin/src/configureStore.js +++ b/packages/strapi-admin/admin/src/configureStore.js @@ -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; diff --git a/packages/strapi-admin/admin/src/containers/Admin/index.js b/packages/strapi-admin/admin/src/containers/Admin/index.js index 807be32aee..a066a455a4 100644 --- a/packages/strapi-admin/admin/src/containers/Admin/index.js +++ b/packages/strapi-admin/admin/src/containers/Admin/index.js @@ -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 ( diff --git a/packages/strapi-admin/admin/src/utils/MiddleWares.js b/packages/strapi-admin/admin/src/utils/MiddleWares.js new file mode 100644 index 0000000000..c40a6310e8 --- /dev/null +++ b/packages/strapi-admin/admin/src/utils/MiddleWares.js @@ -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(); diff --git a/packages/strapi-admin/admin/src/utils/Strapi.js b/packages/strapi-admin/admin/src/utils/Strapi.js index 64f3b265a2..91301ba4a7 100644 --- a/packages/strapi-admin/admin/src/utils/Strapi.js +++ b/packages/strapi-admin/admin/src/utils/Strapi.js @@ -1,9 +1,12 @@ import ComponentApi from './ComponentApi'; import FieldApi from './FieldApi'; +import MiddleWares from './MiddleWares'; class Strapi { componentApi = ComponentApi(); + middlewares = MiddleWares(); + fieldApi = FieldApi(); } diff --git a/packages/strapi-admin/admin/src/utils/reducerInjectors.js b/packages/strapi-admin/admin/src/utils/reducerInjectors.js index 9c55f99fd6..7a2f3faa96 100644 --- a/packages/strapi-admin/admin/src/utils/reducerInjectors.js +++ b/packages/strapi-admin/admin/src/utils/reducerInjectors.js @@ -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( diff --git a/packages/strapi-admin/package.json b/packages/strapi-admin/package.json index 9482720ca2..3783a2e048 100644 --- a/packages/strapi-admin/package.json +++ b/packages/strapi-admin/package.json @@ -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", diff --git a/packages/strapi-plugin-content-type-builder/admin/src/containers/DataManagerProvider/constants.js b/packages/strapi-plugin-content-type-builder/admin/src/containers/DataManagerProvider/constants.js new file mode 100644 index 0000000000..96db174798 --- /dev/null +++ b/packages/strapi-plugin-content-type-builder/admin/src/containers/DataManagerProvider/constants.js @@ -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'; diff --git a/packages/strapi-plugin-content-type-builder/admin/src/containers/DataManagerProvider/index.js b/packages/strapi-plugin-content-type-builder/admin/src/containers/DataManagerProvider/index.js index 0c9cd13562..23e7f950e5 100644 --- a/packages/strapi-plugin-content-type-builder/admin/src/containers/DataManagerProvider/index.js +++ b/packages/strapi-plugin-content-type-builder/admin/src/containers/DataManagerProvider/index.js @@ -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, diff --git a/packages/strapi-plugin-content-type-builder/admin/src/containers/DataManagerProvider/init.js b/packages/strapi-plugin-content-type-builder/admin/src/containers/DataManagerProvider/init.js deleted file mode 100644 index 18c17c07c6..0000000000 --- a/packages/strapi-plugin-content-type-builder/admin/src/containers/DataManagerProvider/init.js +++ /dev/null @@ -1,5 +0,0 @@ -function init(initialState) { - return initialState; -} - -export default init; diff --git a/packages/strapi-plugin-content-type-builder/admin/src/containers/DataManagerProvider/reducer.js b/packages/strapi-plugin-content-type-builder/admin/src/containers/DataManagerProvider/reducer.js index a26c4dc20e..08d093e338 100644 --- a/packages/strapi-plugin-content-type-builder/admin/src/containers/DataManagerProvider/reducer.js +++ b/packages/strapi-plugin-content-type-builder/admin/src/containers/DataManagerProvider/reducer.js @@ -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(); } }; diff --git a/packages/strapi-plugin-content-type-builder/admin/src/containers/DataManagerProvider/selectors.js b/packages/strapi-plugin-content-type-builder/admin/src/containers/DataManagerProvider/selectors.js new file mode 100644 index 0000000000..2cf749a9c8 --- /dev/null +++ b/packages/strapi-plugin-content-type-builder/admin/src/containers/DataManagerProvider/selectors.js @@ -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 }; diff --git a/packages/strapi-plugin-content-type-builder/admin/src/containers/FormModal/tests/reducer.test.js b/packages/strapi-plugin-content-type-builder/admin/src/containers/FormModal/tests/reducer.test.js index 33b49d05fa..e15b3916c8 100644 --- a/packages/strapi-plugin-content-type-builder/admin/src/containers/FormModal/tests/reducer.test.js +++ b/packages/strapi-plugin-content-type-builder/admin/src/containers/FormModal/tests/reducer.test.js @@ -28,8 +28,6 @@ describe('CTB | containers | FormModal | reducer | actions', () => { }) ); - console.log(expected.toJS()); - expect(reducer(state, action)).toEqual(expected); }); diff --git a/packages/strapi-plugin-content-type-builder/admin/src/index.js b/packages/strapi-plugin-content-type-builder/admin/src/index.js index 84fc0fdb37..81b93aa067 100644 --- a/packages/strapi-plugin-content-type-builder/admin/src/index.js +++ b/packages/strapi-plugin-content-type-builder/admin/src/index.js @@ -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); }; diff --git a/packages/strapi-plugin-content-type-builder/admin/src/reducers.js b/packages/strapi-plugin-content-type-builder/admin/src/reducers.js index 72e476167e..0246ce4c6c 100644 --- a/packages/strapi-plugin-content-type-builder/admin/src/reducers.js +++ b/packages/strapi-plugin-content-type-builder/admin/src/reducers.js @@ -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; diff --git a/yarn.lock b/yarn.lock index 45db97c76e..18ce114709 100644 --- a/yarn.lock +++ b/yarn.lock @@ -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"