diff --git a/packages/core/admin/admin/src/StrapiApp.js b/packages/core/admin/admin/src/StrapiApp.js index 274544ad40..85d0535b5f 100644 --- a/packages/core/admin/admin/src/StrapiApp.js +++ b/packages/core/admin/admin/src/StrapiApp.js @@ -5,7 +5,7 @@ import { QueryClientProvider, QueryClient } from 'react-query'; import { ThemeProvider } from 'styled-components'; import { StrapiProvider } from '@strapi/helper-plugin'; import configureStore from './core/store/configureStore'; -import { Middlewares, Plugin } from './core/apis'; +import { Plugin } from './core/apis'; import basename from './utils/basename'; import App from './pages/App'; import LanguageProvider from './components/LanguageProvider'; @@ -19,7 +19,7 @@ import themes from './themes'; import reducers from './reducers'; // TODO -import translationMessages from './translations'; +import translations from './translations'; window.strapi = { backendURL: process.env.STRAPI_ADMIN_BACKEND_URL, @@ -33,14 +33,22 @@ const queryClient = new QueryClient({ }, }); +// FIXME +const appLocales = Object.keys(translations); + class StrapiApp { constructor({ appPlugins }) { + this.translationMessages = translations; this.appPlugins = appPlugins || {}; - this.middlewares = Middlewares(); + this.middlewares = []; this.plugins = {}; this.reducers = { ...reducers }; } + addMiddleware(middleware) { + this.middlewares.push(middleware); + } + addReducers(reducers) { Object.keys(reducers).forEach(reducerName => { this.reducers[reducerName] = reducers[reducerName]; @@ -51,22 +59,60 @@ class StrapiApp { Object.keys(this.appPlugins).forEach(plugin => { this.appPlugins[plugin].register(this); }); - - return this; } async boot() { - console.log('booting'); + Object.keys(this.appPlugins).forEach(plugin => { + const boot = this.appPlugins[plugin].boot; - return this; + if (boot) { + boot(this); + } + }); } getPlugin(pluginId) { return this.plugins[pluginId] || null; } + // FIXME + registerPluginTranslations(pluginId, trads) { + const pluginTranslations = appLocales.reduce((acc, currentLanguage) => { + const currentLocale = trads[currentLanguage]; + + if (currentLocale) { + const localeprefixedWithPluginId = Object.keys(currentLocale).reduce((acc2, current) => { + acc2[`${pluginId}.${current}`] = currentLocale[current]; + + return acc2; + }, {}); + + acc[currentLanguage] = localeprefixedWithPluginId; + } + + return acc; + }, {}); + + this.translationMessages = Object.keys(this.translationMessages).reduce((acc, current) => { + acc[current] = { + ...this.translationMessages[current], + ...(pluginTranslations[current] || {}), + }; + + return acc; + }, {}); + } + registerPlugin(pluginConf) { - this.plugins[pluginConf.id] = Plugin(pluginConf); + const plugin = Plugin(pluginConf); + + this.plugins[plugin.pluginId] = plugin; + + // FIXME + // Translations should be loaded differently + // This is a temporary fix + + this.registerPluginTranslations(plugin.pluginId, plugin.trads); } render() { @@ -79,7 +125,7 @@ class StrapiApp { - + <> diff --git a/packages/core/admin/admin/src/components/PluginsInitializer/index.js b/packages/core/admin/admin/src/components/PluginsInitializer/index.js new file mode 100644 index 0000000000..ceb9af2260 --- /dev/null +++ b/packages/core/admin/admin/src/components/PluginsInitializer/index.js @@ -0,0 +1,41 @@ +import React, { useReducer, useRef } from 'react'; +import { LoadingIndicatorPage, useStrapi } from '@strapi/helper-plugin'; +import Admin from '../../pages/Admin'; +import init from './init'; +import reducer, { initialState } from './reducer'; + +const PluginsInitializer = () => { + // TODO rename strapi to avoid mismatch with the window.strapi + const { strapi: app } = useStrapi(); + const [{ plugins }, dispatch] = useReducer(reducer, initialState, () => init(app.plugins)); + const setPlugin = useRef(pluginId => { + dispatch({ type: 'SET_PLUGIN_READY', pluginId }); + }); + + const hasApluginNotReady = Object.keys(plugins).some(plugin => plugins[plugin].isReady === false); + + if (hasApluginNotReady) { + const initializers = Object.keys(plugins).reduce((acc, current) => { + const InitializerComponent = plugins[current].initializer; + + if (InitializerComponent) { + const key = plugins[current].pluginId; + + acc.push(); + } + + return acc; + }, []); + + return ( + <> + {initializers} + + + ); + } + + return ; +}; + +export default PluginsInitializer; diff --git a/packages/core/admin/admin/src/components/PluginsInitializer/init.js b/packages/core/admin/admin/src/components/PluginsInitializer/init.js new file mode 100644 index 0000000000..50429ad16f --- /dev/null +++ b/packages/core/admin/admin/src/components/PluginsInitializer/init.js @@ -0,0 +1,11 @@ +const init = plugins => { + return { + plugins: Object.keys(plugins).reduce((acc, current) => { + acc[current] = { ...plugins[current] }; + + return acc; + }, {}), + }; +}; + +export default init; diff --git a/packages/core/admin/admin/src/components/PluginsInitializer/reducer.js b/packages/core/admin/admin/src/components/PluginsInitializer/reducer.js new file mode 100644 index 0000000000..d21382e503 --- /dev/null +++ b/packages/core/admin/admin/src/components/PluginsInitializer/reducer.js @@ -0,0 +1,22 @@ +import produce from 'immer'; +import set from 'lodash/set'; + +const initialState = { + plugins: null, +}; + +const reducer = (state = initialState, action) => + /* eslint-disable-next-line consistent-return */ + produce(state, draftState => { + switch (action.type) { + case 'SET_PLUGIN_READY': { + set(draftState, ['plugins', action.pluginId, 'isReady'], true); + break; + } + default: + return draftState; + } + }); + +export { initialState }; +export default reducer; diff --git a/packages/core/admin/admin/src/components/PluginsInitializer/tests/index.test.js b/packages/core/admin/admin/src/components/PluginsInitializer/tests/index.test.js new file mode 100644 index 0000000000..bde576f1ee --- /dev/null +++ b/packages/core/admin/admin/src/components/PluginsInitializer/tests/index.test.js @@ -0,0 +1,18 @@ +import React from 'react'; +import { StrapiProvider } from '@strapi/helper-plugin'; +import { render } from '@testing-library/react'; +import PluginsInitializer from '../index'; + +jest.mock('../../../pages/Admin', () => () =>
ADMIN
); + +describe('ADMIN | COMPONENTS | PluginsInitializer', () => { + it('should not crash', () => { + expect( + render( + + + + ) + ); + }); +}); diff --git a/packages/core/admin/admin/src/components/PluginsInitializer/tests/init.test.js b/packages/core/admin/admin/src/components/PluginsInitializer/tests/init.test.js new file mode 100644 index 0000000000..89ba385879 --- /dev/null +++ b/packages/core/admin/admin/src/components/PluginsInitializer/tests/init.test.js @@ -0,0 +1,16 @@ +import init from '../init'; + +describe('ADMIN | COMPONENT | PluginsInitializer | init', () => { + it('should return the initialState', () => { + const plugins = { + pluginA: { + isReady: false, + }, + pluginB: { + isReady: false, + }, + }; + + expect(init(plugins)).toEqual({ plugins }); + }); +}); diff --git a/packages/core/admin/admin/src/components/PluginsInitializer/tests/reducer.test.js b/packages/core/admin/admin/src/components/PluginsInitializer/tests/reducer.test.js new file mode 100644 index 0000000000..808ebce395 --- /dev/null +++ b/packages/core/admin/admin/src/components/PluginsInitializer/tests/reducer.test.js @@ -0,0 +1,40 @@ +import reducer, { initialState } from '../reducer'; + +describe('ADMIN | COMPONENTS | PluginsInitializer | reducer', () => { + let state; + + beforeEach(() => { + state = initialState; + }); + + describe('DEFAULT_ACTION', () => { + it('should return the initialState', () => { + expect(reducer(state, {})).toEqual(initialState); + }); + }); + + describe('SET_PLUGIN_READY', () => { + it('should set the isReady property to true for a plugin', () => { + state = { + plugins: { + pluginA: { + isReady: false, + }, + }, + }; + + const expected = { + plugins: { + pluginA: { isReady: true }, + }, + }; + + const action = { + type: 'SET_PLUGIN_READY', + pluginId: 'pluginA', + }; + + expect(reducer(state, action)).toEqual(expected); + }); + }); +}); diff --git a/packages/core/admin/admin/src/core/apis/Middlewares.js b/packages/core/admin/admin/src/core/apis/Middlewares.js deleted file mode 100644 index ff3d4e332a..0000000000 --- a/packages/core/admin/admin/src/core/apis/Middlewares.js +++ /dev/null @@ -1,15 +0,0 @@ -import cloneDeep from 'lodash/cloneDeep'; - -class Middlewares { - middlewares = []; - - add(middleware) { - this.middlewares.push(middleware); - } - - get middlewares() { - return cloneDeep(this.middlewares); - } -} - -export default () => new Middlewares(); diff --git a/packages/core/admin/admin/src/core/apis/Plugin.js b/packages/core/admin/admin/src/core/apis/Plugin.js index 954b7b3f3a..c7aee59f2d 100644 --- a/packages/core/admin/admin/src/core/apis/Plugin.js +++ b/packages/core/admin/admin/src/core/apis/Plugin.js @@ -5,8 +5,9 @@ class Plugin { this.description = pluginConf.description; // TODO this.icon = pluginConf.icon; + this.initializer = pluginConf.initializer || null; this.injectionZones = pluginConf.injectionZones || {}; - this.isReady = pluginConf.isReady || true; + this.isReady = pluginConf.isReady !== undefined ? pluginConf.isReady : true; // TODO this.isRequired = pluginConf.isRequired; // TODO diff --git a/packages/core/admin/admin/src/core/apis/index.js b/packages/core/admin/admin/src/core/apis/index.js index db21b53c4d..77c8702e6d 100644 --- a/packages/core/admin/admin/src/core/apis/index.js +++ b/packages/core/admin/admin/src/core/apis/index.js @@ -1,2 +1,2 @@ -export { default as Middlewares } from './Middlewares'; +// eslint-disable-next-line import/prefer-default-export export { default as Plugin } from './Plugin'; diff --git a/packages/core/admin/admin/src/core/store/configureStore.js b/packages/core/admin/admin/src/core/store/configureStore.js index c283d389ef..5d2ecdbbc2 100644 --- a/packages/core/admin/admin/src/core/store/configureStore.js +++ b/packages/core/admin/admin/src/core/store/configureStore.js @@ -2,9 +2,12 @@ import { createStore, applyMiddleware } from 'redux'; import createReducer from './createReducer'; const configureStore = app => { - // TODO const middlewares = []; + middlewares.forEach(middleware => { + middlewares.push(middleware()); + }); + return createStore(createReducer(app.reducers), {}, applyMiddleware(...middlewares)); }; diff --git a/packages/core/admin/admin/src/index.js b/packages/core/admin/admin/src/index.js index 838dae0364..e5eb0ddf93 100644 --- a/packages/core/admin/admin/src/index.js +++ b/packages/core/admin/admin/src/index.js @@ -2,8 +2,6 @@ import ReactDOM from 'react-dom'; import StrapiApp from './StrapiApp'; import plugins from './plugins'; -console.log({ plugins }); - const app = StrapiApp({ appPlugins: plugins }); const MOUNT_NODE = document.getElementById('app'); diff --git a/packages/core/admin/admin/src/pages/Admin/index.js b/packages/core/admin/admin/src/pages/Admin/index.js index 942a2de7bf..c1bbb6061a 100644 --- a/packages/core/admin/admin/src/pages/Admin/index.js +++ b/packages/core/admin/admin/src/pages/Admin/index.js @@ -17,7 +17,6 @@ import { isEmpty } from 'lodash'; import { difference, GlobalContextProvider, - LoadingIndicatorPage, CheckPagePermissions, request, } from '@strapi/helper-plugin'; @@ -149,45 +148,11 @@ export class Admin extends React.Component { } }; - hasApluginNotReady = props => { - const { - global: { plugins }, - } = props; - - return !Object.keys(plugins).every(plugin => plugins[plugin].isReady === true); - }; - initApp = async () => { await this.fetchAppInfo(); await this.fetchStrapiLatestRelease(); }; - /** - * Display the app loader until the app is ready - * @returns {Boolean} - */ - showLoader = () => { - return this.hasApluginNotReady(this.props); - }; - - renderInitializers = () => { - const { - global: { plugins }, - } = this.props; - - return Object.keys(plugins).reduce((acc, current) => { - const InitializerComponent = plugins[current].initializer; - - if (InitializerComponent) { - const key = plugins[current].id; - - acc.push(); - } - - return acc; - }, []); - }; - renderPluginDispatcher = props => { // NOTE: Send the needed props instead of everything... @@ -203,22 +168,14 @@ export class Admin extends React.Component { render() { const { admin: { shouldUpdateStrapi }, - global: { autoReload, currentEnvironment, plugins, strapiVersion }, + global: { autoReload, currentEnvironment, strapiVersion }, // FIXME intl: { formatMessage, locale }, + // FIXME + plugins, updatePlugin, } = this.props; - // We need the admin data in order to make the initializers work - if (this.showLoader()) { - return ( - <> - {this.renderInitializers()} - - - ); - } - return ( ', () => { getUserPermissions: jest.fn(), getUserPermissionsError: jest.fn(), getUserPermissionsSucceeded: jest.fn(), + plugins: {}, global: { autoReload: false, currentEnvironment: 'development', hasAdminUser: false, isLoading: true, - plugins: {}, strapiVersion: '3', uuid: false, }, diff --git a/packages/core/admin/admin/src/pages/App/index.js b/packages/core/admin/admin/src/pages/App/index.js index 57f0a0f3c3..bf5dc95891 100644 --- a/packages/core/admin/admin/src/pages/App/index.js +++ b/packages/core/admin/admin/src/pages/App/index.js @@ -10,8 +10,8 @@ import { Switch, Route } from 'react-router-dom'; import { connect } from 'react-redux'; import { bindActionCreators, compose } from 'redux'; import { LoadingIndicatorPage, auth, request } from '@strapi/helper-plugin'; +import PluginsInitializer from '../../components/PluginsInitializer'; import PrivateRoute from '../../components/PrivateRoute'; -import Admin from '../Admin'; import AuthPage from '../AuthPage'; import NotFoundPage from '../NotFoundPage'; import { getUID } from './utils'; @@ -117,7 +117,7 @@ function App(props) { )} exact /> - + diff --git a/packages/core/admin/admin/src/pages/PluginDispatcher/index.js b/packages/core/admin/admin/src/pages/PluginDispatcher/index.js index 59cbe6f255..7357ad38c4 100644 --- a/packages/core/admin/admin/src/pages/PluginDispatcher/index.js +++ b/packages/core/admin/admin/src/pages/PluginDispatcher/index.js @@ -14,7 +14,7 @@ import PageTitle from '../../components/PageTitle'; export function PluginDispatcher(props) { const { - global: { plugins }, + plugins, match: { params: { pluginId }, }, @@ -53,12 +53,12 @@ export function PluginDispatcher(props) { PluginDispatcher.defaultProps = {}; PluginDispatcher.propTypes = { - global: PropTypes.object.isRequired, match: PropTypes.shape({ params: PropTypes.shape({ pluginId: PropTypes.string, }), }).isRequired, + plugins: PropTypes.object.isRequired, }; export default memo(PluginDispatcher); diff --git a/packages/core/admin/admin/src/pages/PluginDispatcher/tests/index.test.js b/packages/core/admin/admin/src/pages/PluginDispatcher/tests/index.test.js index bac1d139a2..e5f2f6824e 100644 --- a/packages/core/admin/admin/src/pages/PluginDispatcher/tests/index.test.js +++ b/packages/core/admin/admin/src/pages/PluginDispatcher/tests/index.test.js @@ -9,7 +9,7 @@ const Email = () =>
Email Plugin
; describe('', () => { it('Should return null if the params does not match the pluginId', () => { const props = { - global: { plugins: {} }, + plugins: {}, match: { params: { pluginId: 'email' } }, }; @@ -20,13 +20,11 @@ describe('', () => { it('Should return the BlockerComponent if the plugin preventRendering prop is true', () => { const props = { - global: { - plugins: { - email: { - mainComponent: Email, - preventComponentRendering: true, - blockerComponent: null, - }, + plugins: { + email: { + mainComponent: Email, + preventComponentRendering: true, + blockerComponent: null, }, }, match: { params: { pluginId: 'email' } }, @@ -39,13 +37,11 @@ describe('', () => { it('Should return a custom BlockerComponent if the plugin preventRendering prop is true and a custom blocker is given', () => { const props = { - global: { - plugins: { - email: { - mainComponent: Email, - preventComponentRendering: true, - blockerComponent: BlockerComponent2, - }, + plugins: { + email: { + mainComponent: Email, + preventComponentRendering: true, + blockerComponent: BlockerComponent2, }, }, match: { params: { pluginId: 'email' } }, @@ -58,11 +54,9 @@ describe('', () => { it("Should return the plugin's mainComponent if all conditions are met", () => { const props = { - global: { - plugins: { - email: { - mainComponent: Email, - }, + plugins: { + email: { + mainComponent: Email, }, }, match: { params: { pluginId: 'email' } }, diff --git a/packages/core/admin/admin/src/reducers.js b/packages/core/admin/admin/src/reducers.js index 1e3c075a9e..29c1b57b63 100644 --- a/packages/core/admin/admin/src/reducers.js +++ b/packages/core/admin/admin/src/reducers.js @@ -1,15 +1,15 @@ import globalReducer from './pages/App/reducer'; import adminReducer from './pages/Admin/reducer'; import languageProviderReducer from './components/LanguageProvider/reducer'; -import permissionsManagerReducer from './components/PermissionsManager/reducer'; import menuReducer from './components/LeftMenu/reducer'; +import permissionsManagerReducer from './components/PermissionsManager/reducer'; const reducers = { - app: globalReducer, admin: adminReducer, + app: globalReducer, language: languageProviderReducer, - permissionsManager: permissionsManagerReducer, menu: menuReducer, + permissionsManager: permissionsManagerReducer, }; export default reducers; diff --git a/packages/plugins/documentation/admin/src/index.js b/packages/plugins/documentation/admin/src/index.js index d755e12c53..56c2949e8b 100644 --- a/packages/plugins/documentation/admin/src/index.js +++ b/packages/plugins/documentation/admin/src/index.js @@ -4,7 +4,6 @@ // Here's the file: strapi/docs/3.0.0-beta.x/guides/registering-a-field-in-admin.md // Also the strapi-generate-plugins/files/admin/src/index.js needs to be updated // IF THE DOC IS NOT UPDATED THE PULL REQUEST WILL NOT BE MERGED - import pluginPkg from '../../package.json'; import pluginPermissions from './permissions'; import pluginId from './pluginId'; @@ -12,43 +11,6 @@ import pluginLogo from './assets/images/logo.svg'; import App from './containers/App'; import trads from './translations'; -// export default strapi => { -// const pluginDescription = pluginPkg.strapi.description || pluginPkg.description; -// const icon = pluginPkg.strapi.icon; -// const name = pluginPkg.strapi.name; -// const plugin = { -// blockerComponent: null, -// blockerComponentProps: {}, -// description: pluginDescription, -// icon, -// id: pluginId, -// injectedComponents: [], -// isReady: true, -// isRequired: pluginPkg.strapi.required || false, -// mainComponent: App, -// name, -// pluginLogo, -// preventComponentRendering: false, -// trads, -// menu: { -// pluginsSectionLinks: [ -// { -// destination: `/plugins/${pluginId}`, -// icon, -// label: { -// id: `${pluginId}.plugin.name`, -// defaultMessage: 'Documentation', -// }, -// name, -// permissions: pluginPermissions.main, -// }, -// ], -// }, -// }; - -// return strapi.registerPlugin(plugin); -// }; - const pluginDescription = pluginPkg.strapi.description || pluginPkg.description; const icon = pluginPkg.strapi.icon; const name = pluginPkg.strapi.name;