UNPKG

decap-cms-core

Version:

Decap CMS core application, see decap-cms package for the main distribution.

280 lines (255 loc) 8.29 kB
import PropTypes from 'prop-types'; import React from 'react'; import { translate } from 'react-polyglot'; import ImmutablePropTypes from 'react-immutable-proptypes'; import styled from '@emotion/styled'; import { connect } from 'react-redux'; import { Route, Switch, Redirect } from 'react-router-dom'; import TopBarProgress from 'react-topbar-progress-indicator'; import { Loader, colors } from 'decap-cms-ui-default'; import { loginUser, logoutUser } from '../../actions/auth'; import { currentBackend } from '../../backend'; import { createNewEntry } from '../../actions/collections'; import { openMediaLibrary } from '../../actions/mediaLibrary'; import MediaLibrary from '../MediaLibrary/MediaLibrary'; import { Notifications } from '../UI'; import { history } from '../../routing/history'; import { SIMPLE, EDITORIAL_WORKFLOW } from '../../constants/publishModes'; import Collection from '../Collection/Collection'; import Workflow from '../Workflow/Workflow'; import Editor from '../Editor/Editor'; import NotFoundPage from './NotFoundPage'; import Header from './Header'; TopBarProgress.config({ barColors: { 0: colors.active, '1.0': colors.active, }, shadowBlur: 0, barThickness: 2, }); const AppMainContainer = styled.div` min-width: 800px; max-width: 1440px; margin: 0 auto; `; const ErrorContainer = styled.div` margin: 20px; `; const ErrorCodeBlock = styled.pre` margin-left: 20px; font-size: 15px; line-height: 1.5; `; function getDefaultPath(collections) { const first = collections.filter(collection => collection.get('hide') !== true).first(); if (first) { return `/collections/${first.get('name')}`; } else { throw new Error('Could not find a non hidden collection'); } } function RouteInCollection({ collections, render, ...props }) { const defaultPath = getDefaultPath(collections); return ( <Route {...props} render={routeProps => { const collectionExists = collections.get(routeProps.match.params.name); return collectionExists ? render(routeProps) : <Redirect to={defaultPath} />; }} /> ); } class App extends React.Component { static propTypes = { auth: PropTypes.object.isRequired, config: PropTypes.object.isRequired, collections: ImmutablePropTypes.map.isRequired, loginUser: PropTypes.func.isRequired, logoutUser: PropTypes.func.isRequired, user: PropTypes.object, isFetching: PropTypes.bool.isRequired, publishMode: PropTypes.oneOf([SIMPLE, EDITORIAL_WORKFLOW]), siteId: PropTypes.string, useMediaLibrary: PropTypes.bool, openMediaLibrary: PropTypes.func.isRequired, showMediaButton: PropTypes.bool, t: PropTypes.func.isRequired, }; configError(config) { const t = this.props.t; return ( <ErrorContainer> <h1>{t('app.app.errorHeader')}</h1> <div> <strong>{t('app.app.configErrors')}:</strong> <ErrorCodeBlock>{config.error}</ErrorCodeBlock> <span>{t('app.app.checkConfigYml')}</span> </div> </ErrorContainer> ); } handleLogin(credentials) { this.props.loginUser(credentials); } authenticating() { const { auth, t } = this.props; const backend = currentBackend(this.props.config); if (backend == null) { return ( <div> <h1>{t('app.app.waitingBackend')}</h1> </div> ); } return ( <div> <Notifications /> {React.createElement(backend.authComponent(), { onLogin: this.handleLogin.bind(this), error: auth.error, inProgress: auth.isFetching, siteId: this.props.config.backend.site_domain, base_url: this.props.config.backend.base_url, authEndpoint: this.props.config.backend.auth_endpoint, config: this.props.config, clearHash: () => history.replace('/'), t, })} </div> ); } handleLinkClick(event, handler, ...args) { event.preventDefault(); handler(...args); } render() { const { user, config, collections, logoutUser, isFetching, publishMode, useMediaLibrary, openMediaLibrary, t, showMediaButton, } = this.props; if (config === null) { return null; } if (config.error) { return this.configError(config); } if (config.isFetching) { return <Loader active>{t('app.app.loadingConfig')}</Loader>; } if (user == null) { return this.authenticating(t); } const defaultPath = getDefaultPath(collections); const hasWorkflow = publishMode === EDITORIAL_WORKFLOW; return ( <> <Notifications /> <Header user={user} collections={collections} onCreateEntryClick={createNewEntry} onLogoutClick={logoutUser} openMediaLibrary={openMediaLibrary} hasWorkflow={hasWorkflow} displayUrl={config.display_url} isTestRepo={config.backend.name === 'test-repo'} showMediaButton={showMediaButton} /> <AppMainContainer> {isFetching && <TopBarProgress />} <Switch> <Redirect exact from="/" to={defaultPath} /> <Redirect exact from="/search/" to={defaultPath} /> <RouteInCollection exact collections={collections} path="/collections/:name/search/" render={({ match }) => <Redirect to={`/collections/${match.params.name}`} />} /> <Redirect // This happens on Identity + Invite Only + External Provider email not matching // the registered user from="/error=access_denied&error_description=Signups+not+allowed+for+this+instance" to={defaultPath} /> {hasWorkflow ? <Route path="/workflow" component={Workflow} /> : null} <RouteInCollection exact collections={collections} path="/collections/:name" render={props => <Collection {...props} />} /> <RouteInCollection path="/collections/:name/new" collections={collections} render={props => <Editor {...props} newRecord />} /> <RouteInCollection path="/collections/:name/entries/*" collections={collections} render={props => <Editor {...props} />} /> <RouteInCollection path="/collections/:name/search/:searchTerm" collections={collections} render={props => <Collection {...props} isSearchResults isSingleSearchResult />} /> <RouteInCollection collections={collections} path="/collections/:name/filter/:filterTerm*" render={props => <Collection {...props} />} /> <Route path="/search/:searchTerm" render={props => <Collection {...props} isSearchResults />} /> <RouteInCollection path="/edit/:name/:entryName" collections={collections} render={({ match }) => { const { name, entryName } = match.params; return <Redirect to={`/collections/${name}/entries/${entryName}`} />; }} /> <Route component={NotFoundPage} /> </Switch> {useMediaLibrary ? <MediaLibrary /> : null} </AppMainContainer> </> ); } } function mapStateToProps(state) { const { auth, config, collections, globalUI, mediaLibrary } = state; const user = auth.user; const isFetching = globalUI.isFetching; const publishMode = config.publish_mode; const useMediaLibrary = !mediaLibrary.get('externalLibrary'); const showMediaButton = mediaLibrary.get('showMediaButton'); return { auth, config, collections, user, isFetching, publishMode, showMediaButton, useMediaLibrary, }; } const mapDispatchToProps = { openMediaLibrary, loginUser, logoutUser, }; export default connect(mapStateToProps, mapDispatchToProps)(translate()(App));