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:
8
src/components/App/App.css
Normal file
8
src/components/App/App.css
Normal 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
180
src/components/App/App.js
Normal 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);
|
91
src/components/App/Header.css
Normal file
91
src/components/App/Header.css
Normal 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);
|
||||
}
|
115
src/components/App/Header.js
Normal file
115
src/components/App/Header.js
Normal 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>
|
||||
);
|
||||
}
|
||||
}
|
3
src/components/App/NotFoundPage.css
Normal file
3
src/components/App/NotFoundPage.css
Normal file
@ -0,0 +1,3 @@
|
||||
.nc-notFound-container {
|
||||
margin: var(--pageMargin);
|
||||
}
|
7
src/components/App/NotFoundPage.js
Normal file
7
src/components/App/NotFoundPage.js
Normal file
@ -0,0 +1,7 @@
|
||||
import React from 'react';
|
||||
|
||||
export default () => (
|
||||
<div class="nc-notFound-container">
|
||||
<h2>Not Found</h2>
|
||||
</div>
|
||||
);
|
Reference in New Issue
Block a user