From ef208589d5d39abcd86eaa781d435b0629e923f9 Mon Sep 17 00:00:00 2001 From: soupette Date: Wed, 16 Jun 2021 13:05:21 +0200 Subject: [PATCH] Created menu api Signed-off-by: soupette --- packages/core/admin/admin/src/StrapiApp.js | 26 +++++++ .../components/LeftMenu/useMenuSections.js | 13 +--- .../utils/tests/toPluginLinks.test.js | 57 --------------- .../LeftMenu/utils/toPluginLinks.js | 19 ----- .../core/admin/admin/src/core/apis/Plugin.js | 4 +- .../core/admin/admin/src/pages/Admin/index.js | 23 ++++-- .../core/admin/admin/src/pages/App/index.js | 2 +- .../admin/src/pages/PluginDispatcher/index.js | 38 ---------- .../PluginDispatcher/tests/index.test.js | 73 ------------------- .../admin/src/pages/SettingsPage/index.js | 10 +-- .../utils/createSectionsRoutes.js | 2 +- .../src/pages/SettingsPage/utils/index.js | 3 +- .../SettingsPage => }/utils/createRoute.js | 0 packages/core/admin/admin/src/utils/index.js | 2 + .../utils/makeUniqueRoutes.js | 0 .../utils/tests/createRoute.test.js | 0 .../core/content-manager/admin/src/index.js | 22 +++--- .../content-type-builder/admin/src/index.js | 23 +++--- .../src/providers/StrapiAppProvider/index.js | 14 ++++ packages/core/upload/admin/src/index.js | 25 +++---- .../plugins/documentation/admin/src/index.js | 27 +++---- packages/plugins/i18n/admin/src/index.js | 1 - 22 files changed, 111 insertions(+), 273 deletions(-) delete mode 100644 packages/core/admin/admin/src/components/LeftMenu/utils/tests/toPluginLinks.test.js delete mode 100644 packages/core/admin/admin/src/components/LeftMenu/utils/toPluginLinks.js delete mode 100644 packages/core/admin/admin/src/pages/PluginDispatcher/index.js delete mode 100644 packages/core/admin/admin/src/pages/PluginDispatcher/tests/index.test.js rename packages/core/admin/admin/src/{pages/SettingsPage => }/utils/createRoute.js (100%) rename packages/core/admin/admin/src/{pages/SettingsPage => }/utils/makeUniqueRoutes.js (100%) rename packages/core/admin/admin/src/{pages/SettingsPage => }/utils/tests/createRoute.test.js (100%) diff --git a/packages/core/admin/admin/src/StrapiApp.js b/packages/core/admin/admin/src/StrapiApp.js index defac28c0a..a7177d0a14 100644 --- a/packages/core/admin/admin/src/StrapiApp.js +++ b/packages/core/admin/admin/src/StrapiApp.js @@ -63,6 +63,28 @@ class StrapiApp { } }; + addCorePluginMenuLink = link => { + const stringifiedLink = JSON.stringify(link); + + invariant(link.to, `link.to should be defined for ${stringifiedLink}`); + invariant( + typeof link.to === 'string', + `Expected link.to to be a string instead received ${typeof link.to}` + ); + invariant( + ['/plugins/content-manager', '/plugins/content-type-builder', '/plugins/upload'].includes( + link.to + ), + 'This method is not available for your plugin' + ); + invariant( + link.intlLabel?.id && link.intlLabel?.defaultMessage, + `link.intlLabel.id & link.intlLabel.defaultMessage for ${stringifiedLink}` + ); + + this.menu.push(link); + }; + addFields = fields => { if (Array.isArray(fields)) { fields.map(field => this.library.fields.add(field)); @@ -87,6 +109,8 @@ class StrapiApp { link.Component && typeof link.Component === 'function', `link.Component should be a valid React Component` ); + + this.menu.push(link); }; addMiddlewares = middlewares => { @@ -133,6 +157,7 @@ class StrapiApp { Object.keys(this.appPlugins).forEach(plugin => { this.appPlugins[plugin].register({ addComponents: this.addComponents, + addCorePluginMenuLink: this.addCorePluginMenuLink, addFields: this.addFields, addMenuLink: this.addMenuLink, addMiddlewares: this.addMiddlewares, @@ -286,6 +311,7 @@ class StrapiApp { { @@ -12,18 +11,16 @@ const useMenuSections = () => { const dispatch = useDispatch(); const { allPermissions } = useRBACProvider(); const { shouldUpdateStrapi } = useAppInfos(); - const { plugins } = useStrapiApp(); + const { menu } = useStrapiApp(); // We are using a ref because we don't want our effect to have this in its dependencies array const generalSectionLinksRef = useRef(state.generalSectionLinks); const shouldUpdateStrapiRef = useRef(shouldUpdateStrapi); - // Once in the app lifecycle the plugins should not be added into any dependencies array, in order to prevent - // the effect to be run when another plugin is using one plugins internal api for instance - // so it's definitely ok to use a ref here - const pluginsRef = useRef(plugins); + // Once in the app lifecycle the menu should not be added into any dependencies array + const menuRef = useRef(menu); const resolvePermissions = async (permissions = allPermissions) => { - const pluginsSectionLinks = toPluginLinks(pluginsRef.current); + const pluginsSectionLinks = menuRef.current; const authorizedPluginSectionLinks = await getPluginSectionLinks( permissions, @@ -46,8 +43,6 @@ const useMenuSections = () => { resolvePermissionsRef.current(allPermissions); }, [allPermissions, dispatch]); - // TODO remove the isDisplayed key from the links it's not useful anymore - return state; }; diff --git a/packages/core/admin/admin/src/components/LeftMenu/utils/tests/toPluginLinks.test.js b/packages/core/admin/admin/src/components/LeftMenu/utils/tests/toPluginLinks.test.js deleted file mode 100644 index 80030806ce..0000000000 --- a/packages/core/admin/admin/src/components/LeftMenu/utils/tests/toPluginLinks.test.js +++ /dev/null @@ -1,57 +0,0 @@ -import toPluginLinks from '../toPluginLinks'; - -describe('toPluginLinks', () => { - it('transforms a plugin object into an array of plugin page links', async () => { - const plugins = [ - { - id: 'content-type-builder', - description: 'content-type-builder.plugin.description', - name: 'Content Type Builder', - menu: { - pluginsSectionLinks: [ - { - destination: '/plugins/content-type-builder', - icon: 'paint-brush', - label: { - id: 'content-type-builder.plugin.name', - defaultMessage: 'Content-Types Builder', - }, - name: 'Content Type Builder', - permissions: [ - { - action: 'plugins::content-type-builder.read', - subject: null, - }, - ], - }, - ], - }, - }, - { - id: 'content-manager', - description: 'content-manager.plugin.description', - name: 'Content Manager', - }, - ]; - - const expected = [ - { - destination: '/plugins/content-type-builder', - icon: 'paint-brush', - label: { - id: 'content-type-builder.plugin.name', - defaultMessage: 'Content-Types Builder', - }, - permissions: [ - { - action: 'plugins::content-type-builder.read', - subject: null, - }, - ], - }, - ]; - const actual = toPluginLinks(plugins); - - expect(actual).toEqual(expected); - }); -}); diff --git a/packages/core/admin/admin/src/components/LeftMenu/utils/toPluginLinks.js b/packages/core/admin/admin/src/components/LeftMenu/utils/toPluginLinks.js deleted file mode 100644 index 6551779d17..0000000000 --- a/packages/core/admin/admin/src/components/LeftMenu/utils/toPluginLinks.js +++ /dev/null @@ -1,19 +0,0 @@ -import get from 'lodash/get'; -import omit from 'lodash/omit'; -import sortLinks from '../../../utils/sortLinks'; - -const toPluginLinks = plugins => { - const pluginsLinks = Object.values(plugins).reduce((acc, current) => { - const pluginsSectionLinks = get(current, 'menu.pluginsSectionLinks', []); - - return [...acc, ...pluginsSectionLinks]; - }, []); - - const sortedLinks = sortLinks(pluginsLinks).map(link => { - return { ...omit(link, 'name') }; - }); - - return sortedLinks; -}; - -export default toPluginLinks; diff --git a/packages/core/admin/admin/src/core/apis/Plugin.js b/packages/core/admin/admin/src/core/apis/Plugin.js index 9e5e991b64..a8184fec09 100644 --- a/packages/core/admin/admin/src/core/apis/Plugin.js +++ b/packages/core/admin/admin/src/core/apis/Plugin.js @@ -7,9 +7,9 @@ class Plugin { this.injectionZones = pluginConf.injectionZones || {}; this.isReady = pluginConf.isReady !== undefined ? pluginConf.isReady : true; this.isRequired = pluginConf.isRequired; - this.mainComponent = pluginConf.mainComponent || null; + // this.mainComponent = pluginConf.mainComponent || null; // TODO - this.menu = pluginConf.menu || null; + // this.menu = pluginConf.menu || null; this.name = pluginConf.name; this.pluginId = pluginConf.id; this.pluginLogo = pluginConf.pluginLogo; diff --git a/packages/core/admin/admin/src/pages/Admin/index.js b/packages/core/admin/admin/src/pages/Admin/index.js index 0f8e7dce67..3edc8daaa1 100644 --- a/packages/core/admin/admin/src/pages/Admin/index.js +++ b/packages/core/admin/admin/src/pages/Admin/index.js @@ -4,10 +4,15 @@ * */ -import React, { Suspense, useEffect, lazy } from 'react'; +import React, { Suspense, useEffect, useMemo, lazy } from 'react'; import { Switch, Route } from 'react-router-dom'; // Components from @strapi/helper-plugin -import { CheckPagePermissions, useTracking, LoadingIndicatorPage } from '@strapi/helper-plugin'; +import { + CheckPagePermissions, + useTracking, + LoadingIndicatorPage, + useStrapiApp, +} from '@strapi/helper-plugin'; import adminPermissions from '../../permissions'; import Header from '../../components/Header/index'; import NavTopRightWrapper from '../../components/NavTopRightWrapper'; @@ -20,6 +25,7 @@ import { useReleaseNotification } from '../../hooks'; import Logout from './Logout'; import Wrapper from './Wrapper'; import Content from './Content'; +import { createRoute } from '../../utils'; const HomePage = lazy(() => import(/* webpackChunkName: "Admin_homePage" */ '../HomePage')); const InstalledPluginsPage = lazy(() => @@ -29,9 +35,7 @@ const MarketplacePage = lazy(() => import(/* webpackChunkName: "Admin_marketplace" */ '../MarketplacePage') ); const NotFoundPage = lazy(() => import('../NotFoundPage')); -const PluginDispatcher = lazy(() => - import(/* webpackChunkName: "Admin_pluginDispatcher" */ '../PluginDispatcher') -); + const ProfilePage = lazy(() => import(/* webpackChunkName: "Admin_profilePage" */ '../ProfilePage') ); @@ -69,6 +73,13 @@ const Admin = () => { useTrackUsage(); // TODO const { isLoading, generalSectionLinks, pluginsSectionLinks } = useMenuSections(); + const { menu } = useStrapiApp(); + + const routes = useMemo(() => { + return menu + .filter(link => link.Component) + .map(({ to, Component, exact }) => createRoute(Component, to, exact)); + }, [menu]); if (isLoading) { return ; @@ -94,7 +105,7 @@ const Admin = () => { - + {routes} diff --git a/packages/core/admin/admin/src/pages/App/index.js b/packages/core/admin/admin/src/pages/App/index.js index 047b24e0b7..b76f0d5c45 100644 --- a/packages/core/admin/admin/src/pages/App/index.js +++ b/packages/core/admin/admin/src/pages/App/index.js @@ -14,12 +14,12 @@ import { TrackingContext, } from '@strapi/helper-plugin'; import PrivateRoute from '../../components/PrivateRoute'; +import { createRoute, makeUniqueRoutes } from '../../utils'; import AuthPage from '../AuthPage'; import NotFoundPage from '../NotFoundPage'; import { getUID } from './utils'; import { Content, Wrapper } from './components'; import routes from './utils/routes'; -import { makeUniqueRoutes, createRoute } from '../SettingsPage/utils'; const AuthenticatedApp = lazy(() => import(/* webpackChunkName: "Admin-authenticatedApp" */ '../../components/AuthenticatedApp') diff --git a/packages/core/admin/admin/src/pages/PluginDispatcher/index.js b/packages/core/admin/admin/src/pages/PluginDispatcher/index.js deleted file mode 100644 index 802e480e98..0000000000 --- a/packages/core/admin/admin/src/pages/PluginDispatcher/index.js +++ /dev/null @@ -1,38 +0,0 @@ -/** - * - * PluginDispatcher - * - */ - -import React, { memo } from 'react'; -import { Redirect, useParams } from 'react-router-dom'; -import get from 'lodash/get'; -import { ErrorBoundary } from 'react-error-boundary'; -import { ErrorFallback, useStrapiApp } from '@strapi/helper-plugin'; -import PageTitle from '../../components/PageTitle'; - -const PluginDispatcher = () => { - const { pluginId } = useParams(); - const { plugins } = useStrapiApp(); - - const pluginToRender = get(plugins, pluginId, null); - - if (!pluginToRender) { - return ; - } - - const { mainComponent, name } = pluginToRender; - const PluginEntryComponent = mainComponent; - - return ( -
- - - - -
- ); -}; - -export default memo(PluginDispatcher); -export { 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 deleted file mode 100644 index 21d9200502..0000000000 --- a/packages/core/admin/admin/src/pages/PluginDispatcher/tests/index.test.js +++ /dev/null @@ -1,73 +0,0 @@ -import React from 'react'; -import { Router, Route, Link } from 'react-router-dom'; -import { StrapiAppProvider } from '@strapi/helper-plugin'; -import { render, screen } from '@testing-library/react'; -import userEvent from '@testing-library/user-event'; -import { createMemoryHistory } from 'history'; -import { PluginDispatcher } from '../index'; - -const Email = () =>
Email Plugin
; - -const makeApp = (history, plugins) => ( - - - Go to email - -

404

} /> -
-
-); - -describe('', () => { - it('should not crash', () => { - const history = createMemoryHistory(); - const App = makeApp(history, {}); - - const { container } = render(App); - - expect(container.firstChild).toMatchInlineSnapshot(` - - Go to email - - `); - }); - - it('should redirect to the 404 page if the params does not match the pluginId', () => { - const plugins = { - email: { - mainComponent: Email, - name: 'email', - }, - }; - const history = createMemoryHistory(); - const route = '/plugins/email-test'; - history.push(route); - - const App = makeApp(history, plugins); - - render(App); - - expect(screen.getByText(/404/i)).toBeInTheDocument(); - }); - - it('should match the pluginId params with the correct plugin', () => { - const plugins = { - email: { - mainComponent: Email, - name: 'email', - }, - }; - const history = createMemoryHistory(); - - const App = makeApp(history, plugins); - - render(App); - - const leftClick = { button: 0 }; - userEvent.click(screen.getByText(/Go to email/i), leftClick); - - expect(screen.getByText(/Email Plugin/i)).toBeInTheDocument(); - }); -}); diff --git a/packages/core/admin/admin/src/pages/SettingsPage/index.js b/packages/core/admin/admin/src/pages/SettingsPage/index.js index bfdc8f6a67..abbf2f6793 100644 --- a/packages/core/admin/admin/src/pages/SettingsPage/index.js +++ b/packages/core/admin/admin/src/pages/SettingsPage/index.js @@ -22,17 +22,11 @@ import HeaderSearch from '../../components/HeaderSearch'; import PageTitle from '../../components/PageTitle'; import SettingsSearchHeaderProvider from '../../components/SettingsHeaderSearchContextProvider'; import { useSettingsMenu } from '../../hooks'; - +import { createRoute, makeUniqueRoutes } from '../../utils'; import ApplicationInfosPage from '../ApplicationInfosPage'; import { ApplicationDetailLink, MenuWrapper, StyledLeftMenu, Wrapper } from './components'; -import { - createRoute, - createSectionsRoutes, - makeUniqueRoutes, - getSectionsToDisplay, - routes, -} from './utils'; +import { createSectionsRoutes, getSectionsToDisplay, routes } from './utils'; function SettingsPage() { const { settingId } = useParams(); diff --git a/packages/core/admin/admin/src/pages/SettingsPage/utils/createSectionsRoutes.js b/packages/core/admin/admin/src/pages/SettingsPage/utils/createSectionsRoutes.js index b16881d605..d1982f027b 100644 --- a/packages/core/admin/admin/src/pages/SettingsPage/utils/createSectionsRoutes.js +++ b/packages/core/admin/admin/src/pages/SettingsPage/utils/createSectionsRoutes.js @@ -1,5 +1,5 @@ import flatMap from 'lodash/flatMap'; -import createRoute from './createRoute'; +import { createRoute } from '../../../utils'; const createSectionsRoutes = settings => { const allLinks = flatMap(settings, section => section.links); diff --git a/packages/core/admin/admin/src/pages/SettingsPage/utils/index.js b/packages/core/admin/admin/src/pages/SettingsPage/utils/index.js index f0dcb4aff3..3ea369d8c3 100644 --- a/packages/core/admin/admin/src/pages/SettingsPage/utils/index.js +++ b/packages/core/admin/admin/src/pages/SettingsPage/utils/index.js @@ -1,5 +1,4 @@ export { default as createSectionsRoutes } from './createSectionsRoutes'; -export { default as createRoute } from './createRoute'; export { default as getSectionsToDisplay } from './getSectionsToDisplay'; -export { default as makeUniqueRoutes } from './makeUniqueRoutes'; + export { default as routes } from './routes'; diff --git a/packages/core/admin/admin/src/pages/SettingsPage/utils/createRoute.js b/packages/core/admin/admin/src/utils/createRoute.js similarity index 100% rename from packages/core/admin/admin/src/pages/SettingsPage/utils/createRoute.js rename to packages/core/admin/admin/src/utils/createRoute.js diff --git a/packages/core/admin/admin/src/utils/index.js b/packages/core/admin/admin/src/utils/index.js index 98f7e4a2d3..ec1ba4811b 100644 --- a/packages/core/admin/admin/src/utils/index.js +++ b/packages/core/admin/admin/src/utils/index.js @@ -1,6 +1,8 @@ export { default as checkFormValidity } from './checkFormValidity'; +export { default as createRoute } from './createRoute'; export { default as formatAPIErrors } from './formatAPIErrors'; export { default as getAttributesToDisplay } from './getAttributesToDisplay'; +export { default as makeUniqueRoutes } from './makeUniqueRoutes'; export { default as sortLinks } from './sortLinks'; export { default as getExistingActions } from './getExistingActions'; diff --git a/packages/core/admin/admin/src/pages/SettingsPage/utils/makeUniqueRoutes.js b/packages/core/admin/admin/src/utils/makeUniqueRoutes.js similarity index 100% rename from packages/core/admin/admin/src/pages/SettingsPage/utils/makeUniqueRoutes.js rename to packages/core/admin/admin/src/utils/makeUniqueRoutes.js diff --git a/packages/core/admin/admin/src/pages/SettingsPage/utils/tests/createRoute.test.js b/packages/core/admin/admin/src/utils/tests/createRoute.test.js similarity index 100% rename from packages/core/admin/admin/src/pages/SettingsPage/utils/tests/createRoute.test.js rename to packages/core/admin/admin/src/utils/tests/createRoute.test.js diff --git a/packages/core/content-manager/admin/src/index.js b/packages/core/content-manager/admin/src/index.js index cddba123a9..e56705bd19 100644 --- a/packages/core/content-manager/admin/src/index.js +++ b/packages/core/content-manager/admin/src/index.js @@ -18,6 +18,15 @@ const name = pluginPkg.strapi.name; export default { register(app) { app.addReducers(reducers); + app.addCorePluginMenuLink({ + to: `/plugins/${pluginId}`, + icon: 'book-open', + intlLabel: { + id: `${pluginId}.plugin.name`, + defaultMessage: 'Content manager', + }, + permissions: pluginPermissions.main, + }); app.registerPlugin({ description: pluginDescription, @@ -31,19 +40,6 @@ export default { isRequired: pluginPkg.strapi.required || false, name, pluginLogo, - menu: { - pluginsSectionLinks: [ - { - to: `/plugins/${pluginId}`, - icon: 'book-open', - intlLabel: { - id: `${pluginId}.plugin.name`, - defaultMessage: 'Content manager', - }, - permissions: pluginPermissions.main, - }, - ], - }, }); }, boot() {}, diff --git a/packages/core/content-type-builder/admin/src/index.js b/packages/core/content-type-builder/admin/src/index.js index 117e8f76ec..3048cfc423 100644 --- a/packages/core/content-type-builder/admin/src/index.js +++ b/packages/core/content-type-builder/admin/src/index.js @@ -19,6 +19,16 @@ const name = pluginPkg.strapi.name; export default { register(app) { app.addReducers(reducers); + app.addCorePluginMenuLink({ + to: `/plugins/${pluginId}`, + icon, + intlLabel: { + id: `${pluginId}.plugin.name`, + defaultMessage: 'Content-Types Builder', + }, + permissions: pluginPermissions.main, + }); + app.registerPlugin({ description: pluginDescription, icon, @@ -27,19 +37,6 @@ export default { isReady: true, name, pluginLogo, - menu: { - pluginsSectionLinks: [ - { - to: `/plugins/${pluginId}`, - icon, - intlLabel: { - id: `${pluginId}.plugin.name`, - defaultMessage: 'Content-Types Builder', - }, - permissions: pluginPermissions.main, - }, - ], - }, // Internal APIs exposed by the CTB for the other plugins to use apis: { forms: formsAPI, diff --git a/packages/core/helper-plugin/lib/src/providers/StrapiAppProvider/index.js b/packages/core/helper-plugin/lib/src/providers/StrapiAppProvider/index.js index fa29988cac..d92163c839 100644 --- a/packages/core/helper-plugin/lib/src/providers/StrapiAppProvider/index.js +++ b/packages/core/helper-plugin/lib/src/providers/StrapiAppProvider/index.js @@ -10,6 +10,7 @@ import StrapiAppContext from '../../contexts/StrapiAppContext'; const StrapiAppProvider = ({ children, getPlugin, + menu, plugins, runHookParallel, runHookWaterfall, @@ -20,6 +21,7 @@ const StrapiAppProvider = ({