diff --git a/.eslintrc.front.js b/.eslintrc.front.js index fd2fb20510..d0a6fb5f83 100644 --- a/.eslintrc.front.js +++ b/.eslintrc.front.js @@ -36,8 +36,6 @@ module.exports = { BACKEND_URL: true, PUBLIC_PATH: true, NODE_ENV: true, - STRAPI_ADMIN_SHOW_TUTORIALS: true, - STRAPI_ADMIN_UPDATE_NOTIFICATION: true, }, settings: { react: { diff --git a/.eslintrc.js b/.eslintrc.js index 493c94f7d7..ea57b08c8d 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,5 +1,6 @@ const frontPaths = [ 'packages/**/admin/src/**/*.js', + 'packages/generators/app/lib/resources/files/admin/app.js', 'packages/**/ee/admin/**/*.js', 'packages/core/helper-plugin/**/*.js', 'packages/**/tests/front/**/*.js', diff --git a/examples/getstarted/admin/admin.config.js b/examples/getstarted/admin/admin.config.js deleted file mode 100644 index a504354550..0000000000 --- a/examples/getstarted/admin/admin.config.js +++ /dev/null @@ -1,15 +0,0 @@ -'use strict'; - -module.exports = { - webpack: (config, webpack) => { - // Note: we provide webpack above so you should not `require` it - // Perform customizations to webpack config - // Important: return the modified config - return config; - }, - app: config => { - config.locales = ['fr']; - - return config; - }, -}; diff --git a/examples/getstarted/admin/app.js b/examples/getstarted/admin/app.js new file mode 100644 index 0000000000..7aa7ab438e --- /dev/null +++ b/examples/getstarted/admin/app.js @@ -0,0 +1,33 @@ +// import MyCompo from './extensions/MyCompo'; + +export default { + config: { + // Leaving this commented on purpose + // auth: { + // logo: + // 'https://images.unsplash.com/photo-1593642634367-d91a135587b5?ixid=MnwxMjA3fDF8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=750&q=80', + // }, + // head: { + // favicon: + // 'https://images.unsplash.com/photo-1593642634367-d91a135587b5?ixid=MnwxMjA3fDF8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=750&q=80', + // title: 'Strapi', + // }, + // locales: ['fr', 'toto'], + // menu: { + // logo: null, + // }, + // theme: { + // main: { + // colors: { ok: 't' }, + // }, + // }, + // translations: { + // fr: { + // 'Auth.form.email.label': 'test', + // }, + // }, + // tutorials: false, + // notifications: { release: false }, + }, + bootstrap() {}, +}; diff --git a/jest.config.front.js b/jest.config.front.js index 168fa3b07b..ca140b5c42 100644 --- a/jest.config.front.js +++ b/jest.config.front.js @@ -45,8 +45,7 @@ module.exports = { BACKEND_URL: 'http://localhost:1337', ADMIN_PATH: '/admin', NODE_ENV: 'test', - 'process.env.STRAPI_ADMIN_SHOW_TUTORIALS': 'false', - 'process.env.STRAPI_ADMIN_UPDATE_NOTIFICATION': 'false', + // FIXME create a clean config file }, moduleDirectories: [ diff --git a/packages/core/admin/.env b/packages/core/admin/.env index 35e4c2df97..e69de29bb2 100644 --- a/packages/core/admin/.env +++ b/packages/core/admin/.env @@ -1,2 +0,0 @@ -STRAPI_ADMIN_SHOW_TUTORIALS=false -STRAPI_ADMIN_UPDATE_NOTIFICATION=false \ No newline at end of file diff --git a/packages/core/admin/admin/src/StrapiApp.js b/packages/core/admin/admin/src/StrapiApp.js index 34e6a21290..901740ca6d 100644 --- a/packages/core/admin/admin/src/StrapiApp.js +++ b/packages/core/admin/admin/src/StrapiApp.js @@ -1,11 +1,16 @@ import React from 'react'; import { BrowserRouter } from 'react-router-dom'; +import merge from 'lodash/merge'; import pick from 'lodash/pick'; +import isFunction from 'lodash/isFunction'; import invariant from 'invariant'; +import { Helmet } from 'react-helmet'; import { basename, createHook } from './core/utils'; import configureStore from './core/store/configureStore'; import { Plugin } from './core/apis'; import App from './pages/App'; +import AuthLogo from './assets/images/logo_strapi_auth.png'; +import MenuLogo from './assets/images/logo_strapi_menu.png'; import Providers from './components/Providers'; import Theme from './components/Theme'; import languageNativeNames from './translations/languageNativeNames'; @@ -16,11 +21,23 @@ import { MUTATE_SINGLE_TYPES_LINKS, } from './exposedHooks'; import injectionZones from './injectionZones'; +import favicon from './favicon.ico'; import themes from './themes'; class StrapiApp { - constructor({ appPlugins, library, locales, middlewares, reducers }) { - this.appLocales = ['en', ...locales.filter(loc => loc !== 'en')]; + constructor({ adminConfig, appPlugins, library, middlewares, reducers }) { + this.customConfigurations = adminConfig.config; + this.customBootstrapConfiguration = adminConfig.bootstrap; + this.configurations = { + authLogo: AuthLogo, + head: { favicon }, + locales: ['en'], + menuLogo: MenuLogo, + notifications: { releases: true }, + theme: themes, + translations: {}, + tutorials: true, + }; this.appPlugins = appPlugins || {}; this.library = library; this.middlewares = middlewares; @@ -155,19 +172,66 @@ class StrapiApp { }); } }); + + if (isFunction(this.customBootstrapConfiguration)) { + this.customBootstrapConfiguration({ + addComponents: this.addComponents, + addFields: this.addFields, + addMenuLink: this.addMenuLink, + addReducers: this.addReducers, + addSettingsLink: this.addSettingsLink, + addSettingsLinks: this.addSettingsLinks, + getPlugin: this.getPlugin, + injectContentManagerComponent: this.injectContentManagerComponent, + registerHook: this.registerHook, + }); + } } bootstrapAdmin = async () => { + await this.createCustomConfigurations(); + this.createHook(INJECT_COLUMN_IN_TABLE); this.createHook(MUTATE_COLLECTION_TYPES_LINKS); this.createHook(MUTATE_SINGLE_TYPES_LINKS); this.createHook(MUTATE_EDIT_VIEW_LAYOUT); - await this.loadAdminTrads(); - return Promise.resolve(); }; + createCustomConfigurations = async () => { + if (this.customConfigurations?.locales) { + this.configurations.locales = [ + 'en', + ...this.customConfigurations.locales?.filter(loc => loc !== 'en'), + ]; + } + + if (this.customConfigurations?.auth?.logo) { + this.configurations.authLogo = this.customConfigurations.auth.logo; + } + + if (this.customConfigurations?.menu?.logo) { + this.configurations.menuLogo = this.customConfigurations.menu.logo; + } + + if (this.customConfigurations?.head?.favicon) { + this.configurations.head.favicon = this.customConfigurations.head.favicon; + } + + if (this.customConfigurations?.theme) { + this.configurations.theme = merge(this.configurations.theme, this.customConfigurations.theme); + } + + if (this.customConfigurations?.notifications?.releases !== undefined) { + this.configurations.notifications.releases = this.customConfigurations.notifications.releases; + } + + if (this.customConfigurations?.tutorials !== undefined) { + this.configurations.tutorials = this.customConfigurations.tutorials; + } + }; + createHook = name => { this.hooksDict[name] = createHook(); }; @@ -235,34 +299,47 @@ class StrapiApp { this.admin.injectionZones.contentManager[containerName][blockName].push(component); }; + /** + * Load the admin translations + * @returns {Object} The imported admin translations + */ async loadAdminTrads() { - const arrayOfPromises = this.appLocales.map(locale => { + const arrayOfPromises = this.configurations.locales.map(locale => { return import(/* webpackChunkName: "[request]" */ `./translations/${locale}.json`) .then(({ default: data }) => { return { data, locale }; }) .catch(() => { - return { data: {}, locale }; + return { data: null, locale }; }); }); const adminLocales = await Promise.all(arrayOfPromises); - this.translations = adminLocales.reduce((acc, current) => { - acc[current.locale] = current.data; + const translations = adminLocales.reduce((acc, current) => { + if (current.data) { + acc[current.locale] = current.data; + } return acc; }, {}); - return Promise.resolve(); + return translations; } + /** + * Load the application's translations and merged the custom translations + * with the default ones. + * + */ async loadTrads() { + const adminTranslations = await this.loadAdminTrads(); + const arrayOfPromises = Object.keys(this.appPlugins) .map(plugin => { const registerTrads = this.appPlugins[plugin].registerTrads; if (registerTrads) { - return registerTrads({ locales: this.appLocales }); + return registerTrads({ locales: this.configurations.locales }); } return null; @@ -284,15 +361,18 @@ class StrapiApp { return acc; }, {}); - this.translations = Object.keys(this.translations).reduce((acc, current) => { + const translations = this.configurations.locales.reduce((acc, current) => { acc[current] = { - ...this.translations[current], + ...adminTranslations[current], ...(mergedTrads[current] || {}), + ...this.customConfigurations?.translations?.[current], }; return acc; }, {}); + this.configurations.translations = translations; + return Promise.resolve(); } @@ -323,7 +403,7 @@ class StrapiApp { render() { const store = this.createStore(); - const localeNames = pick(languageNativeNames, this.appLocales); + const localeNames = pick(languageNativeNames, this.configurations.locales || []); const { components: { components }, @@ -331,15 +411,17 @@ class StrapiApp { } = this.library; return ( - + { @@ -347,16 +429,29 @@ class StrapiApp { }} runHookSeries={this.runHookSeries} settings={this.settings} + showTutorials={this.configurations.tutorials} + showReleaseNotification={this.configurations.notifications.releases} store={store} > - - - + <> + + + + + ); } } -export default ({ appPlugins, library, locales, middlewares, reducers }) => - new StrapiApp({ appPlugins, library, locales, middlewares, reducers }); +export default ({ adminConfig = {}, appPlugins, library, middlewares, reducers }) => + new StrapiApp({ adminConfig, appPlugins, library, middlewares, reducers }); diff --git a/packages/core/admin/admin/src/admin.config.js b/packages/core/admin/admin/src/admin.config.js deleted file mode 100644 index 94503f6c2c..0000000000 --- a/packages/core/admin/admin/src/admin.config.js +++ /dev/null @@ -1,7 +0,0 @@ -module.exports = { - app: config => { - config.locales = ['fr']; - - return config; - }, -}; diff --git a/packages/core/admin/admin/src/app.js b/packages/core/admin/admin/src/app.js new file mode 100644 index 0000000000..aa67c83185 --- /dev/null +++ b/packages/core/admin/admin/src/app.js @@ -0,0 +1,9 @@ +export default { + config: {}, + bootstrap(app) { + app.injectContentManagerComponent('editView', 'informations', { + name: 'i18n-locale-filter-edit-view', + Component: () => 'test', + }); + }, +}; diff --git a/packages/core/admin/admin/src/assets/images/logo_strapi.png b/packages/core/admin/admin/src/assets/images/logo_strapi_auth.png similarity index 100% rename from packages/core/admin/admin/src/assets/images/logo_strapi.png rename to packages/core/admin/admin/src/assets/images/logo_strapi_auth.png diff --git a/packages/core/admin/admin/src/assets/images/logo-strapi.png b/packages/core/admin/admin/src/assets/images/logo_strapi_menu.png similarity index 100% rename from packages/core/admin/admin/src/assets/images/logo-strapi.png rename to packages/core/admin/admin/src/assets/images/logo_strapi_menu.png diff --git a/packages/core/admin/admin/src/components/AuthenticatedApp/index.js b/packages/core/admin/admin/src/components/AuthenticatedApp/index.js index 2bb1561207..39d49a58a5 100644 --- a/packages/core/admin/admin/src/components/AuthenticatedApp/index.js +++ b/packages/core/admin/admin/src/components/AuthenticatedApp/index.js @@ -2,16 +2,16 @@ import React, { useMemo } from 'react'; import { LoadingIndicatorPage, AppInfosContext } from '@strapi/helper-plugin'; import { useQueries } from 'react-query'; import packageJSON from '../../../../package.json'; +import { useConfigurations } from '../../hooks'; import PluginsInitializer from '../PluginsInitializer'; import RBACProvider from '../RBACProvider'; import { fetchAppInfo, fetchCurrentUserPermissions, fetchStrapiLatestRelease } from './utils/api'; import checkLatestStrapiVersion from './utils/checkLatestStrapiVersion'; -const { STRAPI_ADMIN_UPDATE_NOTIFICATION } = process.env; -const canFetchRelease = STRAPI_ADMIN_UPDATE_NOTIFICATION === 'true'; const strapiVersion = packageJSON.version; const AuthenticatedApp = () => { + const { showReleaseNotification } = useConfigurations(); const [ { data: appInfos, status }, { data: tag_name, isLoading }, @@ -21,7 +21,7 @@ const AuthenticatedApp = () => { { queryKey: 'strapi-release', queryFn: fetchStrapiLatestRelease, - enabled: canFetchRelease, + enabled: showReleaseNotification, initialData: strapiVersion, }, { diff --git a/packages/core/admin/admin/src/components/AuthenticatedApp/tests/index.test.js b/packages/core/admin/admin/src/components/AuthenticatedApp/tests/index.test.js index 60b7fc2db8..068716ef44 100644 --- a/packages/core/admin/admin/src/components/AuthenticatedApp/tests/index.test.js +++ b/packages/core/admin/admin/src/components/AuthenticatedApp/tests/index.test.js @@ -1,6 +1,7 @@ import React from 'react'; import { render } from '@testing-library/react'; import { QueryClientProvider, QueryClient } from 'react-query'; +import { ConfigurationsContext } from '../../../contexts'; import { fetchAppInfo, fetchCurrentUserPermissions, fetchStrapiLatestRelease } from '../utils/api'; import packageJSON from '../../../../../package.json'; import AuthenticatedApp from '..'; @@ -27,7 +28,9 @@ const queryClient = new QueryClient({ const app = ( - + + + ); diff --git a/packages/core/admin/admin/src/components/LeftMenu/compos/Header/Wrapper.js b/packages/core/admin/admin/src/components/LeftMenu/compos/Header/Wrapper.js index 5d43bc284a..d784176480 100644 --- a/packages/core/admin/admin/src/components/LeftMenu/compos/Header/Wrapper.js +++ b/packages/core/admin/admin/src/components/LeftMenu/compos/Header/Wrapper.js @@ -1,8 +1,6 @@ import styled from 'styled-components'; import PropTypes from 'prop-types'; -import Logo from '../../../../assets/images/logo-strapi.png'; - const Wrapper = styled.div` background-color: #007eff; padding-left: 2rem; @@ -22,7 +20,7 @@ const Wrapper = styled.div` letter-spacing: 0.2rem; color: $white; - background-image: url(${Logo}); + background-image: url(${props => props.logo}); background-repeat: no-repeat; background-position: left center; background-size: auto 2.5rem; diff --git a/packages/core/admin/admin/src/components/LeftMenu/compos/Header/index.js b/packages/core/admin/admin/src/components/LeftMenu/compos/Header/index.js index 993583037b..5d8636a97d 100644 --- a/packages/core/admin/admin/src/components/LeftMenu/compos/Header/index.js +++ b/packages/core/admin/admin/src/components/LeftMenu/compos/Header/index.js @@ -1,14 +1,18 @@ import React from 'react'; import { Link } from 'react-router-dom'; - +import { useConfigurations } from '../../../../hooks'; import Wrapper from './Wrapper'; -const LeftMenuHeader = () => ( - - - - - -); +const LeftMenuHeader = () => { + const { menuLogo } = useConfigurations(); + + return ( + + + + + + ); +}; export default LeftMenuHeader; diff --git a/packages/core/admin/admin/src/components/Onboarding/index.js b/packages/core/admin/admin/src/components/Onboarding/index.js index c5b23f5788..ebca9acac8 100644 --- a/packages/core/admin/admin/src/components/Onboarding/index.js +++ b/packages/core/admin/admin/src/components/Onboarding/index.js @@ -5,6 +5,8 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faQuestion, faTimes } from '@fortawesome/free-solid-svg-icons'; import cn from 'classnames'; import { useTracking } from '@strapi/helper-plugin'; + +import { useConfigurations } from '../../hooks'; import formatVideoArray from './utils/formatAndStoreVideoArray'; import StaticLinks from './StaticLinks'; import Video from './Video'; @@ -12,7 +14,9 @@ import Wrapper from './Wrapper'; import reducer, { initialState } from './reducer'; const Onboarding = () => { - if (process.env.STRAPI_ADMIN_SHOW_TUTORIALS !== 'true') { + const { showTutorials } = useConfigurations(); + + if (!showTutorials) { return null; } @@ -21,6 +25,7 @@ const Onboarding = () => { const OnboardingVideos = () => { const { trackUsage } = useTracking(); + const [{ isLoading, isOpen, videos }, dispatch] = useReducer(reducer, initialState); useEffect(() => { diff --git a/packages/core/admin/admin/src/components/PageTitle/index.js b/packages/core/admin/admin/src/components/PageTitle/index.js index 4a8c8cbd7c..9acd0ccdb2 100644 --- a/packages/core/admin/admin/src/components/PageTitle/index.js +++ b/packages/core/admin/admin/src/components/PageTitle/index.js @@ -2,10 +2,8 @@ import React, { memo } from 'react'; import { Helmet } from 'react-helmet'; import PropTypes from 'prop-types'; -import favicon from '../../favicon.ico'; - const PageTitle = ({ title }) => { - return ; + return ; }; PageTitle.propTypes = { diff --git a/packages/core/admin/admin/src/components/Providers/index.js b/packages/core/admin/admin/src/components/Providers/index.js index a9d84c3c4b..0ee5be8a3a 100644 --- a/packages/core/admin/admin/src/components/Providers/index.js +++ b/packages/core/admin/admin/src/components/Providers/index.js @@ -3,7 +3,7 @@ import PropTypes from 'prop-types'; import { QueryClientProvider, QueryClient } from 'react-query'; import { LibraryProvider, StrapiAppProvider } from '@strapi/helper-plugin'; import { Provider } from 'react-redux'; -import { AdminContext } from '../../contexts'; +import { AdminContext, ConfigurationsContext } from '../../contexts'; import LanguageProvider from '../LanguageProvider'; import AutoReloadOverlayBlockerProvider from '../AutoReloadOverlayBlockerProvider'; import Notifications from '../Notifications'; @@ -18,6 +18,7 @@ const queryClient = new QueryClient({ }); const Providers = ({ + authLogo, children, components, fields, @@ -25,37 +26,45 @@ const Providers = ({ getPlugin, localeNames, menu, + menuLogo, messages, plugins, runHookParallel, runHookSeries, runHookWaterfall, settings, + showReleaseNotification, + showTutorials, + store, }) => { return ( - - - - - - {children} - - - - - + + + + + + {children} + + + + + + @@ -63,6 +72,7 @@ const Providers = ({ }; Providers.propTypes = { + authLogo: PropTypes.oneOfType([PropTypes.string, PropTypes.any]).isRequired, children: PropTypes.element.isRequired, components: PropTypes.object.isRequired, fields: PropTypes.object.isRequired, @@ -81,12 +91,15 @@ Providers.propTypes = { Component: PropTypes.func, }) ).isRequired, + menuLogo: PropTypes.oneOfType([PropTypes.string, PropTypes.any]).isRequired, messages: PropTypes.object.isRequired, plugins: PropTypes.object.isRequired, runHookParallel: PropTypes.func.isRequired, runHookWaterfall: PropTypes.func.isRequired, runHookSeries: PropTypes.func.isRequired, settings: PropTypes.object.isRequired, + showReleaseNotification: PropTypes.bool.isRequired, + showTutorials: PropTypes.bool.isRequired, store: PropTypes.object.isRequired, }; diff --git a/packages/core/admin/admin/src/content-manager/testUtils/index.js b/packages/core/admin/admin/src/content-manager/testUtils/index.js index f1da2de75c..d03afeec17 100644 --- a/packages/core/admin/admin/src/content-manager/testUtils/index.js +++ b/packages/core/admin/admin/src/content-manager/testUtils/index.js @@ -1,4 +1,4 @@ -import { permissions } from './data'; +import testData, { permissions } from './data'; -export { default as testData } from './data'; +export { testData }; export { permissions }; diff --git a/packages/core/admin/admin/src/contexts/Configurations/index.js b/packages/core/admin/admin/src/contexts/Configurations/index.js new file mode 100644 index 0000000000..fb08328111 --- /dev/null +++ b/packages/core/admin/admin/src/contexts/Configurations/index.js @@ -0,0 +1,5 @@ +import { createContext } from 'react'; + +const ConfigurationsContext = createContext({}); + +export default ConfigurationsContext; diff --git a/packages/core/admin/admin/src/contexts/index.js b/packages/core/admin/admin/src/contexts/index.js index 6f0d8112ea..f47b4cdd7c 100644 --- a/packages/core/admin/admin/src/contexts/index.js +++ b/packages/core/admin/admin/src/contexts/index.js @@ -1,2 +1,3 @@ export { default as AdminContext } from './Admin'; +export { default as ConfigurationsContext } from './Configurations'; export { default as PermissionsDataManagerContext } from './PermisssionsDataManagerContext'; diff --git a/packages/core/admin/admin/src/hooks/index.js b/packages/core/admin/admin/src/hooks/index.js index 72db74642d..85c519b0aa 100644 --- a/packages/core/admin/admin/src/hooks/index.js +++ b/packages/core/admin/admin/src/hooks/index.js @@ -1,3 +1,4 @@ +export { default as useConfigurations } from './useConfigurations'; export { default as useModels } from './useModels'; export { default as useFetchPermissionsLayout } from './useFetchPermissionsLayout'; export { default as useFetchPluginsFromMarketPlace } from './useFetchPluginsFromMarketPlace'; diff --git a/packages/core/admin/admin/src/hooks/useConfigurations/index.js b/packages/core/admin/admin/src/hooks/useConfigurations/index.js new file mode 100644 index 0000000000..2c276eed65 --- /dev/null +++ b/packages/core/admin/admin/src/hooks/useConfigurations/index.js @@ -0,0 +1,10 @@ +import { useContext } from 'react'; +import { ConfigurationsContext } from '../../contexts'; + +const useConfigurations = () => { + const context = useContext(ConfigurationsContext); + + return context; +}; + +export default useConfigurations; diff --git a/packages/core/admin/admin/src/index.js b/packages/core/admin/admin/src/index.js index 5cf73ac263..694d7de2b6 100644 --- a/packages/core/admin/admin/src/index.js +++ b/packages/core/admin/admin/src/index.js @@ -1,7 +1,7 @@ import ReactDOM from 'react-dom'; import { Components, Fields, Middlewares, Reducers } from './core/apis'; import { axiosInstance } from './core/utils'; -import appCustomisations from './admin.config'; +import appCustomisations from './app'; import plugins from './plugins'; import appReducers from './reducers'; @@ -14,11 +14,7 @@ window.strapi = { projectType: 'Community', }; -const appConfig = { - locales: [], -}; - -const customConfig = appCustomisations.app(appConfig); +const customConfig = appCustomisations; const library = { components: Components(), @@ -56,7 +52,8 @@ const run = async () => { const app = StrapiApp.default({ appPlugins: plugins, library, - locales: customConfig.locales, + adminConfig: customConfig, + bootstrap: customConfig, middlewares, reducers, }); @@ -64,6 +61,7 @@ const run = async () => { await app.bootstrapAdmin(); await app.initialize(); await app.bootstrap(); + await app.loadTrads(); ReactDOM.render(app.render(), MOUNT_NODE); diff --git a/packages/core/admin/admin/src/pages/Admin/tests/index.test.js b/packages/core/admin/admin/src/pages/Admin/tests/index.test.js index 7725d5361b..680e172006 100644 --- a/packages/core/admin/admin/src/pages/Admin/tests/index.test.js +++ b/packages/core/admin/admin/src/pages/Admin/tests/index.test.js @@ -20,6 +20,7 @@ jest.mock('../../../hooks', () => ({ useMenu: jest.fn(() => ({ isLoading: true, generalSectionLinks: [], pluginsSectionLinks: [] })), useTrackUsage: jest.fn(), useReleaseNotification: jest.fn(), + useConfigurations: jest.fn(() => ({ showTutorials: false })), })); jest.mock('../../../components/LeftMenu', () => () =>
menu
); diff --git a/packages/core/admin/admin/src/pages/AuthPage/components/Logo/index.js b/packages/core/admin/admin/src/pages/AuthPage/components/Logo/index.js index 5efdddd54e..359f707cdc 100644 --- a/packages/core/admin/admin/src/pages/AuthPage/components/Logo/index.js +++ b/packages/core/admin/admin/src/pages/AuthPage/components/Logo/index.js @@ -1,7 +1,11 @@ import React from 'react'; -import LogoStrapi from '../../../../assets/images/logo_strapi.png'; +import { useConfigurations } from '../../../../hooks'; import Img from './Img'; -const Logo = () => strapi-logo; +const Logo = () => { + const { authLogo } = useConfigurations(); + + return strapi; +}; export default Logo; diff --git a/packages/core/admin/admin/src/tests/StrapiApp.test.js b/packages/core/admin/admin/src/tests/StrapiApp.test.js index 224d32dd86..d71cfa98a3 100644 --- a/packages/core/admin/admin/src/tests/StrapiApp.test.js +++ b/packages/core/admin/admin/src/tests/StrapiApp.test.js @@ -7,11 +7,10 @@ import appReducers from '../reducers'; const library = { fields: Fields(), components: Components() }; const middlewares = { middlewares: [] }; const reducers = { reducers: appReducers }; -const locales = []; describe('ADMIN | StrapiApp', () => { it('should render the app without plugins', () => { - const app = StrapiApp({ middlewares, reducers, library, locales }); + const app = StrapiApp({ middlewares, reducers, library }); const { container } = render(app.render()); expect(container.firstChild).toMatchInlineSnapshot(` @@ -45,7 +44,7 @@ describe('ADMIN | StrapiApp', () => { }); it('should create a valid store', () => { - const app = StrapiApp({ middlewares, reducers, library, locales }); + const app = StrapiApp({ middlewares, reducers, library }); const store = app.createStore(); @@ -54,7 +53,7 @@ describe('ADMIN | StrapiApp', () => { describe('Hook api', () => { it('runs the "moto" hooks in series', () => { - const app = StrapiApp({ middlewares, reducers, library, locales }); + const app = StrapiApp({ middlewares, reducers, library }); app.createHook('hello'); app.createHook('moto'); @@ -72,7 +71,7 @@ describe('ADMIN | StrapiApp', () => { }); it('runs the "moto" hooks in series asynchronously', async () => { - const app = StrapiApp({ middlewares, reducers, library, locales }); + const app = StrapiApp({ middlewares, reducers, library }); app.createHook('hello'); app.createHook('moto'); @@ -90,7 +89,7 @@ describe('ADMIN | StrapiApp', () => { }); it('runs the "moto" hooks in waterfall', () => { - const app = StrapiApp({ middlewares, reducers, library, locales }); + const app = StrapiApp({ middlewares, reducers, library }); app.createHook('hello'); app.createHook('moto'); @@ -106,7 +105,7 @@ describe('ADMIN | StrapiApp', () => { }); it('runs the "moto" hooks in waterfall asynchronously', async () => { - const app = StrapiApp({ middlewares, reducers, library, locales }); + const app = StrapiApp({ middlewares, reducers, library }); app.createHook('hello'); app.createHook('moto'); @@ -122,7 +121,7 @@ describe('ADMIN | StrapiApp', () => { }); it('runs the "moto" hooks in parallel', async () => { - const app = StrapiApp({ middlewares, reducers, library, locales }); + const app = StrapiApp({ middlewares, reducers, library }); app.createHook('hello'); app.createHook('moto'); @@ -142,14 +141,14 @@ describe('ADMIN | StrapiApp', () => { describe('Settings api', () => { it('the settings should be defined', () => { - const app = StrapiApp({ middlewares, reducers, library, locales }); + const app = StrapiApp({ middlewares, reducers, library }); expect(app.settings).toBeDefined(); expect(app.settings.global).toBeDefined(); }); it('should creates a new section', () => { - const app = StrapiApp({ middlewares, reducers, library, locales }); + const app = StrapiApp({ middlewares, reducers, library }); const section = { id: 'foo', intlLabel: { id: 'foo', defaultMessage: 'foo' } }; const links = [ { @@ -166,7 +165,7 @@ describe('ADMIN | StrapiApp', () => { }); it('should add a link correctly to the global section', () => { - const app = StrapiApp({ middlewares, reducers, library, locales }); + const app = StrapiApp({ middlewares, reducers, library }); const link = { Component: jest.fn(), to: '/bar', @@ -181,7 +180,7 @@ describe('ADMIN | StrapiApp', () => { }); it('should add an array of links correctly to the global section', () => { - const app = StrapiApp({ middlewares, reducers, library, locales }); + const app = StrapiApp({ middlewares, reducers, library }); const links = [ { Component: jest.fn(), @@ -200,14 +199,14 @@ describe('ADMIN | StrapiApp', () => { describe('Menu api', () => { it('the menu should be defined', () => { - const app = StrapiApp({ middlewares, reducers, library, locales }); + const app = StrapiApp({ middlewares, reducers, library }); expect(app.menu).toBeDefined(); expect(Array.isArray(app.menu)).toBe(true); }); it('addMenuLink should add a link to the menu', () => { - const app = StrapiApp({ middlewares, reducers, library, locales }); + const app = StrapiApp({ middlewares, reducers, library }); const link = { Component: jest.fn(), to: '/plugins/bar', @@ -223,7 +222,7 @@ describe('ADMIN | StrapiApp', () => { }); it('addCorePluginMenuLink should add a link to the menu', () => { - const app = StrapiApp({ middlewares, reducers, library, locales }); + const app = StrapiApp({ middlewares, reducers, library }); const link = { to: '/plugins/content-type-builder', icon: 'book', @@ -240,4 +239,83 @@ describe('ADMIN | StrapiApp', () => { expect(app.menu[0]).toEqual(link); }); }); + + describe('createCustomConfigurations', () => { + it('should add a locale', () => { + const adminConfig = { + config: { locales: ['fr'] }, + }; + const app = StrapiApp({ middlewares, reducers, library, adminConfig }); + + app.createCustomConfigurations(); + + expect(app.configurations.locales).toEqual(['en', 'fr']); + }); + + it('should override the authLogo', () => { + const adminConfig = { + config: { auth: { logo: 'fr' } }, + }; + const app = StrapiApp({ middlewares, reducers, library, adminConfig }); + + app.createCustomConfigurations(); + + expect(app.configurations.authLogo).toBe('fr'); + }); + + it('should override the menuLogo', () => { + const adminConfig = { + config: { menu: { logo: 'fr' } }, + }; + const app = StrapiApp({ middlewares, reducers, library, adminConfig }); + + app.createCustomConfigurations(); + + expect(app.configurations.menuLogo).toBe('fr'); + }); + + it('should override the favicon', () => { + const adminConfig = { + config: { head: { favicon: 'fr' } }, + }; + const app = StrapiApp({ middlewares, reducers, library, adminConfig }); + + app.createCustomConfigurations(); + + expect(app.configurations.head.favicon).toBe('fr'); + }); + + it('should override the theme', () => { + const adminConfig = { + config: { theme: { main: { colors: { red: 'black' } } } }, + }; + const app = StrapiApp({ middlewares, reducers, library, adminConfig }); + + app.createCustomConfigurations(); + + expect(app.configurations.theme.main.colors.red).toBe('black'); + }); + + it('should override the tutorials', () => { + const adminConfig = { + config: { tutorials: false }, + }; + const app = StrapiApp({ middlewares, reducers, library, adminConfig }); + + app.createCustomConfigurations(); + + expect(app.configurations.tutorials).toBeFalsy(); + }); + + it('should override the release notification', () => { + const adminConfig = { + config: { notifications: { releases: false } }, + }; + const app = StrapiApp({ middlewares, reducers, library, adminConfig }); + + app.createCustomConfigurations(); + + expect(app.configurations.notifications.releases).toBeFalsy(); + }); + }); }); diff --git a/packages/core/admin/env.js b/packages/core/admin/env.js index 6c96f57ecb..c1d72e0320 100644 --- a/packages/core/admin/env.js +++ b/packages/core/admin/env.js @@ -25,8 +25,6 @@ const getClientEnvironment = options => { ADMIN_PATH: options.adminPath, NODE_ENV: options.env || 'development', STRAPI_ADMIN_BACKEND_URL: options.backend, - STRAPI_ADMIN_SHOW_TUTORIALS: 'true', - STRAPI_ADMIN_UPDATE_NOTIFICATION: 'true', } ); diff --git a/packages/core/admin/index.js b/packages/core/admin/index.js index 5f94ba66ec..3e018bef35 100644 --- a/packages/core/admin/index.js +++ b/packages/core/admin/index.js @@ -6,6 +6,7 @@ const fs = require('fs-extra'); const webpack = require('webpack'); const WebpackDevServer = require('webpack-dev-server'); const chalk = require('chalk'); +const chokidar = require('chokidar'); const getWebpackConfig = require('./webpack.config'); const getPkgPath = name => path.dirname(require.resolve(`${name}/package.json`)); @@ -186,14 +187,18 @@ async function createCacheDir(dir) { // copy plugins code await Promise.all(pluginsToCopy.map(name => copyPlugin(name, cacheDir))); - // Copy admin.config.js - const customAdminConfigFilePath = path.join(dir, 'admin', 'admin.config.js'); + // Copy app.js + const customAdminConfigFilePath = path.join(dir, 'admin', 'app.js'); if (fs.existsSync(customAdminConfigFilePath)) { - await fs.copy( - customAdminConfigFilePath, - path.resolve(cacheDir, 'admin', 'src', 'admin.config.js') - ); + await fs.copy(customAdminConfigFilePath, path.resolve(cacheDir, 'admin', 'src', 'app.js')); + } + + // Copy admin extensions folder + const adminExtensionFolder = path.join(dir, 'admin', 'extensions'); + + if (fs.existsSync(adminExtensionFolder)) { + await fs.copy(adminExtensionFolder, path.resolve(cacheDir, 'admin', 'src', 'extensions')); } // create plugins.js with plugins requires @@ -248,6 +253,57 @@ async function watchAdmin({ dir, host, port, browser, options }) { console.log(); console.log(chalk.green(`Admin development at http://${host}:${port}${opts.publicPath}`)); }); + + watchFiles(dir); +} + +/** + * Listen to files change and copy the changed files in the .cache/admin folder + * when using the dev mode + * @param {string} dir + */ +async function watchFiles(dir) { + await createCacheDir(dir); + const cacheDir = path.join(dir, '.cache'); + const appExtensionFile = path.join(dir, 'admin', 'app.js'); + const extensionsPath = path.join(dir, 'admin', 'extensions'); + + // Only watch the admin/app.js file and the files that are in the ./admin/extensions/folder + const filesToWatch = [appExtensionFile, extensionsPath]; + + const watcher = chokidar.watch(filesToWatch, { + ignoreInitial: true, + ignorePermissionErrors: true, + }); + + watcher.on('all', async (event, filePath) => { + const isAppFile = filePath.includes(appExtensionFile); + + // The app.js file needs to be copied in the .cache/admin/src/app.js and the other ones needs to + // be copied in the .cache/admin/src/extensions folder + const targetPath = isAppFile + ? path.join(path.normalize(filePath.split(appExtensionFile)[1]), 'app.js') + : path.join('extensions', path.normalize(filePath.split(extensionsPath)[1])); + + const destFolder = path.join(cacheDir, 'admin', 'src'); + + if (event === 'unlink' || event === 'unlinkDir') { + // Remove the file or folder + // We need to copy the original files when deleting an override one + try { + fs.removeSync(path.join(destFolder, targetPath)); + } catch (err) { + console.log('An error occured while deleting the file', err); + } + } else { + // In any other case just copy the file into the .cache/admin/src folder + try { + await fs.copy(filePath, path.join(destFolder, targetPath)); + } catch (err) { + console.log(err); + } + } + }); } module.exports = { diff --git a/packages/generators/app/lib/resources/files/admin/admin.config.js b/packages/generators/app/lib/resources/files/admin/admin.config.js deleted file mode 100644 index c2d226613e..0000000000 --- a/packages/generators/app/lib/resources/files/admin/admin.config.js +++ /dev/null @@ -1,10 +0,0 @@ -'use strict'; -/* eslint-disable no-unused-vars */ - -module.exports = { - app: config => { - config.locales = ['fr']; - - return config; - }, -}; diff --git a/packages/generators/app/lib/resources/files/admin/app.js b/packages/generators/app/lib/resources/files/admin/app.js new file mode 100644 index 0000000000..bcd08f160f --- /dev/null +++ b/packages/generators/app/lib/resources/files/admin/app.js @@ -0,0 +1,4 @@ +export default { + config: {}, + bootstrap() {}, +};