Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Plugin requirement hoc #260

Merged
merged 12 commits into from
Sep 29, 2017
30 changes: 24 additions & 6 deletions packages/strapi-admin/admin/src/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -83,13 +83,31 @@ const registerPlugin = (plugin) => {

plugin.leftMenuSections = plugin.leftMenuSections || [];

// Execute bootstrap function.
if (isFunction(plugin.bootstrap)) {
plugin.bootstrap(plugin).then(plugin => {
switch (true) {
// Execute bootstrap function and check if plugin can be rendered
case isFunction(plugin.bootstrap) && isFunction(plugin.pluginRequirements):
plugin.pluginRequirements(plugin)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Avoid nested promises:

plugin.pluginRequirements(plugin)
  .then(plugin => {
    return plugin.bootstrap(plugin);
  })
  .then(plugin => {
    store.dispatch(pluginLoaded(plugin));
  });

.then(plugin => {
plugin.bootstrap(plugin)
.then(plugin => {
store.dispatch(pluginLoaded(plugin));
});
});
break;
// Check if plugin can be rendered
case isFunction(plugin.pluginRequirements):
plugin.pluginRequirements(plugin).then(plugin => {
store.dispatch(pluginLoaded(plugin));
})
break;
// Execute bootstrap function
case isFunction(plugin.bootstrap):
plugin.bootstrap(plugin).then(plugin => {
store.dispatch(pluginLoaded(plugin));
});
break;
default:
store.dispatch(pluginLoaded(plugin));
});
} else {
store.dispatch(pluginLoaded(plugin));
}
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ export class PluginPage extends React.Component { // eslint-disable-line react/p
if (plugin.id === pluginId) {
pluginName = plugin.name;

const Elem = plugin.mainComponent;
const Elem = plugin.preventComponentRendering ? plugin.blockerComponent : plugin.mainComponent;

return <Elem key={plugin.id} {...this.props} />;
}
});
Expand Down
7 changes: 6 additions & 1 deletion packages/strapi-admin/admin/src/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,10 @@
"app.components.LeftMenuLinkContainer.noPluginsInstalled": "No plugins installed yet",
"app.components.LeftMenuLinkContainer.plugins": "Plugins",
"app.components.NotFoundPage.description": "Not Found",
"app.components.NotFoundPage.back": "Back to homepage"
"app.components.NotFoundPage.back": "Back to homepage",
"components.AutoReloadBlocker.header": "Reload feature is required for this plugin.",
"components.AutoReloadBlocker.description": "Open the following file and enable the feature.",
"components.ProductionBlocker.header": "This plugin is only available in development.",
"components.ProductionBlocker.description": "For safety we have to disable this plugin in other environments."

}
6 changes: 5 additions & 1 deletion packages/strapi-admin/admin/src/translations/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,9 @@
"app.components.LeftMenuLinkContainer.noPluginsInstalled": "Aucun plugin installé",
"app.components.LeftMenuLinkContainer.plugins": "Plugins",
"app.components.NotFoundPage.description": "Page introuvable",
"app.components.NotFoundPage.back": "Retourner à la page d'accueil"
"app.components.NotFoundPage.back": "Retourner à la page d'accueil",
"components.AutoReloadBlocker.header": "L' autoReload doit être activé pour ce plugin.",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

L'autoReload doit être activé pour ce plugin.

"components.AutoReloadBlocker.description": "Ouvrez le fichier suivant pour activer cette fonctionnalité.",
"components.ProductionBlocker.header": "Ce plugin est disponible uniquement en développement.",
"components.ProductionBlocker.description": "Pour des raisons de sécurité il est désactivé dans les autres environnements."
}
2 changes: 1 addition & 1 deletion packages/strapi-admin/config/admin.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"languages": ["en", "fr"],
"plugins": {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can remove this because we don't it anymore.

"ports": [3000]
"ports": [3000, 5000, 8000]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This config should be removed.

}
}
19 changes: 18 additions & 1 deletion packages/strapi-helper-plugin/lib/src/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,25 @@
import React from 'react';
import { Provider } from 'react-redux';

import App, { bootstrap } from 'containers/App'; // eslint-disable-line
import App from 'containers/App'; // eslint-disable-line

import configureStore from './store';
import { translationMessages } from './i18n';

const tryRequire = (bootstrap = false) => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The argument should be the name of the file: fileName. So you can call the function in a more generic way: tryRequire('bootstrap'); and tryRequire('pluginRequirements');.

Copy link
Contributor Author

@soupette soupette Sep 28, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tried and it didn't work that's why there was a const path = 'bootstrap'; that I forgot to delete

try {
const path = 'bootstrap';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You don't need this variable.

const config = bootstrap ? require('bootstrap').bootstrap : require('pluginRequirements').shouldRenderCompo;
return config;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use a dynamic return: return require(fileName);

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't do that since passing a fileName parameter didn't work

} catch(err) {
return null;
}
};

const bootstrap = tryRequire(true);
const pluginRequirements = tryRequire();


// Plugin identifier based on the package.json `name` value
const pluginPkg = require('../../../../package.json');
const pluginId = pluginPkg.name.replace(
Expand Down Expand Up @@ -61,6 +75,9 @@ window.Strapi.registerPlugin({
mainComponent: Comp,
translationMessages,
bootstrap,
pluginRequirements,
preventComponentRendering: false,
blockerComponent: null,
});

// Export store
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/**
*
* AutoReloadBlocker
*
*/

import React from 'react';
import { FormattedMessage } from 'react-intl';
import styles from './styles.scss';

function AutoReloadBlocker() {
return (
<div className={styles.autoReloadBlocker}>
<div className={styles.header}>
<div>

<h4>
<FormattedMessage id="components.AutoReloadBlocker.header" />
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't better to create a single, but dynamic Blocker component, that support props like title, description and content (which could even be a component defined in the plugin)?

Copy link
Contributor Author

@soupette soupette Sep 28, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The plugin is in charge of telling which component to render if we prevent it from being displayed. In this case passing such logic is a bit more complex and since we're rendering stateless component instead of the plugin itself I thought that having two different blockers or whatever other component provided by the plugin is a great deal.

</h4>
<p>
<FormattedMessage id="components.AutoReloadBlocker.description" />
</p>
<div className={styles.ide}>
<p>./config/environments/development/server.json</p>
<div>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should use the <pre></pre> instead.

{'{'}
<ul>
<li>"host":&nbsp;&nbsp;&nbsp;"localhost"</li>
<li>"port":&nbsp;&nbsp;&nbsp;1337,</li>
<li>"autoReload":&nbsp;&nbsp;&nbsp;true,</li>
<li>"proxi":&nbsp;&nbsp;&nbsp;{'{'}</li>
<li>&nbsp;&nbsp;&nbsp;"enabled":&nbsp;&nbsp;&nbsp;true</li>
<li>{'}'},</li>
<li>"cron":&nbsp;&nbsp;&nbsp;{'{'}</li>
<li>&nbsp;&nbsp;&nbsp;"enabled":&nbsp;&nbsp;&nbsp;false</li>
<li>{'}'},</li>
</ul>
{'}'}

</div>
</div>
</div>
</div>
</div>
);
}

export default AutoReloadBlocker;
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
.autoReloadBlocker {
padding-top: 5.5rem;
}

.header {
display: flex;
justify-content: center;
font-family: Lato;
> div {
padding-top: 2.5rem;
> h4 {
font-size: 24px;
font-weight: 700;
line-height: 24px;
margin-bottom: 0;
}
> p {
margin-top: -1px;
font-size: 14px;
color: #919BAE;
}
}
&:before{
content: '\f021';
font-family: 'FontAwesome';
font-size: 4.2rem;
color: #323740;
margin-right: 20px;
line-height: 9.3rem;
}

}

.ide {
padding-top: 2.3rem;
> p {
color: #006EE7;
font-size: 14px;
}
> div {
width: 455px;
background: #FFFFFF;
color: #787E8F;
font-size: 12px;
> ul {
padding-left: 20px;
list-style: none;
> li {
list-style: none;
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/**
*
* ProductionBlocker
*
*/

import React from 'react';
import cn from 'classnames';
import { FormattedMessage } from 'react-intl';
import styles from './styles.scss';

function ProductionBlocker() {
return (
<div className={styles.productionBlocker}>
<div className={styles.header}>
<div>
<h4>
<FormattedMessage id="components.ProductionBlocker.header" />
</h4>
<p>
<FormattedMessage id="components.ProductionBlocker.description" />
</p>
<div className={styles.buttonContainer}>
<a className={cn(styles.primary, 'btn')} href="http://strapi.io" target="_blank">Read the documentation</a>
</div>
</div>
</div>
</div>
);
}

export default ProductionBlocker;
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
.productionBlocker {
padding-top: 5.5rem;
}

.header {
display: flex;
justify-content: center;
font-family: Lato;
> div {
padding-top: 2.5rem;
> h4 {
font-size: 24px;
font-weight: 700;
line-height: 24px;
margin-bottom: 0;
}
> p {
margin-top: -1px;
font-size: 14px;
color: #919BAE;
}
}
&:before{
content: '\f05e';
font-family: 'FontAwesome';
font-size: 4.2rem;
color: #323740;
margin-right: 20px;
line-height: 9.3rem;
}

}

.buttonContainer {
padding-top: 2rem;
}

.primary {
border-radius: 0.3rem;
border: none;
font-weight: 500;
min-width: 15rem;
background: linear-gradient(315deg, #0097F6 0%, #005EEA 100%);
-webkit-font-smoothing: antialiased;
color: white;
&:active {
box-shadow: inset 1px 1px 3px rgba(0,0,0,.15);
}
padding-top: 4px;
padding-left: 1.6rem;
padding-right: 1.6rem;
&:before {
content: '\f02d';
font-family: 'FontAwesome';
font-weight: 600;
font-size: 1.3rem;
margin-right: 13px;
}
cursor: pointer;
font-family: Lato;
-webkit-font-smoothing: antialiased;
&:focus {
outline: 0;
}
> i {
margin-right: 1.3rem;
font-weight: 600;
padding-top: 1px;
}
&:hover {
color: white;
&::after {
position: absolute;
width: 100%;
height: 100%;
top: 0;
left: 0;
border-radius: 0.3rem;
content: '';
opacity: 0.1;
background: #FFFFFF;
}
}
}
12 changes: 12 additions & 0 deletions packages/strapi-plugin-content-manager/admin/src/bootstrap.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { generateMenu } from 'containers/App/sagas';

// This method is executed before the load of the plugin
export const bootstrap = (plugin) => new Promise((resole, reject) => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Export as default function.

generateMenu()
.then(menu => {
plugin.leftMenuSections = menu;

resole(plugin);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use resolve instead of resole.

})
.catch(e => reject(e));
});
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import List from 'containers/List';
import { loadModels, updateSchema } from './actions';
import { makeSelectLoading } from './selectors';

import saga, { generateMenu } from './sagas';
import saga from './sagas';

const tryRequire = (path) => {
try {
Expand All @@ -32,17 +32,6 @@ const tryRequire = (path) => {
}
};

// This method is executed before the load of the plugin.
export const bootstrap = (plugin) => new Promise((resolve, reject) => {
generateMenu()
.then(menu => {
plugin.leftMenuSections = menu;

resolve(plugin);
})
.catch(e => reject(e));
});

class App extends React.Component {
componentDidMount() {
const config = tryRequire('../../../../config/admin.json');
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export const shouldRenderCompo = (plugin) => new Promise((resolve) => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

export const shouldRenderCompo = (plugin) => Promise.resolve(plugin);

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Export as default.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The name of the file should be requirements.js instead of pluginRequirements.js in order to be consistent with bootstrap.js.

return resolve(plugin);
});
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ class ContentHeader extends React.Component { // eslint-disable-line react/prefe
if (this.props.isLoading) {
return (
<div className={styles.buttonContainer}>
<Button type="submit" lg primary loader />
<Button type="submit" primary loader />
</div>
);
}
Expand Down
Loading