update sidebar styling, add new entry links (#276)

* udpate sidebar styling, add new entry links

* make sidebar new entry links always visible

* simplify app bar implementation, findbar
This commit is contained in:
Shawn Erquhart 2017-03-15 14:32:41 -04:00 committed by David Calavera
parent e7dc2c4e84
commit 705e348138
16 changed files with 217 additions and 639 deletions

View File

@ -1,13 +1,32 @@
import history from '../routing/history';
import { SEARCH } from '../components/FindBar/FindBar';
import { getCollectionUrl, getNewEntryUrl } from '../lib/urlHelper';
export const RUN_COMMAND = 'RUN_COMMAND';
export const SHOW_COLLECTION = 'SHOW_COLLECTION';
export const CREATE_COLLECTION = 'CREATE_COLLECTION';
export const HELP = 'HELP';
export function run(commandName, payload) {
return { type: RUN_COMMAND, command: commandName, payload };
export function runCommand(command, payload) {
return (dispatch) => {
switch (command) {
case SHOW_COLLECTION:
history.push(getCollectionUrl(payload.collectionName));
break;
case CREATE_COLLECTION:
history.push(getNewEntryUrl(payload.collectionName));
break;
case HELP:
window.alert('Find Bar Help (PLACEHOLDER)\n\nLorem ipsum dolor sit amet, consectetur adipiscing elit.');
break;
case SEARCH:
history.push(`/search/${ payload.searchTerm }`);
break;
default:
break;
}
dispatch({ type: RUN_COMMAND, command, payload });
};
}
export function navigateToCollection(collectionName) {
@ -17,23 +36,3 @@ export function navigateToCollection(collectionName) {
export function createNewEntryInCollection(collectionName) {
return runCommand(CREATE_COLLECTION, { collectionName });
}
export function runCommand(commandName, payload) {
return dispatch => {
switch (commandName) {
case SHOW_COLLECTION:
history.push(`/collections/${payload.collectionName}`);
break;
case CREATE_COLLECTION:
history.push(`/collections/${payload.collectionName}/entries/new`);
break;
case HELP:
window.alert('Find Bar Help (PLACEHOLDER)\n\nLorem ipsum dolor sit amet, consectetur adipiscing elit.');
break;
case SEARCH:
history.push(`/search/${payload.searchTerm}`);
break;
}
dispatch(run(commandName, payload));
};
}

View File

@ -1,19 +1,26 @@
:root {
--foregroundColor: #fff;
--backgroundColor: #272e30;
--textFieldBorderColor: #e7e7e7;
--highlightFGColor: #fff;
--highlightBGColor: #3ab7a5;
}
@import '../UI/theme';
.appBar {
background-color: var(--backgroundColor);
background-color: var(--backgroundAltColor);
}
.icon {
/* Gross stuff below, React Toolbox hacks */
.homeLink,
.iconMenu {
margin-left: 2%;
}
.homeLink .icon {
vertical-align: top;
}
.icon,
.icon span,
.leftIcon span {
/* stylelint-disable */
/* Cascade is evil :( */
color: var(--foregroundColor) !important;
color: var(--foregroundAltColor) !important;
font-size: 30px !important;
/* stylelint-enable */
}

View File

@ -15,8 +15,6 @@ export default class AppHeader extends React.Component {
static propTypes = {
user: ImmutablePropTypes.map.isRequired,
collections: ImmutablePropTypes.orderedMap.isRequired,
commands: PropTypes.array.isRequired, // eslint-disable-line
defaultCommands: PropTypes.array.isRequired, // eslint-disable-line
runCommand: PropTypes.func.isRequired,
toggleDrawer: PropTypes.func.isRequired,
onCreateEntryClick: PropTypes.func.isRequired,
@ -57,17 +55,12 @@ export default class AppHeader extends React.Component {
const {
user,
collections,
commands,
defaultCommands,
runCommand,
toggleDrawer,
onLogoutClick,
} = this.props;
const {
createMenuActive,
userMenuActive,
} = this.state;
const { userMenuActive } = this.state;
const avatarStyle = {
backgroundColor: `#${ stringToRGB(user.get("name")) }`,
@ -97,18 +90,12 @@ export default class AppHeader extends React.Component {
onLeftIconClick={toggleDrawer}
onRightIconClick={this.handleRightIconClick}
>
<IndexLink to="/">
<FontIcon value="home" />
<IndexLink to="/" className={styles.homeLink}>
<FontIcon value="home" className={styles.icon} />
</IndexLink>
<FindBar
commands={commands}
defaultCommands={defaultCommands}
runCommand={runCommand}
/>
<IconMenu
theme={styles}
icon="create"
icon="add"
onClick={this.handleCreateButtonClick}
onHide={this.handleCreateMenuHide}
>
@ -123,6 +110,7 @@ export default class AppHeader extends React.Component {
)
}
</IconMenu>
<FindBar runCommand={runCommand} />
</AppBar>
);
}

View File

@ -1,11 +1,4 @@
:root {
--defaultColorLight: #eee;
--backgroundColor: #fff;
}
.root {
}
@import '../UI/theme';
.controlPane {
height: calc(100% - 55px);
@ -18,14 +11,10 @@
height: calc(100% - 55px);
}
.blocker {
}
.blocker > * {
pointer-events: none;
}
.footer {
height: 55px;
padding: 10px 20px;

View File

@ -1,98 +1,19 @@
:root {
--foregroundColor: #fff;
--backgroundColor: #272e30;
--textFieldBorderColor: #e7e7e7;
--highlightFGColor: #fff;
--highlightBGColor: #3ab7a5;
}
@import '../UI/theme';
.root {
flex: 1;
position: relative;
background-color: var(--backgroundColor);
padding: 5px;
}
.inputArea {
display: table;
width: 100%;
color: var(--foregroundColor);
background-color: #fff;
border: 1px solid var(--textFieldBorderColor);
}
.inputScope {
display: table-cell;
width: 1%;
padding: 0 6px 0 8px;
color: #767676;
font-size: 16px;
white-space: nowrap;
vertical-align: middle;
border-right: 1px solid var(--textFieldBorderColor);
margin:0;
background-color: var(--backgroundAltColor);
}
.inputField {
display: table-cell;
width: 99%;
padding: 6px;
font-size: 16px;
background: none transparent;
border: 0 none;
width: 80%;
max-width: 800px;
display: block;
margin: 0 auto;
padding: 10px 14px;
border: 0;
border-radius: var(--borderRadius);
box-shadow: none;
outline: none;
font-size: inherit;
}
.menu {
position: absolute;
top: 44px;
left: 0;
z-index: 9999;
display: table;
width: 100%;
background: var(--backgroundColor);
font-size: 13px;
font-weight: 400;
padding: 5px;
height: 100%;
}
.suggestions {
display: table-cell;
width: 100%;
padding-right: 10px;
}
.history {
display: table-cell;
width: 50%;
padding-left: 10px;
}
.command {
padding: 6px;
cursor: default;
color: var(--foregroundColor);
}
.command strong, .highlightedCommand strong {
color: #000;
}
.command .faded {
font-weight: 300;
color: #888;
}
.highlightedCommand {
color: var(--highlightFGColor);
background: var(--highlightBGColor);
padding: 6px;
cursor: default;
}
.highlightedCommand .faded {
font-weight: 300;
color: #282c34;
font-size: 18px;
}

View File

@ -1,363 +1,53 @@
import React, { Component, PropTypes } from 'react';
import fuzzy from 'fuzzy';
import _ from 'lodash';
import { Icon } from '../UI';
import styles from './FindBar.css';
export const SEARCH = 'SEARCH';
const PLACEHOLDER = 'Search or enter a command';
const PLACEHOLDER = 'Search entry titles...';
class FindBar extends Component {
static propTypes = {
commands: PropTypes.arrayOf(PropTypes.shape({
id: PropTypes.string.isRequired,
type: PropTypes.string.isRequired,
pattern: PropTypes.string.isRequired,
})).isRequired,
defaultCommands: PropTypes.arrayOf(PropTypes.string),
runCommand: PropTypes.func.isRequired,
};
constructor() {
super();
this._compiledCommands = [];
this._searchCommand = {
search: true,
regexp: `(?:${ SEARCH })?(.*)`,
param: { name: 'searchTerm', display: '' },
token: SEARCH,
};
this.state = {
value: '',
placeholder: PLACEHOLDER,
activeScope: null,
isOpen: false,
highlightedIndex: 0,
};
this._getSuggestions = _.memoize(this._getSuggestions, (value, activeScope) => value + activeScope);
}
componentWillMount() {
this._ignoreBlur = false;
}
componentDidMount() {
this._compiledCommands = this.props.commands.map(this.compileCommand);
}
_escapeRegExp(string) {
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}
_camelCaseToSpace(string) {
const result = string.replace(/([A-Z])/g, ' $1');
return result.charAt(0).toUpperCase() + result.slice(1);
}
// Generates a regexp and splits a token and param details for a command
compileCommand = (command) => {
let regexp = '';
let param = null;
const matcher = /\(:([a-zA-Z_$][a-zA-Z0-9_$]*)(?:(?: as )(.*))?\)/g;
const match = matcher.exec(command.pattern);
const matchIndex = match ? match.index : command.pattern.length;
const token = command.pattern.slice(0, matchIndex) || command.token;
regexp += this._escapeRegExp(command.pattern.slice(0, matchIndex));
if (match && match[1]) {
regexp += '(.*)';
param = { name: match[1], display: match[2] || this._camelCaseToSpace(match[1]) };
}
return Object.assign({}, command, {
regexp,
token,
param,
});
};
// Check if the entered string matches any command.
// adds a scope (so user can type param value) and dispatches action for fully matched commands
matchCommand = () => {
const string = this.state.activeScope ? this.state.activeScope + this.state.value : this.state.value;
let match;
let command = this._compiledCommands.find((command) => {
match = string.match(RegExp(`^${ command.regexp }`, 'i'));
return match;
});
// If no command was found, trigger a search command
if (!command) {
command = this._searchCommand;
match = string.match(RegExp(`^${ this._searchCommand.regexp }`, 'i'));
}
const paramName = command && command.param ? command.param.name : null;
search = () => {
const string = this.state.value;
const command = this._searchCommand;
const match = string.match(RegExp(`^${ this._searchCommand.regexp }`, 'i'));
const enteredParamValue = command && command.param && match[1] ? match[1].trim() : null;
if (command.search) {
this.setState({
value: '',
placeholder: PLACEHOLDER,
activeScope: null,
}, () => {
this._input.blur();
});
enteredParamValue && this.props.runCommand(SEARCH, { searchTerm: enteredParamValue });
} else if (command.param && !enteredParamValue) {
// Partial Match
// Command was partially matched: It requires a param, but param wasn't entered
// Set a scope so user can fill the param
this.setState({
value: '',
activeScope: command.token,
placeholder: command.param.display,
});
} else {
// Match
// Command was matched and either it doesn't require a param or it's required param was entered
// Dispatch action
this.setState({
value: '',
placeholder: PLACEHOLDER,
activeScope: null,
}, () => {
this._input.blur();
});
const payload = command.payload || {};
if (paramName) {
payload[paramName] = enteredParamValue;
}
this.props.runCommand(command.type, payload);
if (enteredParamValue) {
this.props.runCommand(SEARCH, { searchTerm: enteredParamValue });
}
};
maybeRemoveActiveScope = () => {
if (this.state.value.length === 0 && this.state.activeScope) {
this.setState({
activeScope: null,
placeholder: PLACEHOLDER,
});
}
};
getSuggestions = () => {
return this._getSuggestions(this.state.value, this.state.activeScope, this._compiledCommands, this.props.defaultCommands);
};
// Memoized version
_getSuggestions(value, scope, commands, defaultCommands) {
if (scope) return []; // No autocomplete for scoped input
if (value.length === 0 && defaultCommands) {
return commands
.filter(command => defaultCommands.indexOf(command.id) !== -1)
.map(result => (
Object.assign({}, result, { string: result.token }
)));
}
const results = fuzzy.filter(value, commands, {
pre: '<strong>',
post: '</strong>',
extract: el => el.token,
});
const returnResults = results.slice(0, 4).map(result => (
Object.assign({}, result.original, { string: result.string }
)));
returnResults.push(this._searchCommand);
return returnResults;
}
handleKeyDown = (event) => {
let highlightedIndex,
index;
switch (event.key) {
case 'ArrowDown':
event.preventDefault();
highlightedIndex = this.state.highlightedIndex;
index = (
highlightedIndex === this.getSuggestions().length - 1 ||
this.state.isOpen === false
) ? 0 : highlightedIndex + 1;
this.setState({
highlightedIndex: index,
isOpen: true,
});
break;
case 'ArrowUp':
event.preventDefault();
highlightedIndex = this.state.highlightedIndex;
index = (
highlightedIndex === 0
) ? this.getSuggestions().length - 1 : highlightedIndex - 1;
this.setState({
highlightedIndex: index,
isOpen: true,
});
break;
case 'Enter':
if (this.state.isOpen) {
const command = this.getSuggestions()[this.state.highlightedIndex];
const newState = {
isOpen: false,
highlightedIndex: 0,
};
if (command && !command.search) {
newState.value = command.token;
}
this.setState(newState, () => {
this._input.focus();
this._input.setSelectionRange(
this.state.value.length,
this.state.value.length
);
this.matchCommand();
});
}
break;
case 'Escape':
this.setState({
value: '',
highlightedIndex: 0,
isOpen: false,
activeScope: null,
placeholder: PLACEHOLDER,
});
break;
case 'Backspace':
this.setState({
highlightedIndex: 0,
isOpen: true,
}, this.maybeRemoveActiveScope);
break;
default:
this.setState({
highlightedIndex: 0,
isOpen: true,
});
}
};
handleChange = (event) => {
this.setState({
value: event.target.value,
});
};
handleInputBlur = () => {
if (this._ignoreBlur) return;
this.setState({
isOpen: false,
highlightedIndex: 0,
});
};
handleInputFocus = () => {
if (this._ignoreBlur) return;
this.setState({ isOpen: true });
};
handleInputClick = () => {
if (this.state.isOpen === false)
{ this.setState({ isOpen: true }); }
};
highlightCommandFromMouse = (index) => {
this.setState({ highlightedIndex: index });
};
selectCommandFromMouse = (command) => {
const newState = {
isOpen: false,
highlightedIndex: 0,
};
if (command && !command.search) {
newState.value = command.token;
}
this.setState(newState, () => {
this.matchCommand();
this._input.focus();
this.setIgnoreBlur(false);
});
};
setIgnoreBlur = (ignore) => {
this._ignoreBlur = ignore;
};
renderMenu() {
const commands = this.getSuggestions().map((command, index) => {
let children;
if (!command.search) {
children = (
<span><span dangerouslySetInnerHTML={{ __html: command.string }} /></span>
);
} else {
children = (
<span>
{this.state.value.length === 0 ?
<span><Icon type="search" />Search... </span> :
<span className={styles.faded}><Icon type="search" />Search for: </span>
}
<strong>{this.state.value}</strong>
</span>
);
}
return (
<div
className={this.state.highlightedIndex === index ? styles.highlightedCommand : styles.command}
key={command.token.trim().replace(/[^a-z0-9]+/gi, '-')}
onMouseDown={() => this.setIgnoreBlur(true)}
onMouseEnter={() => this.highlightCommandFromMouse(index)}
onClick={() => this.selectCommandFromMouse(command)}
>
{children}
</div>
);
});
return commands.length === 0 ? null : (
<div className={styles.menu}>
<div className={styles.suggestions}>
{commands}
</div>
</div>
);
}
renderActiveScope() {
if (this.state.activeScope === SEARCH) {
return <div className={styles.inputScope}><Icon type="search" /></div>;
} else {
return <div className={styles.inputScope}>{this.state.activeScope}</div>;
}
}
handleKeyDown = event => (event.key === 'Enter' && this.search());
handleChange = event => this.setState({ value: event.target.value });
render() {
const menu = this.state.isOpen && this.renderMenu();
const scope = this.state.activeScope && this.renderActiveScope();
return (
<div className={styles.root}>
<label className={styles.inputArea}>
{scope}
<input
className={styles.inputField}
ref={c => this._input = c}
onFocus={this.handleInputFocus}
onBlur={this.handleInputBlur}
onChange={this.handleChange}
onKeyDown={this.handleKeyDown}
onClick={this.handleInputClick}
placeholder={this.state.placeholder}
value={this.state.value}
/>
</label>
{menu}
<label htmlFor="searchInput" />
<input
id="searchInput"
className={styles.inputField}
ref={c => this._input = c} // eslint-disable-line no-return-assign
onChange={this.handleChange}
onKeyDown={this.handleKeyDown}
placeholder={this.state.placeholder}
value={this.state.value}
/>
</div>
);
}

View File

@ -9,6 +9,11 @@
--errorColor: #f52;
--borderRadius: 2px;
--topmostZindex: 99999;
--foregroundAltColor: #fff;
--backgroundAltColor: #272e30;
--textFieldBorderColor: #e7e7e7;
--highlightFGColor: #fff;
--highlightBGColor: #3ab7a5;
}
.base {

View File

@ -3,28 +3,6 @@ import { storiesOf, action } from '@kadira/storybook';
import FindBar from '../FindBar/FindBar';
const CREATE_COLLECTION = 'CREATE_COLLECTION';
const CREATE_POST = 'CREATE_POST';
const CREATE_ARTICLE = 'CREATE_ARTICLE';
const CREATE_FAQ = 'CREATE_FAQ';
const ADD_NEWS = 'ADD_NEWS';
const ADD_USER = 'ADD_USER';
const OPEN_SETTINGS = 'OPEN_SETTINGS';
const HELP = 'HELP';
const MORE_COMMANDS = 'MORE_COMMANDS';
const commands = [
{ id: CREATE_COLLECTION, pattern: 'Create new Collection(:collectionName)' },
{ id: CREATE_POST, pattern: 'Create new Post(:postName)' },
{ id: CREATE_ARTICLE, pattern: 'Create new Article(:articleName)' },
{ id: CREATE_FAQ, pattern: 'Create new FAQ item(:faqName as FAQ item name)' },
{ id: ADD_NEWS, pattern: 'Add news item(:headline)' },
{ id: ADD_USER, pattern: 'Add new User(:userName as User name)' },
{ id: OPEN_SETTINGS, pattern: 'Go to Settings' },
{ id: HELP, pattern: 'Help' },
{ id: MORE_COMMANDS, pattern: 'More Commands...' },
];
const style = {
width: 800,
margin: 20,
@ -33,10 +11,6 @@ const style = {
storiesOf('FindBar', module)
.add('Default View', () => (
<div style={style}>
<FindBar
commands={commands}
defaultCommands={[CREATE_POST, CREATE_COLLECTION, OPEN_SETTINGS, HELP, MORE_COMMANDS]}
runCommand={action}
/>
<FindBar runCommand={action} />
</div>
));

View File

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

View File

@ -2,27 +2,28 @@ import React, { PropTypes } from 'react';
import ImmutablePropTypes from 'react-immutable-proptypes';
import pluralize from 'pluralize';
import { connect } from 'react-redux';
import { IndexLink } from "react-router";
import FontIcon from 'react-toolbox/lib/font_icon';
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 { loadConfig as actionLoadConfig } from '../actions/config';
import { loginUser as actionLoginUser, logoutUser as actionLogoutUser } from '../actions/auth';
import { toggleSidebar as actionToggleSidebar } from '../actions/globalUI';
import { currentBackend } from '../backends/backend';
import {
SHOW_COLLECTION,
CREATE_COLLECTION,
HELP,
runCommand,
navigateToCollection,
createNewEntryInCollection,
runCommand as actionRunCommand,
navigateToCollection as actionNavigateToCollection,
createNewEntryInCollection as actionCreateNewEntryInCollection,
} from '../actions/findbar';
import AppHeader from '../components/AppHeader/AppHeader';
import { Loader, Toast } from '../components/UI/index';
import { getCollectionUrl, getNewEntryUrl } from '../lib/urlHelper';
import { SIMPLE, EDITORIAL_WORKFLOW } from '../constants/publishModes';
import styles from './App.css';
import sidebarStyles from './Sidebar.css';
TopBarProgress.config({
barColors: {
@ -49,6 +50,7 @@ class App extends React.Component {
user: ImmutablePropTypes.map,
runCommand: PropTypes.func.isRequired,
isFetching: PropTypes.bool.isRequired,
publishMode: PropTypes.oneOf([SIMPLE, EDITORIAL_WORKFLOW]),
};
static configError(config) {
@ -63,11 +65,11 @@ class App extends React.Component {
}
componentDidMount() {
this.props.dispatch(loadConfig());
this.props.dispatch(actionLoadConfig());
}
handleLogin(credentials) {
this.props.dispatch(loginUser(credentials));
this.props.dispatch(actionLoginUser(credentials));
}
authenticating() {
@ -91,35 +93,9 @@ class App extends React.Component {
);
}
generateFindBarCommands() {
// Generate command list
const commands = [];
const defaultCommands = [];
this.props.collections.forEach((collection) => {
commands.push({
id: `show_${ collection.get('name') }`,
pattern: `Show ${ pluralize(collection.get('label')) }`,
type: SHOW_COLLECTION,
payload: { collectionName: collection.get('name') },
});
if (defaultCommands.length < 5) defaultCommands.push(`show_${ collection.get('name') }`);
// if (collection.get('create') === true) {
// commands.push({
// id: `create_${ collection.get('name') }`,
// pattern: `Create new ${ pluralize(collection.get('label'), 1) }(:itemName as ${ pluralize(collection.get('label'), 1) } Name)`,
// type: CREATE_COLLECTION,
// payload: { collectionName: collection.get('name') },
// });
// }
});
// commands.push({ id: HELP, type: HELP, pattern: 'Help' });
// defaultCommands.push(HELP);
return { commands, defaultCommands };
handleLinkClick(event, handler, ...args) {
event.preventDefault();
handler(...args);
}
render() {
@ -134,6 +110,7 @@ class App extends React.Component {
createNewEntryInCollection,
logoutUser,
isFetching,
publishMode,
} = this.props;
@ -153,45 +130,67 @@ class App extends React.Component {
return this.authenticating();
}
const { commands, defaultCommands } = this.generateFindBarCommands();
const sidebarContent = (
<nav className={styles.nav}>
<h1 className={styles.heading}>Collections</h1>
<Navigation type="vertical">
<div>
<Navigation type="vertical" className={sidebarStyles.nav}>
{
collections.valueSeq().map(collection =>
<Link
key={collection.get('name')}
onClick={navigateToCollection.bind(this, collection.get('name'))} // eslint-disable-line
>
{collection.get('label')}
</Link>
)
publishMode === SIMPLE ? null :
<section>
<h1 className={sidebarStyles.heading}>Publishing</h1>
<div className={sidebarStyles.linkWrapper}>
<IndexLink to="/" className={sidebarStyles.viewEntriesLink}>Editorial Workflow</IndexLink>
</div>
</section>
}
<section>
<h1 className={sidebarStyles.heading}>Collections</h1>
{
collections.valueSeq().map((collection) => {
const collectionName = collection.get('name');
return (
<div key={collectionName} className={sidebarStyles.linkWrapper}>
<a
href={getCollectionUrl(collectionName, true)}
className={sidebarStyles.viewEntriesLink}
onClick={e => this.handleLinkClick(e, navigateToCollection, collectionName)}
>
{pluralize(collection.get('label'))}
</a>
{
collection.get('create') ? (
<a
href={getNewEntryUrl(collectionName, true)}
className={sidebarStyles.createEntryLink}
onClick={e => this.handleLinkClick(e, createNewEntryInCollection, collectionName)}
>
<FontIcon value="add_circle_outline" />
</a>
) : null
}
</div>
);
})
}
</section>
</Navigation>
</nav>
</div>
);
return (
<Sidebar content={sidebarContent}>
<Layout theme={styles}>
<Notifs
className={styles.notifsContainer}
CustomComponent={Toast}
/>
<Layout>
<Notifs CustomComponent={Toast} />
<AppHeader
user={user}
collections={collections}
commands={commands}
defaultCommands={defaultCommands}
runCommand={runCommand}
onCreateEntryClick={createNewEntryInCollection}
onLogoutClick={logoutUser}
toggleDrawer={toggleSidebar}
/>
<Panel scrollY>
<Panel scrollY className={styles.entriesPanel}>
{ isFetching && <TopBarProgress /> }
<div className={styles.main}>
<div>
{children}
</div>
</Panel>
@ -206,24 +205,25 @@ function mapStateToProps(state) {
const { auth, config, collections, globalUI } = state;
const user = auth && auth.get('user');
const isFetching = globalUI.get('isFetching');
return { auth, config, collections, user, isFetching };
const publishMode = config && config.get('publish_mode');
return { auth, config, collections, user, isFetching, publishMode };
}
function mapDispatchToProps(dispatch) {
return {
dispatch,
toggleSidebar: () => dispatch(toggleSidebar()),
toggleSidebar: () => dispatch(actionToggleSidebar()),
runCommand: (type, payload) => {
dispatch(runCommand(type, payload));
dispatch(actionRunCommand(type, payload));
},
navigateToCollection: (collection) => {
dispatch(navigateToCollection(collection));
dispatch(actionNavigateToCollection(collection));
},
createNewEntryInCollection: (collectionName) => {
dispatch(createNewEntryInCollection(collectionName));
dispatch(actionCreateNewEntryInCollection(collectionName));
},
logoutUser: () => {
dispatch(logoutUser());
dispatch(actionLogoutUser());
},
};
}

View File

@ -5,7 +5,6 @@ import { loadEntries } from '../actions/entries';
import { selectEntries } from '../reducers';
import { Loader } from '../components/UI';
import EntryListing from '../components/EntryListing/EntryListing';
import styles from './breakpoints.css';
class CollectionPage extends React.Component {
@ -42,7 +41,7 @@ class CollectionPage extends React.Component {
if (collections == null) {
return <h1>No collections defined in your config.yml</h1>;
}
return (<div className={styles.root}>
return (<div>
{entries ?
<EntryListing
collections={collection}
@ -54,7 +53,7 @@ class CollectionPage extends React.Component {
{collection.get('label')}
</EntryListing>
:
<Loader active>{['Loading Entries', 'Caching Entries', 'This might take several minutes']}</Loader>
<Loader active>{['Loading Entries', 'Caching Entries', 'This might take several minutes']}</Loader>
}
</div>);
}

View File

@ -3,7 +3,6 @@ import { connect } from 'react-redux';
import { SIMPLE, EDITORIAL_WORKFLOW } from '../constants/publishModes';
import history from '../routing/history';
import UnpublishedEntriesPanel from './editorialWorkflow/UnpublishedEntriesPanel';
import styles from './breakpoints.css';
class DashboardPage extends Component {
@ -15,7 +14,7 @@ class DashboardPage extends Component {
render() {
return (
<div className={styles.root}>
<div>
<h1>Dashboard</h1>
<UnpublishedEntriesPanel />
</div>

View File

@ -2,10 +2,9 @@ import React, { PropTypes } from 'react';
import ImmutablePropTypes from 'react-immutable-proptypes';
import { connect } from 'react-redux';
import { selectSearchedEntries } from '../reducers';
import { searchEntries, clearSearch } from '../actions/search';
import { searchEntries as actionSearchEntries, clearSearch as actionClearSearch } from '../actions/search';
import { Loader } from '../components/UI';
import EntryListing from '../components/EntryListing/EntryListing';
import styles from './breakpoints.css';
class SearchPage extends React.Component {
@ -42,17 +41,17 @@ class SearchPage extends React.Component {
render() {
const { collections, searchTerm, entries, isFetching, page, publicFolder } = this.props;
return (<div className={styles.root}>
return (<div>
{(isFetching === true || !entries) ?
<Loader active>{['Loading Entries', 'Caching Entries', 'This might take several minutes']}</Loader>
:
<EntryListing
collections={collections}
entries={entries}
page={page}
publicFolder={publicFolder}
onPaginate={this.handleLoadMore}
>
<EntryListing
collections={collections}
entries={entries}
page={page}
publicFolder={publicFolder}
onPaginate={this.handleLoadMore}
>
Results for {searchTerm}
</EntryListing>
}
@ -76,7 +75,7 @@ function mapStateToProps(state, ownProps) {
export default connect(
mapStateToProps,
{
searchEntries,
clearSearch,
searchEntries: actionSearchEntries,
clearSearch: actionClearSearch,
}
)(SearchPage);

View File

@ -1,8 +1,47 @@
@import '../components/UI/theme';
.root {
margin-top: 64px;
}
.sidebar {
width: 200px;
background-color: #fff
width: 220px;
background-color: #fff;
padding: 0 16px;
}
.heading {
margin-bottom: 8px;
}
.nav {
padding: 0;
}
.linkWrapper {
color: var(--defaultColor);
border-radius: var(--borderRadius);
display: flex;
align-items: center;
justify-content: space-between;
&:hover {
background-color: var(--defaultColorLight);
}
}
.viewEntriesLink {
font-size: 18px;
color: inherit;
padding: 10px 6px;
display: block;
width: 100%;
}
.createEntryLink {
color: inherit;
padding: 0 6px;
display: block;
position: relative;
top: 2px;
}

View File

@ -1,38 +0,0 @@
.root {
margin: auto;
}
@media (max-width: 749px) and (min-width: 495px) {
.root {
margin: auto;
width: 495px;
}
}
@media (max-width: 1004px) and (min-width: 750px) {
.root {
margin: auto;
width: 750px;
}
}
@media (max-width: 1514px) and (min-width: 1005px) {
.root {
margin: auto;
width: 1005px;
}
}
@media (max-width: 1769px) and (min-width: 1515px) {
.root {
margin: auto;
width: 1515px;
}
}
@media (min-width: 1770px) {
.root {
margin: auto;
width: 1770px;
}
}

11
src/lib/urlHelper.js Normal file
View File

@ -0,0 +1,11 @@
function getUrl(url, direct) {
return `${ direct ? '/#' : '' }${ url }`;
}
export function getCollectionUrl(collectionName, direct) {
return getUrl(`/collections/${ collectionName }`, direct);
}
export function getNewEntryUrl(collectionName, direct) {
return getUrl(`/collections/${ collectionName }/entries/new`, direct);
}