mirror of
https://github.com/strapi/strapi.git
synced 2025-11-02 19:04:38 +00:00
Created Admin and PluginDispatcher containers, update the history dependency to remove the error log in the console
This commit is contained in:
parent
0f554234d8
commit
661a89642b
15
packages/strapi-admin/admin/src/containers/Admin/actions.js
Normal file
15
packages/strapi-admin/admin/src/containers/Admin/actions.js
Normal file
@ -0,0 +1,15 @@
|
||||
/*
|
||||
*
|
||||
* Admin actions
|
||||
*
|
||||
*/
|
||||
|
||||
import {
|
||||
DEFAULT_ACTION,
|
||||
} from './constants';
|
||||
|
||||
export function defaultAction() {
|
||||
return {
|
||||
type: DEFAULT_ACTION,
|
||||
};
|
||||
}
|
||||
@ -0,0 +1,7 @@
|
||||
/*
|
||||
*
|
||||
* Admin constants
|
||||
*
|
||||
*/
|
||||
|
||||
export const DEFAULT_ACTION = 'StrapiAdmin/Admin/DEFAULT_ACTION';
|
||||
53
packages/strapi-admin/admin/src/containers/Admin/index.js
Normal file
53
packages/strapi-admin/admin/src/containers/Admin/index.js
Normal file
@ -0,0 +1,53 @@
|
||||
/**
|
||||
*
|
||||
* Admin
|
||||
*
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
// import PropTypes from 'prop-types';
|
||||
import { connect } from 'react-redux';
|
||||
import { createStructuredSelector } from 'reselect';
|
||||
import { bindActionCreators, compose } from 'redux';
|
||||
|
||||
import makeSelectAdmin from './selectors';
|
||||
import reducer from './reducer';
|
||||
import saga from './saga';
|
||||
|
||||
export class Admin extends React.Component {
|
||||
// eslint-disable-line react/prefer-stateless-function
|
||||
render() {
|
||||
return <div />;
|
||||
}
|
||||
}
|
||||
|
||||
Admin.propTypes = {};
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
admin: makeSelectAdmin(),
|
||||
});
|
||||
|
||||
function mapDispatchToProps(dispatch) {
|
||||
return bindActionCreators({}, 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 = strapi.injectReducer({ key: 'admin', 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 = strapi.injectSaga({ key: 'admin', saga });
|
||||
|
||||
export default compose(
|
||||
withReducer,
|
||||
withSaga,
|
||||
withConnect,
|
||||
)(Admin);
|
||||
23
packages/strapi-admin/admin/src/containers/Admin/reducer.js
Normal file
23
packages/strapi-admin/admin/src/containers/Admin/reducer.js
Normal file
@ -0,0 +1,23 @@
|
||||
/*
|
||||
*
|
||||
* Admin reducer
|
||||
*
|
||||
*/
|
||||
|
||||
import { fromJS } from 'immutable';
|
||||
import {
|
||||
DEFAULT_ACTION,
|
||||
} from './constants';
|
||||
|
||||
const initialState = fromJS({});
|
||||
|
||||
function adminReducer(state = initialState, action) {
|
||||
switch (action.type) {
|
||||
case DEFAULT_ACTION:
|
||||
return state;
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
export default adminReducer;
|
||||
6
packages/strapi-admin/admin/src/containers/Admin/saga.js
Normal file
6
packages/strapi-admin/admin/src/containers/Admin/saga.js
Normal file
@ -0,0 +1,6 @@
|
||||
// import { take, call, put, select } from 'redux-saga/effects';
|
||||
|
||||
// Individual exports for testing
|
||||
export default function* defaultSaga() {
|
||||
// See example in containers/HomePage/saga.js
|
||||
}
|
||||
@ -0,0 +1,23 @@
|
||||
import { createSelector } from 'reselect';
|
||||
|
||||
/**
|
||||
* Direct selector to the admin state domain
|
||||
*/
|
||||
const selectAdminDomain = () => state => state.get('admin');
|
||||
|
||||
/**
|
||||
* Other specific selectors
|
||||
*/
|
||||
|
||||
/**
|
||||
* Default selector used by Admin
|
||||
*/
|
||||
|
||||
const makeSelectAdmin = () =>
|
||||
createSelector(
|
||||
selectAdminDomain(),
|
||||
substate => substate.toJS(),
|
||||
);
|
||||
|
||||
export default makeSelectAdmin;
|
||||
export { selectAdminDomain };
|
||||
@ -0,0 +1,18 @@
|
||||
|
||||
import {
|
||||
defaultAction,
|
||||
} from '../actions';
|
||||
import {
|
||||
DEFAULT_ACTION,
|
||||
} from '../constants';
|
||||
|
||||
describe('Admin actions', () => {
|
||||
describe('Default Action', () => {
|
||||
it('has a type of DEFAULT_ACTION', () => {
|
||||
const expected = {
|
||||
type: DEFAULT_ACTION,
|
||||
};
|
||||
expect(defaultAction()).toEqual(expected);
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,21 @@
|
||||
// import React from 'react';
|
||||
// import { shallow } from 'enzyme';
|
||||
// import mountWithIntl from 'testUtils/mountWithIntl';
|
||||
// import formatMessagesWithPluginId from 'testUtils/formatMessages';
|
||||
|
||||
// This part is needed if you need to test the lifecycle of a container that contains FormattedMessages
|
||||
|
||||
// import pluginId from '../../../pluginId';
|
||||
// import pluginTradsEn from '../../../translations/en.json';
|
||||
|
||||
// import { Admin } from '../index';
|
||||
|
||||
// const messages = formatMessagesWithPluginId(pluginId, pluginTradsEn);
|
||||
// const renderComponent = (props = {}) => mountWithIntl(<Admin {...props} />, messages);
|
||||
|
||||
describe('<Admin />', () => {
|
||||
it('should not crash', () => {
|
||||
// shallow(<Admin />);
|
||||
// renderComponent({});
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,9 @@
|
||||
|
||||
import { fromJS } from 'immutable';
|
||||
import adminReducer from '../reducer';
|
||||
|
||||
describe('adminReducer', () => {
|
||||
it('returns the initial state', () => {
|
||||
expect(adminReducer(undefined, {})).toEqual(fromJS({}));
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,15 @@
|
||||
/**
|
||||
* Test sagas
|
||||
*/
|
||||
|
||||
/* eslint-disable redux-saga/yield-effects */
|
||||
// import { take, call, put, select } from 'redux-saga/effects';
|
||||
// import { defaultSaga } from '../saga';
|
||||
|
||||
// const generator = defaultSaga();
|
||||
|
||||
describe('defaultSaga Saga', () => {
|
||||
it('Expect to have unit tests specified', () => {
|
||||
expect(true).toEqual(false);
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,10 @@
|
||||
// import { fromJS } from 'immutable';
|
||||
// import { makeSelectAdminDomain } from '../selectors';
|
||||
|
||||
// const selector = makeSelectAdminDomain();
|
||||
|
||||
describe('makeSelectAdminDomain', () => {
|
||||
it('Expect to have unit tests specified', () => {
|
||||
expect(true).toEqual(false);
|
||||
});
|
||||
});
|
||||
@ -12,7 +12,6 @@
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Switch, Route } from 'react-router-dom';
|
||||
// From strapi-helper-plugin
|
||||
import LoadingIndicatorPage from 'components/LoadingIndicatorPage';
|
||||
@ -25,36 +24,30 @@ import NotificationProvider from '../NotificationProvider';
|
||||
import AppLoader from '../AppLoader';
|
||||
import styles from './styles.scss';
|
||||
|
||||
export class App extends React.Component { // eslint-disable-line react/prefer-stateless-function
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<NotificationProvider />
|
||||
<AppLoader>
|
||||
{({ shouldLoad }) => {
|
||||
if (shouldLoad) {
|
||||
return <LoadingIndicatorPage />;
|
||||
}
|
||||
function App() {
|
||||
return (
|
||||
<div>
|
||||
<NotificationProvider />
|
||||
<AppLoader>
|
||||
{({ shouldLoad }) => {
|
||||
if (shouldLoad) {
|
||||
return <LoadingIndicatorPage />;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<Switch>
|
||||
<Route path="/" component={AdminPage} />
|
||||
<Route path="" component={NotFoundPage} />
|
||||
</Switch>
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
</AppLoader>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<Switch>
|
||||
<Route path='/' component={AdminPage} />
|
||||
<Route path='' component={NotFoundPage} />
|
||||
</Switch>
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
</AppLoader>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
App.contextTypes = {
|
||||
router: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
App.propTypes = {};
|
||||
|
||||
export default App;
|
||||
|
||||
@ -0,0 +1,31 @@
|
||||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
import { Route } from 'react-router-dom';
|
||||
|
||||
import AppLoader from '../../AppLoader';
|
||||
import App from '../index';
|
||||
|
||||
describe('<App />', () => {
|
||||
it('should render the <AppLoader />', () => {
|
||||
const renderedComponent = shallow(<App />);
|
||||
expect(renderedComponent.find(AppLoader)).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('Should render the <Switch /> if the app is loading', () => {
|
||||
const topComp = shallow(<App />);
|
||||
const insideAppLoaderNotLoading = shallow(
|
||||
topComp.find(AppLoader).prop('children')({ shouldLoad: false }),
|
||||
);
|
||||
|
||||
expect(insideAppLoaderNotLoading.find(Route).length).toBe(2);
|
||||
});
|
||||
|
||||
it('should not render the <Switch /> if the app is loading', () => {
|
||||
const topComp = shallow(<App />);
|
||||
|
||||
const insideAppLoaderLoading = shallow(
|
||||
topComp.find(AppLoader).prop('children')({ shouldLoad: true }),
|
||||
);
|
||||
expect(insideAppLoaderLoading.find(Route).length).toBe(0);
|
||||
});
|
||||
});
|
||||
@ -1,7 +1,7 @@
|
||||
/**
|
||||
*
|
||||
*
|
||||
* AppLoader
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
@ -9,16 +9,16 @@ import { connect } from 'react-redux';
|
||||
import PropTypes from 'prop-types';
|
||||
import makeSelectApp from '../App/selectors';
|
||||
|
||||
class AppLoader extends React.Component {
|
||||
export class AppLoader extends React.Component {
|
||||
shouldLoad = () => {
|
||||
const { appPlugins, plugins: mountedPlugins } = this.props;
|
||||
|
||||
return appPlugins.length !== Object.keys(mountedPlugins).length;
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const { children } = this.props;
|
||||
|
||||
|
||||
return children({ shouldLoad: this.shouldLoad() });
|
||||
}
|
||||
}
|
||||
@ -31,4 +31,7 @@ AppLoader.propTypes = {
|
||||
|
||||
const mapStateToProps = makeSelectApp();
|
||||
|
||||
export default connect(mapStateToProps, null)(AppLoader);
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
null,
|
||||
)(AppLoader);
|
||||
|
||||
@ -0,0 +1,19 @@
|
||||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
|
||||
import { AppLoader } from '../index';
|
||||
|
||||
describe('<AppLoader />', () => {
|
||||
let props;
|
||||
|
||||
beforeEach(() => {
|
||||
props = {
|
||||
appPlugins: [],
|
||||
plugins: {},
|
||||
};
|
||||
});
|
||||
|
||||
it('should not crash', () => {
|
||||
shallow(<AppLoader {...props}>{() => null}</AppLoader>);
|
||||
});
|
||||
});
|
||||
@ -4,12 +4,9 @@
|
||||
*
|
||||
*/
|
||||
|
||||
import { dispatch } from 'app';
|
||||
import { dispatch } from '../../app';
|
||||
|
||||
import {
|
||||
SHOW_NOTIFICATION,
|
||||
HIDE_NOTIFICATION,
|
||||
} from './constants';
|
||||
import { SHOW_NOTIFICATION, HIDE_NOTIFICATION } from './constants';
|
||||
|
||||
let nextNotificationId = 0;
|
||||
|
||||
@ -17,7 +14,7 @@ export function showNotification(message, status) {
|
||||
nextNotificationId++; // eslint-disable-line no-plusplus
|
||||
|
||||
// Start timeout to hide the notification
|
||||
((id) => {
|
||||
(id => {
|
||||
setTimeout(() => {
|
||||
dispatch(hideNotification(id));
|
||||
}, 2500);
|
||||
|
||||
@ -10,12 +10,19 @@ import cn from 'classnames';
|
||||
import { connect } from 'react-redux';
|
||||
import { bindActionCreators, compose } from 'redux';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import injectSaga from 'utils/injectSaga';
|
||||
import injectReducer from 'utils/injectReducer';
|
||||
import injectSaga from '../../utils/injectSaga';
|
||||
import injectReducer from '../../utils/injectReducer';
|
||||
|
||||
import OnboardingVideo from 'components/OnboardingVideo';
|
||||
import OnboardingVideo from '../../components/OnboardingVideo';
|
||||
|
||||
import { getVideos, onClick, removeVideos, setVideoDuration, setVideoEnd, updateVideoStartTime } from './actions';
|
||||
import {
|
||||
getVideos,
|
||||
onClick,
|
||||
removeVideos,
|
||||
setVideoDuration,
|
||||
setVideoEnd,
|
||||
updateVideoStartTime,
|
||||
} from './actions';
|
||||
import makeSelectOnboarding from './selectors';
|
||||
import reducer from './reducer';
|
||||
import saga from './saga';
|
||||
@ -43,17 +50,17 @@ export class Onboarding extends React.Component {
|
||||
|
||||
setVideoEnd = () => {
|
||||
this.setVideoEnd();
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
didPlayVideo = (index, currTime) => {
|
||||
const eventName = `didPlay${index}GetStartedVideo`;
|
||||
this.context.emitEvent(eventName, {timestamp: currTime});
|
||||
}
|
||||
this.context.emitEvent(eventName, { timestamp: currTime });
|
||||
};
|
||||
|
||||
didStopVideo = (index, currTime) => {
|
||||
const eventName = `didStop${index}Video`;
|
||||
this.context.emitEvent(eventName, {timestamp: currTime});
|
||||
}
|
||||
this.context.emitEvent(eventName, { timestamp: currTime });
|
||||
};
|
||||
|
||||
handleOpenModal = () => this.setState({ showVideos: true });
|
||||
|
||||
@ -61,16 +68,17 @@ export class Onboarding extends React.Component {
|
||||
this.setState(prevState => ({ showVideos: !prevState.showVideos }));
|
||||
|
||||
const { showVideos } = this.state;
|
||||
const eventName = showVideos ? 'didOpenGetStartedVideoContainer' : 'didCloseGetStartedVideoContainer';
|
||||
const eventName = showVideos
|
||||
? 'didOpenGetStartedVideoContainer'
|
||||
: 'didCloseGetStartedVideoContainer';
|
||||
|
||||
this.context.emitEvent(eventName);
|
||||
};
|
||||
|
||||
updateCurrentTime = (index, current, duration) => {
|
||||
|
||||
this.props.updateVideoStartTime(index, current);
|
||||
|
||||
const percent = current * 100 / duration;
|
||||
const percent = (current * 100) / duration;
|
||||
const video = this.props.videos[index];
|
||||
|
||||
if (percent >= 80) {
|
||||
@ -80,7 +88,7 @@ export class Onboarding extends React.Component {
|
||||
}
|
||||
};
|
||||
|
||||
updateEnd = (index) => {
|
||||
updateEnd = index => {
|
||||
this.props.setVideoEnd(index, true);
|
||||
};
|
||||
|
||||
@ -89,12 +97,29 @@ export class Onboarding extends React.Component {
|
||||
const { videos, onClick, setVideoDuration } = this.props;
|
||||
|
||||
return (
|
||||
<div className={cn(styles.videosWrapper, videos.length > 0 ? styles.visible : styles.hidden)}>
|
||||
<div className={cn(styles.videosContent, this.state.showVideos ? styles.shown : styles.hide)}>
|
||||
<div
|
||||
className={cn(
|
||||
styles.videosWrapper,
|
||||
videos.length > 0 ? styles.visible : styles.hidden,
|
||||
)}
|
||||
>
|
||||
<div
|
||||
className={cn(
|
||||
styles.videosContent,
|
||||
this.state.showVideos ? styles.shown : styles.hide,
|
||||
)}
|
||||
>
|
||||
<div className={styles.videosHeader}>
|
||||
<p><FormattedMessage id="app.components.Onboarding.title" /></p>
|
||||
<p>
|
||||
<FormattedMessage id='app.components.Onboarding.title' />
|
||||
</p>
|
||||
{videos.length && (
|
||||
<p>{Math.floor((videos.filter(v => v.end).length)*100/videos.length)}<FormattedMessage id="app.components.Onboarding.label.completed" /></p>
|
||||
<p>
|
||||
{Math.floor(
|
||||
(videos.filter(v => v.end).length * 100) / videos.length,
|
||||
)}
|
||||
<FormattedMessage id='app.components.Onboarding.label.completed' />
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
<ul className={styles.onboardingList}>
|
||||
@ -120,8 +145,8 @@ export class Onboarding extends React.Component {
|
||||
onClick={this.handleVideosToggle}
|
||||
className={this.state.showVideos ? styles.active : ''}
|
||||
>
|
||||
<i className="fa fa-question" />
|
||||
<i className="fa fa-times" />
|
||||
<i className='fa fa-question' />
|
||||
<i className='fa fa-times' />
|
||||
<span />
|
||||
</button>
|
||||
</div>
|
||||
@ -157,7 +182,17 @@ Onboarding.propTypes = {
|
||||
const mapStateToProps = makeSelectOnboarding();
|
||||
|
||||
function mapDispatchToProps(dispatch) {
|
||||
return bindActionCreators({ getVideos, onClick, setVideoDuration, updateVideoStartTime, setVideoEnd, removeVideos }, dispatch);
|
||||
return bindActionCreators(
|
||||
{
|
||||
getVideos,
|
||||
onClick,
|
||||
setVideoDuration,
|
||||
updateVideoStartTime,
|
||||
setVideoEnd,
|
||||
removeVideos,
|
||||
},
|
||||
dispatch,
|
||||
);
|
||||
}
|
||||
|
||||
const withConnect = connect(
|
||||
|
||||
@ -2,12 +2,13 @@
|
||||
* Common configuration for the app in both dev an prod mode
|
||||
*/
|
||||
|
||||
import createHistory from 'history/createBrowserHistory';
|
||||
import { createBrowserHistory } from 'history';
|
||||
|
||||
import './public-path';
|
||||
import configureStore from './configureStore';
|
||||
|
||||
const basename = strapi.remoteURL.replace(window.location.origin, '');
|
||||
const history = createHistory({
|
||||
const history = createBrowserHistory({
|
||||
basename,
|
||||
});
|
||||
const store = configureStore({}, history);
|
||||
|
||||
@ -83,7 +83,7 @@
|
||||
"babel-polyfill": "6.26.0",
|
||||
"bootstrap": "^4.0.0-alpha.6",
|
||||
"classnames": "^2.2.5",
|
||||
"history": "^4.6.3",
|
||||
"history": "^4.9.0",
|
||||
"immutable": "^3.8.2",
|
||||
"imports-loader": "^0.7.1",
|
||||
"invariant": "2.2.1",
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user