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:
Cássio Souza
2016-11-11 17:54:58 -02:00
committed by GitHub
parent 3420273691
commit 2a2497072d
42 changed files with 490 additions and 603 deletions

View File

@ -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;
}

View File

@ -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));
},

View File

@ -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 {

View File

@ -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));

View File

@ -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 {

View File

@ -0,0 +1,8 @@
.root {
margin-top: 64px;
}
.sidebar {
width: 200px;
background-color: #fff
}

63
src/containers/Sidebar.js Normal file
View 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);

View File

@ -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