Fix conflict
@ -1,16 +1,7 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
end_of_line = lf
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
|
||||
[{package.json,*.yml}]
|
||||
insert_final_newline = false
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
|
||||
103
packages/strapi-admin/.gitattributes
vendored
Normal file
@ -0,0 +1,103 @@
|
||||
# From https://github.com/Danimoth/gitattributes/blob/master/Web.gitattributes
|
||||
|
||||
# Handle line endings automatically for files detected as text
|
||||
# and leave all files detected as binary untouched.
|
||||
* text=auto
|
||||
|
||||
#
|
||||
# The above will handle all files NOT found below
|
||||
#
|
||||
|
||||
#
|
||||
## These files are text and should be normalized (Convert crlf => lf)
|
||||
#
|
||||
|
||||
# source code
|
||||
*.php text
|
||||
*.css text
|
||||
*.sass text
|
||||
*.scss text
|
||||
*.less text
|
||||
*.styl text
|
||||
*.js text eol=lf
|
||||
*.coffee text
|
||||
*.json text
|
||||
*.htm text
|
||||
*.html text
|
||||
*.xml text
|
||||
*.svg text
|
||||
*.txt text
|
||||
*.ini text
|
||||
*.inc text
|
||||
*.pl text
|
||||
*.rb text
|
||||
*.py text
|
||||
*.scm text
|
||||
*.sql text
|
||||
*.sh text
|
||||
*.bat text
|
||||
|
||||
# templates
|
||||
*.ejs text
|
||||
*.hbt text
|
||||
*.jade text
|
||||
*.haml text
|
||||
*.hbs text
|
||||
*.dot text
|
||||
*.tmpl text
|
||||
*.phtml text
|
||||
|
||||
# git config
|
||||
.gitattributes text
|
||||
.gitignore text
|
||||
.gitconfig text
|
||||
|
||||
# code analysis config
|
||||
.jshintrc text
|
||||
.jscsrc text
|
||||
.jshintignore text
|
||||
.csslintrc text
|
||||
|
||||
# misc config
|
||||
*.yaml text
|
||||
*.yml text
|
||||
.editorconfig text
|
||||
|
||||
# build config
|
||||
*.npmignore text
|
||||
*.bowerrc text
|
||||
|
||||
# Heroku
|
||||
Procfile text
|
||||
.slugignore text
|
||||
|
||||
# Documentation
|
||||
*.md text
|
||||
LICENSE text
|
||||
AUTHORS text
|
||||
|
||||
|
||||
#
|
||||
## These files are binary and should be left untouched
|
||||
#
|
||||
|
||||
# (binary is a macro for -text -diff)
|
||||
*.png binary
|
||||
*.jpg binary
|
||||
*.jpeg binary
|
||||
*.gif binary
|
||||
*.ico binary
|
||||
*.mov binary
|
||||
*.mp4 binary
|
||||
*.mp3 binary
|
||||
*.flv binary
|
||||
*.fla binary
|
||||
*.swf binary
|
||||
*.gz binary
|
||||
*.zip binary
|
||||
*.7z binary
|
||||
*.ttf binary
|
||||
*.eot binary
|
||||
*.woff binary
|
||||
*.pyc binary
|
||||
*.pdf binary
|
||||
105
packages/strapi-admin/.gitignore
vendored
Executable file → Normal file
@ -1,102 +1,11 @@
|
||||
############################
|
||||
# OS X
|
||||
############################
|
||||
|
||||
.DS_Store
|
||||
.AppleDouble
|
||||
.LSOverride
|
||||
Icon
|
||||
.Spotlight-V100
|
||||
.Trashes
|
||||
._*
|
||||
|
||||
|
||||
############################
|
||||
# Linux
|
||||
############################
|
||||
|
||||
*~
|
||||
|
||||
|
||||
############################
|
||||
# Windows
|
||||
############################
|
||||
|
||||
Thumbs.db
|
||||
ehthumbs.db
|
||||
Desktop.ini
|
||||
$RECYCLE.BIN/
|
||||
*.cab
|
||||
*.msi
|
||||
*.msm
|
||||
*.msp
|
||||
|
||||
|
||||
############################
|
||||
# Packages
|
||||
############################
|
||||
|
||||
*.7z
|
||||
*.csv
|
||||
*.dat
|
||||
*.dmg
|
||||
*.gz
|
||||
*.iso
|
||||
*.jar
|
||||
*.rar
|
||||
*.tar
|
||||
*.zip
|
||||
*.com
|
||||
*.class
|
||||
*.dll
|
||||
*.exe
|
||||
*.o
|
||||
*.seed
|
||||
*.so
|
||||
*.swo
|
||||
*.swp
|
||||
*.swn
|
||||
*.swm
|
||||
*.out
|
||||
*.pid
|
||||
|
||||
|
||||
############################
|
||||
# Logs and databases
|
||||
############################
|
||||
|
||||
*.log
|
||||
*.sql
|
||||
|
||||
|
||||
############################
|
||||
# Misc.
|
||||
############################
|
||||
|
||||
*#
|
||||
ssl
|
||||
.idea
|
||||
nbproject
|
||||
|
||||
|
||||
############################
|
||||
# Node.js
|
||||
############################
|
||||
|
||||
lib-cov
|
||||
lcov.info
|
||||
pids
|
||||
logs
|
||||
results
|
||||
# Don't check auto-generated stuff into git
|
||||
coverage
|
||||
build
|
||||
node_modules
|
||||
.node_history
|
||||
stats.json
|
||||
package-lock.json
|
||||
|
||||
|
||||
############################
|
||||
# Tests
|
||||
############################
|
||||
|
||||
testApp
|
||||
coverage
|
||||
# Cruft
|
||||
.DS_Store
|
||||
npm-debug.log
|
||||
.idea
|
||||
|
||||
@ -14,16 +14,16 @@ Go in your project: `cd myApp`.
|
||||
|
||||
Remove the generated admin panel: `rm -rf admin`.
|
||||
|
||||
Create a symlink in order to be able to easily develop the admin panel from your generated
|
||||
Strapi application: `ln -s /usr/local/lib/node_modules/strapi-generate-admin/files/admin admin`
|
||||
Create a symlink in order to be able to easily develop the admin panel from your generated
|
||||
Strapi application: `ln -s /usr/local/lib/node_modules/strapi-generate-admin admin`
|
||||
(supposing `/usr/local/lib/node_modules` is your global node modules folder).
|
||||
|
||||
### Development
|
||||
|
||||
Start the React application: `cd myApp/admin/public`, then `npm start`.
|
||||
Start the React application: `cd myApp/admin`, then `npm start`.
|
||||
|
||||
The admin panel should now be available at [http://localhost:4000](http://localhost:4000).
|
||||
|
||||
### Build
|
||||
|
||||
In order to check your updates, you can build the admin panel: `cd myApp/admin/public`, then `npm run build`.
|
||||
In order to check your updates, you can build the admin panel: `cd myApp`, then `npm run build`.
|
||||
|
||||
@ -10,58 +10,36 @@ import 'babel-polyfill';
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { Provider } from 'react-redux';
|
||||
import { applyRouterMiddleware, Router, browserHistory } from 'react-router';
|
||||
import { syncHistoryWithStore } from 'react-router-redux';
|
||||
import useScroll from 'react-router-scroll';
|
||||
import { ConnectedRouter } from 'react-router-redux';
|
||||
import createHistory from 'history/createBrowserHistory';
|
||||
import _ from 'lodash';
|
||||
import LanguageProvider from 'containers/LanguageProvider';
|
||||
import NotificationProvider from 'containers/NotificationProvider';
|
||||
import configureStore from './store';
|
||||
import 'sanitize.css/sanitize.css';
|
||||
import 'whatwg-fetch';
|
||||
|
||||
// Import i18n messages
|
||||
import LanguageProvider from 'containers/LanguageProvider';
|
||||
|
||||
import App from 'containers/App';
|
||||
import { showNotification } from 'containers/NotificationProvider/actions';
|
||||
import { pluginLoaded, updatePlugin } from 'containers/App/actions';
|
||||
|
||||
import { plugins } from '../../config/admin.json';
|
||||
import configureStore from './store';
|
||||
import { translationMessages, languages } from './i18n';
|
||||
|
||||
// Import the CSS reset, which HtmlWebpackPlugin transfers to the build folder
|
||||
import 'sanitize.css/sanitize.css';
|
||||
|
||||
// Create redux store with history
|
||||
// this uses the singleton browserHistory provided by react-router
|
||||
// Optionally, this could be changed to leverage a created history
|
||||
// e.g. `const browserHistory = useRouterHistory(createBrowserHistory)();`
|
||||
const initialState = {};
|
||||
const store = configureStore(initialState, browserHistory);
|
||||
|
||||
// Sync history and store, as the react-router-redux reducer
|
||||
// is under the non-default key ("routing"), selectLocationState
|
||||
// must be provided for resolving how to retrieve the "route" in the state
|
||||
import { selectLocationState } from 'containers/App/selectors';
|
||||
const history = syncHistoryWithStore(browserHistory, store, {
|
||||
selectLocationState: selectLocationState(),
|
||||
const history = createHistory({
|
||||
basename: '/admin',
|
||||
});
|
||||
|
||||
// Set up the router, wrapping all Routes in the App component
|
||||
import App from 'containers/App';
|
||||
import createRoutes from './routes';
|
||||
const rootRoute = {
|
||||
component: App,
|
||||
childRoutes: createRoutes(store),
|
||||
};
|
||||
const store = configureStore(initialState, history);
|
||||
|
||||
const render = (translatedMessages) => {
|
||||
ReactDOM.render(
|
||||
<Provider store={store}>
|
||||
<LanguageProvider messages={translatedMessages}>
|
||||
<NotificationProvider>
|
||||
<Router
|
||||
history={history}
|
||||
routes={rootRoute}
|
||||
render={
|
||||
// Scroll to top when going to a new page, imitating default browser
|
||||
// behaviour
|
||||
applyRouterMiddleware(useScroll())
|
||||
}
|
||||
/>
|
||||
</NotificationProvider>
|
||||
<ConnectedRouter history={history}>
|
||||
<App />
|
||||
</ConnectedRouter>
|
||||
</LanguageProvider>
|
||||
</Provider>,
|
||||
document.getElementById('app')
|
||||
@ -91,14 +69,6 @@ window.onload = function onLoad() {
|
||||
}
|
||||
};
|
||||
|
||||
// Install ServiceWorker and AppCache in the end since
|
||||
// it's not most important operation and if main code fails,
|
||||
// we do not want it installed
|
||||
// import { install } from 'offline-plugin/runtime';
|
||||
// install();
|
||||
|
||||
import { pluginLoaded, updatePlugin } from './containers/App/actions';
|
||||
|
||||
/**
|
||||
* Public Strapi object exposed to the `window` object
|
||||
*/
|
||||
@ -111,22 +81,6 @@ import { pluginLoaded, updatePlugin } from './containers/App/actions';
|
||||
const registerPlugin = (plugin) => {
|
||||
const formattedPlugin = plugin;
|
||||
|
||||
// Add routes
|
||||
// Initial list of routes
|
||||
const homeRoute = rootRoute.childRoutes[0];
|
||||
const pluginsRoute = _.find(homeRoute.childRoutes, { name: 'plugins' });
|
||||
|
||||
// Create a new prefixed route for each plugin routes
|
||||
if (formattedPlugin && formattedPlugin.routes) {
|
||||
formattedPlugin.routes.forEach(route => {
|
||||
pluginsRoute.childRoutes.push({
|
||||
path: `/plugins/${formattedPlugin.id}${route.path}`,
|
||||
name: `plugins_${formattedPlugin.id}_${route.name}`,
|
||||
getComponent: route.getComponent,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Merge admin translation messages
|
||||
_.merge(translationMessages, formattedPlugin.translationMessages);
|
||||
|
||||
@ -135,8 +89,6 @@ const registerPlugin = (plugin) => {
|
||||
store.dispatch(pluginLoaded(formattedPlugin));
|
||||
};
|
||||
|
||||
import { showNotification } from './containers/NotificationProvider/actions';
|
||||
|
||||
const displayNotification = (message, status) => {
|
||||
store.dispatch(showNotification(message, status));
|
||||
};
|
||||
@ -170,11 +122,28 @@ window.Strapi = {
|
||||
store.dispatch(updatePlugin(pluginId, 'leftMenuSections', leftMenuSectionsUpdated));
|
||||
},
|
||||
}),
|
||||
router: browserHistory,
|
||||
router: history,
|
||||
languages,
|
||||
};
|
||||
|
||||
// Ping each plugins port defined in configuration
|
||||
if (window.location.hostname === 'localhost') {
|
||||
plugins.ports.forEach(pluginPort => {
|
||||
// Define plugin url
|
||||
const pluginUrl = `http://localhost:${pluginPort}/main.js`;
|
||||
|
||||
// Check that the server in running
|
||||
fetch(pluginUrl)
|
||||
.then(() => {
|
||||
// Inject `script` tag in DOM
|
||||
const script = window.document.createElement('script');
|
||||
script.src = pluginUrl;
|
||||
window.document.body.appendChild(script);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
const dispatch = store.dispatch;
|
||||
export {
|
||||
dispatch,
|
||||
};
|
||||
};
|
||||
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
@ -6,8 +6,10 @@
|
||||
|
||||
import React from 'react';
|
||||
import { defineMessages, FormattedMessage } from 'react-intl';
|
||||
import styles from './styles.scss';
|
||||
|
||||
import LocaleToggle from 'containers/LocaleToggle';
|
||||
|
||||
import styles from './styles.scss';
|
||||
import messages from './messages.json';
|
||||
defineMessages(messages);
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { Link } from 'react-router';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import styles from './styles.scss';
|
||||
|
||||
@ -17,8 +17,7 @@
|
||||
font-size: 2rem;
|
||||
letter-spacing: 0.2rem;
|
||||
color: $white;
|
||||
|
||||
// TMP
|
||||
|
||||
background-image: url('../../assets/images/logo-strapi.png');
|
||||
background-repeat: no-repeat;
|
||||
background-position: center center;
|
||||
@ -7,7 +7,7 @@
|
||||
import _ from 'lodash';
|
||||
import React from 'react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import { Link } from 'react-router';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import styles from './styles.scss';
|
||||
|
||||
@ -17,10 +17,6 @@ class LeftMenuLink extends React.Component { // eslint-disable-line react/prefer
|
||||
// because of the two levels router.
|
||||
const isLinkActive = _.startsWith(window.location.pathname.replace('/admin', ''), this.props.destination);
|
||||
|
||||
// const label = this.props.label.id
|
||||
// ? <FormattedMessage id={this.props.label.id} className={styles.linkLabel} />
|
||||
// : <span className={styles.linkLabel}>{this.props.label}</span>;
|
||||
|
||||
return (
|
||||
<li className={styles.item}>
|
||||
<Link className={`${styles.link} ${isLinkActive ? styles.linkActive : ''}`} to={this.props.destination}>
|
||||
@ -33,11 +29,9 @@ class LeftMenuLink extends React.Component { // eslint-disable-line react/prefer
|
||||
}
|
||||
|
||||
LeftMenuLink.propTypes = {
|
||||
icon: React.PropTypes.string,
|
||||
label: React.PropTypes.string,
|
||||
destination: React.PropTypes.string,
|
||||
isActive: React.PropTypes.bool,
|
||||
leftMenuSections: React.PropTypes.object,
|
||||
destination: React.PropTypes.string.isRequired,
|
||||
icon: React.PropTypes.string.isRequired,
|
||||
label: React.PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
export default LeftMenuLink;
|
||||
@ -8,7 +8,7 @@
|
||||
}
|
||||
|
||||
.link {
|
||||
padding-top: 0.8rem;
|
||||
padding-top: .9rem;
|
||||
padding-bottom: 0.2rem;
|
||||
padding-left: 1.6rem;
|
||||
min-height: 3.6rem;
|
||||
@ -24,10 +24,19 @@
|
||||
border-left: 0.3rem solid $strapi-blue;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
&:focus {
|
||||
color: $white;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
&:visited {
|
||||
color: $left-menu-link-color;
|
||||
}
|
||||
}
|
||||
|
||||
.linkActive {
|
||||
color: $white;
|
||||
color: $white !important;
|
||||
border-left: 0.3rem solid $strapi-blue;
|
||||
}
|
||||
|
||||
@ -6,6 +6,8 @@
|
||||
|
||||
import React from 'react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import { map } from 'lodash';
|
||||
|
||||
import LeftMenuLink from 'components/LeftMenuLink';
|
||||
|
||||
import styles from './styles.scss';
|
||||
@ -16,20 +18,11 @@ class LeftMenuLinkContainer extends React.Component { // eslint-disable-line rea
|
||||
// Generate the list of sections
|
||||
const linkSections = this.props.plugins.valueSeq().map(plugin => (
|
||||
plugin.get('leftMenuSections').map((leftMenuSection, j) => {
|
||||
const sectionlinks = leftMenuSection.get('links').map((sectionLink, k) => (
|
||||
<LeftMenuLink
|
||||
key={k}
|
||||
icon={sectionLink.get('icon') || 'link'}
|
||||
label={sectionLink.get('label')}
|
||||
destination={`/plugins/${plugin.get('id')}/${sectionLink.get('destination')}`}
|
||||
/>
|
||||
));
|
||||
|
||||
return (
|
||||
<div key={j}>
|
||||
<p className={styles.title}>{leftMenuSection.get('name')}</p>
|
||||
<ul className={styles.list}>
|
||||
{sectionlinks}
|
||||
{map(this.links, (link, k) => <LeftMenuLink key={k} icon={link.get('icon') || 'link'} label={link.get('label')} destination={`/plugins/${plugin.get('id')}/${link.get('destination')}`} /> )}
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
@ -46,7 +39,11 @@ class LeftMenuLinkContainer extends React.Component { // eslint-disable-line rea
|
||||
destination={`/plugins/${plugin.get('id')}`}
|
||||
/>
|
||||
))
|
||||
: <span className={styles.noPluginsInstalled}>No plugins installed yet.</span>;
|
||||
: (
|
||||
<li className={styles.noPluginsInstalled}>
|
||||
<FormattedMessage {...messages.noPluginsInstalled} />.
|
||||
</li>
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={styles.leftMenuLinkContainer}>
|
||||
@ -83,8 +80,7 @@ class LeftMenuLinkContainer extends React.Component { // eslint-disable-line rea
|
||||
}
|
||||
|
||||
LeftMenuLinkContainer.propTypes = {
|
||||
plugins: React.PropTypes.object,
|
||||
params: React.PropTypes.object,
|
||||
plugins: React.PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
export default LeftMenuLinkContainer;
|
||||
@ -18,5 +18,9 @@
|
||||
"general": {
|
||||
"id": "app.components.LeftMenuLinkContainer.general",
|
||||
"defaultMessage": "General"
|
||||
},
|
||||
"noPluginsInstalled": {
|
||||
"id": "app.components.LeftMenuLinkContainer.noPluginsInstalled",
|
||||
"defaultMessage": "No plugins installed yet"
|
||||
}
|
||||
}
|
||||
@ -28,4 +28,6 @@
|
||||
padding-left: 1.6rem;
|
||||
padding-right: 1.6rem;
|
||||
font-weight: 300;
|
||||
min-height: 3.6rem;
|
||||
padding-top: .9rem;
|
||||
}
|
||||
@ -16,22 +16,22 @@ class Notification extends React.Component { // eslint-disable-line react/prefer
|
||||
|
||||
options = {
|
||||
success: {
|
||||
icon: 'ion-ios-checkmark-outline',
|
||||
icon: 'fa-check',
|
||||
title: 'Success',
|
||||
class: 'notificationSuccess',
|
||||
},
|
||||
warning: {
|
||||
icon: 'ion-ios-information-outline',
|
||||
icon: 'fa-exclamation',
|
||||
title: 'Warning',
|
||||
class: 'notificationWarning',
|
||||
},
|
||||
error: {
|
||||
icon: 'ion-ios-close-outline',
|
||||
icon: 'fa-exclamation',
|
||||
title: 'Error',
|
||||
class: 'notificationError',
|
||||
},
|
||||
info: {
|
||||
icon: 'ion-ios-information-outline',
|
||||
icon: 'fa-info',
|
||||
title: 'Info',
|
||||
class: 'notificationInfo',
|
||||
},
|
||||
@ -42,20 +42,19 @@ class Notification extends React.Component { // eslint-disable-line react/prefer
|
||||
|
||||
return (
|
||||
<li key={this.props.notification.id} className={`${styles.notification} ${styles[options.class]}`}>
|
||||
<icon className={`ion ${options.icon} ${styles.notificationIcon}`}></icon>
|
||||
<p className={styles.notificationContent}>
|
||||
<span className={styles.notificationTitle}>{options.title}: </span>
|
||||
<span><FormattedMessage id={this.props.notification.message} /></span>
|
||||
</p>
|
||||
<icon className={`ion ion-ios-close-empty pull-right ${styles.notificationClose}`} onClick={this.onCloseClicked}></icon>
|
||||
<icon className={`fa ${options.icon} ${styles.notificationIcon}`}></icon>
|
||||
<div className={styles.notificationContent}>
|
||||
<p className={styles.notificationTitle}><FormattedMessage id={this.props.notification.message} /></p>
|
||||
</div>
|
||||
<icon className={`fa fa-close ${styles.notificationClose}`} onClick={this.onCloseClicked}></icon>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Notification.propTypes = {
|
||||
notification: React.PropTypes.object,
|
||||
onHideNotification: React.PropTypes.func,
|
||||
notification: React.PropTypes.object.isRequired,
|
||||
onHideNotification: React.PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default Notification;
|
||||
@ -0,0 +1,103 @@
|
||||
/* Import */
|
||||
@import '../../styles/variables/variables';
|
||||
|
||||
.notification {
|
||||
position: relative;
|
||||
display: flex;
|
||||
width: 28.6rem;
|
||||
align-items: stretch;
|
||||
background: $white;
|
||||
margin-bottom: 1.4rem;
|
||||
transition: all 0.2s ease;
|
||||
border-radius: 0.1rem;
|
||||
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.15);
|
||||
color: #333740;
|
||||
}
|
||||
|
||||
.notification:hover {
|
||||
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.notificationIcon {
|
||||
position: relative;
|
||||
display: block;
|
||||
width: 18%;
|
||||
text-align: center;
|
||||
font-size: 2.4rem;
|
||||
|
||||
&:before {
|
||||
position: absolute;
|
||||
top: calc(50% - 1rem);
|
||||
left: calc(50% - 1rem);
|
||||
border-radius: 100%;
|
||||
border: 1px solid $brand-success;
|
||||
color: $brand-success;
|
||||
height: 2rem;
|
||||
width: 2rem;
|
||||
font-size: 1.2rem;
|
||||
padding-top: .3rem;
|
||||
padding-left: 0.1rem;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
.notificationContent {
|
||||
display: block;
|
||||
margin: 0;
|
||||
padding-top: 1.5rem;
|
||||
padding-bottom: 1.5rem;
|
||||
padding-right: 1rem;
|
||||
border-right: 1px solid rgba(255, 255, 255, 0.3);
|
||||
width: 70%;
|
||||
}
|
||||
|
||||
.notificationTitle {
|
||||
font-weight: 500;
|
||||
font-size: 1.4rem;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
|
||||
.notificationClose {
|
||||
position: relative;
|
||||
display: block;
|
||||
cursor: pointer;
|
||||
width: 12%;
|
||||
opacity: 0.6;
|
||||
transition: opacity 0.1s ease;
|
||||
font-size: 1.6rem;
|
||||
color: #c2c4c7;
|
||||
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
&:before {
|
||||
position: absolute;
|
||||
top: calc(50% - .9rem);
|
||||
left: calc(50% - 0.5rem);
|
||||
}
|
||||
}
|
||||
|
||||
.notificationWarning {
|
||||
.notificationIcon:before {
|
||||
border-color: $brand-warning;
|
||||
color: $brand-warning;
|
||||
padding-top: .4rem;
|
||||
}
|
||||
}
|
||||
|
||||
.notificationError {
|
||||
.notificationIcon:before {
|
||||
border-color: $brand-danger;
|
||||
color: $brand-danger;
|
||||
padding-top: .4rem;
|
||||
}
|
||||
}
|
||||
|
||||
.notificationInfo {
|
||||
.notificationIcon:before {
|
||||
border-color: $brand-primary;
|
||||
color: $brand-primary;
|
||||
}
|
||||
}
|
||||
@ -38,8 +38,8 @@ class NotificationsContainer extends React.Component { // eslint-disable-line re
|
||||
}
|
||||
|
||||
NotificationsContainer.propTypes = {
|
||||
notifications: React.PropTypes.object,
|
||||
onHideNotification: React.PropTypes.func,
|
||||
notifications: React.PropTypes.object.isRequired,
|
||||
onHideNotification: React.PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default NotificationsContainer;
|
||||
@ -3,7 +3,7 @@
|
||||
|
||||
.notificationsContainer { /* stylelint-disable */
|
||||
position: absolute;
|
||||
top: 1rem;
|
||||
top: 7rem;
|
||||
right: 1rem;
|
||||
z-index: 1000;
|
||||
list-style: none;
|
||||
@ -6,11 +6,11 @@
|
||||
|
||||
import React from 'react';
|
||||
|
||||
// import { FormattedMessage } from 'react-intl';
|
||||
import styles from './styles.scss';
|
||||
import ToggleOption from '../ToggleOption';
|
||||
import ToggleOption from 'components/ToggleOption';
|
||||
|
||||
function Toggle(props) { // eslint-disable-line react/prefer-stateless-function
|
||||
import styles from './styles.scss';
|
||||
|
||||
function Toggle(props) { // eslint-disable-line react/prefer-stateless-function
|
||||
let content = (<option>--</option>);
|
||||
|
||||
// If we have items, render them
|
||||
@ -21,16 +21,17 @@ function Toggle(props) { // eslint-disable-line react/prefer-stateless-function
|
||||
}
|
||||
|
||||
return (
|
||||
<select onChange={props.onToggle} className={styles.toggle}>
|
||||
<select onChange={props.onToggle} className={styles.toggle} defaultValue={props.value}>
|
||||
{content}
|
||||
</select>
|
||||
);
|
||||
}
|
||||
|
||||
Toggle.propTypes = {
|
||||
onToggle: React.PropTypes.func,
|
||||
values: React.PropTypes.array,
|
||||
messages: React.PropTypes.object,
|
||||
messages: React.PropTypes.object.isRequired,
|
||||
onToggle: React.PropTypes.func.isRequired,
|
||||
value: React.PropTypes.string.isRequired,
|
||||
values: React.PropTypes.array.isRequired,
|
||||
};
|
||||
|
||||
export default Toggle;
|
||||
@ -14,12 +14,12 @@ const ToggleOption = ({ value, message, intl }) => (
|
||||
);
|
||||
|
||||
ToggleOption.propTypes = {
|
||||
value: React.PropTypes.string.isRequired,
|
||||
message: React.PropTypes.oneOfType([
|
||||
React.PropTypes.object,
|
||||
React.PropTypes.string,
|
||||
]),
|
||||
intl: intlShape.isRequired,
|
||||
message: React.PropTypes.oneOfType([
|
||||
React.PropTypes.object.isRequired,
|
||||
React.PropTypes.string.isRequired,
|
||||
]).isRequired,
|
||||
value: React.PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
export default injectIntl(ToggleOption);
|
||||
@ -0,0 +1,74 @@
|
||||
/*
|
||||
* AdminPage
|
||||
*
|
||||
* This is the first thing users see of our AdminPage, at the '/' 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 { connect } from 'react-redux';
|
||||
import { createStructuredSelector } from 'reselect';
|
||||
import { Switch, Route } from 'react-router-dom';
|
||||
|
||||
import HomePage from 'containers/HomePage';
|
||||
import PluginPage from 'containers/PluginPage';
|
||||
import ComingSoonPage from 'containers/ComingSoonPage';
|
||||
import LeftMenu from 'containers/LeftMenu';
|
||||
import Content from 'containers/Content';
|
||||
import NotFoundPage from 'containers/NotFoundPage';
|
||||
|
||||
import { selectPlugins } from 'containers/App/selectors';
|
||||
import { hideNotification } from 'containers/NotificationProvider/actions';
|
||||
|
||||
import Header from 'components/Header/index';
|
||||
|
||||
import styles from './syles.scss';
|
||||
|
||||
export class AdminPage extends React.Component { // eslint-disable-line react/prefer-stateless-function
|
||||
render() {
|
||||
return (
|
||||
<div className={styles.adminPage}>
|
||||
<LeftMenu plugins={this.props.plugins} />
|
||||
<div className={styles.adminPageRightWrapper}>
|
||||
<Header />
|
||||
<Content {...this.props}>
|
||||
<Switch>
|
||||
<Route path="/" component={HomePage} exact />
|
||||
<Route path="/plugins/:pluginId" component={PluginPage} />
|
||||
<Route path="/plugins" component={ComingSoonPage} />
|
||||
<Route path="/list-plugins" component={ComingSoonPage} exact />
|
||||
<Route path="/install-plugin" component={ComingSoonPage} exact />
|
||||
<Route path="/configuration" component={ComingSoonPage} exact />
|
||||
<Route path="" component={NotFoundPage} />
|
||||
</Switch>
|
||||
</Content>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
AdminPage.contextTypes = {
|
||||
router: React.PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
AdminPage.propTypes = {
|
||||
plugins: React.PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
plugins: selectPlugins(),
|
||||
});
|
||||
|
||||
function mapDispatchToProps(dispatch) {
|
||||
return {
|
||||
onHideNotification: (id) => { dispatch(hideNotification(id)); },
|
||||
dispatch,
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(AdminPage);
|
||||
@ -1,10 +1,10 @@
|
||||
/* Import */
|
||||
@import '../../styles/variables/variables';
|
||||
|
||||
.homePage { /* stylelint-disable */
|
||||
.adminPage { /* stylelint-disable */
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.homePageRightWrapper {
|
||||
.adminPageRightWrapper {
|
||||
width: calc(100% - #{$left-menu-width});
|
||||
}
|
||||
}
|
||||
47
packages/strapi-admin/admin/src/containers/App/index.js
Normal file
@ -0,0 +1,47 @@
|
||||
/**
|
||||
*
|
||||
* App.js
|
||||
*
|
||||
* This component is the skeleton around the actual pages, and should only
|
||||
* contain code that should be seen on all pages. (e.g. navigation bar)
|
||||
*
|
||||
* 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 { Switch, Route } from 'react-router-dom';
|
||||
|
||||
import AdminPage from 'containers/AdminPage';
|
||||
import NotFoundPage from 'containers/NotFoundPage';
|
||||
|
||||
import NotificationProvider from 'containers/NotificationProvider';
|
||||
|
||||
import '../../styles/main.scss';
|
||||
import styles from './styles.scss';
|
||||
|
||||
export class App extends React.Component { // eslint-disable-line react/prefer-stateless-function
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<NotificationProvider />
|
||||
<div className={styles.container}>
|
||||
<Switch>
|
||||
<Route path="/" component={AdminPage} />
|
||||
<Route path="" component={NotFoundPage} />
|
||||
</Switch>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
App.contextTypes = {
|
||||
router: React.PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
App.propTypes = {};
|
||||
|
||||
export default App;
|
||||
20
packages/strapi-admin/admin/src/containers/App/selectors.js
Normal file
@ -0,0 +1,20 @@
|
||||
import { createSelector } from 'reselect';
|
||||
|
||||
/**
|
||||
* Direct selector to the languageToggle state domain
|
||||
*/
|
||||
const selectApp = () => (state) => state.get('app');
|
||||
|
||||
/**
|
||||
* Select the language locale
|
||||
*/
|
||||
|
||||
const selectPlugins = () => createSelector(
|
||||
selectApp(),
|
||||
(appState) => appState.get('plugins')
|
||||
);
|
||||
|
||||
export {
|
||||
selectApp,
|
||||
selectPlugins,
|
||||
};
|
||||
@ -6,15 +6,13 @@
|
||||
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import styles from './styles.scss';
|
||||
import { createSelector } from 'reselect';
|
||||
|
||||
import { selectPlugins } from 'containers/App/selectors';
|
||||
|
||||
export class Content extends React.Component { // eslint-disable-line react/prefer-stateless-function
|
||||
static propTypes = {
|
||||
children: React.PropTypes.node,
|
||||
};
|
||||
import styles from './styles.scss';
|
||||
|
||||
export class Content extends React.Component { // eslint-disable-line react/prefer-stateless-function
|
||||
render() {
|
||||
return (
|
||||
<div className={styles.content}>
|
||||
@ -25,9 +23,7 @@ export class Content extends React.Component { // eslint-disable-line react/pref
|
||||
}
|
||||
|
||||
Content.propTypes = {
|
||||
plugins: React.PropTypes.object,
|
||||
onRegisterPluginClicked: React.PropTypes.func,
|
||||
params: React.PropTypes.object,
|
||||
children: React.PropTypes.node.isRequired,
|
||||
};
|
||||
|
||||
const mapStateToProps = createSelector(
|
||||
34
packages/strapi-admin/admin/src/containers/HomePage/index.js
Normal file
@ -0,0 +1,34 @@
|
||||
/*
|
||||
*
|
||||
* HomePage
|
||||
*
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import Helmet from 'react-helmet';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import messages from './messages.json';
|
||||
import styles from './styles.scss';
|
||||
|
||||
export class HomePage extends React.Component { // eslint-disable-line react/prefer-stateless-function
|
||||
render() {
|
||||
return (
|
||||
<div className={styles.wrapper}>
|
||||
<Helmet
|
||||
title="Home Page"
|
||||
/>
|
||||
<p><FormattedMessage {...messages.welcome} />.</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function mapDispatchToProps(dispatch) {
|
||||
return {
|
||||
dispatch,
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapDispatchToProps)(HomePage);
|
||||
@ -0,0 +1,6 @@
|
||||
{
|
||||
"welcome": {
|
||||
"id": "app.components.HomePage.welcome",
|
||||
"defaultMessage": "Welcome to the home page"
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,3 @@
|
||||
.wrapper {
|
||||
padding: 2.3rem;
|
||||
}
|
||||
@ -23,9 +23,9 @@ export class LanguageProvider extends React.Component { // eslint-disable-line r
|
||||
}
|
||||
|
||||
LanguageProvider.propTypes = {
|
||||
locale: React.PropTypes.string,
|
||||
messages: React.PropTypes.object,
|
||||
children: React.PropTypes.element.isRequired,
|
||||
locale: React.PropTypes.string.isRequired,
|
||||
messages: React.PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
|
||||
@ -0,0 +1,45 @@
|
||||
/*
|
||||
*
|
||||
* LanguageProvider reducer
|
||||
*
|
||||
*/
|
||||
|
||||
import { fromJS } from 'immutable';
|
||||
import { first, get, includes, split } from 'lodash';
|
||||
|
||||
// Import supported languages from admin config.
|
||||
import { languages } from '../../../../config/admin.json';
|
||||
|
||||
import {
|
||||
CHANGE_LOCALE,
|
||||
} from './constants';
|
||||
|
||||
// Define a key to store and get user preferences in local storage.
|
||||
const localStorageKey = 'strapi-admin-language';
|
||||
|
||||
// Detect user language.
|
||||
const userLanguage = window.localStorage.getItem(localStorageKey) || window.navigator.language || window.navigator.userLanguage;
|
||||
|
||||
// Split user language in a correct format.
|
||||
const userLanguageShort = get(split(userLanguage, '-'), '0');
|
||||
|
||||
// Check that the language is included in the admin configuration.
|
||||
const foundLanguage = includes(languages, userLanguageShort) && userLanguageShort;
|
||||
|
||||
const initialState = fromJS({
|
||||
locale: foundLanguage || first(languages) || 'en',
|
||||
});
|
||||
|
||||
function languageProviderReducer(state = initialState, action) {
|
||||
switch (action.type) {
|
||||
case CHANGE_LOCALE:
|
||||
// Set user language in local storage.
|
||||
window.localStorage.setItem(localStorageKey, action.locale);
|
||||
return state
|
||||
.set('locale', action.locale);
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
export default languageProviderReducer;
|
||||
@ -3,7 +3,7 @@ import { createSelector } from 'reselect';
|
||||
/**
|
||||
* Direct selector to the languageToggle state domain
|
||||
*/
|
||||
const selectLanguage = () => state => state.get('language');
|
||||
const selectLanguage = () => (state) => state.get('language');
|
||||
|
||||
/**
|
||||
* Select the language locale
|
||||
@ -6,9 +6,11 @@
|
||||
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import LeftMenuHeader from 'components/LeftMenuHeader';
|
||||
import LeftMenuLinkContainer from 'components/LeftMenuLinkContainer';
|
||||
import LeftMenuFooter from 'components/LeftMenuFooter';
|
||||
|
||||
import styles from './styles.scss';
|
||||
|
||||
export class LeftMenu extends React.Component { // eslint-disable-line react/prefer-stateless-function
|
||||
@ -24,8 +26,7 @@ export class LeftMenu extends React.Component { // eslint-disable-line react/pre
|
||||
}
|
||||
|
||||
LeftMenu.propTypes = {
|
||||
plugins: React.PropTypes.object,
|
||||
params: React.PropTypes.object,
|
||||
plugins: React.PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
function mapDispatchToProps(dispatch) {
|
||||
@ -6,12 +6,14 @@
|
||||
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { selectLocale } from '../LanguageProvider/selectors';
|
||||
import { changeLocale } from '../LanguageProvider/actions';
|
||||
import { languages } from '../../i18n';
|
||||
import { createSelector } from 'reselect';
|
||||
import styles from './styles.scss';
|
||||
|
||||
import Toggle from 'components/Toggle';
|
||||
import { selectLocale } from 'containers/LanguageProvider/selectors';
|
||||
import { changeLocale } from 'containers/LanguageProvider/actions';
|
||||
import { languages } from 'i18n';
|
||||
|
||||
import styles from './styles.scss';
|
||||
|
||||
export class LocaleToggle extends React.Component { // eslint-disable-line
|
||||
render() {
|
||||
@ -23,14 +25,15 @@ export class LocaleToggle extends React.Component { // eslint-disable-line
|
||||
|
||||
return (
|
||||
<div className={styles.localeToggle}>
|
||||
<Toggle values={languages} messages={messages} onToggle={this.props.onLocaleToggle} />
|
||||
<Toggle values={languages} value={this.props.locale} messages={messages} onToggle={this.props.onLocaleToggle} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
LocaleToggle.propTypes = {
|
||||
onLocaleToggle: React.PropTypes.func,
|
||||
locale: React.PropTypes.string.isRequired,
|
||||
onLocaleToggle: React.PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
const mapStateToProps = createSelector(
|
||||
@ -11,9 +11,11 @@
|
||||
|
||||
import React from 'react';
|
||||
import { defineMessages, FormattedMessage } from 'react-intl';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import styles from './styles.scss';
|
||||
import { Link } from 'react-router';
|
||||
import messages from './messages.json';
|
||||
|
||||
defineMessages(messages);
|
||||
|
||||
export default class NotFound extends React.Component { // eslint-disable-line react/prefer-stateless-function
|
||||
@ -26,7 +28,7 @@ export default class NotFound extends React.Component { // eslint-disable-line r
|
||||
<h2 className={styles.notFoundDescription}>
|
||||
<FormattedMessage {...messages.description} />
|
||||
</h2>
|
||||
<Link to={'/'}>Back to home page.</Link>
|
||||
<Link to="/admin">Back to home page.</Link>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -4,16 +4,17 @@
|
||||
*
|
||||
*/
|
||||
|
||||
import { dispatch } from 'app';
|
||||
|
||||
import {
|
||||
SHOW_NOTIFICATION,
|
||||
HIDE_NOTIFICATION,
|
||||
} from './constants';
|
||||
|
||||
import { dispatch } from '../../app';
|
||||
let nextNotificationId = 0;
|
||||
|
||||
export function showNotification(message, status) {
|
||||
nextNotificationId++;
|
||||
nextNotificationId++; // eslint-disable-line no-plusplus
|
||||
|
||||
// Start timeout to hide the notification
|
||||
((id) => {
|
||||
@ -0,0 +1,45 @@
|
||||
/*
|
||||
*
|
||||
* NotificationProvider
|
||||
*
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { createStructuredSelector } from 'reselect';
|
||||
|
||||
import NotificationsContainer from 'components/NotificationsContainer';
|
||||
import { selectNotifications } from './selectors';
|
||||
import { hideNotification } from './actions';
|
||||
|
||||
|
||||
export class NotificationProvider extends React.Component { // eslint-disable-line react/prefer-stateless-function
|
||||
render() {
|
||||
return (
|
||||
<NotificationsContainer
|
||||
onHideNotification={this.props.onHideNotification}
|
||||
notifications={this.props.notifications}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
NotificationProvider.propTypes = {
|
||||
notifications: React.PropTypes.object.isRequired,
|
||||
onHideNotification: React.PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
notifications: selectNotifications(),
|
||||
});
|
||||
|
||||
function mapDispatchToProps(dispatch) {
|
||||
return {
|
||||
onHideNotification: (id) => {
|
||||
dispatch(hideNotification(id));
|
||||
},
|
||||
dispatch,
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(NotificationProvider);
|
||||
@ -9,17 +9,17 @@ import { connect } from 'react-redux';
|
||||
import Helmet from 'react-helmet';
|
||||
import { createSelector } from 'reselect';
|
||||
import { selectPlugins } from 'containers/App/selectors';
|
||||
import PluginHeader from 'components/PluginHeader';
|
||||
|
||||
const exposedComponents = {
|
||||
PluginHeader,
|
||||
};
|
||||
|
||||
export class PluginPage extends React.Component { // eslint-disable-line react/prefer-stateless-function
|
||||
render() {
|
||||
const containers = this.props.plugins.valueSeq().map((plugin, i) => {
|
||||
const Elem = plugin.get('mainComponent');
|
||||
return <Elem key={i} {...this.props} exposedComponents={exposedComponents}></Elem>;
|
||||
// Detect plugin id from url params
|
||||
const pluginId = this.props.match.params.pluginId;
|
||||
|
||||
const containers = this.props.plugins.valueSeq().map((plugin) => {
|
||||
if (plugin.get('id') === pluginId) {
|
||||
const Elem = plugin.get('mainComponent');
|
||||
return <Elem key={plugin.get('id')} {...this.props} />;
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
@ -41,7 +41,8 @@ PluginPage.contextTypes = {
|
||||
};
|
||||
|
||||
PluginPage.propTypes = {
|
||||
plugins: React.PropTypes.object,
|
||||
match: React.PropTypes.object.isRequired,
|
||||
plugins: React.PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
const mapStateToProps = createSelector(
|
||||
BIN
packages/strapi-admin/admin/src/favicon.ico
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
@ -5,16 +5,12 @@
|
||||
<meta charset="utf-8">
|
||||
<!-- Make the page mobile compatible -->
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<!-- Allow installing the app to the homescreen -->
|
||||
<link rel="manifest" href="manifest.json">
|
||||
<meta name="mobile-web-app-capable" content="yes">
|
||||
<title>Strapi Admin</title>
|
||||
<base href="/admin">
|
||||
</head>
|
||||
<body>
|
||||
<!-- The app hooks into this div -->
|
||||
<div id="app"></div>
|
||||
<!-- A lot of magic happens in this file. HtmlWebpackPlugin automatically includes all assets (e.g. bundle.js, main.css) with the correct HTML tags, which is why they are missing in this HTML file. Don't add any assets here! (Check out webpackconfig.js if you want to know more) -->
|
||||
</body>
|
||||
<script type="text/javascript" src="http://localhost:3000/main.js"></script>
|
||||
</html>
|
||||
@ -1,12 +1,12 @@
|
||||
/**
|
||||
* Combine all reducers in this file and export the combined reducers.
|
||||
* If we were to do this in store.js, reducers wouldn't be hot reloadable.
|
||||
*/
|
||||
|
||||
import { combineReducers } from 'redux-immutable';
|
||||
import { fromJS } from 'immutable';
|
||||
import { combineReducers } from 'redux-immutable';
|
||||
import { LOCATION_CHANGE } from 'react-router-redux';
|
||||
import appReducer from 'containers/App/reducer';
|
||||
|
||||
import globalReducer from 'containers/App/reducer';
|
||||
import languageProviderReducer from 'containers/LanguageProvider/reducer';
|
||||
import notificationProviderReducer from 'containers/NotificationProvider/reducer';
|
||||
|
||||
@ -14,13 +14,13 @@ import notificationProviderReducer from 'containers/NotificationProvider/reducer
|
||||
* routeReducer
|
||||
*
|
||||
* The reducer merges route location changes into our immutable state.
|
||||
* The change is necessitated by moving to react-router-redux@4
|
||||
* The change is necessitated by moving to react-router-redux@5
|
||||
*
|
||||
*/
|
||||
|
||||
// Initial routing state
|
||||
const routeInitialState = fromJS({
|
||||
locationBeforeTransitions: null,
|
||||
location: null,
|
||||
});
|
||||
|
||||
/**
|
||||
@ -31,7 +31,7 @@ function routeReducer(state = routeInitialState, action) {
|
||||
/* istanbul ignore next */
|
||||
case LOCATION_CHANGE:
|
||||
return state.merge({
|
||||
locationBeforeTransitions: action.payload,
|
||||
location: action.payload,
|
||||
});
|
||||
default:
|
||||
return state;
|
||||
@ -39,14 +39,14 @@ function routeReducer(state = routeInitialState, action) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the main reducer with the asynchronously loaded ones
|
||||
* Creates the main reducer with the dynamically injected ones
|
||||
*/
|
||||
export default function createReducer(asyncReducers) {
|
||||
export default function createReducer(injectedReducers) {
|
||||
return combineReducers({
|
||||
route: routeReducer,
|
||||
app: globalReducer,
|
||||
language: languageProviderReducer,
|
||||
notification: notificationProviderReducer,
|
||||
app: appReducer,
|
||||
...asyncReducers,
|
||||
...injectedReducers,
|
||||
});
|
||||
}
|
||||
@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Create the store with asynchronously loaded reducers
|
||||
* Create the store with dynamic reducers
|
||||
*/
|
||||
|
||||
import { createStore, applyMiddleware, compose } from 'redux';
|
||||
@ -9,7 +9,6 @@ import createSagaMiddleware from 'redux-saga';
|
||||
import createReducer from './reducers';
|
||||
|
||||
const sagaMiddleware = createSagaMiddleware();
|
||||
const devtools = window.devToolsExtension || (() => noop => noop);
|
||||
|
||||
export default function configureStore(initialState = {}, history) {
|
||||
// Create the store with two middlewares
|
||||
@ -22,30 +21,40 @@ export default function configureStore(initialState = {}, history) {
|
||||
|
||||
const enhancers = [
|
||||
applyMiddleware(...middlewares),
|
||||
devtools(),
|
||||
];
|
||||
|
||||
// If Redux DevTools Extension is installed use it, otherwise use Redux compose
|
||||
/* eslint-disable no-underscore-dangle */
|
||||
const composeEnhancers =
|
||||
process.env.NODE_ENV !== 'production' &&
|
||||
typeof window === 'object' &&
|
||||
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__
|
||||
? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({
|
||||
// TODO Try to remove when `react-router-redux` is out of beta, LOCATION_CHANGE should not be fired more than once after hot reloading
|
||||
// Prevent recomputing reducers for `replaceReducer`
|
||||
shouldHotReload: false,
|
||||
})
|
||||
: compose;
|
||||
/* eslint-enable */
|
||||
|
||||
const store = createStore(
|
||||
createReducer(),
|
||||
fromJS(initialState),
|
||||
compose(...enhancers)
|
||||
composeEnhancers(...enhancers)
|
||||
);
|
||||
|
||||
// Create hook for async sagas
|
||||
// Extensions
|
||||
store.runSaga = sagaMiddleware.run;
|
||||
store.injectedReducers = {}; // Reducer registry
|
||||
store.injectedSagas = {}; // Saga registry
|
||||
|
||||
// Make reducers hot reloadable, see http://mxs.is/googmo
|
||||
/* istanbul ignore next */
|
||||
if (module.hot) {
|
||||
System.import('./reducers').then((reducerModule) => {
|
||||
const createReducers = reducerModule.default;
|
||||
const nextReducers = createReducers(store.asyncReducers);
|
||||
|
||||
store.replaceReducer(nextReducers);
|
||||
module.hot.accept('./reducers', () => {
|
||||
store.replaceReducer(createReducer(store.injectedReducers));
|
||||
});
|
||||
}
|
||||
|
||||
// Initialize it with no other reducers
|
||||
store.asyncReducers = {};
|
||||
return store;
|
||||
}
|
||||
|
Before Width: | Height: | Size: 106 KiB After Width: | Height: | Size: 106 KiB |
|
Before Width: | Height: | Size: 434 KiB After Width: | Height: | Size: 434 KiB |