UI updates (#151)
* infer card title * Infer entry body & image * infer image * Better terminology: EntryListing accept a single Collection * remove log * Refactored Collections VO into selectors * use selectors when showning card * fixed size cards * Added 'bio' and 'biography' to collection description inference synonyms * Removed unused card file * throw error instance * bugfix for file based collections * lint * moved components with css to own folder * Search Bugfix: More than one collection might be returned * Changed sidebar implementation. Closes #104 & #152 * Show spinning loading for unpublished entries * Refactored Sidebar into a separate container * Make preview widgets more robust
This commit is contained in:
@ -1,28 +1,8 @@
|
||||
@import '../components/UI/theme.css';
|
||||
|
||||
.nav {
|
||||
display: block;
|
||||
padding: 1rem;
|
||||
|
||||
& .heading {
|
||||
border: none;
|
||||
}
|
||||
.root {
|
||||
margin-top: 64px;
|
||||
}
|
||||
|
||||
.navDrawer {
|
||||
max-width: 240px !important;
|
||||
|
||||
& .drawerContent {
|
||||
max-width: 240px !important;
|
||||
}
|
||||
}
|
||||
|
||||
.notifsContainer {
|
||||
position: fixed;
|
||||
top: 60px;
|
||||
right: 0;
|
||||
bottom: 60px;
|
||||
z-index: var(--topmostZindex);
|
||||
width: 360px;
|
||||
pointer-events: none;
|
||||
.sidebar {
|
||||
width: 200px;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
@ -2,13 +2,15 @@ import React, { PropTypes } from 'react';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import pluralize from 'pluralize';
|
||||
import { connect } from 'react-redux';
|
||||
import { Layout, Panel, NavDrawer } from 'react-toolbox/lib/layout';
|
||||
import { Layout, Panel } from 'react-toolbox/lib/layout';
|
||||
import { Navigation } from 'react-toolbox/lib/navigation';
|
||||
import { Link } from 'react-toolbox/lib/link';
|
||||
import { Notifs } from 'redux-notifications';
|
||||
import TopBarProgress from 'react-topbar-progress-indicator';
|
||||
import Sidebar from './Sidebar';
|
||||
import { loadConfig } from '../actions/config';
|
||||
import { loginUser, logoutUser } from '../actions/auth';
|
||||
import { toggleSidebar } from '../actions/globalUI';
|
||||
import { currentBackend } from '../backends/backend';
|
||||
import {
|
||||
SHOW_COLLECTION,
|
||||
@ -42,6 +44,7 @@ class App extends React.Component {
|
||||
createNewEntryInCollection: PropTypes.func.isRequired,
|
||||
logoutUser: PropTypes.func.isRequired,
|
||||
dispatch: PropTypes.func.isRequired,
|
||||
toggleSidebar: PropTypes.func.isRequired,
|
||||
navigateToCollection: PropTypes.func.isRequired,
|
||||
user: ImmutablePropTypes.map,
|
||||
runCommand: PropTypes.func.isRequired,
|
||||
@ -59,10 +62,6 @@ class App extends React.Component {
|
||||
</div>);
|
||||
}
|
||||
|
||||
state = {
|
||||
navDrawerIsVisible: true,
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
this.props.dispatch(loadConfig());
|
||||
}
|
||||
@ -123,19 +122,13 @@ class App extends React.Component {
|
||||
return { commands, defaultCommands };
|
||||
}
|
||||
|
||||
toggleNavDrawer = () => {
|
||||
this.setState({
|
||||
navDrawerIsVisible: !this.state.navDrawerIsVisible,
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
const { navDrawerIsVisible } = this.state;
|
||||
const {
|
||||
user,
|
||||
config,
|
||||
children,
|
||||
collections,
|
||||
toggleSidebar,
|
||||
runCommand,
|
||||
navigateToCollection,
|
||||
createNewEntryInCollection,
|
||||
@ -160,67 +153,65 @@ class App extends React.Component {
|
||||
}
|
||||
|
||||
const { commands, defaultCommands } = this.generateFindBarCommands();
|
||||
const sidebarContent = (
|
||||
<nav className={styles.nav}>
|
||||
<h1 className={styles.heading}>Collections</h1>
|
||||
<Navigation type="vertical">
|
||||
{
|
||||
collections.valueSeq().map(collection =>
|
||||
<Link
|
||||
key={collection.get('name')}
|
||||
onClick={navigateToCollection.bind(this, collection.get('name'))} // eslint-disable-line
|
||||
>
|
||||
{collection.get('label')}
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
</Navigation>
|
||||
</nav>
|
||||
);
|
||||
|
||||
return (
|
||||
<Layout theme={styles}>
|
||||
<Notifs
|
||||
className={styles.notifsContainer}
|
||||
CustomComponent={Toast}
|
||||
/>
|
||||
<NavDrawer
|
||||
active={navDrawerIsVisible}
|
||||
scrollY
|
||||
permanentAt={navDrawerIsVisible ? 'lg' : null}
|
||||
onOverlayClick={this.toggleNavDrawer} // eslint-disable-line
|
||||
theme={styles}
|
||||
>
|
||||
<nav className={styles.nav}>
|
||||
<h1 className={styles.heading}>Collections</h1>
|
||||
<Navigation type="vertical">
|
||||
{
|
||||
collections.valueSeq().map(collection =>
|
||||
<Link
|
||||
key={collection.get('name')}
|
||||
onClick={navigateToCollection.bind(this, collection.get('name'))} // eslint-disable-line
|
||||
>
|
||||
{collection.get('label')}
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
</Navigation>
|
||||
</nav>
|
||||
</NavDrawer>
|
||||
<AppHeader
|
||||
user={user}
|
||||
collections={collections}
|
||||
commands={commands}
|
||||
defaultCommands={defaultCommands}
|
||||
runCommand={runCommand}
|
||||
onCreateEntryClick={createNewEntryInCollection}
|
||||
onLogoutClick={logoutUser}
|
||||
toggleNavDrawer={this.toggleNavDrawer}
|
||||
/>
|
||||
<Panel scrollY>
|
||||
{ isFetching && <TopBarProgress /> }
|
||||
<div className={styles.main}>
|
||||
{children}
|
||||
</div>
|
||||
</Panel>
|
||||
</Layout>
|
||||
<Sidebar content={sidebarContent}>
|
||||
<Layout theme={styles}>
|
||||
<Notifs
|
||||
className={styles.notifsContainer}
|
||||
CustomComponent={Toast}
|
||||
/>
|
||||
<AppHeader
|
||||
user={user}
|
||||
collections={collections}
|
||||
commands={commands}
|
||||
defaultCommands={defaultCommands}
|
||||
runCommand={runCommand}
|
||||
onCreateEntryClick={createNewEntryInCollection}
|
||||
onLogoutClick={logoutUser}
|
||||
toggleDrawer={toggleSidebar}
|
||||
/>
|
||||
<Panel scrollY>
|
||||
{ isFetching && <TopBarProgress /> }
|
||||
<div className={styles.main}>
|
||||
{children}
|
||||
</div>
|
||||
</Panel>
|
||||
|
||||
</Layout>
|
||||
</Sidebar>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function mapStateToProps(state) {
|
||||
const { auth, config, collections, global } = state;
|
||||
const { auth, config, collections, globalUI } = state;
|
||||
const user = auth && auth.get('user');
|
||||
const { isFetching } = global;
|
||||
const isFetching = globalUI.get('isFetching');
|
||||
return { auth, config, collections, user, isFetching };
|
||||
}
|
||||
|
||||
function mapDispatchToProps(dispatch) {
|
||||
return {
|
||||
dispatch,
|
||||
toggleSidebar: () => dispatch(toggleSidebar()),
|
||||
runCommand: (type, payload) => {
|
||||
dispatch(runCommand(type, payload));
|
||||
},
|
||||
|
@ -4,7 +4,7 @@ import { connect } from 'react-redux';
|
||||
import { loadEntries } from '../actions/entries';
|
||||
import { selectEntries } from '../reducers';
|
||||
import { Loader } from '../components/UI';
|
||||
import EntryListing from '../components/EntryListing';
|
||||
import EntryListing from '../components/EntryListing/EntryListing';
|
||||
import styles from './breakpoints.css';
|
||||
|
||||
class CollectionPage extends React.Component {
|
||||
|
@ -11,8 +11,9 @@ import {
|
||||
} from '../actions/entries';
|
||||
import { cancelEdit } from '../actions/editor';
|
||||
import { addMedia, removeMedia } from '../actions/media';
|
||||
import { openSidebar } from '../actions/globalUI';
|
||||
import { selectEntry, getMedia } from '../reducers';
|
||||
import Collection from '../valueObjects/Collection';
|
||||
import { selectFields } from '../reducers/collections';
|
||||
import EntryEditor from '../components/EntryEditor/EntryEditor';
|
||||
import entryPageHOC from './editorialWorkflow/EntryPageHOC';
|
||||
import { Loader } from '../components/UI';
|
||||
@ -32,6 +33,7 @@ class EntryPage extends React.Component {
|
||||
persistEntry: PropTypes.func.isRequired,
|
||||
removeMedia: PropTypes.func.isRequired,
|
||||
cancelEdit: PropTypes.func.isRequired,
|
||||
openSidebar: PropTypes.func.isRequired,
|
||||
fields: ImmutablePropTypes.list.isRequired,
|
||||
slug: PropTypes.string,
|
||||
newEntry: PropTypes.bool.isRequired,
|
||||
@ -39,6 +41,7 @@ class EntryPage extends React.Component {
|
||||
|
||||
componentDidMount() {
|
||||
const { entry, newEntry, collection, slug, loadEntry } = this.props;
|
||||
this.props.openSidebar();
|
||||
if (newEntry) {
|
||||
createEmptyDraft(collection);
|
||||
} else {
|
||||
@ -108,9 +111,8 @@ function mapStateToProps(state, ownProps) {
|
||||
const { collections, entryDraft } = state;
|
||||
const slug = ownProps.params.slug;
|
||||
const collection = collections.get(ownProps.params.name);
|
||||
const collectionModel = new Collection(collection);
|
||||
const newEntry = ownProps.route && ownProps.route.newRecord === true;
|
||||
|
||||
const fields = selectFields(collection, slug);
|
||||
const entry = newEntry ? null : selectEntry(state, collection.get('name'), slug);
|
||||
const boundGetMedia = getMedia.bind(null, state);
|
||||
return {
|
||||
@ -119,7 +121,7 @@ function mapStateToProps(state, ownProps) {
|
||||
newEntry,
|
||||
entryDraft,
|
||||
boundGetMedia,
|
||||
fields: collectionModel.entryFields(slug),
|
||||
fields,
|
||||
slug,
|
||||
entry,
|
||||
};
|
||||
@ -137,5 +139,6 @@ export default connect(
|
||||
discardDraft,
|
||||
persistEntry,
|
||||
cancelEdit,
|
||||
openSidebar,
|
||||
}
|
||||
)(entryPageHOC(EntryPage));
|
||||
|
@ -4,7 +4,7 @@ import { connect } from 'react-redux';
|
||||
import { selectSearchedEntries } from '../reducers';
|
||||
import { searchEntries } from '../actions/entries';
|
||||
import { Loader } from '../components/UI';
|
||||
import EntryListing from '../components/EntryListing';
|
||||
import EntryListing from '../components/EntryListing/EntryListing';
|
||||
import styles from './breakpoints.css';
|
||||
|
||||
class SearchPage extends React.Component {
|
||||
|
8
src/containers/Sidebar.css
Normal file
8
src/containers/Sidebar.css
Normal file
@ -0,0 +1,8 @@
|
||||
.root {
|
||||
margin-top: 64px;
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
width: 200px;
|
||||
background-color: #fff
|
||||
}
|
63
src/containers/Sidebar.js
Normal file
63
src/containers/Sidebar.js
Normal file
@ -0,0 +1,63 @@
|
||||
import React, { PropTypes } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import ReactSidebar from 'react-sidebar';
|
||||
import _ from 'lodash';
|
||||
import { openSidebar } from '../actions/globalUI';
|
||||
import styles from './Sidebar.css';
|
||||
|
||||
class Sidebar extends React.Component {
|
||||
|
||||
static propTypes = {
|
||||
children: PropTypes.node.isRequired,
|
||||
content: PropTypes.node.isRequired,
|
||||
sidebarIsOpen: PropTypes.bool.isRequired,
|
||||
openSidebar: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
state = { sidebarDocked: false };
|
||||
|
||||
componentWillMount() {
|
||||
this.mql = window.matchMedia('(min-width: 1200px)');
|
||||
this.mql.addListener(this.mediaQueryChanged);
|
||||
this.setState({ sidebarDocked: this.mql.matches });
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.mql.removeListener(this.mediaQueryChanged);
|
||||
}
|
||||
|
||||
mediaQueryChanged = _.throttle(() => {
|
||||
this.setState({ sidebarDocked: this.mql.matches });
|
||||
}, 500);
|
||||
|
||||
|
||||
render() {
|
||||
const {
|
||||
children,
|
||||
content,
|
||||
sidebarIsOpen,
|
||||
openSidebar,
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<ReactSidebar
|
||||
sidebar={content}
|
||||
rootClassName={styles.root}
|
||||
sidebarClassName={styles.sidebar}
|
||||
docked={sidebarIsOpen && this.state.sidebarDocked} // ALWAYS can hide sidebar
|
||||
open={sidebarIsOpen}
|
||||
onSetOpen={openSidebar}
|
||||
>
|
||||
{children}
|
||||
</ReactSidebar>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function mapStateToProps(state) {
|
||||
const { globalUI } = state;
|
||||
const sidebarIsOpen = globalUI.get('sidebarIsOpen');
|
||||
return { sidebarIsOpen };
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, { openSidebar })(Sidebar);
|
@ -5,11 +5,13 @@ import { connect } from 'react-redux';
|
||||
import { loadUnpublishedEntries, updateUnpublishedEntryStatus, publishUnpublishedEntry } from '../../actions/editorialWorkflow';
|
||||
import { selectUnpublishedEntries } from '../../reducers';
|
||||
import { EDITORIAL_WORKFLOW, status } from '../../constants/publishModes';
|
||||
import UnpublishedListing from '../../components/UnpublishedListing';
|
||||
import UnpublishedListing from '../../components/UnpublishedListing/UnpublishedListing';
|
||||
import { Loader } from '../../components/UI';
|
||||
|
||||
class unpublishedEntriesPanel extends Component {
|
||||
static propTypes = {
|
||||
isEditorialWorkflow: PropTypes.bool.isRequired,
|
||||
isFetching: PropTypes.bool.isRequired,
|
||||
unpublishedEntries: ImmutablePropTypes.map,
|
||||
loadUnpublishedEntries: PropTypes.func.isRequired,
|
||||
updateUnpublishedEntryStatus: PropTypes.func.isRequired,
|
||||
@ -24,9 +26,9 @@ class unpublishedEntriesPanel extends Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { isEditorialWorkflow, unpublishedEntries, updateUnpublishedEntryStatus, publishUnpublishedEntry } = this.props;
|
||||
const { isEditorialWorkflow, isFetching, unpublishedEntries, updateUnpublishedEntryStatus, publishUnpublishedEntry } = this.props;
|
||||
if (!isEditorialWorkflow) return null;
|
||||
|
||||
if (isFetching) return <Loader active>Loading Editorial Workflow Entries</Loader>;
|
||||
return (
|
||||
<UnpublishedListing
|
||||
entries={unpublishedEntries}
|
||||
@ -42,6 +44,8 @@ function mapStateToProps(state) {
|
||||
const returnObj = { isEditorialWorkflow };
|
||||
|
||||
if (isEditorialWorkflow) {
|
||||
returnObj.isFetching = state.editorialWorkflow.getIn(['pages', 'isFetching'], false);
|
||||
|
||||
/*
|
||||
* Generates an ordered Map of the available status as keys.
|
||||
* Each key containing a List of available unpubhlished entries
|
||||
|
Reference in New Issue
Block a user