WIP - Global UI (#785)

* update top bar and collections sidebar UI

* update collection entries UI

* improve global layout

* merge search page into collection page

* enable new entry button

* search fixup

* wip -initial editor update

* update editor scrolling and markdown toolbar position

* wip

* editor toolbar progress

* editor toolbar wip

* finished basic editor toolbar

* add standalone toggle component

* improve markdown toolbar spacing

* add user avatar placeholder

* finish markdown toggle styling

* refactor icon setup, add new icons

* add new icons to markdown editor toolbar

* remove extra app container

* add markdown active mark style

* relation and text widget styling

* widget design updates, basic list/object design update

* widget style updates, image widget improvements

* refactor widget directory, fix file removal

* widget focus styles

* finish editor widget focus styles

* migrate media library modal to react-modal

* wip - migrate editor component form to modal

* wip - move editor component form to modal

* wip - embed plugin forms in the editor

* inline shortcode forms working

* disable react hot loading, its breaking things

* improve shortcode form styles

* make shortcode form collapsible, improve styling

* add close functionality to shortcode blocks

* improve base media library styling

* fix shortcode label

* migrate unstyled workflow to new UI

* wip - reorganizing everything

* more work moving everything

* finish more moving and eliminating stuff

* restructure, remove react-toolbox

* wip - removing old stuff, more restructure

* finish restructure

* wip - css arch

* switch back to test repo

* update react-datetime to ^2.11.0

* remove leftover react-toolbox button

* more restructuring clean-up

* fix UI component directory case

* wip -css editor control style

* wip - consolidate widget styles

* wip - use a single control renderer

* fixed object values breaking

* wip - editor control active styles

* pass control wrapper to widgets

* ensure branch name is trimmed

* wip - improve widget authoring support

* import Map to Widget component

* refactor toolbar buttons

* wip - more widget active styles

* break out editor toggle component

* add local scroll sync back

* update editor toggle icons

* limit editor control pane content width

* fix editor control spacing

* migrate markdown toolbar stickiness to css

* fix markdown toolbar border radius

* temporarily use test backend

* stop markdown toolbar from going to bottom

* restore disabled markdown toolbar buttons for raw

* test markdown widget without focus styles

* more widget updates

* remove card visuals from editor

* disable dragging editor split off screen

* use editorControl component for shortcode fields

* make header site link configurable

* add configurable collection descriptions

* temporarily add example assets

* add basic list view

* remove outdated css mixins

* add and implement search icon

* activate quick add menu

* visualize usable space in editor view

* fix entry close, other improvements

* wip - editorial workflow updates

* some dropshadow and other CSS tweaks

* workflow ui updates

* add worfklow card buttons

* fix workflow card button handlers

* some dropshadow and other CSS tweaks

* make workflow board wider

* center workflow and collection views

* add basic responsiveness

* a bunch of fun UI fixes! a BUNCH! (#875)

* give `.nc-entryEditor-toolbar-mainSection` left and right child divs

* a bunch of fun UI fixes! a BUNCH!

* remove obscure --buttonShadow

* revert to test repo

* fix not found page styling

* allow workflow publishing from any column

* disallow publishing from all columns, with feedback

* fix new entry button

* fix markdown state persisting across entries

* enable simple workflow save and new from editor

* update slug in address bar when saving new entry

* wip - workflow updates, deletion working

* add status change functionality to editor

* wip - improving status change from editor

* editor toolbar back button improvements, loading improvements, cleanup

* progress on the media library UI cleanup

* remove font smothing css

* a quick fix for these buttons

* tweaks

* progress on media library modal— broken FYI

* fix media library functionality, finish migrating footer

* remove media library footer files

* remove leftover css import

* fix media library

* editor publishing functionality complete (unstyled)

* remove leftover loader var from media library

* wip - editor publishing styles

* add status dropdown styling

* editor toolbar style updates

* editor toolbar state improvements

* progress on the media library UI cleanup, style improvements

* finish editorial workflow editor styling

* finish media library styling

* fix config

* add what-input to optimize focus styling

* fix button

* fix navigation blocking for simple workflow

* improve simple workflow publishing

* add avatar dropdown to editor top bar

* style github and test-repo auth pages

* add git gateway auth page styles

* improve editor error styling
This commit is contained in:
Shawn Erquhart
2017-12-07 12:37:10 -05:00
committed by GitHub
parent 41af113d5b
commit cfbf31b130
344 changed files with 6964 additions and 7415 deletions

View File

@ -0,0 +1,8 @@
@import "./NotFoundPage.css";
@import "./Header.css";
.nc-app-main {
min-width: 800px;
max-width: 1440px;
margin: 0 auto;
}

180
src/components/App/App.js Normal file
View File

@ -0,0 +1,180 @@
import PropTypes from 'prop-types';
import React from 'react';
import ImmutablePropTypes from 'react-immutable-proptypes';
import { connect } from 'react-redux';
import { Route, Switch, Link, Redirect } from 'react-router-dom';
import { Notifs } from 'redux-notifications';
import TopBarProgress from 'react-topbar-progress-indicator';
import { loadConfig as actionLoadConfig } from 'Actions/config';
import { loginUser as actionLoginUser, logoutUser as actionLogoutUser } from 'Actions/auth';
import { currentBackend } from 'Backends/backend';
import { showCollection, createNewEntry } from 'Actions/collections';
import { openMediaLibrary as actionOpenMediaLibrary } from 'Actions/mediaLibrary';
import MediaLibrary from 'MediaLibrary/MediaLibrary';
import { Loader, Toast } from 'UI';
import { getCollectionUrl, getNewEntryUrl } from 'Lib/urlHelper';
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: {
/**
* Uses value from CSS --colorActive.
*/
"0": '#3a69c8',
'1.0': '#3a69c8',
},
shadowBlur: 0,
barThickness: 2,
});
class App extends React.Component {
static propTypes = {
auth: ImmutablePropTypes.map,
config: ImmutablePropTypes.map,
collections: ImmutablePropTypes.orderedMap,
logoutUser: PropTypes.func.isRequired,
dispatch: PropTypes.func.isRequired,
user: ImmutablePropTypes.map,
isFetching: PropTypes.bool.isRequired,
publishMode: PropTypes.oneOf([SIMPLE, EDITORIAL_WORKFLOW]),
siteId: PropTypes.string,
};
static configError(config) {
return (<div>
<h1>Error loading the CMS configuration</h1>
<div>
<p>The <code>config.yml</code> file could not be loaded or failed to parse properly.</p>
<p><strong>Error message:</strong> {config.get('error')}</p>
</div>
</div>);
}
componentDidMount() {
this.props.dispatch(actionLoadConfig());
}
handleLogin(credentials) {
this.props.dispatch(actionLoginUser(credentials));
}
authenticating() {
const { auth } = this.props;
const backend = currentBackend(this.props.config);
if (backend == null) {
return <div><h1>Waiting for backend...</h1></div>;
}
return (
<div>
{
React.createElement(backend.authComponent(), {
onLogin: this.handleLogin.bind(this),
error: auth && auth.get('error'),
isFetching: auth && auth.get('isFetching'),
siteId: this.props.config.getIn(["backend", "site_domain"]),
base_url: this.props.config.getIn(["backend", "base_url"], null)
})
}
</div>
);
}
handleLinkClick(event, handler, ...args) {
event.preventDefault();
handler(...args);
}
render() {
const {
user,
config,
collections,
logoutUser,
isFetching,
publishMode,
openMediaLibrary,
} = this.props;
if (config === null) {
return null;
}
if (config.get('error')) {
return App.configError(config);
}
if (config.get('isFetching')) {
return <Loader active>Loading configuration...</Loader>;
}
if (user == null) {
return this.authenticating();
}
const defaultPath = `/collections/${collections.first().get('name')}`;
const hasWorkflow = publishMode === EDITORIAL_WORKFLOW;
return (
<div className="nc-app-container">
<Notifs CustomComponent={Toast} />
<Header
user={user}
collections={collections}
onCreateEntryClick={createNewEntry}
onLogoutClick={logoutUser}
openMediaLibrary={openMediaLibrary}
hasWorkflow={hasWorkflow}
displayUrl={config.get('display_url')}
/>
<div className="nc-app-main">
{ isFetching && <TopBarProgress /> }
<div>
<Switch>
<Redirect exact from="/" to={defaultPath} />
<Redirect exact from="/search/" to={defaultPath} />
{ hasWorkflow ? <Route path="/workflow" component={Workflow}/> : null }
<Route exact path="/collections/:name" component={Collection} />
<Route path="/collections/:name/new" render={props => <Editor {...props} newRecord />} />
<Route path="/collections/:name/entries/:slug" component={Editor} />
<Route path="/search/:searchTerm" render={props => <Collection {...props} isSearchResults />} />
<Route component={NotFoundPage} />
</Switch>
<MediaLibrary/>
</div>
</div>
</div>
);
}
}
function mapStateToProps(state, ownProps) {
const { auth, config, collections, globalUI } = state;
const user = auth && auth.get('user');
const isFetching = globalUI.get('isFetching');
const publishMode = config && config.get('publish_mode');
return { auth, config, collections, user, isFetching, publishMode };
}
function mapDispatchToProps(dispatch) {
return {
dispatch,
openMediaLibrary: () => {
dispatch(actionOpenMediaLibrary());
},
logoutUser: () => {
dispatch(actionLogoutUser());
},
};
}
export default connect(mapStateToProps, mapDispatchToProps)(App);

View File

@ -0,0 +1,91 @@
.nc-appHeader-container {
z-index: 300;
}
.nc-appHeader-main {
@apply(--dropShadowMain);
position: fixed;
width: 100%;
top: 0;
background-color: var(--colorForeground);
z-index: 300;
height: var(--topBarHeight);
}
.nc-appHeader-content {
display: flex;
justify-content: space-between;
min-width: 800px;
max-width: 1440px;
padding: 0 12px;
margin: 0 auto;
}
.nc-appHeader-button {
background-color: transparent;
color: #7b8290;
font-size: 16px;
font-weight: 500;
display: inline-flex;
padding: 16px 20px;
align-items: center;
& .nc-icon {
margin-right: 4px;
color: #b3b9c4;
}
&:hover,
&:active,
&:focus,
&.nc-appHeader-button-active {
background-color: white;
color: var(--colorActive);
& .nc-icon {
color: var(--colorActive);
}
}
}
.nc-appHeader-actions {
display: inline-flex;
align-items: center;
}
.nc-appHeader-siteLink {
font-size: 14px;
font-weight: 400;
color: #7b8290;
padding: 10px 16px;
}
.nc-appHeader-quickNew {
@apply(--buttonMedium);
@apply(--buttonGray);
margin-right: 8px;
&:after {
top: 11px;
}
}
.nc-appHeader-avatar {
border: 0;
padding: 8px;
cursor: pointer;
color: #1e2532;
background-color: transparent;
}
.nc-appHeader-avatar-image,
.nc-appHeader-avatar-placeholder {
width: 32px;
border-radius: 32px;
}
.nc-appHeader-avatar-placeholder {
height: 32px;
color: #1e2532;
background-color: var(--textFieldBorderColor);
}

View File

@ -0,0 +1,115 @@
import PropTypes from 'prop-types';
import React from "react";
import ImmutablePropTypes from "react-immutable-proptypes";
import { NavLink } from 'react-router-dom';
import { Icon, Dropdown, DropdownItem } from 'UI';
import { stripProtocol } from 'Lib/urlHelper';
export default class Header extends React.Component {
static propTypes = {
user: ImmutablePropTypes.map.isRequired,
collections: ImmutablePropTypes.orderedMap.isRequired,
onCreateEntryClick: PropTypes.func.isRequired,
onLogoutClick: PropTypes.func.isRequired,
displayUrl: PropTypes.string,
};
handleCreatePostClick = (collectionName) => {
const { onCreateEntryClick } = this.props;
if (onCreateEntryClick) {
onCreateEntryClick(collectionName);
}
};
render() {
const {
user,
collections,
toggleDrawer,
onLogoutClick,
openMediaLibrary,
hasWorkflow,
displayUrl,
} = this.props;
const avatarUrl = user.get('avatar_url');
return (
<div className="nc-appHeader-container">
<div className="nc-appHeader-main">
<div className="nc-appHeader-content">
<nav>
<NavLink
to="/"
className="nc-appHeader-button"
activeClassName="nc-appHeader-button-active"
isActive={(match, location) => location.pathname.startsWith('/collections/')}
>
<Icon type="page"/>
Content
</NavLink>
{
hasWorkflow
? <NavLink to="/workflow" className="nc-appHeader-button" activeClassName="nc-appHeader-button-active">
<Icon type="workflow"/>
Workflow
</NavLink>
: null
}
<button onClick={openMediaLibrary} className="nc-appHeader-button">
<Icon type="media-alt"/>
Media
</button>
</nav>
<div className="nc-appHeader-actions">
<Dropdown
classNameButton="nc-appHeader-button nc-appHeader-quickNew"
label="Quick add"
dropdownTopOverlap="30px"
dropdownWidth="160px"
dropdownPosition="left"
>
{
collections.filter(collection => collection.get('create')).toList().map(collection =>
<DropdownItem
key={collection.get("name")}
label={collection.get("label")}
onClick={() => this.handleCreatePostClick(collection.get('name'))}
/>
)
}
</Dropdown>
{
displayUrl
? <a
className="nc-appHeader-siteLink"
href={displayUrl}
target="_blank"
>
{stripProtocol(displayUrl)}
</a>
: null
}
<Dropdown
dropdownTopOverlap="50px"
dropdownWidth="100px"
dropdownPosition="right"
button={
<button className="nc-appHeader-avatar">
{
avatarUrl
? <img className="nc-appHeader-avatar-image" src={user.get('avatar_url')}/>
: <Icon className="nc-appHeader-avatar-placeholder" type="user" size="large"/>
}
</button>
}
>
<DropdownItem label="Log Out" onClick={onLogoutClick}/>
</Dropdown>
</div>
</div>
</div>
</div>
);
}
}

View File

@ -0,0 +1,3 @@
.nc-notFound-container {
margin: var(--pageMargin);
}

View File

@ -0,0 +1,7 @@
import React from 'react';
export default () => (
<div class="nc-notFound-container">
<h2>Not Found</h2>
</div>
);