mirror of
https://github.com/strapi/strapi.git
synced 2025-11-09 14:51:29 +00:00
Merge pull request #4229 from strapi/update/plugin-generators
Add front-end files in the plugin generator
This commit is contained in:
commit
b121a0da4b
@ -9,10 +9,13 @@ const Wrapper = styled.div`
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
line-height: 5.8rem;
|
line-height: 5.8rem;
|
||||||
z-index: 999;
|
z-index: 999;
|
||||||
> button {
|
> button,
|
||||||
|
> button.btn {
|
||||||
|
position: relative;
|
||||||
|
z-index: 9;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding-right: 20px;
|
padding-right: 20px;
|
||||||
background: transparent;
|
background: white;
|
||||||
border: none;
|
border: none;
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
color: #333740;
|
color: #333740;
|
||||||
@ -27,6 +30,7 @@ const Wrapper = styled.div`
|
|||||||
&:active {
|
&:active {
|
||||||
color: #333740;
|
color: #333740;
|
||||||
background-color: #fafafb !important;
|
background-color: #fafafb !important;
|
||||||
|
z-index: 9;
|
||||||
}
|
}
|
||||||
|
|
||||||
> i {
|
> i {
|
||||||
@ -57,6 +61,8 @@ const Wrapper = styled.div`
|
|||||||
}
|
}
|
||||||
|
|
||||||
.dropDownContent {
|
.dropDownContent {
|
||||||
|
z-index: 8;
|
||||||
|
top: -3px !important;
|
||||||
left: auto !important;
|
left: auto !important;
|
||||||
min-width: 100% !important;
|
min-width: 100% !important;
|
||||||
margin: 0 !important;
|
margin: 0 !important;
|
||||||
@ -72,7 +78,7 @@ const Wrapper = styled.div`
|
|||||||
&:before {
|
&:before {
|
||||||
content: '';
|
content: '';
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: -3px;
|
top: 0;
|
||||||
left: -1px;
|
left: -1px;
|
||||||
width: calc(100% + 1px);
|
width: calc(100% + 1px);
|
||||||
height: 3px;
|
height: 3px;
|
||||||
@ -80,21 +86,16 @@ const Wrapper = styled.div`
|
|||||||
}
|
}
|
||||||
|
|
||||||
> button {
|
> button {
|
||||||
height: 40px;
|
height: 54px;
|
||||||
padding: 0px 15px;
|
padding: 0px 15px;
|
||||||
line-height: 40px;
|
|
||||||
&:hover,
|
&:hover,
|
||||||
&:focus,
|
&:focus,
|
||||||
&:active {
|
&:active {
|
||||||
background-color: #fafafb !important;
|
background-color: #fafafb !important;
|
||||||
border-radius: 0px;
|
border-radius: 0px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
outline: 0;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
> button {
|
|
||||||
height: 44px;
|
|
||||||
line-height: 48px;
|
|
||||||
&:hover,
|
&:hover,
|
||||||
&:active {
|
&:active {
|
||||||
color: #333740;
|
color: #333740;
|
||||||
|
|||||||
@ -10,15 +10,15 @@ const chokidar = require('chokidar');
|
|||||||
const getPkgPath = name =>
|
const getPkgPath = name =>
|
||||||
path.dirname(require.resolve(`${name}/package.json`));
|
path.dirname(require.resolve(`${name}/package.json`));
|
||||||
|
|
||||||
async function createPluginsJs(plugins, dest) {
|
async function createPluginsJs(plugins, localPlugins, dest) {
|
||||||
const content = `
|
const content = `
|
||||||
const injectReducer = require('./utils/injectReducer').default;
|
const injectReducer = require('./utils/injectReducer').default;
|
||||||
const injectSaga = require('./utils/injectSaga').default;
|
const injectSaga = require('./utils/injectSaga').default;
|
||||||
const useInjectReducer = require('./utils/injectReducer').useInjectReducer;
|
const useInjectReducer = require('./utils/injectReducer').useInjectReducer;
|
||||||
const useInjectSaga = require('./utils/injectSaga').useInjectSaga;
|
const useInjectSaga = require('./utils/injectSaga').useInjectSaga;
|
||||||
const { languages } = require('./i18n');
|
const { languages } = require('./i18n');
|
||||||
|
|
||||||
window.strapi = Object.assign(window.strapi || {}, {
|
window.strapi = Object.assign(window.strapi || {}, {
|
||||||
node: MODE || 'host',
|
node: MODE || 'host',
|
||||||
backendURL: BACKEND_URL === '/' ? window.location.origin : BACKEND_URL,
|
backendURL: BACKEND_URL === '/' ? window.location.origin : BACKEND_URL,
|
||||||
languages,
|
languages,
|
||||||
@ -31,17 +31,24 @@ async function createPluginsJs(plugins, dest) {
|
|||||||
injectSaga,
|
injectSaga,
|
||||||
useInjectReducer,
|
useInjectReducer,
|
||||||
useInjectSaga,
|
useInjectSaga,
|
||||||
});
|
});
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
${plugins
|
${plugins
|
||||||
.map(name => {
|
.map(name => {
|
||||||
const shortName = name.replace(/^strapi-plugin-/i, '');
|
const shortName = name.replace(/^strapi-plugin-/i, '');
|
||||||
const req = `require('../../plugins/${name}/admin/src').default`;
|
const req = `require('../../plugins/${name}/admin/src').default`;
|
||||||
|
return `'${shortName}': ${req},`;
|
||||||
|
})
|
||||||
|
.join('\n')}
|
||||||
|
${localPlugins
|
||||||
|
.map(name => {
|
||||||
|
const shortName = name.replace(/^strapi-plugin-/i, '');
|
||||||
|
const req = `require('../../../plugins/${name}/admin/src').default`;
|
||||||
return `'${shortName}': ${req}`;
|
return `'${shortName}': ${req}`;
|
||||||
})
|
})
|
||||||
.join(',\n')}
|
.join(',\n')}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
return fs.writeFile(
|
return fs.writeFile(
|
||||||
@ -98,6 +105,14 @@ async function createCacheDir(dir) {
|
|||||||
fs.existsSync(path.resolve(getPkgPath(dep), 'admin', 'src', 'index.js'))
|
fs.existsSync(path.resolve(getPkgPath(dep), 'admin', 'src', 'index.js'))
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const localPluginsToCopy = fs
|
||||||
|
.readdirSync(path.join(dir, 'plugins'))
|
||||||
|
.filter(plugin =>
|
||||||
|
fs.existsSync(
|
||||||
|
path.resolve(dir, 'plugins', plugin, 'admin', 'src', 'index.js')
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
// TODO: add logic to avoid copying files if not necessary
|
// TODO: add logic to avoid copying files if not necessary
|
||||||
|
|
||||||
// create .cache dir
|
// create .cache dir
|
||||||
@ -110,7 +125,7 @@ async function createCacheDir(dir) {
|
|||||||
await Promise.all(pluginsToCopy.map(name => copyPlugin(name, cacheDir)));
|
await Promise.all(pluginsToCopy.map(name => copyPlugin(name, cacheDir)));
|
||||||
|
|
||||||
// create plugins.js with plugins requires
|
// create plugins.js with plugins requires
|
||||||
await createPluginsJs(pluginsToCopy, cacheDir);
|
await createPluginsJs(pluginsToCopy, localPluginsToCopy, cacheDir);
|
||||||
|
|
||||||
// override admin code with user customizations
|
// override admin code with user customizations
|
||||||
if (fs.pathExistsSync(path.join(dir, 'admin'))) {
|
if (fs.pathExistsSync(path.join(dir, 'admin'))) {
|
||||||
|
|||||||
@ -1,5 +0,0 @@
|
|||||||
/*
|
|
||||||
*
|
|
||||||
* App actions
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
@ -1,5 +0,0 @@
|
|||||||
/*
|
|
||||||
*
|
|
||||||
* App constants
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
@ -6,66 +6,22 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import { connect } from 'react-redux';
|
|
||||||
import { createStructuredSelector } from 'reselect';
|
|
||||||
import { Switch, Route } from 'react-router-dom';
|
import { Switch, Route } from 'react-router-dom';
|
||||||
import { bindActionCreators, compose } from 'redux';
|
import { NotFound } from 'strapi-helper-plugin';
|
||||||
|
|
||||||
// Utils
|
// Utils
|
||||||
import pluginId from 'pluginId';
|
import pluginId from '../../pluginId';
|
||||||
|
|
||||||
// Containers
|
// Containers
|
||||||
import HomePage from 'containers/HomePage';
|
import HomePage from '../HomePage';
|
||||||
import NotFoundPage from 'containers/NotFoundPage';
|
|
||||||
// When you're done studying the ExamplePage container, remove the following line and delete the ExamplePage container
|
|
||||||
import ExamplePage from 'containers/ExamplePage';
|
|
||||||
|
|
||||||
import reducer from './reducer';
|
const App = () => {
|
||||||
|
|
||||||
class App extends React.Component {
|
|
||||||
// When you're done studying the ExamplePage container, remove the following lines and delete the ExamplePage container
|
|
||||||
componentDidMount() {
|
|
||||||
this.props.history.push(`/plugins/${pluginId}/example`);
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return (
|
return (
|
||||||
<div className={pluginId}>
|
<div className={pluginId}>
|
||||||
<Switch>
|
<Switch>
|
||||||
<Route path={`/plugins/${pluginId}`} component={HomePage} exact />
|
<Route path={`/plugins/${pluginId}`} component={HomePage} exact />
|
||||||
{/* When you're done studying the ExamplePage container, remove the following line and delete the ExamplePage container */}
|
<Route component={NotFound} />
|
||||||
<Route path={`/plugins/${pluginId}/example`} component={ExamplePage} exact />
|
|
||||||
<Route component={NotFoundPage} />
|
|
||||||
</Switch>
|
</Switch>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
App.contextTypes = {
|
|
||||||
plugins: PropTypes.object,
|
|
||||||
updatePlugin: PropTypes.func,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
App.propTypes = {
|
export default App;
|
||||||
history: PropTypes.object.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
export function mapDispatchToProps(dispatch) {
|
|
||||||
return bindActionCreators(
|
|
||||||
{},
|
|
||||||
dispatch,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({});
|
|
||||||
|
|
||||||
// Wrap the component to inject dispatch and state into it
|
|
||||||
const withConnect = connect(mapStateToProps, mapDispatchToProps);
|
|
||||||
const withReducer = strapi.injectReducer({ key: 'global', reducer, pluginId });
|
|
||||||
|
|
||||||
export default compose(
|
|
||||||
withReducer,
|
|
||||||
withConnect,
|
|
||||||
)(App);
|
|
||||||
|
|||||||
@ -1,18 +0,0 @@
|
|||||||
/*
|
|
||||||
*
|
|
||||||
* App reducer
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { fromJS } from 'immutable';
|
|
||||||
|
|
||||||
const initialState = fromJS({});
|
|
||||||
|
|
||||||
function appReducer(state = initialState, action) {
|
|
||||||
switch (action.type) {
|
|
||||||
default:
|
|
||||||
return state;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default appReducer;
|
|
||||||
@ -1,10 +0,0 @@
|
|||||||
// import { createSelector } from 'reselect';
|
|
||||||
// import pluginId from 'pluginId';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Direct selector to the list state domain
|
|
||||||
*/
|
|
||||||
|
|
||||||
// const selectGlobalDomain = () => state => state.get(`${pluginId}_global`);
|
|
||||||
|
|
||||||
export {};
|
|
||||||
@ -1,20 +0,0 @@
|
|||||||
/*
|
|
||||||
*
|
|
||||||
* ExamplePage actions
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { LOAD_DATA, LOADED_DATA } from './constants';
|
|
||||||
|
|
||||||
export function loadData() {
|
|
||||||
return {
|
|
||||||
type: LOAD_DATA,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function loadedData(data) {
|
|
||||||
return {
|
|
||||||
type: LOADED_DATA,
|
|
||||||
data,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@ -1,10 +0,0 @@
|
|||||||
/*
|
|
||||||
*
|
|
||||||
* ExamplePage constants
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
import pluginId from 'pluginId';
|
|
||||||
|
|
||||||
export const LOAD_DATA = `${pluginId}/ExamplePage/LOAD_DATA`;
|
|
||||||
export const LOADED_DATA = `${pluginId}/ExamplePage/LOADED_DATA`;
|
|
||||||
@ -1,98 +0,0 @@
|
|||||||
/*
|
|
||||||
*
|
|
||||||
* ExamplePage
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
import React from 'react';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import { connect } from 'react-redux';
|
|
||||||
import { createStructuredSelector } from 'reselect';
|
|
||||||
import { injectIntl } from 'react-intl';
|
|
||||||
import { bindActionCreators, compose } from 'redux';
|
|
||||||
import pluginId from 'pluginId';
|
|
||||||
|
|
||||||
import Button from 'components/Button';
|
|
||||||
|
|
||||||
import styles from './styles.scss';
|
|
||||||
import { loadData } from './actions';
|
|
||||||
import { makeSelectLoading, makeSelectData } from './selectors';
|
|
||||||
import reducer from './reducer';
|
|
||||||
import saga from './saga';
|
|
||||||
|
|
||||||
export class ExamplePage extends React.Component {
|
|
||||||
generateDataBlock() {
|
|
||||||
if (this.props.data) {
|
|
||||||
const items = this.props.data.map((item, i) => <li key={i}>{item}</li>);
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<p>Data:</p>
|
|
||||||
<ul>{items}</ul>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
console.log('Don\'t forget to delete the ExampleContainer when you\'re done studying it');
|
|
||||||
// Generate the data block
|
|
||||||
const dataBlock = this.generateDataBlock();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={styles.examplePage}>
|
|
||||||
<div className="row">
|
|
||||||
<div className="col-md-12">
|
|
||||||
<p>This is an example of a fake API call.</p>
|
|
||||||
<p>Loading: {this.props.loading ? 'yes' : 'no'}.</p>
|
|
||||||
{dataBlock}
|
|
||||||
<Button
|
|
||||||
label={this.props.loading ? 'Loading...' : 'Submit'}
|
|
||||||
disabled={this.props.loading}
|
|
||||||
onClick={this.props.loadData}
|
|
||||||
primary
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ExamplePage.contextTypes = {
|
|
||||||
router: PropTypes.object,
|
|
||||||
};
|
|
||||||
|
|
||||||
ExamplePage.propTypes = {
|
|
||||||
data: PropTypes.oneOfType([
|
|
||||||
PropTypes.bool,
|
|
||||||
PropTypes.object,
|
|
||||||
]).isRequired,
|
|
||||||
loadData: PropTypes.func.isRequired,
|
|
||||||
loading: PropTypes.bool.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
function mapDispatchToProps(dispatch) {
|
|
||||||
return bindActionCreators(
|
|
||||||
{
|
|
||||||
loadData,
|
|
||||||
},
|
|
||||||
dispatch,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
|
||||||
loading: makeSelectLoading(),
|
|
||||||
data: makeSelectData(),
|
|
||||||
});
|
|
||||||
|
|
||||||
const withConnect = connect(mapStateToProps, mapDispatchToProps);
|
|
||||||
|
|
||||||
const withReducer = strapi.injectReducer({ key: 'examplePage', reducer, pluginId });
|
|
||||||
const withSaga = strapi.injectSaga({ key: 'examplePage', saga, pluginId });
|
|
||||||
|
|
||||||
export default compose(
|
|
||||||
withReducer,
|
|
||||||
withSaga,
|
|
||||||
withConnect,
|
|
||||||
)(injectIntl(ExamplePage));
|
|
||||||
@ -1,27 +0,0 @@
|
|||||||
/*
|
|
||||||
*
|
|
||||||
* ExamplePage reducer
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { fromJS } from 'immutable';
|
|
||||||
|
|
||||||
import { LOAD_DATA, LOADED_DATA } from './constants';
|
|
||||||
|
|
||||||
const initialState = fromJS({
|
|
||||||
loading: false,
|
|
||||||
data: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
function examplePageReducer(state = initialState, action) {
|
|
||||||
switch (action.type) {
|
|
||||||
case LOAD_DATA:
|
|
||||||
return state.set('loading', true);
|
|
||||||
case LOADED_DATA:
|
|
||||||
return state.set('loading', false).set('data', fromJS(action.data));
|
|
||||||
default:
|
|
||||||
return state;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default examplePageReducer;
|
|
||||||
@ -1,34 +0,0 @@
|
|||||||
/* eslint-disable */
|
|
||||||
import { LOCATION_CHANGE } from 'react-router-redux';
|
|
||||||
import { takeLatest, put, fork, take, cancel } from 'redux-saga/effects';
|
|
||||||
|
|
||||||
import { loadedData } from './actions';
|
|
||||||
import { LOAD_DATA } from './constants';
|
|
||||||
|
|
||||||
export function* loadData() {
|
|
||||||
// Fake API request delay
|
|
||||||
yield new Promise(resolve => {
|
|
||||||
setTimeout(() => {
|
|
||||||
resolve();
|
|
||||||
}, 1000);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Generate a random array
|
|
||||||
const data = Array(4)
|
|
||||||
.fill(0)
|
|
||||||
.map(() => Math.floor(Math.random() * 100));
|
|
||||||
|
|
||||||
yield put(loadedData(data));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Individual exports for testing
|
|
||||||
export function* defaultSaga() {
|
|
||||||
const loadDataWatcher = yield fork(takeLatest, LOAD_DATA, loadData);
|
|
||||||
|
|
||||||
// Suspend execution until location changes
|
|
||||||
yield take(LOCATION_CHANGE);
|
|
||||||
yield cancel(loadDataWatcher);
|
|
||||||
}
|
|
||||||
|
|
||||||
// All sagas to be loaded
|
|
||||||
export default defaultSaga;
|
|
||||||
@ -1,25 +0,0 @@
|
|||||||
import { createSelector } from 'reselect';
|
|
||||||
import pluginId from 'pluginId';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Direct selector to the examplePage state domain
|
|
||||||
*/
|
|
||||||
const selectExamplePageDomain = () => state => state.get(`${pluginId}_examplePage`);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Default selector used by HomePage
|
|
||||||
*/
|
|
||||||
|
|
||||||
const makeSelectLoading = () =>
|
|
||||||
createSelector(
|
|
||||||
selectExamplePageDomain(),
|
|
||||||
substate => substate.get('loading'),
|
|
||||||
);
|
|
||||||
|
|
||||||
const makeSelectData = () =>
|
|
||||||
createSelector(
|
|
||||||
selectExamplePageDomain(),
|
|
||||||
substate => substate.get('data'),
|
|
||||||
);
|
|
||||||
|
|
||||||
export { makeSelectLoading, makeSelectData };
|
|
||||||
@ -1,3 +0,0 @@
|
|||||||
.examplePage {
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -1,13 +0,0 @@
|
|||||||
/*
|
|
||||||
*
|
|
||||||
* HomePage actions
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { DEFAULT_ACTION } from './constants';
|
|
||||||
|
|
||||||
export function defaultAction() {
|
|
||||||
return {
|
|
||||||
type: DEFAULT_ACTION,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@ -1,9 +0,0 @@
|
|||||||
/*
|
|
||||||
*
|
|
||||||
* HomePage constants
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
import pluginId from 'pluginId';
|
|
||||||
|
|
||||||
export const DEFAULT_ACTION = `${pluginId}/HomePage/DEFAULT_ACTION`;
|
|
||||||
@ -4,60 +4,17 @@
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React, { memo } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
// import PropTypes from 'prop-types';
|
||||||
import { connect } from 'react-redux';
|
import pluginId from '../../pluginId';
|
||||||
import { createStructuredSelector } from 'reselect';
|
|
||||||
import { injectIntl } from 'react-intl';
|
|
||||||
import { bindActionCreators, compose } from 'redux';
|
|
||||||
import pluginId from 'pluginId';
|
|
||||||
|
|
||||||
// Selectors
|
const HomePage = () => {
|
||||||
import selectHomePage from './selectors';
|
|
||||||
|
|
||||||
// Styles
|
|
||||||
import styles from './styles.scss';
|
|
||||||
|
|
||||||
import reducer from './reducer';
|
|
||||||
import saga from './saga';
|
|
||||||
|
|
||||||
export class HomePage extends React.Component {
|
|
||||||
render() {
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.homePage}>
|
<div>
|
||||||
|
<h1>{pluginId}'s HomePage</h1>
|
||||||
|
<p>Happy coding</p>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
HomePage.contextTypes = {
|
|
||||||
router: PropTypes.object,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
HomePage.propTypes = {
|
export default memo(HomePage);
|
||||||
// homePage: PropTypes.object,
|
|
||||||
};
|
|
||||||
|
|
||||||
function mapDispatchToProps(dispatch) {
|
|
||||||
return bindActionCreators(
|
|
||||||
{
|
|
||||||
// Your actions here
|
|
||||||
},
|
|
||||||
dispatch,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
|
||||||
homePage: selectHomePage(),
|
|
||||||
});
|
|
||||||
|
|
||||||
const withConnect = connect(mapStateToProps, mapDispatchToProps);
|
|
||||||
|
|
||||||
const withReducer = strapi.injectReducer({ key: 'homePage', reducer, pluginId });
|
|
||||||
const withSaga = strapi.injectSaga({ key: 'homePage', saga, pluginId });
|
|
||||||
|
|
||||||
export default compose(
|
|
||||||
withReducer,
|
|
||||||
withSaga,
|
|
||||||
withConnect,
|
|
||||||
)(injectIntl(HomePage));
|
|
||||||
|
|||||||
@ -1,22 +0,0 @@
|
|||||||
/*
|
|
||||||
*
|
|
||||||
* HomePage reducer
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { fromJS } from 'immutable';
|
|
||||||
|
|
||||||
import { DEFAULT_ACTION } from './constants';
|
|
||||||
|
|
||||||
const initialState = fromJS({});
|
|
||||||
|
|
||||||
function homePageReducer(state = initialState, action) {
|
|
||||||
switch (action.type) {
|
|
||||||
case DEFAULT_ACTION:
|
|
||||||
return state;
|
|
||||||
default:
|
|
||||||
return state;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default homePageReducer;
|
|
||||||
@ -1,9 +0,0 @@
|
|||||||
// import { LOCATION_CHANGE } from 'react-router-redux';
|
|
||||||
// import { takeLatest, put, fork, take, cancel } from 'redux-saga/effects';
|
|
||||||
|
|
||||||
// Individual exports for testing
|
|
||||||
export function* defaultSaga() {
|
|
||||||
}
|
|
||||||
|
|
||||||
// All sagas to be loaded
|
|
||||||
export default defaultSaga;
|
|
||||||
@ -1,18 +0,0 @@
|
|||||||
import { createSelector } from 'reselect';
|
|
||||||
import pluginId from 'pluginId';
|
|
||||||
/**
|
|
||||||
* Direct selector to the homePage state domain
|
|
||||||
*/
|
|
||||||
const selectHomePageDomain = () => state => state.get(`${pluginId}_homePage`);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Default selector used by HomePage
|
|
||||||
*/
|
|
||||||
|
|
||||||
const selectHomePage = () =>
|
|
||||||
createSelector(
|
|
||||||
selectHomePageDomain(),
|
|
||||||
substate => substate.toJS(),
|
|
||||||
);
|
|
||||||
|
|
||||||
export default selectHomePage;
|
|
||||||
@ -1,3 +0,0 @@
|
|||||||
.homePage {
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -4,21 +4,20 @@
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import { useEffect, useRef } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import pluginId from '../../pluginId';
|
import pluginId from '../../pluginId';
|
||||||
|
|
||||||
class Initializer extends React.PureComponent {
|
const Initializer = ({ updatePlugin }) => {
|
||||||
// eslint-disable-line react/prefer-stateless-function
|
const ref = useRef();
|
||||||
componentDidMount() {
|
ref.current = updatePlugin;
|
||||||
// Emit the event 'pluginReady'
|
|
||||||
this.props.updatePlugin(pluginId, 'isReady', true);
|
useEffect(() => {
|
||||||
}
|
ref.current(pluginId, 'isReady', true);
|
||||||
|
}, []);
|
||||||
|
|
||||||
render() {
|
|
||||||
return null;
|
return null;
|
||||||
}
|
};
|
||||||
}
|
|
||||||
|
|
||||||
Initializer.propTypes = {
|
Initializer.propTypes = {
|
||||||
updatePlugin: PropTypes.func.isRequired,
|
updatePlugin: PropTypes.func.isRequired,
|
||||||
|
|||||||
@ -1,25 +0,0 @@
|
|||||||
// import React from 'react';
|
|
||||||
// import { mount, shallow } from 'enzyme';
|
|
||||||
// import pluginId from '../../pluginId';
|
|
||||||
|
|
||||||
// import Initializer from '../index';
|
|
||||||
|
|
||||||
describe('<Initializer />', () => {
|
|
||||||
// it('Should not crash', () => {
|
|
||||||
// const updatePlugin = jest.fn();
|
|
||||||
// const renderedComponent = shallow(<Initializer updatePlugin={updatePlugin} />);
|
|
||||||
|
|
||||||
// expect(renderedComponent.children()).toHaveLength(0);
|
|
||||||
// });
|
|
||||||
|
|
||||||
// it('should call the updatePlugin props when mounted', () => {
|
|
||||||
// const updatePlugin = jest.fn();
|
|
||||||
|
|
||||||
// const wrapper = mount(<Initializer updatePlugin={updatePlugin} />);
|
|
||||||
|
|
||||||
// expect(wrapper.prop('updatePlugin')).toHaveBeenCalledWith(pluginId, 'isReady', true);
|
|
||||||
// });
|
|
||||||
it('should have unit tests specified', () => {
|
|
||||||
expect(true).toBe(true);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@ -1,20 +0,0 @@
|
|||||||
/**
|
|
||||||
* NotFoundPage
|
|
||||||
*
|
|
||||||
* This is the page we show when the user visits a url that doesn't have a route
|
|
||||||
*
|
|
||||||
* NOTE: while this component should technically be a stateless functional
|
|
||||||
* component (SFC), hot reloading does not currently support SFCs. If hot
|
|
||||||
* reloading is not a neccessity for you then you can refactor it and remove
|
|
||||||
* the linting exception.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import React from 'react';
|
|
||||||
|
|
||||||
import NotFound from 'components/NotFound';
|
|
||||||
|
|
||||||
export default class NotFoundPage extends React.Component {
|
|
||||||
render() {
|
|
||||||
return <NotFound {...this.props} />;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
34
packages/strapi-generate-plugin/files/admin/src/index.js
Normal file
34
packages/strapi-generate-plugin/files/admin/src/index.js
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import pluginPkg from '../../package.json';
|
||||||
|
import pluginId from './pluginId';
|
||||||
|
import App from './containers/App';
|
||||||
|
import Initializer from './containers/Initializer';
|
||||||
|
import lifecycles from './lifecycles';
|
||||||
|
import trads from './translations';
|
||||||
|
|
||||||
|
const pluginDescription = pluginPkg.strapi.description || pluginPkg.description;
|
||||||
|
|
||||||
|
function Comp(props) {
|
||||||
|
return <App {...props} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
const plugin = {
|
||||||
|
blockerComponent: null,
|
||||||
|
blockerComponentProps: {},
|
||||||
|
description: pluginDescription,
|
||||||
|
icon: pluginPkg.strapi.icon,
|
||||||
|
id: pluginId,
|
||||||
|
initializer: Initializer,
|
||||||
|
injectedComponents: [],
|
||||||
|
isReady: false,
|
||||||
|
layout: null,
|
||||||
|
lifecycles,
|
||||||
|
leftMenuLinks: [],
|
||||||
|
leftMenuSections: [],
|
||||||
|
mainComponent: Comp,
|
||||||
|
name: pluginPkg.strapi.name,
|
||||||
|
preventComponentRendering: false,
|
||||||
|
trads,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default plugin;
|
||||||
@ -1,23 +1,3 @@
|
|||||||
/*
|
function lifecycles() {}
|
||||||
*
|
|
||||||
* SET THE HOOKS TO ENABLE THE MAGIC OF STRAPI.
|
|
||||||
* -------------------------------------------
|
|
||||||
*
|
|
||||||
* Secure, customise and enhance your project by setting
|
|
||||||
* the hooks via this file.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
module.exports = function lifecycles() {
|
export default lifecycles;
|
||||||
// Set hooks for the AdminPage container.
|
|
||||||
// Note: we don't need to specify the first argument because we already know what "willSecure" refers to.
|
|
||||||
this.setHooks({
|
|
||||||
didGetSecuredData: () => console.log('do something'),
|
|
||||||
});
|
|
||||||
|
|
||||||
// Set hooks for the App container of the Content Manager.
|
|
||||||
// Note: we have to specify the first argument to select a specific container which is located in a plugin, or not.
|
|
||||||
// this.setHooks('content-manager.App', {
|
|
||||||
// willSomething: () => { console.log("Do Something"); }
|
|
||||||
// });
|
|
||||||
};
|
|
||||||
|
|||||||
@ -0,0 +1,35 @@
|
|||||||
|
import ar from './ar.json';
|
||||||
|
import de from './de.json';
|
||||||
|
import en from './en.json';
|
||||||
|
import es from './es.json';
|
||||||
|
import fr from './fr.json';
|
||||||
|
import it from './it.json';
|
||||||
|
import ko from './ko.json';
|
||||||
|
import nl from './nl.json';
|
||||||
|
import pl from './pl.json';
|
||||||
|
import ptBR from './pt-BR.json';
|
||||||
|
import pt from './pt.json';
|
||||||
|
import ru from './ru.json';
|
||||||
|
import tr from './tr.json';
|
||||||
|
import zhHans from './zh-Hans.json';
|
||||||
|
import zh from './zh.json';
|
||||||
|
|
||||||
|
const trads = {
|
||||||
|
ar,
|
||||||
|
de,
|
||||||
|
en,
|
||||||
|
es,
|
||||||
|
fr,
|
||||||
|
it,
|
||||||
|
ko,
|
||||||
|
nl,
|
||||||
|
pl,
|
||||||
|
'pt-BR': ptBR,
|
||||||
|
pt,
|
||||||
|
ru,
|
||||||
|
tr,
|
||||||
|
'zh-Hans': zhHans,
|
||||||
|
zh,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default trads;
|
||||||
@ -0,0 +1 @@
|
|||||||
|
{}
|
||||||
@ -56,6 +56,12 @@ module.exports = (scope, cb) => {
|
|||||||
const pluginDir = path.resolve(scope.rootPath, 'plugins');
|
const pluginDir = path.resolve(scope.rootPath, 'plugins');
|
||||||
fs.ensureDirSync(pluginDir);
|
fs.ensureDirSync(pluginDir);
|
||||||
|
|
||||||
|
// Copy the admin files.
|
||||||
|
fs.copySync(
|
||||||
|
path.resolve(__dirname, '..', 'files'),
|
||||||
|
path.resolve(pluginDir, scope.humanizeId)
|
||||||
|
);
|
||||||
|
|
||||||
// Trigger callback with no error to proceed.
|
// Trigger callback with no error to proceed.
|
||||||
return cb.success();
|
return cb.success();
|
||||||
};
|
};
|
||||||
|
|||||||
@ -4,17 +4,26 @@ import { Button } from 'reactstrap';
|
|||||||
const StyledButtonSecondary = styled(Button)`
|
const StyledButtonSecondary = styled(Button)`
|
||||||
position: relative;
|
position: relative;
|
||||||
height: 3rem;
|
height: 3rem;
|
||||||
padding-left: 1.5rem;
|
font-family: Lato;
|
||||||
padding-right: 1.5rem;
|
border-radius: 3px;
|
||||||
|
padding-left: 1.5rem !important;
|
||||||
|
padding-right: 1.5rem !important;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font-family: Lato;
|
font-family: Lato;
|
||||||
color: #f64d0a;
|
color: #f64d0a;
|
||||||
border: 0.1rem solid #f64d0a;
|
border: 0.1rem solid #f64d0a;
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
&:hover,
|
border: 0.1rem solid #f64d0a;
|
||||||
&:active {
|
|
||||||
color: #f64d0a;
|
color: #f64d0a;
|
||||||
|
|
||||||
|
&:hover,
|
||||||
|
&:active,
|
||||||
|
&.btn-secondary:not(:disabled):not(.disabled):active,
|
||||||
|
&.btn-secondary:not(:disabled):not(.disabled):focus,
|
||||||
|
&.btn-secondary:not(:disabled):not(.disabled):focus:active,
|
||||||
|
&.btn-secondary:hover {
|
||||||
|
color: #f64d0a !important;
|
||||||
background-color: white;
|
background-color: white;
|
||||||
border: 0.1rem solid #f64d0a;
|
border: 0.1rem solid #f64d0a;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,8 +7,8 @@ import colors from '../../assets/styles/colors';
|
|||||||
const StyledButtonModalSuccess = styled(Button)`
|
const StyledButtonModalSuccess = styled(Button)`
|
||||||
position: relative;
|
position: relative;
|
||||||
height: 3rem;
|
height: 3rem;
|
||||||
padding-left: 1.5rem;
|
padding-left: 1.5rem !important;
|
||||||
padding-right: 1.5rem;
|
padding-right: 1.5rem !important;
|
||||||
font-family: Lato;
|
font-family: Lato;
|
||||||
color: ${colors.green};
|
color: ${colors.green};
|
||||||
border: 0.1rem solid ${colors.green};
|
border: 0.1rem solid ${colors.green};
|
||||||
@ -32,8 +32,12 @@ const StyledButtonModalSuccess = styled(Button)`
|
|||||||
background-position: center;
|
background-position: center;
|
||||||
}
|
}
|
||||||
&:hover,
|
&:hover,
|
||||||
&:active {
|
&:active,
|
||||||
color: ${colors.green};
|
&.btn-secondary:not(:disabled):not(.disabled):active,
|
||||||
|
&.btn-secondary:not(:disabled):not(.disabled):focus,
|
||||||
|
&.btn-secondary:not(:disabled):not(.disabled):focus:active,
|
||||||
|
&.btn-secondary:hover {
|
||||||
|
color: ${colors.green} !important;
|
||||||
background-color: white;
|
background-color: white;
|
||||||
border: 0.1rem solid ${colors.green};
|
border: 0.1rem solid ${colors.green};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -31,11 +31,9 @@ const Wrapper = styled.div`
|
|||||||
font-size: 1.3rem;
|
font-size: 1.3rem;
|
||||||
color: #333740 !important;
|
color: #333740 !important;
|
||||||
line-height: 1.6rem;
|
line-height: 1.6rem;
|
||||||
}
|
|
||||||
|
|
||||||
.linkActive {
|
&.linkActive {
|
||||||
z-index: 10;
|
z-index: 10;
|
||||||
&:not(:first-child, :last-child) {
|
|
||||||
background-color: #ffffff !important;
|
background-color: #ffffff !important;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
text-decoration: none !important;
|
text-decoration: none !important;
|
||||||
@ -44,30 +42,6 @@ const Wrapper = styled.div`
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.linkActive:first-child {
|
|
||||||
background-color: #ffffff !important;
|
|
||||||
font-weight: bold;
|
|
||||||
text-decoration: none !important;
|
|
||||||
box-shadow: 0 0 2px rgba(#dbdbdb, 0.5);
|
|
||||||
border-top: 0.2rem solid #1c5de7;
|
|
||||||
}
|
|
||||||
|
|
||||||
.linkActive:last-child {
|
|
||||||
background-color: #ffffff !important;
|
|
||||||
font-weight: bold;
|
|
||||||
text-decoration: none !important;
|
|
||||||
box-shadow: 0 0 2px rgba(#dbdbdb, 0.5);
|
|
||||||
border-top: 0.2rem solid #1c5de7;
|
|
||||||
}
|
|
||||||
|
|
||||||
.linkActive:not(:first-child, :last-child) {
|
|
||||||
background-color: #ffffff !important;
|
|
||||||
font-weight: bold;
|
|
||||||
text-decoration: none !important;
|
|
||||||
box-shadow: 0 0 2px rgba(#dbdbdb, 0.5);
|
|
||||||
border-top: 0.2rem solid #1c5de7;
|
|
||||||
}
|
|
||||||
|
|
||||||
.linkText {
|
.linkText {
|
||||||
display: flex;
|
display: flex;
|
||||||
margin: auto;
|
margin: auto;
|
||||||
|
|||||||
@ -39,6 +39,7 @@ const FormWrapper = styled.div`
|
|||||||
padding-top: 27px;
|
padding-top: 27px;
|
||||||
padding-left: 20px;
|
padding-left: 20px;
|
||||||
padding-right: 20px;
|
padding-right: 20px;
|
||||||
|
padding-bottom: 8px;
|
||||||
border-top: 1px solid
|
border-top: 1px solid
|
||||||
${({ hasErrors, isOpen }) => {
|
${({ hasErrors, isOpen }) => {
|
||||||
if (hasErrors) {
|
if (hasErrors) {
|
||||||
|
|||||||
@ -1,143 +1,7 @@
|
|||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
|
|
||||||
const Wrapper = styled.div`
|
const Wrapper = styled.div`
|
||||||
.modalHeader {
|
padding-bottom: 20px;
|
||||||
margin: 0 2.9rem;
|
|
||||||
padding: 1.4rem 0 2.8rem 0;
|
|
||||||
border-bottom: 1px solid #f6f6f6;
|
|
||||||
position: relative;
|
|
||||||
> button {
|
|
||||||
margin-right: -2.5rem !important;
|
|
||||||
color: #c3c5c8;
|
|
||||||
opacity: 1;
|
|
||||||
font-size: 1.8rem;
|
|
||||||
font-weight: 100;
|
|
||||||
z-index: 999;
|
|
||||||
&:hover,
|
|
||||||
&:focus {
|
|
||||||
color: #c3c5c8;
|
|
||||||
opacity: 1;
|
|
||||||
outline: 0 !important;
|
|
||||||
}
|
|
||||||
> span {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
&:before {
|
|
||||||
-webkit-font-smoothing: antialiased;
|
|
||||||
content: '\F00d';
|
|
||||||
font-family: 'FontAwesome';
|
|
||||||
font-weight: 400;
|
|
||||||
font-size: 1.2rem;
|
|
||||||
margin-right: 10px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.modalBody {
|
|
||||||
padding: 2.2rem 1.4rem 0 1.4rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.modalFooter {
|
|
||||||
padding: 1.2rem 1rem 2.8rem 1rem;
|
|
||||||
border: none;
|
|
||||||
> button {
|
|
||||||
height: 3rem;
|
|
||||||
position: relative;
|
|
||||||
border-radius: 0.3rem;
|
|
||||||
text-transform: capitalize;
|
|
||||||
margin-right: 1.8rem;
|
|
||||||
cursor: pointer;
|
|
||||||
font-family: Lato;
|
|
||||||
&:focus {
|
|
||||||
outline: 0;
|
|
||||||
}
|
|
||||||
> i {
|
|
||||||
margin-right: 1.3rem;
|
|
||||||
}
|
|
||||||
&:hover {
|
|
||||||
&::after {
|
|
||||||
position: absolute;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
border-radius: 0.3rem;
|
|
||||||
content: '';
|
|
||||||
opacity: 0.1;
|
|
||||||
background: #ffffff;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
&.primary {
|
|
||||||
width: 15rem;
|
|
||||||
height: 3rem;
|
|
||||||
margin-left: 1.9rem !important;
|
|
||||||
cursor: pointer;
|
|
||||||
font-family: Lato;
|
|
||||||
border: none !important;
|
|
||||||
font-family: Lato !important;
|
|
||||||
line-height: 1.6rem;
|
|
||||||
font-weight: 600;
|
|
||||||
border-radius: 3px;
|
|
||||||
background: linear-gradient(315deg, #0097f6 0%, #005eea 100%);
|
|
||||||
-webkit-font-smoothing: antialiased;
|
|
||||||
color: white !important;
|
|
||||||
&:hover,
|
|
||||||
&:active {
|
|
||||||
border: none !important;
|
|
||||||
background: linear-gradient(315deg, #0097f6 0%, #005eea 100%);
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
&:focus {
|
|
||||||
outline: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
&.secondary {
|
|
||||||
position: relative;
|
|
||||||
min-width: 100px;
|
|
||||||
height: 3rem;
|
|
||||||
cursor: pointer;
|
|
||||||
background-color: transparent;
|
|
||||||
border: 0.1rem solid #f64d0a;
|
|
||||||
border-radius: 3px;
|
|
||||||
color: #f64d0a;
|
|
||||||
font-family: Lato;
|
|
||||||
&:hover,
|
|
||||||
&:active {
|
|
||||||
color: #f64d0a;
|
|
||||||
background-color: white;
|
|
||||||
border: 0.1rem solid #f64d0a;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const Header = styled.div`
|
export { Wrapper };
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
width: 100%;
|
|
||||||
display: flex;
|
|
||||||
padding: 1.6rem 2.9rem 0 2.9rem;
|
|
||||||
font-size: 1.8rem;
|
|
||||||
font-weight: bold;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const ProviderContainer = styled.div`
|
|
||||||
> div {
|
|
||||||
&:last-child {
|
|
||||||
> input {
|
|
||||||
&:disabled {
|
|
||||||
background-color: #fafafb !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
const Separator = styled.div`
|
|
||||||
width: 100%;
|
|
||||||
margin: 14px 15px 20px 15px;
|
|
||||||
border-bottom: 2px solid #f6f6f6;
|
|
||||||
`;
|
|
||||||
|
|
||||||
export { Header, ProviderContainer, Separator, Wrapper };
|
|
||||||
|
|||||||
@ -272,7 +272,11 @@ class PopUpForm extends React.Component {
|
|||||||
type={includes(value, 'object') ? 'text' : 'textarea'}
|
type={includes(value, 'object') ? 'text' : 'textarea'}
|
||||||
validations={{ required: true }}
|
validations={{ required: true }}
|
||||||
value={get(values, value)}
|
value={get(values, value)}
|
||||||
inputStyle={!includes(value, 'object') ? { height: '16rem' } : {}}
|
inputStyle={
|
||||||
|
!includes(value, 'object')
|
||||||
|
? { height: '16rem', marginBottom: '-0.8rem' }
|
||||||
|
: {}
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</>
|
</>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user