mirror of
https://github.com/strapi/strapi.git
synced 2025-11-02 10:55:37 +00:00
Merge pull request #2690 from strapi/design/marketplace
Redesign marketplace and add new blocker component
This commit is contained in:
commit
c72d5698d8
@ -23,7 +23,7 @@ const dispatch = store.dispatch;
|
|||||||
|
|
||||||
// Don't inject plugins in development mode.
|
// Don't inject plugins in development mode.
|
||||||
if (window.location.port !== '4000') {
|
if (window.location.port !== '4000') {
|
||||||
fetch(`${strapi.remoteURL}/config/plugins.json`)
|
fetch(`${strapi.remoteURL}/config/plugins.json`, { cache: 'no-cache' })
|
||||||
.then(response => {
|
.then(response => {
|
||||||
return response.json();
|
return response.json();
|
||||||
})
|
})
|
||||||
|
|||||||
@ -110,7 +110,7 @@ function LeftMenuLinkContainer({ layout, plugins }) {
|
|||||||
<LeftMenuLink
|
<LeftMenuLink
|
||||||
icon="shopping-basket"
|
icon="shopping-basket"
|
||||||
label={messages.installNewPlugin.id}
|
label={messages.installNewPlugin.id}
|
||||||
destination="/install-plugin"
|
destination="/marketplace"
|
||||||
/>
|
/>
|
||||||
{hasSettingsManager && (
|
{hasSettingsManager && (
|
||||||
<LeftMenuLink
|
<LeftMenuLink
|
||||||
|
|||||||
@ -10,32 +10,25 @@ import cn from 'classnames';
|
|||||||
import { isEmpty, replace } from 'lodash';
|
import { isEmpty, replace } from 'lodash';
|
||||||
import { FormattedMessage } from 'react-intl';
|
import { FormattedMessage } from 'react-intl';
|
||||||
|
|
||||||
// Temporary picture
|
|
||||||
import Button from 'components/Button';
|
import Button from 'components/Button';
|
||||||
import InstallPluginPopup from 'components/InstallPluginPopup';
|
import InstallPluginPopup from 'components/InstallPluginPopup';
|
||||||
import Official from 'components/Official';
|
|
||||||
// import StarsContainer from 'components/StarsContainer';
|
|
||||||
|
|
||||||
import logoTShirt from 'assets/images/logo-t-shirt.svg';
|
|
||||||
import styles from './styles.scss';
|
import styles from './styles.scss';
|
||||||
import Screenshot from './screenshot.png';
|
|
||||||
|
const PLUGINS_WITH_CONFIG = ['content-manager', 'email', 'upload'];
|
||||||
|
|
||||||
/* eslint-disable react/no-unused-state */
|
/* eslint-disable react/no-unused-state */
|
||||||
class PluginCard extends React.Component {
|
class PluginCard extends React.Component {
|
||||||
state = { isOpen: false, boostrapCol: 'col-lg-4' };
|
state = {
|
||||||
|
boostrapCol: 'col-lg-4',
|
||||||
|
};
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
this.shouldOpenModal(this.props);
|
// Listen window resize.
|
||||||
window.addEventListener('resize', this.setBoostrapCol);
|
window.addEventListener('resize', this.setBoostrapCol);
|
||||||
this.setBoostrapCol();
|
this.setBoostrapCol();
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillReceiveProps(nextProps) {
|
|
||||||
if (nextProps.history.location.hash !== this.props.history.location.hash) {
|
|
||||||
this.shouldOpenModal(nextProps);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
window.removeEventListener('resize', this.setBoostrapCol);
|
window.removeEventListener('resize', this.setBoostrapCol);
|
||||||
}
|
}
|
||||||
@ -65,6 +58,15 @@ class PluginCard extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleClickSettings = (e) => {
|
||||||
|
const settingsPath = this.props.plugin.id === 'content-manager' ? '/plugins/content-manager/ctm-configurations' : `/plugins/${this.props.plugin.id}/configurations/${this.props.currentEnvironment}`;
|
||||||
|
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
|
||||||
|
this.props.history.push(settingsPath);
|
||||||
|
}
|
||||||
|
|
||||||
handleDownloadPlugin = (e) => {
|
handleDownloadPlugin = (e) => {
|
||||||
if (!this.props.isAlreadyInstalled && this.props.plugin.id !== 'support-us') {
|
if (!this.props.isAlreadyInstalled && this.props.plugin.id !== 'support-us') {
|
||||||
this.props.downloadPlugin(e);
|
this.props.downloadPlugin(e);
|
||||||
@ -75,23 +77,15 @@ class PluginCard extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
shouldOpenModal = (props) => {
|
|
||||||
this.setState({ isOpen: !isEmpty(props.history.location.hash) });
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const buttonClass = !this.props.isAlreadyInstalled || this.props.showSupportUsButton ? styles.primary : styles.secondary;
|
const buttonClass = !this.props.isAlreadyInstalled ? styles.primary : styles.secondary;
|
||||||
|
const buttonLabel = this.props.isAlreadyInstalled ? 'app.components.PluginCard.Button.label.install' : 'app.components.PluginCard.Button.label.download';
|
||||||
|
|
||||||
let buttonLabel = this.props.isAlreadyInstalled ? 'app.components.PluginCard.Button.label.install' : 'app.components.PluginCard.Button.label.download';
|
// Display settings link for a selection of plugins.
|
||||||
|
const settingsComponent = (PLUGINS_WITH_CONFIG.includes(this.props.plugin.id) &&
|
||||||
if (this.props.showSupportUsButton) {
|
<div className={styles.settings} onClick={this.handleClickSettings}>
|
||||||
buttonLabel = 'app.components.PluginCard.Button.label.support';
|
<i className='fa fa-cog' />
|
||||||
}
|
<FormattedMessage id='app.components.PluginCard.settings' />
|
||||||
|
|
||||||
const pluginIcon = (
|
|
||||||
<div className={styles.frame}>
|
|
||||||
<span className={styles.helper} />
|
|
||||||
<img src={`${this.props.plugin.id === 'support-us' ? logoTShirt : this.props.plugin.logo}`} alt="icon" />
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -101,37 +95,21 @@ class PluginCard extends React.Component {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={cn(this.state.boostrapCol, styles.pluginCard)} onClick={this.handleClick}>
|
<div className={cn(this.state.boostrapCol, styles.pluginCard)}>
|
||||||
<div className={styles.wrapper}>
|
<div className={styles.wrapper}>
|
||||||
<div className={styles.cardTitle}>
|
<div className={styles.cardTitle}>
|
||||||
{pluginIcon}
|
<div className={styles.frame}>
|
||||||
<div>{this.props.plugin.name}</div>
|
<span className={styles.helper} />
|
||||||
|
<img src={this.props.plugin.logo} alt="icon" />
|
||||||
|
</div>
|
||||||
|
<div>{this.props.plugin.name} <i className='fa fa-external-link' onClick={() => window.open(`https://github.com/strapi/strapi/tree/master/packages/strapi-plugin-${this.props.plugin.id}`, '_blank')} /></div>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.cardDescription}>
|
<div className={styles.cardDescription}>
|
||||||
{descriptions.short}
|
{descriptions.long}
|
||||||
<FormattedMessage id="app.components.PluginCard.more-details" />
|
{/* <FormattedMessage id="app.components.PluginCard.more-details" /> */}
|
||||||
</div>
|
|
||||||
<div className={styles.cardScreenshot} style={{ backgroundImage: `url(${Screenshot})` }}>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
<div className={styles.cardPrice}>
|
|
||||||
<div>
|
|
||||||
<i className={`fa fa-${this.props.plugin.isCompatible ? 'check' : 'times'}`} />
|
|
||||||
<FormattedMessage id={`app.components.PluginCard.compatible${this.props.plugin.id === 'support-us' ? 'Community' : ''}`} />
|
|
||||||
</div>
|
|
||||||
<div>{this.props.plugin.price !== 0 ? `${this.props.plugin.price}€` : ''}</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.cardFooter} onClick={e => e.stopPropagation()}>
|
<div className={styles.cardFooter} onClick={e => e.stopPropagation()}>
|
||||||
<div className={styles.ratings}>
|
<div className={styles.cardFooterButton}>
|
||||||
{/*<StarsContainer ratings={this.props.plugin.ratings} />
|
|
||||||
<div>
|
|
||||||
<span style={{ fontWeight: '600', color: '#333740' }}>{this.props.plugin.ratings}</span>
|
|
||||||
<span style={{ fontWeight: '500', color: '#666666' }}>/5</span>
|
|
||||||
</div>
|
|
||||||
*/}
|
|
||||||
<Official />
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<Button
|
<Button
|
||||||
className={cn(buttonClass, styles.button)}
|
className={cn(buttonClass, styles.button)}
|
||||||
label={buttonLabel}
|
label={buttonLabel}
|
||||||
@ -146,6 +124,18 @@ class PluginCard extends React.Component {
|
|||||||
|
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
{this.props.isAlreadyInstalled ?
|
||||||
|
(
|
||||||
|
settingsComponent
|
||||||
|
)
|
||||||
|
:
|
||||||
|
(
|
||||||
|
<div className={styles.compatible}>
|
||||||
|
<i className={`fa fa-${this.props.plugin.isCompatible ? 'check' : 'times'}`} />
|
||||||
|
<FormattedMessage id={`app.components.PluginCard.compatible${this.props.plugin.id === 'support-us' ? 'Community' : ''}`} />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<InstallPluginPopup
|
<InstallPluginPopup
|
||||||
@ -168,15 +158,14 @@ PluginCard.defaultProps = {
|
|||||||
price: 0,
|
price: 0,
|
||||||
ratings: 5,
|
ratings: 5,
|
||||||
},
|
},
|
||||||
showSupportUsButton: false,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
PluginCard.propTypes = {
|
PluginCard.propTypes = {
|
||||||
|
currentEnvironment: PropTypes.string.isRequired,
|
||||||
downloadPlugin: PropTypes.func.isRequired,
|
downloadPlugin: PropTypes.func.isRequired,
|
||||||
history: PropTypes.object.isRequired,
|
history: PropTypes.object.isRequired,
|
||||||
isAlreadyInstalled: PropTypes.bool,
|
isAlreadyInstalled: PropTypes.bool,
|
||||||
plugin: PropTypes.object,
|
plugin: PropTypes.object,
|
||||||
showSupportUsButton: PropTypes.bool,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default PluginCard;
|
export default PluginCard;
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 4.5 KiB |
@ -1,14 +1,37 @@
|
|||||||
.button {
|
.wrapper {
|
||||||
height: 26px;
|
position: relative;
|
||||||
min-width: 89px !important;
|
min-height: 216px;
|
||||||
padding-top: 2px;
|
margin-bottom: 3.6rem;
|
||||||
padding-left: 15px;
|
padding: 1.2rem 1.5rem;
|
||||||
padding-right: 15px;
|
padding-bottom: 0;
|
||||||
margin: 0;
|
background-color: #fff;
|
||||||
border-radius: 2px !important;
|
box-shadow: 0 2px 4px #E3E9F3;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cardTitle {
|
||||||
|
display: flex;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
font-weight: 500 !important;
|
font-weight: 600;
|
||||||
cursor: pointer;
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
|
||||||
|
> div:first-child {
|
||||||
|
margin-right: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
> div:last-child {
|
||||||
|
height: 36px;
|
||||||
|
line-height: 36px;
|
||||||
|
}
|
||||||
|
|
||||||
|
i {
|
||||||
|
margin-left: 7px;
|
||||||
|
color: #B3B5B9;
|
||||||
|
font-size: 1rem;
|
||||||
|
vertical-align: baseline;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.cardDescription {
|
.cardDescription {
|
||||||
@ -25,62 +48,60 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.cardFooter {
|
.cardFooter {
|
||||||
cursor: initial;
|
position: absolute;
|
||||||
|
bottom: 0; left: 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
|
||||||
height: 54px;
|
|
||||||
margin-left: -1.5rem;
|
|
||||||
margin-right: -1.5rem;
|
|
||||||
padding: 1.5rem 1.5rem 0 1.5rem;
|
|
||||||
background-color: #FAFAFB;
|
|
||||||
|
|
||||||
&>div:first-of-type {
|
|
||||||
margin-top: 3px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.cardPrice {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin-bottom: 10px;
|
height: 45px;
|
||||||
padding-top: 2rem;
|
padding: 0.9rem 1.5rem 1rem;
|
||||||
> div:first-child {
|
background-color: #FAFAFB;
|
||||||
> i {
|
justify-content: space-between;
|
||||||
margin-right: 10px;
|
flex-direction: row-reverse;
|
||||||
}
|
cursor: initial;
|
||||||
color: #5A9E06;
|
}
|
||||||
|
|
||||||
|
.compatible {
|
||||||
|
margin-top: 3px;
|
||||||
|
color: #5A9E06;
|
||||||
|
font-size: 1.3rem;
|
||||||
|
font-style: italic;
|
||||||
|
|
||||||
|
> i {
|
||||||
|
margin-right: 7px;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
font-style: italic;
|
|
||||||
line-height: 2.1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
> div:last-child {
|
|
||||||
font-size: 16px;
|
|
||||||
font-weight: 700;
|
|
||||||
line-height: 1.8rem;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.cardScreenshot {
|
.settings{
|
||||||
height: 78px;
|
margin-top: 3px;
|
||||||
border-radius: 2px;
|
color: #323740;
|
||||||
background-size: cover;
|
font-size: 1.3rem;
|
||||||
|
font-weight: 500;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
|
||||||
|
> i {
|
||||||
|
margin-right: 7px;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.cardTitle {
|
.button {
|
||||||
display: flex;
|
height: 26px;
|
||||||
|
min-width: 89px !important;
|
||||||
|
padding: 0 15px;
|
||||||
|
margin: 0;
|
||||||
|
border-radius: 2px !important;
|
||||||
|
line-height: 24px;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
font-weight: 700;
|
font-weight: 500 !important;
|
||||||
text-transform: uppercase;
|
cursor: pointer;
|
||||||
letter-spacing: 0.5px;
|
|
||||||
> div:first-child {
|
|
||||||
margin-right: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
> div:last-child {
|
span {
|
||||||
height: 36px;
|
display: inline-block;
|
||||||
line-height: 36px;
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
line-height: 24px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -89,7 +110,6 @@
|
|||||||
height: 36px;
|
height: 36px;
|
||||||
margin: auto 0;
|
margin: auto 0;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
background: #FAFAFB;
|
|
||||||
border: 1px solid #F3F3F7;
|
border: 1px solid #F3F3F7;
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
@ -99,18 +119,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.iconContainer {
|
|
||||||
height: 36px;
|
|
||||||
width: 70px;
|
|
||||||
margin-right: 14px;
|
|
||||||
background: #FAFAFB;
|
|
||||||
border: 1px solid #F3F3F7;
|
|
||||||
border-radius: 3px;
|
|
||||||
text-align: center;
|
|
||||||
font-size: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
.helper {
|
.helper {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
@ -118,58 +126,21 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.pluginCard {
|
.pluginCard {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.primary {
|
.primary {
|
||||||
font-weight: 500;
|
|
||||||
background: linear-gradient(315deg, #0097F6 0%, #005EEA 100%);
|
background: linear-gradient(315deg, #0097F6 0%, #005EEA 100%);
|
||||||
-webkit-font-smoothing: antialiased;
|
|
||||||
color: white;
|
color: white;
|
||||||
|
font-weight: 500;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
|
||||||
&:active {
|
&:active {
|
||||||
box-shadow: inset 1px 1px 3px rgba(0,0,0,.15);
|
box-shadow: inset 1px 1px 3px rgba(0,0,0,.15);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.ratings {
|
|
||||||
// display: flex;
|
|
||||||
// padding-top: 1px;
|
|
||||||
// TODO uncomment when ratings
|
|
||||||
// > div:last-child {
|
|
||||||
// padding-top: 4px;
|
|
||||||
// font-size: 12px;
|
|
||||||
// font-style: italic;
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
|
|
||||||
.secondary {
|
.secondary {
|
||||||
border: 1px solid #DFE0E1;
|
border: 1px solid #DFE0E1;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
.starsContainer {
|
|
||||||
display: flex;
|
|
||||||
margin-right: 10px;
|
|
||||||
> div {
|
|
||||||
> i {
|
|
||||||
margin-right: 2px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
> div:first-child {
|
|
||||||
color: #EEA348;
|
|
||||||
}
|
|
||||||
> div:last-child {
|
|
||||||
color: #B3B5B9;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.wrapper {
|
|
||||||
min-height: 320px;
|
|
||||||
margin-bottom: 3.9rem;
|
|
||||||
padding: 1.2rem 1.5rem;
|
|
||||||
padding-bottom: 0;
|
|
||||||
background-color: #fff;
|
|
||||||
box-shadow: 0 2px 4px #E3E9F3;
|
|
||||||
-webkit-font-smoothing: antialiased;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
@ -1,8 +0,0 @@
|
|||||||
import Loadable from 'react-loadable';
|
|
||||||
|
|
||||||
import LoadingIndicator from 'components/LoadingIndicator';
|
|
||||||
|
|
||||||
export default Loadable({
|
|
||||||
loader: () => import('./index'),
|
|
||||||
loading: LoadingIndicator,
|
|
||||||
});
|
|
||||||
@ -1,32 +0,0 @@
|
|||||||
/**
|
|
||||||
*
|
|
||||||
* SupportUsBanner
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
import React from 'react';
|
|
||||||
import { FormattedMessage } from 'react-intl';
|
|
||||||
import SupportUsTitle from 'components/SupportUsTitle';
|
|
||||||
import SupportUsCta from 'components/SupportUsCta';
|
|
||||||
|
|
||||||
import styles from './styles.scss';
|
|
||||||
|
|
||||||
function SupportUsBanner() {
|
|
||||||
return (
|
|
||||||
<div className={styles.supportUsBanner}>
|
|
||||||
<div>
|
|
||||||
<div>
|
|
||||||
<SupportUsTitle />
|
|
||||||
<FormattedMessage id="app.components.HomePage.support.content">
|
|
||||||
{message => <p>{message}</p>}
|
|
||||||
</FormattedMessage>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<SupportUsCta />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default SupportUsBanner;
|
|
||||||
@ -1,59 +0,0 @@
|
|||||||
.supportUsBanner {
|
|
||||||
position: relative;
|
|
||||||
height: 135px;
|
|
||||||
margin-top: 2px;
|
|
||||||
margin-bottom: 43px;
|
|
||||||
box-shadow: 0 2px 4px 0 rgba(0,0,0,0.20);
|
|
||||||
line-height: 18px;
|
|
||||||
> div {
|
|
||||||
padding: 38px 50px 0 50px;
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
bottom: 0;
|
|
||||||
right: 0;
|
|
||||||
left: 0;
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
border-radius: 2px;
|
|
||||||
z-index: 999;
|
|
||||||
color: #FFFFFF;
|
|
||||||
> div {
|
|
||||||
> p {
|
|
||||||
max-width: 50rem;
|
|
||||||
margin-top: 17px;
|
|
||||||
margin-bottom: 125px;
|
|
||||||
padding-right: 35px;
|
|
||||||
font-size: 13px;
|
|
||||||
font-weight: 400;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
> div:last-child {
|
|
||||||
margin-right: 260px;
|
|
||||||
padding-top: 20px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
&:before {
|
|
||||||
content: '';
|
|
||||||
border-radius: 2px;
|
|
||||||
background-image: linear-gradient(45deg, #1A67DA 0%, #0097F6 100%) !important;
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
bottom: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
}
|
|
||||||
&:after {
|
|
||||||
opacity: 0.8;
|
|
||||||
content: '';
|
|
||||||
border-radius: 2px;
|
|
||||||
background-image: url('../../assets/images/banner_t-shirt.png') !important;
|
|
||||||
background-size: contain;
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
background-position: right;
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
bottom: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
File diff suppressed because one or more lines are too long
@ -23,6 +23,7 @@ import { pluginLoaded, updatePlugin } from 'containers/App/actions';
|
|||||||
import {
|
import {
|
||||||
makeSelectAppPlugins,
|
makeSelectAppPlugins,
|
||||||
makeSelectBlockApp,
|
makeSelectBlockApp,
|
||||||
|
makeSelectOverlayBlockerProps,
|
||||||
makeSelectIsAppLoading,
|
makeSelectIsAppLoading,
|
||||||
makeSelectShowGlobalAppBlocker,
|
makeSelectShowGlobalAppBlocker,
|
||||||
selectHasUserPlugin,
|
selectHasUserPlugin,
|
||||||
@ -35,7 +36,7 @@ import LocaleToggle from 'containers/LocaleToggle';
|
|||||||
import CTAWrapper from 'components/CtaWrapper';
|
import CTAWrapper from 'components/CtaWrapper';
|
||||||
import Header from 'components/Header/index';
|
import Header from 'components/Header/index';
|
||||||
import HomePage from 'containers/HomePage/Loadable';
|
import HomePage from 'containers/HomePage/Loadable';
|
||||||
import InstallPluginPage from 'containers/InstallPluginPage/Loadable';
|
import Marketplace from 'containers/Marketplace/Loadable';
|
||||||
import LeftMenu from 'containers/LeftMenu';
|
import LeftMenu from 'containers/LeftMenu';
|
||||||
import ListPluginsPage from 'containers/ListPluginsPage/Loadable';
|
import ListPluginsPage from 'containers/ListPluginsPage/Loadable';
|
||||||
import LoadingIndicatorPage from 'components/LoadingIndicatorPage';
|
import LoadingIndicatorPage from 'components/LoadingIndicatorPage';
|
||||||
@ -188,6 +189,8 @@ export class AdminPage extends React.Component {
|
|||||||
return plugins;
|
return plugins;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
renderMarketPlace = props => <Marketplace {...props} {...this.props} />;
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { adminPage } = this.props;
|
const { adminPage } = this.props;
|
||||||
const header = this.showLeftMenu() ? <Header /> : '';
|
const header = this.showLeftMenu() ? <Header /> : '';
|
||||||
@ -219,14 +222,17 @@ export class AdminPage extends React.Component {
|
|||||||
<Route path="/plugins/:pluginId" component={PluginPage} />
|
<Route path="/plugins/:pluginId" component={PluginPage} />
|
||||||
<Route path="/plugins" component={ComingSoonPage} />
|
<Route path="/plugins" component={ComingSoonPage} />
|
||||||
<Route path="/list-plugins" component={ListPluginsPage} exact />
|
<Route path="/list-plugins" component={ListPluginsPage} exact />
|
||||||
<Route path="/install-plugin" component={InstallPluginPage} exact />
|
<Route path="/marketplace" render={this.renderMarketPlace} exact />
|
||||||
<Route path="/configuration" component={ComingSoonPage} exact />
|
<Route path="/configuration" component={ComingSoonPage} exact />
|
||||||
<Route path="" component={NotFoundPage} />
|
<Route path="" component={NotFoundPage} />
|
||||||
<Route path="404" component={NotFoundPage} />
|
<Route path="404" component={NotFoundPage} />
|
||||||
</Switch>
|
</Switch>
|
||||||
</Content>
|
</Content>
|
||||||
</div>
|
</div>
|
||||||
<OverlayBlocker isOpen={this.props.blockApp && this.props.showGlobalAppBlocker} />
|
<OverlayBlocker
|
||||||
|
isOpen={this.props.blockApp && this.props.showGlobalAppBlocker}
|
||||||
|
{...this.props.overlayBlockerData}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -248,6 +254,7 @@ AdminPage.defaultProps = {
|
|||||||
appPlugins: [],
|
appPlugins: [],
|
||||||
hasUserPlugin: true,
|
hasUserPlugin: true,
|
||||||
isAppLoading: true,
|
isAppLoading: true,
|
||||||
|
overlayBlockerData: {},
|
||||||
};
|
};
|
||||||
|
|
||||||
AdminPage.propTypes = {
|
AdminPage.propTypes = {
|
||||||
@ -261,6 +268,7 @@ AdminPage.propTypes = {
|
|||||||
history: PropTypes.object.isRequired,
|
history: PropTypes.object.isRequired,
|
||||||
isAppLoading: PropTypes.bool,
|
isAppLoading: PropTypes.bool,
|
||||||
location: PropTypes.object.isRequired,
|
location: PropTypes.object.isRequired,
|
||||||
|
overlayBlockerData: PropTypes.object,
|
||||||
pluginLoaded: PropTypes.func.isRequired,
|
pluginLoaded: PropTypes.func.isRequired,
|
||||||
plugins: PropTypes.object.isRequired,
|
plugins: PropTypes.object.isRequired,
|
||||||
showGlobalAppBlocker: PropTypes.bool.isRequired,
|
showGlobalAppBlocker: PropTypes.bool.isRequired,
|
||||||
@ -271,6 +279,7 @@ const mapStateToProps = createStructuredSelector({
|
|||||||
adminPage: selectAdminPage(),
|
adminPage: selectAdminPage(),
|
||||||
appPlugins: makeSelectAppPlugins(),
|
appPlugins: makeSelectAppPlugins(),
|
||||||
blockApp: makeSelectBlockApp(),
|
blockApp: makeSelectBlockApp(),
|
||||||
|
overlayBlockerData: makeSelectOverlayBlockerProps(),
|
||||||
hasUserPlugin: selectHasUserPlugin(),
|
hasUserPlugin: selectHasUserPlugin(),
|
||||||
isAppLoading: makeSelectIsAppLoading(),
|
isAppLoading: makeSelectIsAppLoading(),
|
||||||
plugins: selectPlugins(),
|
plugins: selectPlugins(),
|
||||||
@ -295,4 +304,3 @@ const withReducer = injectReducer({ key: 'adminPage', reducer });
|
|||||||
const withSaga = injectSaga({ key: 'adminPage', saga });
|
const withSaga = injectSaga({ key: 'adminPage', saga });
|
||||||
|
|
||||||
export default compose(withReducer, withSaga, withConnect)(AdminPage);
|
export default compose(withReducer, withSaga, withConnect)(AdminPage);
|
||||||
|
|
||||||
|
|||||||
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
.adminPage { /* stylelint-disable */
|
.adminPage { /* stylelint-disable */
|
||||||
display: flex;
|
display: flex;
|
||||||
|
overflow-x: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.adminPageRightWrapper {
|
.adminPageRightWrapper {
|
||||||
|
|||||||
@ -15,9 +15,10 @@ import {
|
|||||||
UPDATE_PLUGIN,
|
UPDATE_PLUGIN,
|
||||||
} from './constants';
|
} from './constants';
|
||||||
|
|
||||||
export function freezeApp() {
|
export function freezeApp(data) {
|
||||||
return {
|
return {
|
||||||
type: FREEZE_APP,
|
type: FREEZE_APP,
|
||||||
|
data,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -19,6 +19,7 @@ import {
|
|||||||
const initialState = fromJS({
|
const initialState = fromJS({
|
||||||
appPlugins: List([]),
|
appPlugins: List([]),
|
||||||
blockApp: false,
|
blockApp: false,
|
||||||
|
overlayBlockerData: null,
|
||||||
hasUserPlugin: true,
|
hasUserPlugin: true,
|
||||||
isAppLoading: true,
|
isAppLoading: true,
|
||||||
plugins: {},
|
plugins: {},
|
||||||
@ -32,7 +33,15 @@ function appReducer(state = initialState, action) {
|
|||||||
case ENABLE_GLOBAL_OVERLAY_BLOCKER:
|
case ENABLE_GLOBAL_OVERLAY_BLOCKER:
|
||||||
return state.set('showGlobalAppBlocker', true);
|
return state.set('showGlobalAppBlocker', true);
|
||||||
case FREEZE_APP:
|
case FREEZE_APP:
|
||||||
return state.set('blockApp', true);
|
return state
|
||||||
|
.set('blockApp', true)
|
||||||
|
.update('overlayBlockerData', () => {
|
||||||
|
if (action.data) {
|
||||||
|
return action.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
});
|
||||||
case GET_APP_PLUGINS_SUCCEEDED:
|
case GET_APP_PLUGINS_SUCCEEDED:
|
||||||
return state
|
return state
|
||||||
.update('appPlugins', () => List(action.appPlugins))
|
.update('appPlugins', () => List(action.appPlugins))
|
||||||
@ -44,7 +53,9 @@ function appReducer(state = initialState, action) {
|
|||||||
case PLUGIN_DELETED:
|
case PLUGIN_DELETED:
|
||||||
return state.deleteIn(['plugins', action.plugin]);
|
return state.deleteIn(['plugins', action.plugin]);
|
||||||
case UNFREEZE_APP:
|
case UNFREEZE_APP:
|
||||||
return state.set('blockApp', false);
|
return state
|
||||||
|
.set('blockApp', false)
|
||||||
|
.set('overlayBlockerData', null);
|
||||||
case UNSET_HAS_USERS_PLUGIN:
|
case UNSET_HAS_USERS_PLUGIN:
|
||||||
return state.set('hasUserPlugin', false);
|
return state.set('hasUserPlugin', false);
|
||||||
default:
|
default:
|
||||||
|
|||||||
@ -34,6 +34,11 @@ const makeSelectBlockApp = () => createSelector(
|
|||||||
(appState) => appState.get('blockApp'),
|
(appState) => appState.get('blockApp'),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const makeSelectOverlayBlockerProps = () => createSelector(
|
||||||
|
selectApp(),
|
||||||
|
(appState) => appState.get('overlayBlockerData'),
|
||||||
|
);
|
||||||
|
|
||||||
const makeSelectIsAppLoading = () => createSelector(
|
const makeSelectIsAppLoading = () => createSelector(
|
||||||
selectApp(),
|
selectApp(),
|
||||||
appState => appState.get('isAppLoading'),
|
appState => appState.get('isAppLoading'),
|
||||||
@ -50,6 +55,7 @@ export {
|
|||||||
selectPlugins,
|
selectPlugins,
|
||||||
makeSelectAppPlugins,
|
makeSelectAppPlugins,
|
||||||
makeSelectBlockApp,
|
makeSelectBlockApp,
|
||||||
|
makeSelectOverlayBlockerProps,
|
||||||
makeSelectIsAppLoading,
|
makeSelectIsAppLoading,
|
||||||
makeSelectShowGlobalAppBlocker,
|
makeSelectShowGlobalAppBlocker,
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,69 +0,0 @@
|
|||||||
/*
|
|
||||||
*
|
|
||||||
* InstallPluginPage actions
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
import {
|
|
||||||
DOWNLOAD_PLUGIN,
|
|
||||||
DOWNLOAD_PLUGIN_ERROR,
|
|
||||||
DOWNLOAD_PLUGIN_SUCCEEDED,
|
|
||||||
GET_AVAILABLE_PLUGINS,
|
|
||||||
GET_AVAILABLE_PLUGINS_SUCCEEDED,
|
|
||||||
GET_INSTALLED_PLUGINS,
|
|
||||||
GET_INSTALLED_PLUGINS_SUCCEEDED,
|
|
||||||
ON_CHANGE,
|
|
||||||
} from './constants';
|
|
||||||
|
|
||||||
export function downloadPlugin(pluginToDownload) {
|
|
||||||
return {
|
|
||||||
type: DOWNLOAD_PLUGIN,
|
|
||||||
pluginToDownload,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function downloadPluginError() {
|
|
||||||
return {
|
|
||||||
type: DOWNLOAD_PLUGIN_ERROR,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function downloadPluginSucceeded() {
|
|
||||||
return {
|
|
||||||
type: DOWNLOAD_PLUGIN_SUCCEEDED,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getAvailablePlugins() {
|
|
||||||
return {
|
|
||||||
type: GET_AVAILABLE_PLUGINS,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getAvailablePluginsSucceeded(availablePlugins) {
|
|
||||||
return {
|
|
||||||
type: GET_AVAILABLE_PLUGINS_SUCCEEDED,
|
|
||||||
availablePlugins,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getInstalledPlugins() {
|
|
||||||
return {
|
|
||||||
type: GET_INSTALLED_PLUGINS,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getInstalledPluginsSucceeded(installedPlugins) {
|
|
||||||
return {
|
|
||||||
type: GET_INSTALLED_PLUGINS_SUCCEEDED,
|
|
||||||
installedPlugins,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function onChange({ target }) {
|
|
||||||
return {
|
|
||||||
type: ON_CHANGE,
|
|
||||||
keys: target.name.split('.'),
|
|
||||||
value: target.value,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@ -1,14 +0,0 @@
|
|||||||
/*
|
|
||||||
*
|
|
||||||
* InstallPluginPage constants
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
export const DOWNLOAD_PLUGIN = 'StrapiAdmin/InstallPluginPage/DOWNLOAD_PLUGIN';
|
|
||||||
export const DOWNLOAD_PLUGIN_ERROR = 'StrapiAdmin/InstallPluginPage/DOWNLOAD_PLUGIN_ERROR';
|
|
||||||
export const DOWNLOAD_PLUGIN_SUCCEEDED = 'StrapiAdmin/InstallPluginPage/DOWNLOAD_PLUGIN_SUCCEEDED';
|
|
||||||
export const GET_AVAILABLE_PLUGINS = 'StrapiAdmin/InstallPluginPage/GET_AVAILABLE_PLUGINS';
|
|
||||||
export const GET_AVAILABLE_PLUGINS_SUCCEEDED = 'StrapiAdmin/InstallPluginPage/GET_AVAILABLE_PLUGINS_SUCCEEDED';
|
|
||||||
export const GET_INSTALLED_PLUGINS = 'StrapiAdmin/InstallPluginPage/GET_INSTALLED_PLUGINS';
|
|
||||||
export const GET_INSTALLED_PLUGINS_SUCCEEDED = 'StrapiAdmin/InstallPluginPage/GET_INSTALLED_PLUGINS_SUCCEEDED';
|
|
||||||
export const ON_CHANGE = 'StrapiAdmin/InstallPluginPage/ON_CHANGE';
|
|
||||||
@ -1,189 +0,0 @@
|
|||||||
/**
|
|
||||||
*
|
|
||||||
* InstallPluginPage
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
import React from 'react';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import { connect } from 'react-redux';
|
|
||||||
import { Helmet } from 'react-helmet';
|
|
||||||
import { FormattedMessage } from 'react-intl';
|
|
||||||
import { bindActionCreators, compose } from 'redux';
|
|
||||||
import cn from 'classnames';
|
|
||||||
import { map } from 'lodash';
|
|
||||||
|
|
||||||
import {
|
|
||||||
disableGlobalOverlayBlocker,
|
|
||||||
enableGlobalOverlayBlocker,
|
|
||||||
} from 'actions/overlayBlocker';
|
|
||||||
|
|
||||||
// Design
|
|
||||||
// import Input from 'components/Input';
|
|
||||||
import DownloadInfo from 'components/DownloadInfo';
|
|
||||||
import OverlayBlocker from 'components/OverlayBlocker';
|
|
||||||
import PluginCard from 'components/PluginCard';
|
|
||||||
import PluginHeader from 'components/PluginHeader';
|
|
||||||
import SupportUsBanner from 'components/SupportUsBanner';
|
|
||||||
import LoadingIndicatorPage from 'components/LoadingIndicatorPage';
|
|
||||||
|
|
||||||
import injectSaga from 'utils/injectSaga';
|
|
||||||
import injectReducer from 'utils/injectReducer';
|
|
||||||
|
|
||||||
import {
|
|
||||||
downloadPlugin,
|
|
||||||
getAvailablePlugins,
|
|
||||||
getInstalledPlugins,
|
|
||||||
onChange,
|
|
||||||
} from './actions';
|
|
||||||
|
|
||||||
import makeSelectInstallPluginPage from './selectors';
|
|
||||||
import reducer from './reducer';
|
|
||||||
import saga from './saga';
|
|
||||||
|
|
||||||
import styles from './styles.scss';
|
|
||||||
|
|
||||||
export class InstallPluginPage extends React.Component { // eslint-disable-line react/prefer-stateless-function
|
|
||||||
getChildContext = () => (
|
|
||||||
{
|
|
||||||
downloadPlugin: this.props.downloadPlugin,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
componentDidMount() {
|
|
||||||
// Disable the AdminPage OverlayBlocker in order to give it a custom design (children)
|
|
||||||
this.props.disableGlobalOverlayBlocker();
|
|
||||||
|
|
||||||
// Don't fetch the available plugins if it has already been done
|
|
||||||
if (!this.props.didFetchPlugins) {
|
|
||||||
this.props.getAvailablePlugins();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get installed plugins
|
|
||||||
this.props.getInstalledPlugins();
|
|
||||||
}
|
|
||||||
|
|
||||||
componentWillUnmount() {
|
|
||||||
// Enable the AdminPage OverlayBlocker so it is displayed when the server is restarting
|
|
||||||
this.props.enableGlobalOverlayBlocker();
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
if (!this.props.didFetchPlugins || !this.props.didFetchInstalledPlugins) {
|
|
||||||
return <LoadingIndicatorPage />;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<OverlayBlocker isOpen={this.props.blockApp}>
|
|
||||||
<DownloadInfo />
|
|
||||||
</OverlayBlocker>
|
|
||||||
<FormattedMessage id="app.components.InstallPluginPage.helmet">
|
|
||||||
{message => (
|
|
||||||
<Helmet>
|
|
||||||
<title>{message}</title>
|
|
||||||
<meta name="description" content="Description of InstallPluginPage" />
|
|
||||||
</Helmet>
|
|
||||||
)}
|
|
||||||
</FormattedMessage>
|
|
||||||
<div className={cn('container-fluid', styles.containerFluid)}>
|
|
||||||
<PluginHeader
|
|
||||||
title={{ id: 'app.components.InstallPluginPage.title' }}
|
|
||||||
description={{ id: 'app.components.InstallPluginPage.description' }}
|
|
||||||
actions={[]}
|
|
||||||
/>
|
|
||||||
<div className="row">
|
|
||||||
<div className="col-md-12 col-lg-12">
|
|
||||||
<SupportUsBanner />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{/*}<div className={cn('row', styles.inputContainer)}>
|
|
||||||
<Input
|
|
||||||
customBootstrapClass="col-md-12"
|
|
||||||
label="app.components.InstallPluginPage.InputSearch.label"
|
|
||||||
name="search"
|
|
||||||
onChange={this.props.onChange}
|
|
||||||
placeholder="app.components.InstallPluginPage.InputSearch.placeholder"
|
|
||||||
type="search"
|
|
||||||
validations={{}}
|
|
||||||
value={this.props.search}
|
|
||||||
/>
|
|
||||||
</div>*/}
|
|
||||||
<div className={cn('row', styles.wrapper)}>
|
|
||||||
{map(this.props.availablePlugins, (plugin) => (
|
|
||||||
<PluginCard
|
|
||||||
history={this.props.history}
|
|
||||||
key={plugin.id}
|
|
||||||
plugin={plugin}
|
|
||||||
showSupportUsButton={plugin.id === 'support-us'}
|
|
||||||
isAlreadyInstalled={this.props.installedPlugins.includes(plugin.id)}
|
|
||||||
downloadPlugin={(e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
e.stopPropagation();
|
|
||||||
|
|
||||||
if (plugin.id !== 'support-us') {
|
|
||||||
this.props.downloadPlugin(plugin.id);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
InstallPluginPage.childContextTypes = {
|
|
||||||
downloadPlugin: PropTypes.func.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
InstallPluginPage.propTypes = {
|
|
||||||
availablePlugins: PropTypes.array.isRequired,
|
|
||||||
blockApp: PropTypes.bool.isRequired,
|
|
||||||
didFetchInstalledPlugins: PropTypes.bool.isRequired,
|
|
||||||
didFetchPlugins: PropTypes.bool.isRequired,
|
|
||||||
disableGlobalOverlayBlocker: PropTypes.func.isRequired,
|
|
||||||
downloadPlugin: PropTypes.func.isRequired,
|
|
||||||
enableGlobalOverlayBlocker: PropTypes.func.isRequired,
|
|
||||||
getAvailablePlugins: PropTypes.func.isRequired,
|
|
||||||
getInstalledPlugins: PropTypes.func.isRequired,
|
|
||||||
history: PropTypes.object.isRequired,
|
|
||||||
installedPlugins: PropTypes.array.isRequired,
|
|
||||||
// onChange: PropTypes.func.isRequired,
|
|
||||||
// search: PropTypes.string.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
const mapStateToProps = makeSelectInstallPluginPage();
|
|
||||||
|
|
||||||
function mapDispatchToProps(dispatch) {
|
|
||||||
return bindActionCreators(
|
|
||||||
{
|
|
||||||
disableGlobalOverlayBlocker,
|
|
||||||
downloadPlugin,
|
|
||||||
enableGlobalOverlayBlocker,
|
|
||||||
getAvailablePlugins,
|
|
||||||
getInstalledPlugins,
|
|
||||||
onChange,
|
|
||||||
},
|
|
||||||
dispatch,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const withConnect = connect(mapStateToProps, mapDispatchToProps);
|
|
||||||
|
|
||||||
/* Remove this line if the container doesn't have a route and
|
|
||||||
* check the documentation to see how to create the container's store
|
|
||||||
*/
|
|
||||||
const withReducer = injectReducer({ key: 'installPluginPage', reducer });
|
|
||||||
|
|
||||||
/* Remove the line below the container doesn't have a route and
|
|
||||||
* check the documentation to see how to create the container's store
|
|
||||||
*/
|
|
||||||
const withSaga = injectSaga({ key: 'installPluginPage', saga });
|
|
||||||
|
|
||||||
export default compose(
|
|
||||||
withReducer,
|
|
||||||
withSaga,
|
|
||||||
withConnect,
|
|
||||||
)(InstallPluginPage);
|
|
||||||
@ -1,56 +0,0 @@
|
|||||||
/*
|
|
||||||
*
|
|
||||||
* InstallPluginPage reducer
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { fromJS, List } from 'immutable';
|
|
||||||
import {
|
|
||||||
DOWNLOAD_PLUGIN,
|
|
||||||
DOWNLOAD_PLUGIN_ERROR,
|
|
||||||
DOWNLOAD_PLUGIN_SUCCEEDED,
|
|
||||||
GET_AVAILABLE_PLUGINS_SUCCEEDED,
|
|
||||||
GET_INSTALLED_PLUGINS_SUCCEEDED,
|
|
||||||
ON_CHANGE,
|
|
||||||
} from './constants';
|
|
||||||
|
|
||||||
const initialState = fromJS({
|
|
||||||
availablePlugins: List([]),
|
|
||||||
installedPlugins: List([]),
|
|
||||||
blockApp: false,
|
|
||||||
didFetchPlugins: false,
|
|
||||||
didFetchInstalledPlugins: false,
|
|
||||||
pluginToDownload: '',
|
|
||||||
search: '',
|
|
||||||
});
|
|
||||||
|
|
||||||
function installPluginPageReducer(state = initialState, action) {
|
|
||||||
switch (action.type) {
|
|
||||||
case DOWNLOAD_PLUGIN:
|
|
||||||
return state
|
|
||||||
.set('blockApp', true)
|
|
||||||
.set('pluginToDownload', action.pluginToDownload);
|
|
||||||
case DOWNLOAD_PLUGIN_ERROR:
|
|
||||||
return state
|
|
||||||
.set('blockApp', false)
|
|
||||||
.set('pluginToDownload', '');
|
|
||||||
case DOWNLOAD_PLUGIN_SUCCEEDED:
|
|
||||||
return state
|
|
||||||
.set('blockApp', false)
|
|
||||||
.set('pluginToDownload', '');
|
|
||||||
case GET_AVAILABLE_PLUGINS_SUCCEEDED:
|
|
||||||
return state
|
|
||||||
.set('didFetchPlugins', true)
|
|
||||||
.set('availablePlugins', List(action.availablePlugins));
|
|
||||||
case GET_INSTALLED_PLUGINS_SUCCEEDED:
|
|
||||||
return state
|
|
||||||
.set('didFetchInstalledPlugins', true)
|
|
||||||
.set('installedPlugins', List(action.installedPlugins));
|
|
||||||
case ON_CHANGE:
|
|
||||||
return state.updateIn(action.keys, () => action.value);
|
|
||||||
default:
|
|
||||||
return state;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default installPluginPageReducer;
|
|
||||||
@ -1,117 +0,0 @@
|
|||||||
import { LOCATION_CHANGE } from 'react-router-redux';
|
|
||||||
import {
|
|
||||||
call,
|
|
||||||
cancel,
|
|
||||||
fork,
|
|
||||||
put,
|
|
||||||
select,
|
|
||||||
take,
|
|
||||||
takeLatest,
|
|
||||||
} from 'redux-saga/effects';
|
|
||||||
|
|
||||||
import request from 'utils/request';
|
|
||||||
|
|
||||||
import { selectLocale } from '../LanguageProvider/selectors';
|
|
||||||
import {
|
|
||||||
downloadPluginError,
|
|
||||||
downloadPluginSucceeded,
|
|
||||||
getAvailablePluginsSucceeded,
|
|
||||||
getInstalledPluginsSucceeded,
|
|
||||||
} from './actions';
|
|
||||||
import { DOWNLOAD_PLUGIN, GET_AVAILABLE_PLUGINS, GET_INSTALLED_PLUGINS } from './constants';
|
|
||||||
import { makeSelectPluginToDownload } from './selectors';
|
|
||||||
|
|
||||||
|
|
||||||
export function* pluginDownload() {
|
|
||||||
try {
|
|
||||||
const pluginToDownload = yield select(makeSelectPluginToDownload());
|
|
||||||
const opts = {
|
|
||||||
method: 'POST',
|
|
||||||
body: {
|
|
||||||
plugin: pluginToDownload,
|
|
||||||
port: window.location.port,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
const response = yield call(request, '/admin/plugins/install', opts, true);
|
|
||||||
|
|
||||||
if (response.ok) {
|
|
||||||
|
|
||||||
yield new Promise(resolve => {
|
|
||||||
setTimeout(() => {
|
|
||||||
resolve();
|
|
||||||
}, 8000);
|
|
||||||
});
|
|
||||||
|
|
||||||
yield put(downloadPluginSucceeded());
|
|
||||||
window.location.reload();
|
|
||||||
}
|
|
||||||
} catch(err) {
|
|
||||||
yield put(downloadPluginError());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function* getAvailablePlugins() {
|
|
||||||
try {
|
|
||||||
// Get current locale.
|
|
||||||
const locale = yield select(selectLocale());
|
|
||||||
|
|
||||||
const opts = {
|
|
||||||
method: 'GET',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
params: {
|
|
||||||
lang: locale,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
let availablePlugins;
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Retrieve plugins list.
|
|
||||||
availablePlugins = yield call(request, 'https://marketplace.strapi.io/plugins', opts);
|
|
||||||
} catch (e) {
|
|
||||||
availablePlugins = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
yield put(getAvailablePluginsSucceeded(availablePlugins));
|
|
||||||
} catch(err) {
|
|
||||||
strapi.notification.error('notification.error');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function* getInstalledPlugins() {
|
|
||||||
try {
|
|
||||||
const opts = {
|
|
||||||
method: 'GET',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
let installedPlugins;
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Retrieve plugins list.
|
|
||||||
installedPlugins = yield call(request, '/admin/plugins', opts);
|
|
||||||
} catch (e) {
|
|
||||||
installedPlugins = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
yield put(getInstalledPluginsSucceeded(Object.keys(installedPlugins.plugins)));
|
|
||||||
} catch(err) {
|
|
||||||
strapi.notification.error('notification.error');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Individual exports for testing
|
|
||||||
export default function* defaultSaga() {
|
|
||||||
const loadAvailablePluginsWatcher = yield fork(takeLatest, GET_AVAILABLE_PLUGINS, getAvailablePlugins);
|
|
||||||
const loadInstalledPluginsWatcher = yield fork(takeLatest, GET_INSTALLED_PLUGINS, getInstalledPlugins);
|
|
||||||
yield fork(takeLatest, DOWNLOAD_PLUGIN, pluginDownload);
|
|
||||||
|
|
||||||
yield take(LOCATION_CHANGE);
|
|
||||||
|
|
||||||
yield cancel(loadAvailablePluginsWatcher);
|
|
||||||
yield cancel(loadInstalledPluginsWatcher);
|
|
||||||
}
|
|
||||||
@ -1,31 +0,0 @@
|
|||||||
import { createSelector } from 'reselect';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Direct selector to the installPluginPage state domain
|
|
||||||
*/
|
|
||||||
const selectInstallPluginPageDomain = () => (state) => state.get('installPluginPage');
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Other specific selectors
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Default selector used by InstallPluginPage
|
|
||||||
*/
|
|
||||||
|
|
||||||
const makeSelectInstallPluginPage = () => createSelector(
|
|
||||||
selectInstallPluginPageDomain(),
|
|
||||||
(substate) => substate.toJS()
|
|
||||||
);
|
|
||||||
|
|
||||||
const makeSelectPluginToDownload = () => createSelector(
|
|
||||||
selectInstallPluginPageDomain(),
|
|
||||||
(substate) => substate.get('pluginToDownload'),
|
|
||||||
);
|
|
||||||
|
|
||||||
export default makeSelectInstallPluginPage;
|
|
||||||
export {
|
|
||||||
selectInstallPluginPageDomain,
|
|
||||||
makeSelectPluginToDownload,
|
|
||||||
};
|
|
||||||
@ -0,0 +1,40 @@
|
|||||||
|
import {
|
||||||
|
DOWNLOAD_PLUGIN,
|
||||||
|
DOWNLOAD_PLUGIN_SUCCEEDED,
|
||||||
|
GET_AVAILABLE_AND_INSTALLED_PLUGINS,
|
||||||
|
GET_AVAILABLE_AND_INSTALLED_PLUGINS_SUCCEEDED,
|
||||||
|
RESET_PROPS,
|
||||||
|
} from './constants';
|
||||||
|
|
||||||
|
export function downloadPlugin(pluginToDownload) {
|
||||||
|
return {
|
||||||
|
type: DOWNLOAD_PLUGIN,
|
||||||
|
pluginToDownload,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function downloadPluginSucceeded() {
|
||||||
|
return {
|
||||||
|
type: DOWNLOAD_PLUGIN_SUCCEEDED,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getAvailableAndInstalledPlugins() {
|
||||||
|
return {
|
||||||
|
type: GET_AVAILABLE_AND_INSTALLED_PLUGINS,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getAvailableAndInstalledPluginsSucceeded(availablePlugins, installedPlugins) {
|
||||||
|
return {
|
||||||
|
type: GET_AVAILABLE_AND_INSTALLED_PLUGINS_SUCCEEDED,
|
||||||
|
availablePlugins,
|
||||||
|
installedPlugins,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function resetProps() {
|
||||||
|
return {
|
||||||
|
type: RESET_PROPS,
|
||||||
|
};
|
||||||
|
}
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
export const DOWNLOAD_PLUGIN = 'StrapiAdmin/Marketplace/DOWNLOAD_PLUGIN';
|
||||||
|
export const DOWNLOAD_PLUGIN_SUCCEEDED = 'StrapiAdmin/Marketplace/DOWNLOAD_PLUGIN_SUCCEEDED';
|
||||||
|
export const GET_AVAILABLE_AND_INSTALLED_PLUGINS = 'StrapiAdmin/Marketplace/GET_AVAILABLE_AND_INSTALLED_PLUGINS';
|
||||||
|
export const GET_AVAILABLE_AND_INSTALLED_PLUGINS_SUCCEEDED = 'StrapiAdmin/Marketplace/GET_AVAILABLE_AND_INSTALLED_PLUGINS_SUCCEEDED';
|
||||||
|
export const RESET_PROPS = 'StrapiAdmin/Marketplace/RESET_PROPS';
|
||||||
155
packages/strapi-admin/admin/src/containers/Marketplace/index.js
Normal file
155
packages/strapi-admin/admin/src/containers/Marketplace/index.js
Normal file
@ -0,0 +1,155 @@
|
|||||||
|
/**
|
||||||
|
*
|
||||||
|
* Marketplace
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { Helmet } from 'react-helmet';
|
||||||
|
import { FormattedMessage } from 'react-intl';
|
||||||
|
import { bindActionCreators, compose } from 'redux';
|
||||||
|
import cn from 'classnames';
|
||||||
|
|
||||||
|
// Design
|
||||||
|
import PluginCard from 'components/PluginCard';
|
||||||
|
import PluginHeader from 'components/PluginHeader';
|
||||||
|
import LoadingIndicatorPage from 'components/LoadingIndicatorPage';
|
||||||
|
|
||||||
|
import injectSaga from 'utils/injectSaga';
|
||||||
|
import injectReducer from 'utils/injectReducer';
|
||||||
|
|
||||||
|
import {
|
||||||
|
downloadPlugin,
|
||||||
|
getAvailableAndInstalledPlugins,
|
||||||
|
resetProps,
|
||||||
|
} from './actions';
|
||||||
|
import makeSelectMarketplace from './selectors';
|
||||||
|
|
||||||
|
import reducer from './reducer';
|
||||||
|
import saga from './saga';
|
||||||
|
|
||||||
|
import styles from './styles.scss';
|
||||||
|
|
||||||
|
class Marketplace extends React.Component {
|
||||||
|
getChildContext = () => (
|
||||||
|
{
|
||||||
|
downloadPlugin: this.props.downloadPlugin,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
// Fetch the available and installed plugins
|
||||||
|
this.props.getAvailableAndInstalledPlugins();
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
this.props.resetProps();
|
||||||
|
}
|
||||||
|
|
||||||
|
renderHelmet = message => (
|
||||||
|
<Helmet>
|
||||||
|
<title>{message}</title>
|
||||||
|
<meta name="description" content="Description of InstallPluginPage" />
|
||||||
|
</Helmet>
|
||||||
|
);
|
||||||
|
|
||||||
|
renderPluginCard = plugin => {
|
||||||
|
const { adminPage: { currentEnvironment }, availablePlugins, downloadPlugin, history, installedPlugins } = this.props;
|
||||||
|
const currentPlugin = availablePlugins[plugin];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PluginCard
|
||||||
|
currentEnvironment={currentEnvironment}
|
||||||
|
history={history}
|
||||||
|
key={currentPlugin.id}
|
||||||
|
plugin={currentPlugin}
|
||||||
|
showSupportUsButton={currentPlugin.id === 'support-us'}
|
||||||
|
isAlreadyInstalled={installedPlugins.includes(currentPlugin.id)}
|
||||||
|
downloadPlugin={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
|
||||||
|
if (plugin.id !== 'support-us') {
|
||||||
|
downloadPlugin(currentPlugin.id);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { availablePlugins, isLoading } = this.props;
|
||||||
|
|
||||||
|
if (isLoading) {
|
||||||
|
return <LoadingIndicatorPage />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<FormattedMessage id="app.components.InstallPluginPage.helmet">
|
||||||
|
{this.renderHelmet}
|
||||||
|
</FormattedMessage>
|
||||||
|
<div className={cn('container-fluid', styles.containerFluid)}>
|
||||||
|
<PluginHeader
|
||||||
|
title={{ id: 'app.components.InstallPluginPage.title' }}
|
||||||
|
description={{ id: 'app.components.InstallPluginPage.description' }}
|
||||||
|
actions={[]}
|
||||||
|
/>
|
||||||
|
<div className={cn('row', styles.wrapper)}>
|
||||||
|
{Object.keys(availablePlugins).map(this.renderPluginCard)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Marketplace.childContextTypes = {
|
||||||
|
downloadPlugin: PropTypes.func.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
Marketplace.defaultProps = {};
|
||||||
|
|
||||||
|
Marketplace.propTypes = {
|
||||||
|
adminPage: PropTypes.object.isRequired,
|
||||||
|
availablePlugins: PropTypes.array.isRequired,
|
||||||
|
downloadPlugin: PropTypes.func.isRequired,
|
||||||
|
getAvailableAndInstalledPlugins: PropTypes.func.isRequired,
|
||||||
|
history: PropTypes.object.isRequired,
|
||||||
|
installedPlugins: PropTypes.array.isRequired,
|
||||||
|
isLoading: PropTypes.bool.isRequired,
|
||||||
|
resetProps: PropTypes.func.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
const mapStateToProps = makeSelectMarketplace();
|
||||||
|
|
||||||
|
function mapDispatchToProps(dispatch) {
|
||||||
|
return bindActionCreators(
|
||||||
|
{
|
||||||
|
downloadPlugin,
|
||||||
|
getAvailableAndInstalledPlugins,
|
||||||
|
resetProps,
|
||||||
|
},
|
||||||
|
dispatch,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const withConnect = connect(mapStateToProps, mapDispatchToProps);
|
||||||
|
|
||||||
|
/* Remove this line if the container doesn't have a route and
|
||||||
|
* check the documentation to see how to create the container's store
|
||||||
|
*/
|
||||||
|
const withReducer = injectReducer({ key: 'marketplace', reducer });
|
||||||
|
|
||||||
|
/* Remove the line below the container doesn't have a route and
|
||||||
|
* check the documentation to see how to create the container's store
|
||||||
|
*/
|
||||||
|
const withSaga = injectSaga({ key: 'marketplace', saga });
|
||||||
|
|
||||||
|
export default compose(
|
||||||
|
withReducer,
|
||||||
|
withSaga,
|
||||||
|
withConnect,
|
||||||
|
)(Marketplace);
|
||||||
@ -0,0 +1,37 @@
|
|||||||
|
import { fromJS, List } from 'immutable';
|
||||||
|
|
||||||
|
import {
|
||||||
|
DOWNLOAD_PLUGIN,
|
||||||
|
DOWNLOAD_PLUGIN_SUCCEEDED,
|
||||||
|
GET_AVAILABLE_AND_INSTALLED_PLUGINS_SUCCEEDED,
|
||||||
|
RESET_PROPS,
|
||||||
|
} from './constants';
|
||||||
|
|
||||||
|
const initialState = fromJS({
|
||||||
|
availablePlugins: List([]),
|
||||||
|
installedPlugins: List([]),
|
||||||
|
isLoading: true,
|
||||||
|
pluginToDownload: null,
|
||||||
|
});
|
||||||
|
|
||||||
|
function marketplaceReducer(state = initialState, action) {
|
||||||
|
switch (action.type) {
|
||||||
|
case DOWNLOAD_PLUGIN:
|
||||||
|
return state.update('pluginToDownload', () => action.pluginToDownload);
|
||||||
|
case DOWNLOAD_PLUGIN_SUCCEEDED:
|
||||||
|
return state
|
||||||
|
.update('installedPlugins', list => list.push(state.get('pluginToDownload')))
|
||||||
|
.update('pluginToDownload', () => null);
|
||||||
|
case GET_AVAILABLE_AND_INSTALLED_PLUGINS_SUCCEEDED:
|
||||||
|
return state
|
||||||
|
.update('availablePlugins', () => List(action.availablePlugins))
|
||||||
|
.update('installedPlugins', () => List(action.installedPlugins))
|
||||||
|
.update('isLoading', () => false);
|
||||||
|
case RESET_PROPS:
|
||||||
|
return initialState;
|
||||||
|
default:
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default marketplaceReducer;
|
||||||
@ -0,0 +1,89 @@
|
|||||||
|
import { LOCATION_CHANGE } from 'react-router-redux';
|
||||||
|
import {
|
||||||
|
all,
|
||||||
|
call,
|
||||||
|
cancel,
|
||||||
|
fork,
|
||||||
|
put,
|
||||||
|
select,
|
||||||
|
take,
|
||||||
|
takeLatest,
|
||||||
|
} from 'redux-saga/effects';
|
||||||
|
|
||||||
|
import request from 'utils/request';
|
||||||
|
|
||||||
|
import { selectLocale } from '../LanguageProvider/selectors';
|
||||||
|
import {
|
||||||
|
getAvailableAndInstalledPluginsSucceeded,
|
||||||
|
downloadPluginSucceeded,
|
||||||
|
} from './actions';
|
||||||
|
import { DOWNLOAD_PLUGIN, GET_AVAILABLE_AND_INSTALLED_PLUGINS } from './constants';
|
||||||
|
import { makeSelectPluginToDownload } from './selectors';
|
||||||
|
|
||||||
|
export function* pluginDownload() {
|
||||||
|
try {
|
||||||
|
// Force the Overlayblocker to be displayed
|
||||||
|
const overlayblockerParams = {
|
||||||
|
enabled: true,
|
||||||
|
title: 'app.components.InstallPluginPage.Download.title',
|
||||||
|
description: 'app.components.InstallPluginPage.Download.description',
|
||||||
|
};
|
||||||
|
strapi.lockApp(overlayblockerParams);
|
||||||
|
|
||||||
|
const pluginToDownload = yield select(makeSelectPluginToDownload());
|
||||||
|
const opts = {
|
||||||
|
method: 'POST',
|
||||||
|
body: {
|
||||||
|
plugin: pluginToDownload,
|
||||||
|
port: window.location.port,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const response = yield call(request, '/admin/plugins/install', opts, overlayblockerParams);
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
yield put(downloadPluginSucceeded());
|
||||||
|
window.location.reload();
|
||||||
|
}
|
||||||
|
} catch(err) {
|
||||||
|
// Hide the global OverlayBlocker
|
||||||
|
strapi.unlockApp();
|
||||||
|
strapi.notification.error('notification.error');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function* getData() {
|
||||||
|
// Get current locale.
|
||||||
|
const locale = yield select(selectLocale());
|
||||||
|
|
||||||
|
try {
|
||||||
|
const opts = {
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
params: {
|
||||||
|
lang: locale,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const [availablePlugins, { plugins }] = yield all([
|
||||||
|
call(request, 'https://marketplace.strapi.io/plugins', opts),
|
||||||
|
call(request, '/admin/plugins', { method: 'GET' }),
|
||||||
|
]);
|
||||||
|
|
||||||
|
yield put(getAvailableAndInstalledPluginsSucceeded(availablePlugins, Object.keys(plugins)));
|
||||||
|
} catch(err) {
|
||||||
|
strapi.notification.error('notification.error');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Individual exports for testing
|
||||||
|
export default function* defaultSaga() {
|
||||||
|
const loadDataWatcher = yield fork(takeLatest, GET_AVAILABLE_AND_INSTALLED_PLUGINS, getData);
|
||||||
|
yield fork(takeLatest, DOWNLOAD_PLUGIN, pluginDownload);
|
||||||
|
|
||||||
|
yield take(LOCATION_CHANGE);
|
||||||
|
|
||||||
|
yield cancel(loadDataWatcher);
|
||||||
|
}
|
||||||
@ -0,0 +1,31 @@
|
|||||||
|
import { createSelector } from 'reselect';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Direct selector to the marketplace state domain
|
||||||
|
*/
|
||||||
|
const selectMarketplaceDomain = () => (state) => state.get('marketplace');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Other specific selectors
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default selector used by Marketplace
|
||||||
|
*/
|
||||||
|
|
||||||
|
const makeSelectMarketplace = () => createSelector(
|
||||||
|
selectMarketplaceDomain(),
|
||||||
|
(substate) => substate.toJS()
|
||||||
|
);
|
||||||
|
|
||||||
|
const makeSelectPluginToDownload = () => createSelector(
|
||||||
|
selectMarketplaceDomain(),
|
||||||
|
(substate) => substate.get('pluginToDownload'),
|
||||||
|
);
|
||||||
|
|
||||||
|
export default makeSelectMarketplace;
|
||||||
|
export {
|
||||||
|
selectMarketplaceDomain,
|
||||||
|
makeSelectPluginToDownload,
|
||||||
|
};
|
||||||
@ -22,5 +22,5 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.wrapper {
|
.wrapper {
|
||||||
padding-top: .3rem;
|
padding-top: 3.8rem;
|
||||||
}
|
}
|
||||||
@ -57,8 +57,8 @@ const registerPlugin = (plugin) => {
|
|||||||
const displayNotification = (message, status) => {
|
const displayNotification = (message, status) => {
|
||||||
store.dispatch(showNotification(message, status));
|
store.dispatch(showNotification(message, status));
|
||||||
};
|
};
|
||||||
const lockApp = () => {
|
const lockApp = (data) => {
|
||||||
store.dispatch(freezeApp());
|
store.dispatch(freezeApp(data));
|
||||||
};
|
};
|
||||||
const unlockApp = () => {
|
const unlockApp = () => {
|
||||||
store.dispatch(unfreezeApp());
|
store.dispatch(unfreezeApp());
|
||||||
|
|||||||
@ -52,6 +52,8 @@
|
|||||||
"app.components.InputFileDetails.originalName": "Original name:",
|
"app.components.InputFileDetails.originalName": "Original name:",
|
||||||
"app.components.InputFileDetails.remove": "Remove this file",
|
"app.components.InputFileDetails.remove": "Remove this file",
|
||||||
"app.components.InputFileDetails.size": "Size:",
|
"app.components.InputFileDetails.size": "Size:",
|
||||||
|
"app.components.InstallPluginPage.Download.title": "Downloading...",
|
||||||
|
"app.components.InstallPluginPage.Download.description": "It might take a few seconds to download and install the plugin.",
|
||||||
"app.components.InstallPluginPage.InputSearch.label": " ",
|
"app.components.InstallPluginPage.InputSearch.label": " ",
|
||||||
"app.components.InstallPluginPage.InputSearch.placeholder": "Search for a plugin... (ex: authentication)",
|
"app.components.InstallPluginPage.InputSearch.placeholder": "Search for a plugin... (ex: authentication)",
|
||||||
"app.components.InstallPluginPage.description": "Extend your app effortlessly.",
|
"app.components.InstallPluginPage.description": "Extend your app effortlessly.",
|
||||||
@ -89,6 +91,7 @@
|
|||||||
"app.components.PluginCard.compatibleCommunity": "Compatible with the community",
|
"app.components.PluginCard.compatibleCommunity": "Compatible with the community",
|
||||||
"app.components.PluginCard.more-details": "More details",
|
"app.components.PluginCard.more-details": "More details",
|
||||||
"app.components.PluginCard.price.free": "Free",
|
"app.components.PluginCard.price.free": "Free",
|
||||||
|
"app.components.PluginCard.settings": "Settings",
|
||||||
"app.components.listPlugins.button": "Add New Plugin",
|
"app.components.listPlugins.button": "Add New Plugin",
|
||||||
"app.components.listPlugins.title.none": "No plugins installed",
|
"app.components.listPlugins.title.none": "No plugins installed",
|
||||||
"app.components.listPlugins.title.plural": "{number} plugins are installed",
|
"app.components.listPlugins.title.plural": "{number} plugins are installed",
|
||||||
|
|||||||
@ -53,6 +53,8 @@
|
|||||||
"app.components.InputFileDetails.originalName": "Nom d'origine :",
|
"app.components.InputFileDetails.originalName": "Nom d'origine :",
|
||||||
"app.components.InputFileDetails.remove": "Supprimer ce fichier",
|
"app.components.InputFileDetails.remove": "Supprimer ce fichier",
|
||||||
"app.components.InputFileDetails.size": "Taille:",
|
"app.components.InputFileDetails.size": "Taille:",
|
||||||
|
"app.components.InstallPluginPage.Download.title": "Téléchargement en cours...",
|
||||||
|
"app.components.InstallPluginPage.Download.description": "L'installation d'un plugin peut prendre quelques secondes.",
|
||||||
"app.components.InstallPluginPage.InputSearch.label": " ",
|
"app.components.InstallPluginPage.InputSearch.label": " ",
|
||||||
"app.components.InstallPluginPage.InputSearch.placeholder": "Recherchez un plugin... (ex : authentification)",
|
"app.components.InstallPluginPage.InputSearch.placeholder": "Recherchez un plugin... (ex : authentification)",
|
||||||
"app.components.InstallPluginPage.description": "Améliorez votre app sans efforts",
|
"app.components.InstallPluginPage.description": "Améliorez votre app sans efforts",
|
||||||
@ -90,6 +92,7 @@
|
|||||||
"app.components.PluginCard.compatibleCommunity": "Compatible avec la communauté",
|
"app.components.PluginCard.compatibleCommunity": "Compatible avec la communauté",
|
||||||
"app.components.PluginCard.more-details": "Plus de détails",
|
"app.components.PluginCard.more-details": "Plus de détails",
|
||||||
"app.components.PluginCard.price.free": "Gratuit",
|
"app.components.PluginCard.price.free": "Gratuit",
|
||||||
|
"app.components.PluginCard.settings": "Réglages",
|
||||||
"app.components.listPlugins.button": "Ajouter un Nouveau Plugin",
|
"app.components.listPlugins.button": "Ajouter un Nouveau Plugin",
|
||||||
"app.components.listPlugins.title.none": "Aucun plugin n'est installé",
|
"app.components.listPlugins.title.none": "Aucun plugin n'est installé",
|
||||||
"app.components.listPlugins.title.plural": "{number} sont disponibles",
|
"app.components.listPlugins.title.plural": "{number} sont disponibles",
|
||||||
|
|||||||
@ -51,4 +51,4 @@
|
|||||||
"npm": ">= 6.0.0"
|
"npm": ">= 6.0.0"
|
||||||
},
|
},
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -10,9 +10,7 @@ import PropTypes from 'prop-types';
|
|||||||
import styles from './styles.scss';
|
import styles from './styles.scss';
|
||||||
|
|
||||||
const LoadingIndicatorPage = (props) => {
|
const LoadingIndicatorPage = (props) => {
|
||||||
|
|
||||||
if (props.error) {
|
if (props.error) {
|
||||||
console.log(props.error);
|
|
||||||
return <div>An error occurred</div>;
|
return <div>An error occurred</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -15,7 +15,9 @@ import styles from './styles.scss';
|
|||||||
class OverlayBlocker extends React.Component {
|
class OverlayBlocker extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this.overlayContainer = document.createElement('div');
|
this.overlayContainer = document.createElement('div');
|
||||||
|
|
||||||
document.body.appendChild(this.overlayContainer);
|
document.body.appendChild(this.overlayContainer);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -24,19 +26,21 @@ class OverlayBlocker extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
const { title, description, icon } = this.props;
|
||||||
|
|
||||||
const content = this.props.children ? (
|
const content = this.props.children ? (
|
||||||
this.props.children
|
this.props.children
|
||||||
) : (
|
) : (
|
||||||
<div className={styles.container}>
|
<div className={styles.container}>
|
||||||
<div className={styles.icoContainer}>
|
<div className={styles.icoContainer}>
|
||||||
<i className="fa fa-refresh" />
|
<i className={icon} />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h4>
|
<h4>
|
||||||
<FormattedMessage id="components.OverlayBlocker.title" />
|
<FormattedMessage id={title} />
|
||||||
</h4>
|
</h4>
|
||||||
<p>
|
<p>
|
||||||
<FormattedMessage id="components.OverlayBlocker.description" />
|
<FormattedMessage id={description} />
|
||||||
</p>
|
</p>
|
||||||
<div className={styles.buttonContainer}>
|
<div className={styles.buttonContainer}>
|
||||||
<a className={cn(styles.primary, 'btn')} href="https://strapi.io/documentation/configurations/configurations.html#server" target="_blank">Read the documentation</a>
|
<a className={cn(styles.primary, 'btn')} href="https://strapi.io/documentation/configurations/configurations.html#server" target="_blank">Read the documentation</a>
|
||||||
@ -62,12 +66,18 @@ class OverlayBlocker extends React.Component {
|
|||||||
|
|
||||||
OverlayBlocker.defaultProps = {
|
OverlayBlocker.defaultProps = {
|
||||||
children: '',
|
children: '',
|
||||||
|
description: 'components.OverlayBlocker.description',
|
||||||
|
icon: 'fa fa-refresh',
|
||||||
isOpen: false,
|
isOpen: false,
|
||||||
|
title: 'components.OverlayBlocker.title',
|
||||||
};
|
};
|
||||||
|
|
||||||
OverlayBlocker.propTypes = {
|
OverlayBlocker.propTypes = {
|
||||||
children: PropTypes.node,
|
children: PropTypes.node,
|
||||||
|
description: PropTypes.string,
|
||||||
|
icon: PropTypes.string,
|
||||||
isOpen: PropTypes.bool,
|
isOpen: PropTypes.bool,
|
||||||
|
title: PropTypes.string,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default OverlayBlocker;
|
export default OverlayBlocker;
|
||||||
|
|||||||
@ -145,7 +145,7 @@ export default function request(url, options = {}, shouldWatchServerRestart = fa
|
|||||||
.then((response) => {
|
.then((response) => {
|
||||||
if (shouldWatchServerRestart) {
|
if (shouldWatchServerRestart) {
|
||||||
// Display the global OverlayBlocker
|
// Display the global OverlayBlocker
|
||||||
strapi.lockApp();
|
strapi.lockApp(shouldWatchServerRestart);
|
||||||
return serverRestartWatcher(response);
|
return serverRestartWatcher(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user