mirror of
https://github.com/strapi/strapi.git
synced 2025-11-01 02:16:03 +00:00
Merge pull request #10655 from strapi/core/admin-customisations
[Core] Enable admin panel customisations
This commit is contained in:
commit
c2ae41acb0
@ -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: {
|
||||
|
||||
@ -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',
|
||||
|
||||
@ -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;
|
||||
},
|
||||
};
|
||||
33
examples/getstarted/admin/app.js
Normal file
33
examples/getstarted/admin/app.js
Normal file
@ -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() {},
|
||||
};
|
||||
@ -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: [
|
||||
|
||||
@ -1,2 +0,0 @@
|
||||
STRAPI_ADMIN_SHOW_TUTORIALS=false
|
||||
STRAPI_ADMIN_UPDATE_NOTIFICATION=false
|
||||
@ -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 (
|
||||
<Theme theme={themes}>
|
||||
<Theme theme={this.configurations.theme}>
|
||||
<Providers
|
||||
authLogo={this.configurations.authLogo}
|
||||
components={components}
|
||||
fields={fields}
|
||||
localeNames={localeNames}
|
||||
getAdminInjectedComponents={this.getAdminInjectedComponents}
|
||||
getPlugin={this.getPlugin}
|
||||
messages={this.translations}
|
||||
messages={this.configurations.translations}
|
||||
menu={this.menu}
|
||||
menuLogo={this.configurations.menuLogo}
|
||||
plugins={this.plugins}
|
||||
runHookParallel={this.runHookParallel}
|
||||
runHookWaterfall={(name, initialValue, async = false) => {
|
||||
@ -347,16 +429,29 @@ class StrapiApp {
|
||||
}}
|
||||
runHookSeries={this.runHookSeries}
|
||||
settings={this.settings}
|
||||
showTutorials={this.configurations.tutorials}
|
||||
showReleaseNotification={this.configurations.notifications.releases}
|
||||
store={store}
|
||||
>
|
||||
<BrowserRouter basename={basename}>
|
||||
<App store={store} />
|
||||
</BrowserRouter>
|
||||
<>
|
||||
<Helmet
|
||||
link={[
|
||||
{
|
||||
rel: 'icon',
|
||||
type: 'image/png',
|
||||
href: this.configurations.head.favicon,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
<BrowserRouter basename={basename}>
|
||||
<App store={store} />
|
||||
</BrowserRouter>
|
||||
</>
|
||||
</Providers>
|
||||
</Theme>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
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 });
|
||||
|
||||
@ -1,7 +0,0 @@
|
||||
module.exports = {
|
||||
app: config => {
|
||||
config.locales = ['fr'];
|
||||
|
||||
return config;
|
||||
},
|
||||
};
|
||||
9
packages/core/admin/admin/src/app.js
Normal file
9
packages/core/admin/admin/src/app.js
Normal file
@ -0,0 +1,9 @@
|
||||
export default {
|
||||
config: {},
|
||||
bootstrap(app) {
|
||||
app.injectContentManagerComponent('editView', 'informations', {
|
||||
name: 'i18n-locale-filter-edit-view',
|
||||
Component: () => 'test',
|
||||
});
|
||||
},
|
||||
};
|
||||
|
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 24 KiB |
@ -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,
|
||||
},
|
||||
{
|
||||
|
||||
@ -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 = (
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<AuthenticatedApp />
|
||||
<ConfigurationsContext.Provider value={{ showReleaseNotification: false }}>
|
||||
<AuthenticatedApp />
|
||||
</ConfigurationsContext.Provider>
|
||||
</QueryClientProvider>
|
||||
);
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -1,14 +1,18 @@
|
||||
import React from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import { useConfigurations } from '../../../../hooks';
|
||||
import Wrapper from './Wrapper';
|
||||
|
||||
const LeftMenuHeader = () => (
|
||||
<Wrapper>
|
||||
<Link to="/" className="leftMenuHeaderLink">
|
||||
<span className="projectName" />
|
||||
</Link>
|
||||
</Wrapper>
|
||||
);
|
||||
const LeftMenuHeader = () => {
|
||||
const { menuLogo } = useConfigurations();
|
||||
|
||||
return (
|
||||
<Wrapper logo={menuLogo}>
|
||||
<Link to="/" className="leftMenuHeaderLink">
|
||||
<span className="projectName" />
|
||||
</Link>
|
||||
</Wrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export default LeftMenuHeader;
|
||||
|
||||
@ -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(() => {
|
||||
|
||||
@ -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 <Helmet title={title} link={[{ rel: 'icon', type: 'image/png', href: favicon }]} />;
|
||||
return <Helmet title={title} />;
|
||||
};
|
||||
|
||||
PageTitle.propTypes = {
|
||||
|
||||
@ -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 (
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<Provider store={store}>
|
||||
<AdminContext.Provider value={{ getAdminInjectedComponents }}>
|
||||
<StrapiAppProvider
|
||||
getPlugin={getPlugin}
|
||||
menu={menu}
|
||||
plugins={plugins}
|
||||
runHookParallel={runHookParallel}
|
||||
runHookWaterfall={runHookWaterfall}
|
||||
runHookSeries={runHookSeries}
|
||||
settings={settings}
|
||||
<ConfigurationsContext.Provider
|
||||
value={{ authLogo, menuLogo, showReleaseNotification, showTutorials }}
|
||||
>
|
||||
<LibraryProvider components={components} fields={fields}>
|
||||
<LanguageProvider messages={messages} localeNames={localeNames}>
|
||||
<AutoReloadOverlayBlockerProvider>
|
||||
<OverlayBlocker>
|
||||
<Notifications>{children}</Notifications>
|
||||
</OverlayBlocker>
|
||||
</AutoReloadOverlayBlockerProvider>
|
||||
</LanguageProvider>
|
||||
</LibraryProvider>
|
||||
</StrapiAppProvider>
|
||||
<StrapiAppProvider
|
||||
getPlugin={getPlugin}
|
||||
menu={menu}
|
||||
plugins={plugins}
|
||||
runHookParallel={runHookParallel}
|
||||
runHookWaterfall={runHookWaterfall}
|
||||
runHookSeries={runHookSeries}
|
||||
settings={settings}
|
||||
>
|
||||
<LibraryProvider components={components} fields={fields}>
|
||||
<LanguageProvider messages={messages} localeNames={localeNames}>
|
||||
<AutoReloadOverlayBlockerProvider>
|
||||
<OverlayBlocker>
|
||||
<Notifications>{children}</Notifications>
|
||||
</OverlayBlocker>
|
||||
</AutoReloadOverlayBlockerProvider>
|
||||
</LanguageProvider>
|
||||
</LibraryProvider>
|
||||
</StrapiAppProvider>
|
||||
</ConfigurationsContext.Provider>
|
||||
</AdminContext.Provider>
|
||||
</Provider>
|
||||
</QueryClientProvider>
|
||||
@ -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,
|
||||
};
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { permissions } from './data';
|
||||
import testData, { permissions } from './data';
|
||||
|
||||
export { default as testData } from './data';
|
||||
export { testData };
|
||||
export { permissions };
|
||||
|
||||
@ -0,0 +1,5 @@
|
||||
import { createContext } from 'react';
|
||||
|
||||
const ConfigurationsContext = createContext({});
|
||||
|
||||
export default ConfigurationsContext;
|
||||
@ -1,2 +1,3 @@
|
||||
export { default as AdminContext } from './Admin';
|
||||
export { default as ConfigurationsContext } from './Configurations';
|
||||
export { default as PermissionsDataManagerContext } from './PermisssionsDataManagerContext';
|
||||
|
||||
@ -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';
|
||||
|
||||
@ -0,0 +1,10 @@
|
||||
import { useContext } from 'react';
|
||||
import { ConfigurationsContext } from '../../contexts';
|
||||
|
||||
const useConfigurations = () => {
|
||||
const context = useContext(ConfigurationsContext);
|
||||
|
||||
return context;
|
||||
};
|
||||
|
||||
export default useConfigurations;
|
||||
@ -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);
|
||||
|
||||
@ -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', () => () => <div>menu</div>);
|
||||
|
||||
@ -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 = () => <Img src={LogoStrapi} alt="strapi-logo" />;
|
||||
const Logo = () => {
|
||||
const { authLogo } = useConfigurations();
|
||||
|
||||
return <Img src={authLogo} alt="strapi" />;
|
||||
};
|
||||
|
||||
export default Logo;
|
||||
|
||||
@ -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();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -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',
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
@ -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 = {
|
||||
|
||||
@ -1,10 +0,0 @@
|
||||
'use strict';
|
||||
/* eslint-disable no-unused-vars */
|
||||
|
||||
module.exports = {
|
||||
app: config => {
|
||||
config.locales = ['fr'];
|
||||
|
||||
return config;
|
||||
},
|
||||
};
|
||||
4
packages/generators/app/lib/resources/files/admin/app.js
Normal file
4
packages/generators/app/lib/resources/files/admin/app.js
Normal file
@ -0,0 +1,4 @@
|
||||
export default {
|
||||
config: {},
|
||||
bootstrap() {},
|
||||
};
|
||||
Loading…
x
Reference in New Issue
Block a user