Accessibility stuff
This commit is contained in:
parent
136fb59ed5
commit
ef4dfd1176
@ -43,6 +43,9 @@ module.exports = {
|
||||
"error",
|
||||
],
|
||||
"react/jsx-uses-react": "error",
|
||||
"react/jsx-uses-vars": "error"
|
||||
"react/jsx-uses-vars": "error",
|
||||
|
||||
// Disable no-console rule in production
|
||||
"no-console": process.env.NODE_ENV !== "production" ? "off" : ["error"]
|
||||
}
|
||||
};
|
||||
|
14
TODO
14
TODO
@ -11,6 +11,14 @@
|
||||
* /artist/:id and /album/:id arts in responsive view
|
||||
* Scroll horizontal sidebar
|
||||
* Move CSS in modules
|
||||
=> https://github.com/gajus/react-css-modules
|
||||
|
||||
# API middleware
|
||||
* https://github.com/reactjs/redux/issues/1824#issuecomment-228609501
|
||||
* https://medium.com/@adamrackis/querying-a-redux-store-37db8c7f3b0f#.eezt3dano
|
||||
* https://github.com/reactjs/redux/issues/644
|
||||
* https://github.com/peterpme/redux-crud-api-middleware/blob/master/README.md
|
||||
* https://github.com/madou/armory-front/tree/master/src/app/reducers
|
||||
|
||||
|
||||
## Global UI
|
||||
@ -20,9 +28,5 @@
|
||||
|
||||
## Miscellaneous
|
||||
* See TODOs in the code
|
||||
* https://facebook.github.io/immutable-js/ ?
|
||||
* Web workers?
|
||||
* Accessibility and semantics
|
||||
|
||||
* Uncaught TypeError: this.props.tracks.forEach is not a function
|
||||
=> Be more robust, after, getHostNode is null
|
||||
=> Be more robust, else, getHostNode is null after
|
||||
|
@ -50,7 +50,9 @@ export class LoginForm extends Component {
|
||||
this.props.error ?
|
||||
<div className="row">
|
||||
<div className="alert alert-danger">
|
||||
<span className="glyphicon glyphicon-exclamation-sign"></span> { this.props.error }
|
||||
<p id="loginFormError">
|
||||
<span className="glyphicon glyphicon-exclamation-sign" aria-hidden="true"></span> { this.props.error }
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
: null
|
||||
@ -58,40 +60,40 @@ export class LoginForm extends Component {
|
||||
{
|
||||
this.props.info ?
|
||||
<div className="row">
|
||||
<div className="alert alert-info">
|
||||
{ this.props.info }
|
||||
<div className="alert alert-info" id="loginFormInfo">
|
||||
<p>{ this.props.info }</p>
|
||||
</div>
|
||||
</div>
|
||||
: null
|
||||
}
|
||||
<div className="row">
|
||||
<form className="col-sm-9 col-sm-offset-1 col-md-6 col-md-offset-3 text-left form-horizontal login" onSubmit={this.handleSubmit} ref="loginForm">
|
||||
<form className="col-sm-9 col-sm-offset-1 col-md-6 col-md-offset-3 text-left form-horizontal login" onSubmit={this.handleSubmit} ref="loginForm" aria-describedby="loginFormInfo loginFormError">
|
||||
<div className="row">
|
||||
<div className="form-group" ref="usernameFormGroup">
|
||||
<div className="col-xs-12">
|
||||
<input type="text" className="form-control" ref="username" placeholder="Username" autoFocus defaultValue={this.props.username} />
|
||||
<input type="text" className="form-control" ref="username" aria-label="Username" placeholder="Username" autoFocus defaultValue={this.props.username} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="form-group" ref="passwordFormGroup">
|
||||
<div className="col-xs-12">
|
||||
<input type="password" className="form-control" ref="password" placeholder="Password" />
|
||||
<input type="password" className="form-control" ref="password" aria-label="Password" placeholder="Password" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="form-group" ref="endpointFormGroup">
|
||||
<div className="col-xs-12">
|
||||
<input type="text" className="form-control" ref="endpoint" placeholder="http://ampache.example.com" defaultValue={this.props.endpoint} />
|
||||
<input type="text" className="form-control" ref="endpoint" aria-label="URL of your Ampache instance (e.g. http://ampache.example.com)" placeholder="http://ampache.example.com" defaultValue={this.props.endpoint} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<div className="col-xs-12">
|
||||
<div className="row">
|
||||
<div className="col-sm-6 col-xs-12 checkbox">
|
||||
<label>
|
||||
<input type="checkbox" ref="rememberMe" defaultChecked={this.props.rememberMe} /> Remember me
|
||||
<label id="rememberMeLabel">
|
||||
<input type="checkbox" ref="rememberMe" defaultChecked={this.props.rememberMe} aria-labelledby="rememberMeLabel" /> Remember me
|
||||
</label>
|
||||
</div>
|
||||
<div className="col-sm-6 col-sm-12 submit text-right">
|
||||
<input type="submit" className="btn btn-default" defaultValue="Sign in" disabled={this.props.isAuthenticating} />
|
||||
<input type="submit" className="btn btn-default" aria-label="Sign in" defaultValue="Sign in" disabled={this.props.isAuthenticating} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -15,9 +15,9 @@ export default class FilterBar extends Component {
|
||||
render () {
|
||||
return (
|
||||
<div className="filter">
|
||||
<p className="col-xs-12 col-sm-6 col-md-4 col-md-offset-1 filter-legend">What are we listening to today?</p>
|
||||
<p className="col-xs-12 col-sm-6 col-md-4 col-md-offset-1 filter-legend" id="filterInputDescription">What are we listening to today?</p>
|
||||
<div className="col-xs-12 col-sm-6 col-md-4 input-group">
|
||||
<form className="form-inline" onSubmit={this.handleChange}>
|
||||
<form className="form-inline" onSubmit={this.handleChange} aria-describedby="filterInputDescription">
|
||||
<div className="form-group">
|
||||
<input type="text" className="form-control filter-input" placeholder="Filter…" aria-label="Filter…" value={this.props.filterText} onChange={this.handleChange} ref="filterTextInput" />
|
||||
</div>
|
||||
|
@ -20,10 +20,11 @@ export class GridItem extends Component {
|
||||
}
|
||||
const to = "/" + this.props.itemsType.rstrip("s") + "/" + this.props.item.id;
|
||||
const id = "grid-item-" + this.props.item.type + "/" + this.props.item.id;
|
||||
const title = "Go to " + this.props.itemsType.rstrip("s") + " page";
|
||||
return (
|
||||
<div className="grid-item col-xs-6 col-sm-3 placeholders" id={id}>
|
||||
<div className="grid-item-content placeholder text-center">
|
||||
<Link to={to}><img src={this.props.item.art} width="200" height="200" className="img-responsive art" alt={this.props.item.name}/></Link>
|
||||
<Link title={title} to={to}><img src={this.props.item.art} width="200" height="200" className="img-responsive img-circle art" alt={this.props.item.name}/></Link>
|
||||
<h4 className="name">{this.props.item.name}</h4>
|
||||
<span className="sub-items text-muted"><span className="n-sub-items">{nSubItems}</span> <span className="sub-items-type">{subItemsLabel}</span></span>
|
||||
</div>
|
||||
|
@ -38,8 +38,26 @@ export class Pagination extends Component {
|
||||
|
||||
goToPage() {
|
||||
const pageNumber = parseInt(this.refs.pageInput.value);
|
||||
$("#paginationModal").modal("hide");
|
||||
this.props.router.push(this.buildLinkTo(pageNumber));
|
||||
$(this.refs.paginationModal).modal("hide");
|
||||
if (pageNumber) {
|
||||
this.props.router.push(this.buildLinkTo(pageNumber));
|
||||
}
|
||||
}
|
||||
|
||||
dotsOnClick() {
|
||||
$(this.refs.paginationModal).modal();
|
||||
}
|
||||
|
||||
dotsOnKeyDown(ev) {
|
||||
ev.preventDefault;
|
||||
const code = ev.keyCode || ev.which;
|
||||
if (code == 13 || code == 32) { // Enter or Space key
|
||||
this.dotsOnClick(); // Fire same event as onClick
|
||||
}
|
||||
}
|
||||
|
||||
cancelModalBox() {
|
||||
$(this.refs.paginationModal).modal("hide");
|
||||
}
|
||||
|
||||
render () {
|
||||
@ -50,7 +68,7 @@ export class Pagination extends Component {
|
||||
// Push first page
|
||||
pagesButton.push(
|
||||
<li className="page-item" key={key}>
|
||||
<Link className="page-link" to={this.buildLinkTo(1)}>1</Link>
|
||||
<Link className="page-link" title="Go to page 1" to={this.buildLinkTo(1)}><span className="sr-only">Go to page </span>1</Link>
|
||||
</li>
|
||||
);
|
||||
key++;
|
||||
@ -58,7 +76,7 @@ export class Pagination extends Component {
|
||||
// Eventually push "…"
|
||||
pagesButton.push(
|
||||
<li className="page-item" key={key}>
|
||||
<span onClick={() => $("#paginationModal").modal() }>…</span>
|
||||
<span tabIndex="0" role="button" onKeyDown={this.dotsOnKeyDown.bind(this)} onClick={this.dotsOnClick.bind(this)}>…</span>
|
||||
</li>
|
||||
);
|
||||
key++;
|
||||
@ -67,12 +85,15 @@ export class Pagination extends Component {
|
||||
var i = 0;
|
||||
for (i = lowerLimit; i < upperLimit; i++) {
|
||||
var className = "page-item";
|
||||
var currentSpan = null;
|
||||
if (this.props.currentPage == i) {
|
||||
className += " active";
|
||||
currentSpan = <span className="sr-only">(current)</span>;
|
||||
}
|
||||
const title = "Go to page " + i;
|
||||
pagesButton.push(
|
||||
<li className={className} key={key}>
|
||||
<Link className="page-link" to={this.buildLinkTo(i)}>{i}</Link>
|
||||
<Link className="page-link" title={title} to={this.buildLinkTo(i)}><span className="sr-only">Go to page </span>{i} {currentSpan}</Link>
|
||||
</li>
|
||||
);
|
||||
key++;
|
||||
@ -82,40 +103,41 @@ export class Pagination extends Component {
|
||||
// Eventually push "…"
|
||||
pagesButton.push(
|
||||
<li className="page-item" key={key}>
|
||||
<span onClick={() => $("#paginationModal").modal() }>…</span>
|
||||
<span tabIndex="0" role="button" onKeyDown={this.dotsOnKeyDown.bind(this)} onClick={this.dotsOnClick.bind(this)}>…</span>
|
||||
</li>
|
||||
);
|
||||
key++;
|
||||
}
|
||||
const title = "Go to page " + this.props.nPages;
|
||||
// Push last page
|
||||
pagesButton.push(
|
||||
<li className="page-item" key={key}>
|
||||
<Link className="page-link" to={this.buildLinkTo(this.props.nPages)}>{this.props.nPages}</Link>
|
||||
<Link className="page-link" title={title} to={this.buildLinkTo(this.props.nPages)}><span className="sr-only">Go to page </span>{this.props.nPages}</Link>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
if (pagesButton.length > 1) {
|
||||
return (
|
||||
<div>
|
||||
<nav className="pagination-nav">
|
||||
<nav className="pagination-nav" aria-label="Page navigation">
|
||||
<ul className="pagination">
|
||||
{ pagesButton }
|
||||
</ul>
|
||||
</nav>
|
||||
<div className="modal fade" id="paginationModal" tabIndex="-1" role="dialog" aria-hidden="false">
|
||||
<div className="modal-dialog">
|
||||
<div className="modal fade" ref="paginationModal" tabIndex="-1" role="dialog" aria-labelledby="paginationModalLabel">
|
||||
<div className="modal-dialog" role="document">
|
||||
<div className="modal-content">
|
||||
<div className="modal-header">
|
||||
<button type="button" className="close" data-dismiss="modal" aria-hidden="true">×</button>
|
||||
<h4 className="modal-title">Page to go to?</h4>
|
||||
<button type="button" className="close" data-dismiss="modal" aria-label="Close">×</button>
|
||||
<h4 className="modal-title" id="paginationModalLabel">Page to go to?</h4>
|
||||
</div>
|
||||
<div className="modal-body">
|
||||
<form>
|
||||
<input className="form-control" autoComplete="off" type="number" ref="pageInput" />
|
||||
<input className="form-control" autoComplete="off" type="number" ref="pageInput" aria-label="Page number to go to" />
|
||||
</form>
|
||||
</div>
|
||||
<div className="modal-footer">
|
||||
<button type="button" className="btn btn-default" onClick={ () => $("#paginationModal").modal("hide") }>Cancel</button>
|
||||
<button type="button" className="btn btn-default" onClick={this.cancelModalBox.bind(this)}>Cancel</button>
|
||||
<button type="button" className="btn btn-primary" onClick={this.goToPage.bind(this)}>OK</button>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -7,42 +7,42 @@ export default class SidebarLayout extends Component {
|
||||
<div>
|
||||
<div className="col-sm-3 col-md-2 sidebar hidden-xs">
|
||||
<h1 className="text-center"><IndexLink to="/"><img alt="A" src="./app/assets/img/ampache-blue.png"/>mpache</IndexLink></h1>
|
||||
<nav>
|
||||
<nav aria-label="Main navigation menu">
|
||||
<div className="navbar text-center icon-navbar">
|
||||
<div className="container-fluid">
|
||||
<ul className="nav navbar-nav icon-navbar-nav">
|
||||
<li aria-hidden="true">
|
||||
<Link to="/" className="glyphicon glyphicon-home"></Link>
|
||||
<li>
|
||||
<Link to="/" title="Home"><span className="glyphicon glyphicon-home" aria-hidden="true"></span> <span className="sr-only">Home</span></Link>
|
||||
</li>
|
||||
<li aria-hidden="true">
|
||||
<Link to="/settings" className="glyphicon glyphicon-wrench"></Link>
|
||||
<li>
|
||||
<Link to="/settings" title="Settings"><span className="glyphicon glyphicon-wrench" aria-hidden="true"></span> <span className="sr-only">Settings</span></Link>
|
||||
</li>
|
||||
<li aria-hidden="true">
|
||||
<Link to="/logout" className="glyphicon glyphicon-off"></Link>
|
||||
<li>
|
||||
<Link to="/logout" title="Logout"><span className="glyphicon glyphicon-off" aria-hidden="true"></span> <span className="sr-only">Logout</span></Link>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<ul className="nav nav-sidebar">
|
||||
<li>
|
||||
<Link to="/discover">
|
||||
<Link to="/discover" title="Discover">
|
||||
<span className="glyphicon glyphicon-globe" aria-hidden="true"></span>
|
||||
<span className="hidden-sm"> Discover</span>
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link to="/browse">
|
||||
<Link to="/browse" title="Browse">
|
||||
<span className="glyphicon glyphicon-headphones" aria-hidden="true"></span>
|
||||
<span className="hidden-sm"> Browse</span>
|
||||
</Link>
|
||||
<ul className="nav nav-sidebar text-center">
|
||||
<li><Link to="/artists"><span className="glyphicon glyphicon-user"></span> Artists</Link></li>
|
||||
<li><Link to="/albums"><span className="glyphicon glyphicon-cd" aria-hidden="true"></span> Albums</Link></li>
|
||||
<li><Link to="/songs"><span className="glyphicon glyphicon-music"></span> Songs</Link></li>
|
||||
<li><Link to="/artists" title="Browse artists"><span className="glyphicon glyphicon-user" aria-hidden="true"></span> Artists</Link></li>
|
||||
<li><Link to="/albums" title="Browse albums"><span className="glyphicon glyphicon-cd" aria-hidden="true"></span> Albums</Link></li>
|
||||
<li><Link to="/songs" title="Browse songs"><span className="glyphicon glyphicon-music" aria-hidden="true"></span> Songs</Link></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<Link to="/search">
|
||||
<Link to="/search" title="Search">
|
||||
<span className="glyphicon glyphicon-search" aria-hidden="true"></span>
|
||||
<span className="hidden-sm"> Search</span>
|
||||
</Link>
|
||||
|
1656
app/dist/index.js
vendored
1656
app/dist/index.js
vendored
File diff suppressed because one or more lines are too long
1
app/dist/style.css
vendored
1
app/dist/style.css
vendored
@ -6914,7 +6914,6 @@ div.filter {
|
||||
|
||||
.art {
|
||||
display: inline-block;
|
||||
border-radius: 50%;
|
||||
margin-bottom: 0.5em;
|
||||
width: 75%;
|
||||
height: auto;
|
||||
|
@ -1,4 +1,5 @@
|
||||
// TODO: Refactor using normalizr
|
||||
// TODO: https://facebook.github.io/immutable-js/ ?
|
||||
import "babel-polyfill";
|
||||
import fetch from "isomorphic-fetch";
|
||||
import humps from "humps";
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { createReducer } from "../utils";
|
||||
|
||||
export const DEFAULT_LIMIT = 30; /** Default max number of elements to retrieve. */
|
||||
export const DEFAULT_LIMIT = 1; /** Default max number of elements to retrieve. */
|
||||
|
||||
const initialState = {
|
||||
isFetching: false,
|
||||
|
@ -156,7 +156,6 @@ div.filter {
|
||||
|
||||
.art {
|
||||
display: inline-block;
|
||||
border-radius: 50%;
|
||||
margin-bottom: 0.5em;
|
||||
width: 75%;
|
||||
height: auto;
|
||||
|
21
index.all.js
Normal file
21
index.all.js
Normal file
@ -0,0 +1,21 @@
|
||||
// Export
|
||||
import "bootstrap";
|
||||
import "bootstrap/dist/css/bootstrap.css";
|
||||
import "./app/styles/ampache.css";
|
||||
|
||||
// Handle app init
|
||||
import React from "react";
|
||||
import { render } from "react-dom";
|
||||
import { hashHistory } from "react-router";
|
||||
import { syncHistoryWithStore } from "react-router-redux";
|
||||
|
||||
import Root from "./app/containers/Root";
|
||||
import configureStore from "./app/store/configureStore";
|
||||
|
||||
const store = configureStore();
|
||||
const history = syncHistoryWithStore(hashHistory, store);
|
||||
|
||||
render(
|
||||
<Root store={store} history={history} />,
|
||||
document.getElementById("root")
|
||||
);
|
7
index.development.js
Normal file
7
index.development.js
Normal file
@ -0,0 +1,7 @@
|
||||
import React from "react";
|
||||
import ReactDOM from "react-dom";
|
||||
|
||||
var a11y = require("react-a11y");
|
||||
a11y(React, { ReactDOM: ReactDOM, includeSrcNode: true });
|
||||
|
||||
require("./index.all.js");
|
26
index.js
26
index.js
@ -1,21 +1,5 @@
|
||||
// Export
|
||||
import "bootstrap";
|
||||
import "bootstrap/dist/css/bootstrap.css";
|
||||
import "./app/styles/ampache.css";
|
||||
|
||||
// Handle app init
|
||||
import React from "react";
|
||||
import { render } from "react-dom";
|
||||
import { hashHistory } from "react-router";
|
||||
import { syncHistoryWithStore } from "react-router-redux";
|
||||
|
||||
import Root from "./app/containers/Root";
|
||||
import configureStore from "./app/store/configureStore";
|
||||
|
||||
const store = configureStore();
|
||||
const history = syncHistoryWithStore(hashHistory, store);
|
||||
|
||||
render(
|
||||
<Root store={store} history={history} />,
|
||||
document.getElementById("root")
|
||||
);
|
||||
if (process.env.NODE_ENV === "production") {
|
||||
module.exports = require("./index.production.js");
|
||||
} else {
|
||||
module.exports = require("./index.development.js");
|
||||
}
|
||||
|
1
index.production.js
Normal file
1
index.production.js
Normal file
@ -0,0 +1 @@
|
||||
require("./index.all.js");
|
@ -45,6 +45,7 @@
|
||||
"postcss-loader": "^0.9.1",
|
||||
"postcss-reporter": "^1.4.1",
|
||||
"precss": "^1.4.0",
|
||||
"react-a11y": "^0.3.3",
|
||||
"redux-logger": "^2.6.1",
|
||||
"style-loader": "^0.13.1",
|
||||
"stylelint": "^7.0.3",
|
||||
|
Loading…
Reference in New Issue
Block a user