Merge pull request #47 from EbookFoundation/feature/mobile

breakpoints for mobile displays
pull/48/head
Theodore Kluge 2019-03-19 16:25:28 -04:00 committed by GitHub
commit 9c6c841957
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 9839 additions and 266 deletions

View File

@ -18,11 +18,16 @@ const ACTIONS = {
add_publisher: 'add_publisher', add_publisher: 'add_publisher',
delete_publisher: 'delete_publisher', delete_publisher: 'delete_publisher',
set_publishers: 'set_publishers', set_publishers: 'set_publishers',
update_publisher: 'update_publisher' update_publisher: 'update_publisher',
toggle_menu: 'toggle_menu'
} }
export default ACTIONS export default ACTIONS
export const toggleMenu = () => ({
type: ACTIONS.toggle_menu
})
export const setWorking = working => ({ export const setWorking = working => ({
type: ACTIONS.set_working, type: ACTIONS.set_working,
data: working data: working

View File

@ -7,8 +7,10 @@ import Progress from './components/Progress'
import appReducer from './reducers' import appReducer from './reducers'
import adminReducer from './reducers/admin' import adminReducer from './reducers/admin'
import { fetchAdminData, patchUser, patchPublisher } from './actions/admin' import { fetchAdminData, patchUser, patchPublisher } from './actions/admin'
import { toggleMenu } from './actions'
import Util from './lib/Util' import Util from './lib/Util'
import Icon from './components/Icon' import Icon from './components/Icon'
import IconButton from './components/IconButton'
import '../styles/admin.scss' import '../styles/admin.scss'
import './containers/listitem.scss' import './containers/listitem.scss'
@ -28,7 +30,8 @@ class App extends React.Component {
}, },
users: [], users: [],
publishers: [], publishers: [],
working: false working: false,
navMenu: false
} }
this.dispatch = this.dispatch.bind(this) this.dispatch = this.dispatch.bind(this)
@ -64,10 +67,10 @@ class App extends React.Component {
getRegisteredUsers () { getRegisteredUsers () {
return this.state.users.map(user => { return this.state.users.map(user => {
return ( return (
<li className='uri-list-item flex-container' key={`is-admin-${user.id}`}> <li className='uri-list-item cols flex-container' key={`is-admin-${user.id}`}>
<span className='flex'>{user.email}</span> <span className='flex'>{user.email}</span>
<span className='flex'> <span className='flex'>
<label for={`is-admin-${user.id}`} className='cb-label'>Admin?</label> <label htmlFor={`is-admin-${user.id}`} className='cb-label'>Admin?</label>
<input className='checkbox' type='checkbox' checked={user.admin} onChange={() => this.dispatch(patchUser({ id: user.id, admin: !user.admin }))} id={`is-admin-${user.id}`} /> <input className='checkbox' type='checkbox' checked={user.admin} onChange={() => this.dispatch(patchUser({ id: user.id, admin: !user.admin }))} id={`is-admin-${user.id}`} />
<label htmlFor={`is-admin-${user.id}`} /> <label htmlFor={`is-admin-${user.id}`} />
</span> </span>
@ -84,7 +87,7 @@ class App extends React.Component {
return ( return (
<li className='uri-list-item flex-container flex-vertical' key={`is-whitelisted-${pub.id}`}> <li className='uri-list-item flex-container flex-vertical' key={`is-whitelisted-${pub.id}`}>
<header><h3>{pub.name}</h3></header> <header><h3>{pub.name}</h3></header>
<div className='flex flex-container'> <div className='cols flex flex-container'>
<div className='flex flex-container flex-vertical key-value'> <div className='flex flex-container flex-vertical key-value'>
<span className='flex'><span className='key'>Owner:</span><span className='value'>{pub.user.email}</span></span> <span className='flex'><span className='key'>Owner:</span><span className='value'>{pub.user.email}</span></span>
<span className='flex'><span className='key'>App ID:</span><span className='value'>{pub.appid}</span></span> <span className='flex'><span className='key'>App ID:</span><span className='value'>{pub.appid}</span></span>
@ -111,19 +114,19 @@ class App extends React.Component {
render () { render () {
return ( return (
<Router basename='/admin'> <Router basename='/admin'>
<div className='root-container flex-container admin-container'> <div className={'root-container flex-container admin-container two-panels' + (this.state.navMenu ? ' nav-active' : '')}>
<aside className='nav nav-left'> <aside className='nav nav-left'>
<header> <header>
<h1>RoE Admin</h1> <h1 className='flex-container'><IconButton icon='menu' className='menu-small' onClick={() => this.dispatch(toggleMenu())} /><span className='flex'>RoE Admin</span></h1>
<h2 className='flex-container'> <h2 className='flex-container'>
<span className='flex'>{this.state.user.email}</span> <span className='flex'>{this.state.user.email}</span>
<a href='/logout'>Log out</a> <a href='/logout'>Log out</a>
</h2> </h2>
</header> </header>
<ul> <ul>
<li><NavLink to='/users'>Users</NavLink></li> <li className='flex-container'><NavLink to='/users'><Icon icon='account' /><span className='flex'>Users</span></NavLink></li>
<li><NavLink to='/publishers'>Publishers</NavLink></li> <li className='flex-container'><NavLink to='/publishers'><Icon icon='transfer-right' /><span className='flex'>Publishers</span></NavLink></li>
<li><a href='/keys'>Exit admin</a></li> <li className='flex-container'><a href='/keys'><Icon icon='exit' /><span className='flex'>Exit admin</span></a></li>
</ul> </ul>
</aside> </aside>
<section className={'content flex' + (this.state.working ? ' working' : '')}> <section className={'content flex' + (this.state.working ? ' working' : '')}>

View File

@ -18,6 +18,12 @@ function getSVG (icon) {
case 'shield-check': return '<path d="M10,17L6,13L7.41,11.59L10,14.17L16.59,7.58L18,9M12,1L3,5V11C3,16.55 6.84,21.74 12,23C17.16,21.74 21,16.55 21,11V5L12,1Z" />' case 'shield-check': return '<path d="M10,17L6,13L7.41,11.59L10,14.17L16.59,7.58L18,9M12,1L3,5V11C3,16.55 6.84,21.74 12,23C17.16,21.74 21,16.55 21,11V5L12,1Z" />'
case 'alert-circle': return '<path d="M13,13H11V7H13M13,17H11V15H13M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2Z" />' case 'alert-circle': return '<path d="M13,13H11V7H13M13,17H11V15H13M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2Z" />'
case 'refresh': return '<path d="M17.65,6.35C16.2,4.9 14.21,4 12,4A8,8 0 0,0 4,12A8,8 0 0,0 12,20C15.73,20 18.84,17.45 19.73,14H17.65C16.83,16.33 14.61,18 12,18A6,6 0 0,1 6,12A6,6 0 0,1 12,6C13.66,6 15.14,6.69 16.22,7.78L13,11H20V4L17.65,6.35Z" />' case 'refresh': return '<path d="M17.65,6.35C16.2,4.9 14.21,4 12,4A8,8 0 0,0 4,12A8,8 0 0,0 12,20C15.73,20 18.84,17.45 19.73,14H17.65C16.83,16.33 14.61,18 12,18A6,6 0 0,1 6,12A6,6 0 0,1 12,6C13.66,6 15.14,6.69 16.22,7.78L13,11H20V4L17.65,6.35Z" />'
case 'key': return '<path d="M22,18V22H18V19H15V16H12L9.74,13.74C9.19,13.91 8.61,14 8,14A6,6 0 0,1 2,8A6,6 0 0,1 8,2A6,6 0 0,1 14,8C14,8.61 13.91,9.19 13.74,9.74L22,18M7,5A2,2 0 0,0 5,7A2,2 0 0,0 7,9A2,2 0 0,0 9,7A2,2 0 0,0 7,5Z" />'
case 'transfer-right': return '<path d="M3,8H5V16H3V8M7,8H9V16H7V8M11,8H13V16H11V8M15,19.25V4.75L22.25,12L15,19.25Z" />'
case 'account': return '<path d="M12,4A4,4 0 0,1 16,8A4,4 0 0,1 12,12A4,4 0 0,1 8,8A4,4 0 0,1 12,4M12,14C16.42,14 20,15.79 20,18V20H4V18C4,15.79 7.58,14 12,14Z" />'
case 'flash': return '<path d="M7,2V13H10V22L17,10H13L17,2H7Z" />'
case 'menu': return '<path d="M3,6H21V8H3V6M3,11H21V13H3V11M3,16H21V18H3V16Z" />'
case 'exit': return ' <path d="M19,3H5C3.89,3 3,3.89 3,5V9H5V5H19V19H5V15H3V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V5C21,3.89 20.1,3 19,3M10.08,15.58L11.5,17L16.5,12L11.5,7L10.08,8.41L12.67,11H3V13H12.67L10.08,15.58Z" />'
default: return icon || 'missing icon prop' default: return icon || 'missing icon prop'
} }
} }

View File

@ -28,6 +28,7 @@
width: 100%; width: 100%;
height: 46px; height: 46px;
pointer-events: none; pointer-events: none;
overflow: hidden;
label { label {
position: absolute; position: absolute;
@ -100,5 +101,9 @@
& + .underlined-input-readonly.stack-h { & + .underlined-input-readonly.stack-h {
margin-left: 14px; margin-left: 14px;
margin-top: 0; margin-top: 0;
@include break('small') {
margin-left: 0;
}
} }
} }

View File

@ -34,7 +34,7 @@ class PublisherListItem extends React.Component {
<h3 className='flex'>{`${this.props.item.name}${this.props.item.whitelisted ? '' : ' (awaiting approval)'}`}</h3> <h3 className='flex'>{`${this.props.item.name}${this.props.item.whitelisted ? '' : ' (awaiting approval)'}`}</h3>
<ConfirmIconButton icon='delete' onClick={() => this.props.dispatch(removePublisher(this.props.item.id))} /> <ConfirmIconButton icon='delete' onClick={() => this.props.dispatch(removePublisher(this.props.item.id))} />
</header> </header>
<div className='flex flex-container'> <div className='cols flex flex-container'>
<div className='col flex flex-container flex-vertical'> <div className='col flex flex-container flex-vertical'>
<div className='stack flex-container flex-vertical'> <div className='stack flex-container flex-vertical'>
<span className='label'>AppID</span> <span className='label'>AppID</span>
@ -73,7 +73,7 @@ class PublisherListItem extends React.Component {
<h3 className='flex'>{this.props.item.name}</h3> <h3 className='flex'>{this.props.item.name}</h3>
<ConfirmIconButton icon='delete' onClick={() => this.props.dispatch(removePublisher(this.props.item.id))} /> <ConfirmIconButton icon='delete' onClick={() => this.props.dispatch(removePublisher(this.props.item.id))} />
</header> </header>
<div className='flex flex-container'> <div className='cols flex flex-container'>
<div className='col flex flex-container flex-vertical'> <div className='col flex flex-container flex-vertical'>
<p> <p>
Download <span className='name'>{this.props.item.verification_key}.html</span> and upload it to the root directory of your webserver. Then, click <strong>VERIFY</strong> to verify that you own and control <span className='name'>{this.props.item.url}</span>. Download <span className='name'>{this.props.item.verification_key}.html</span> and upload it to the root directory of your webserver. Then, click <strong>VERIFY</strong> to verify that you own and control <span className='name'>{this.props.item.url}</span>.

View File

@ -23,12 +23,12 @@ class UriListItem extends React.Component {
} }
getView () { getView () {
return ( return (
<li className='uri-list-item flex-container' onClick={(e) => this.cancelEvent(e, this.props.item.id)}> <li className='cols uri-list-item flex-container' onClick={(e) => this.cancelEvent(e, this.props.item.id)}>
<div className='stack flex flex-container flex-vertical'> <div className='col stack flex flex-container flex-vertical'>
<span className='label'>Destination URL</span> <span className='label'>Destination URL</span>
<span className='value'>{this.props.item.url}</span> <span className='value'>{this.props.item.url}</span>
</div> </div>
<div className='stack flex flex-container flex-vertical'> <div className='col stack flex flex-container flex-vertical'>
<span className='label'>Filters</span> <span className='label'>Filters</span>
<span className='value'>{['publisher', 'title', 'author', 'isbn'].reduce((a, x) => a + (this.props.item[x] ? 1 : 0), 0) || 'None'}</span> <span className='value'>{['publisher', 'title', 'author', 'isbn'].reduce((a, x) => a + (this.props.item[x] ? 1 : 0), 0) || 'None'}</span>
</div> </div>

View File

@ -5,10 +5,11 @@ import Progress from './components/Progress'
import UnderlineInput from './components/UnderlineInput' import UnderlineInput from './components/UnderlineInput'
import UriListItem from './containers/UriListItem' import UriListItem from './containers/UriListItem'
import PublisherListItem from './containers/PublisherListItem' import PublisherListItem from './containers/PublisherListItem'
import Icon from './components/Icon'
import IconButton from './components/IconButton' import IconButton from './components/IconButton'
import ConfirmIconButton from './containers/ConfirmIconButton' import ConfirmIconButton from './containers/ConfirmIconButton'
import reducer from './reducers' import reducer from './reducers'
import { fetchData, createNewUrl, setEditing, editUser, createNewPublisher, regenerateSigningSecret } from './actions' import { fetchData, createNewUrl, setEditing, editUser, createNewPublisher, regenerateSigningSecret, toggleMenu } from './actions'
import '../styles/index.scss' import '../styles/index.scss'
@ -32,7 +33,8 @@ class App extends React.Component {
newPublisher: { name: '', url: '' }, newPublisher: { name: '', url: '' },
editingUrl: null, editingUrl: null,
editingPublisher: null, editingPublisher: null,
working: false working: false,
navMenu: false
} }
this.dispatch = this.dispatch.bind(this) this.dispatch = this.dispatch.bind(this)
@ -111,21 +113,21 @@ class App extends React.Component {
render () { render () {
return ( return (
<Router> <Router>
<div className='root-container flex-container' onClick={() => this.dispatch(setEditing(null))}> <div className={'root-container flex-container two-panels' + (this.state.navMenu ? ' nav-active' : '')} onClick={() => this.dispatch(setEditing(null))}>
<aside className='nav nav-left'> <aside className='nav nav-left'>
<header> <header>
<h1>River of Ebooks</h1> <h1 className='flex-container'><IconButton icon='menu' className='menu-small' onClick={() => this.dispatch(toggleMenu())} /><span className='flex'>River of Ebooks</span></h1>
<h2 className='flex-container'> <h2 className='flex-container'>
<span className='flex'>{this.state.user.email}</span> <span className='flex'>{this.state.user.email}</span>
<a href='/logout'>Log out</a> <a href='/logout'>Log out</a>
</h2> </h2>
</header> </header>
<ul> <ul>
<li><NavLink to='/keys'>Publishing keys</NavLink></li> <li><NavLink to='/keys' className='flex-container'><Icon icon='key' /><span className='flex'>Publishing keys</span></NavLink></li>
<li><NavLink to='/targets'>Push URIs</NavLink></li> <li><NavLink to='/targets' className='flex-container'><Icon icon='transfer-right' /><span className='flex'>Push URIs</span></NavLink></li>
<li><NavLink to='/account'>My account</NavLink></li> <li><NavLink to='/account' className='flex-container'><Icon icon='account' /><span className='flex'>My account</span></NavLink></li>
{(this.state.user.id === 1 || this.state.user.admin) && {(this.state.user.id === 1 || this.state.user.admin) &&
<li><a href='/admin'>Admin</a></li> <li><a href='/admin' className='flex-container'><Icon icon='flash' /><span className='flex'>Admin</span></a></li>
} }
</ul> </ul>
</aside> </aside>
@ -155,7 +157,7 @@ class App extends React.Component {
<h2>If you own a publishing site, generate a publishing key for it here.</h2> <h2>If you own a publishing site, generate a publishing key for it here.</h2>
</div> </div>
</header> </header>
<div className='creator flex-container'> <div className='creator flex-container cols'>
<UnderlineInput <UnderlineInput
className='flex stack-h' className='flex stack-h'
placeholder='Website name' placeholder='Website name'

View File

@ -10,6 +10,10 @@ const reducer = (state = {}, action) => {
return { return {
working: data working: data
} }
case Actions.toggle_menu:
return {
navMenu: !state.navMenu
}
case Actions.set_user: case Actions.set_user:
return { return {
user: { user: {

View File

@ -33,10 +33,13 @@
.creator { .creator {
padding: 0 14px; padding: 0 14px;
line-height: 60px; line-height: 60px;
// max-width: 500px;
.btn { .btn {
margin: 12px 0 12px 12px; margin: 12px 0 12px 12px;
@include break('small') {
margin: 0 12px;
}
} }
} }
} }
@ -44,7 +47,6 @@
margin: 20px 14px; margin: 20px 14px;
padding: 0; padding: 0;
list-style: none; list-style: none;
// overflow: hidden;
} }
.inputs, .inputs,
.details { .details {
@ -105,6 +107,9 @@
padding: 0 20px; padding: 0 20px;
color: $accent-2; color: $accent-2;
@include break('small') {
padding: 0 5px;
}
&:hover { &:hover {
text-decoration: underline; text-decoration: underline;
} }
@ -115,6 +120,10 @@
background: $accent-2; background: $accent-2;
color: $text-light-1; color: $text-light-1;
border-radius: 3px; border-radius: 3px;
@include break('small') {
margin: 5px 5px 5px 0;
}
} }
} }
} }
@ -164,7 +173,7 @@
background: $black-5; background: $black-5;
padding: 10px; padding: 10px;
border-radius: 3px; border-radius: 3px;
overflow-x: scroll; overflow-x: auto;
&:before { &:before {
display: block; display: block;

View File

@ -1,57 +1,157 @@
.nav-left { .two-panels {
min-width: 300px; .nav-left {
height: 100%; width: 300px;
background: $accent-1; height: 100%;
color: $text-light-1; background: $accent-1;
box-shadow: $shadow-1; color: $text-light-1;
box-shadow: $shadow-1;
overflow-y: auto;
z-index: 100;
header { header {
line-height: 50px;
padding: 0 14px;
h2 {
margin: -10px 0 0 0;
padding: 0;
font-weight: normal;
font-size: 12pt;
height: 36px;
line-height: 36px;
color: $white-2;
}
a {
text-decoration: none;
color: $accent-3;
}
}
ul {
list-style: none;
margin: 0;
padding: 0;
li {
height: 50px;
line-height: 50px; line-height: 50px;
border-bottom: 1px solid $white-4; padding: 0 14px;
&:hover a { h1 {
background: $white-5; height: 50px;
}
&:last-of-type {
border-bottom: none
}
a { .menu-small {
display: inline-block; display: none;
height: 100%; margin: 5px 5px 5px 0;
width: 100%;
padding: 0 12px; path {
text-decoration: none !important; fill: $text-light-1;
}
@include break('medium') {
display: inline-block;
margin: 5px;
}
}
span:not(.icon) {
@include break('medium') {
display: none;
}
}
}
h2 {
margin: -10px 0 0 0;
padding: 0;
font-weight: normal;
font-size: 12pt;
height: 36px;
line-height: 36px;
color: $white-2; color: $white-2;
}
a {
text-decoration: none;
color: $accent-3;
}
}
ul {
list-style: none;
margin: 0;
padding: 0;
&.active { li {
background: $white-4; height: 50px;
line-height: 50px;
overflow: hidden;
border-bottom: 1px solid $white-4;
&:hover a {
background: $white-5;
}
&:last-of-type {
border-bottom: none
}
a {
display: inline-block;
height: 100%;
width: 100%;
padding: 0 12px;
text-decoration: none !important;
color: $white-2;
&.active {
background: $white-4;
}
.icon {
margin: 5px 5px 5px 0;
vertical-align: middle;
@include break('medium') {
margin: 5px;
}
}
span:not(.icon) {
vertical-align: middle;
display: inline-block;
line-height: 50px;
@include break('medium') {
display: none;
}
@include ellip();
}
@include break('medium') {
padding: 0;
}
} }
} }
} }
} }
.content {
z-index: 1;
.cols {
@include break('small') {
flex-direction: column;
}
}
}
@include break('medium') {
.nav-left {
position: absolute;
transition: width .3s $transition;
header {
min-height: 76px;
}
}
&:not(.nav-active) {
.nav-left {
width: 50px;
header {
padding: 0;
h2 {
display: none;
}
}
}
}
&.nav-active {
span:not(.icon) {
overflow: hidden;
display: inline-block !important;
}
.nav-left {
header {
padding: 0 14px 0 0;
h2 {
display: flex;
margin-left: 50px;
}
}
}
}
> .content {
margin-left: 50px;
}
}
} }

File diff suppressed because it is too large Load Diff