Fix grid + begin play buttons
This commit is contained in:
parent
2b3207ec44
commit
d5da4e4818
|
@ -143,7 +143,7 @@ export function loginUser(username, passwordOrToken, endpoint, rememberMe, redir
|
|||
loginUserRequest,
|
||||
jsonData => dispatch => {
|
||||
if (!jsonData.auth || !jsonData.sessionExpire) {
|
||||
return Promise.reject(new i18nRecord({ id: "app.api.error", values: {} }));
|
||||
return dispatch(loginUserFailure(new i18nRecord({ id: "app.api.error", values: {} })));
|
||||
}
|
||||
const token = {
|
||||
token: jsonData.auth,
|
||||
|
|
|
@ -1,15 +1,26 @@
|
|||
import React, { Component, PropTypes } from "react";
|
||||
import CSSModules from "react-css-modules";
|
||||
import { defineMessages, FormattedMessage } from "react-intl";
|
||||
|
||||
import { formatLength } from "../utils";
|
||||
import { formatLength, messagesMap } from "../utils";
|
||||
|
||||
import commonMessages from "../locales/messagesDescriptors/common";
|
||||
|
||||
import css from "../styles/Album.scss";
|
||||
|
||||
const albumMessages = defineMessages(messagesMap(commonMessages));
|
||||
|
||||
export class AlbumTrackRow extends Component {
|
||||
render () {
|
||||
const length = formatLength(this.props.track.length);
|
||||
return (
|
||||
<tr>
|
||||
<td>
|
||||
<span className="sr-only">
|
||||
<FormattedMessage {...albumMessages["app.common.play"]} />
|
||||
</span>
|
||||
<span className="glyphicon glyphicon-play-circle" aria-hidden="true"></span>
|
||||
</td>
|
||||
<td>{this.props.track.track}</td>
|
||||
<td>{this.props.track.name}</td>
|
||||
<td>{length}</td>
|
||||
|
|
|
@ -8,6 +8,7 @@ export default class Albums extends Component {
|
|||
const grid = {
|
||||
isFetching: this.props.isFetching,
|
||||
items: this.props.albums,
|
||||
itemsType: "album",
|
||||
itemsLabel: "app.common.album",
|
||||
subItemsType: "tracks",
|
||||
subItemsLabel: "app.common.track"
|
||||
|
|
|
@ -8,6 +8,7 @@ class Artists extends Component {
|
|||
const grid = {
|
||||
isFetching: this.props.isFetching,
|
||||
items: this.props.artists,
|
||||
itemsType: "artist",
|
||||
itemsLabel: "app.common.artist",
|
||||
subItemsType: "albums",
|
||||
subItemsLabel: "app.common.album"
|
||||
|
|
|
@ -175,8 +175,8 @@ Login.propTypes = {
|
|||
rememberMe: PropTypes.bool,
|
||||
onSubmit: PropTypes.func.isRequired,
|
||||
isAuthenticating: PropTypes.bool,
|
||||
error: PropTypes.string,
|
||||
info: PropTypes.oneOfType([PropTypes.string, PropTypes.object])
|
||||
info: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
|
||||
error: PropTypes.oneOfType([PropTypes.string, PropTypes.object])
|
||||
};
|
||||
|
||||
export default CSSModules(Login, css);
|
||||
|
|
|
@ -18,12 +18,17 @@ const songsMessages = defineMessages(messagesMap(Array.concat([], commonMessages
|
|||
|
||||
export class SongsTableRow extends Component {
|
||||
render () {
|
||||
const length = formatLength(this.props.song.length);
|
||||
const length = formatLength(this.props.song.time);
|
||||
const linkToArtist = "/artist/" + this.props.song.artist.id;
|
||||
const linkToAlbum = "/album/" + this.props.song.album.id;
|
||||
return (
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>
|
||||
<span className="sr-only">
|
||||
<FormattedMessage {...songsMessages["app.common.play"]} />
|
||||
</span>
|
||||
<span className="glyphicon glyphicon-play-circle" aria-hidden="true"></span>
|
||||
</td>
|
||||
<td className="title">{this.props.song.name}</td>
|
||||
<td className="artist"><Link to={linkToArtist}>{this.props.song.artist.name}</Link></td>
|
||||
<td className="album"><Link to={linkToAlbum}>{this.props.song.album.name}</Link></td>
|
||||
|
@ -45,7 +50,7 @@ class SongsTableCSS extends Component {
|
|||
if (this.props.filterText) {
|
||||
// Use Fuse for the filter
|
||||
displayedSongs = new Fuse(
|
||||
this.props.songs,
|
||||
this.props.songs.toArray(),
|
||||
{
|
||||
"keys": ["name"],
|
||||
"threshold": 0.4,
|
||||
|
|
|
@ -29,7 +29,7 @@ class FilterBarCSSIntl extends Component {
|
|||
<FormattedMessage {...filterMessages["app.filter.whatAreWeListeningToToday"]} />
|
||||
</p>
|
||||
<div className="col-xs-12 col-sm-6 col-md-4 input-group">
|
||||
<form className="form-inline" onSubmit={this.handleChange} aria-describedby="filterInputDescription" role="search">
|
||||
<form className="form-inline" onSubmit={this.handleChange} aria-describedby="filterInputDescription" role="search" aria-label={formatMessage(filterMessages["app.filter.filter"])}>
|
||||
<div className="form-group" styleName="form-group">
|
||||
<input type="text" className="form-control" placeholder={formatMessage(filterMessages["app.filter.filter"])} aria-label={formatMessage(filterMessages["app.filter.filter"])} value={this.props.filterText} onChange={this.handleChange} ref="filterTextInput" />
|
||||
</div>
|
||||
|
|
|
@ -30,10 +30,10 @@ class GridItemCSSIntl extends Component {
|
|||
|
||||
var subItemsLabel = formatMessage(gridMessages[this.props.subItemsLabel], { itemCount: nSubItems });
|
||||
|
||||
const to = "/" + this.props.item.type + "/" + this.props.item.id;
|
||||
const id = "grid-item-" + this.props.item.type + "/" + this.props.item.id;
|
||||
const to = "/" + this.props.itemsType + "/" + this.props.item.id;
|
||||
const id = "grid-item-" + this.props.itemsType + "/" + this.props.item.id;
|
||||
|
||||
const title = formatMessage(gridMessages["app.grid.goTo" + this.props.item.type.capitalize() + "Page"]);
|
||||
const title = formatMessage(gridMessages["app.grid.goTo" + this.props.itemsType.capitalize() + "Page"]);
|
||||
return (
|
||||
<div className="grid-item col-xs-6 col-sm-3" styleName="placeholders" id={id}>
|
||||
<div className="grid-item-content text-center">
|
||||
|
@ -48,6 +48,7 @@ class GridItemCSSIntl extends Component {
|
|||
|
||||
GridItemCSSIntl.propTypes = {
|
||||
item: PropTypes.object.isRequired,
|
||||
itemsType: PropTypes.string.isRequired,
|
||||
itemsLabel: PropTypes.string.isRequired,
|
||||
subItemsType: PropTypes.string.isRequired,
|
||||
subItemsLabel: PropTypes.string.isRequired,
|
||||
|
@ -96,7 +97,7 @@ export class Grid extends Component {
|
|||
}
|
||||
// Use Fuse for the filter
|
||||
var result = new Fuse(
|
||||
props.items,
|
||||
props.items.toArray(),
|
||||
{
|
||||
"keys": ["name"],
|
||||
"threshold": 0.4,
|
||||
|
@ -150,11 +151,12 @@ export class Grid extends Component {
|
|||
componentDidUpdate(prevProps) {
|
||||
// The list of keys seen in the previous render
|
||||
let currentKeys = prevProps.items.map(
|
||||
(n) => "grid-item-" + n.type + "/" + n.id);
|
||||
(n) => "grid-item-" + prevProps.itemsType + "/" + n.id);
|
||||
|
||||
// The latest list of keys that have been rendered
|
||||
const {itemsType} = this.props;
|
||||
let newKeys = this.props.items.map(
|
||||
(n) => "grid-item-" + n.type + "/" + n.id);
|
||||
(n) => "grid-item-" + itemsType + "/" + n.id);
|
||||
|
||||
// Find which keys are new between the current set of keys and any new children passed to this component
|
||||
let addKeys = immutableDiff(newKeys, currentKeys);
|
||||
|
@ -162,17 +164,17 @@ export class Grid extends Component {
|
|||
// Find which keys have been removed between the current set of keys and any new children passed to this component
|
||||
let removeKeys = immutableDiff(currentKeys, newKeys);
|
||||
|
||||
var iso = this.iso;
|
||||
if (removeKeys.count() > 0) {
|
||||
removeKeys.forEach(removeKey => this.iso.remove(document.getElementById(removeKey)));
|
||||
this.iso.arrange();
|
||||
removeKeys.forEach(removeKey => iso.remove(document.getElementById(removeKey)));
|
||||
iso.arrange();
|
||||
}
|
||||
if (addKeys.count() > 0) {
|
||||
const itemsToAdd = addKeys.map((addKey) => document.getElementById(addKey)).toArray();
|
||||
this.iso.addItems(itemsToAdd);
|
||||
this.iso.arrange();
|
||||
iso.addItems(itemsToAdd);
|
||||
iso.arrange();
|
||||
}
|
||||
|
||||
var iso = this.iso;
|
||||
// Layout again after images are loaded
|
||||
imagesLoaded(this.refs.grid).on("progress", function() {
|
||||
// Layout after each image load, fix for responsive grid
|
||||
|
@ -185,11 +187,9 @@ export class Grid extends Component {
|
|||
|
||||
render () {
|
||||
var gridItems = [];
|
||||
const itemsLabel = this.props.itemsLabel;
|
||||
const subItemsType = this.props.subItemsType;
|
||||
const subItemsLabel = this.props.subItemsLabel;
|
||||
const { itemsType, itemsLabel, subItemsType, subItemsLabel } = this.props;
|
||||
this.props.items.forEach(function (item) {
|
||||
gridItems.push(<GridItem item={item} itemsLabel={itemsLabel} subItemsType={subItemsType} subItemsLabel={subItemsLabel} key={item.id} />);
|
||||
gridItems.push(<GridItem item={item} itemsType={itemsType} itemsLabel={itemsLabel} subItemsType={subItemsType} subItemsLabel={subItemsLabel} key={item.id} />);
|
||||
});
|
||||
var loading = null;
|
||||
if (gridItems.length == 0 && this.props.isFetching) {
|
||||
|
@ -220,8 +220,10 @@ export class Grid extends Component {
|
|||
Grid.propTypes = {
|
||||
isFetching: PropTypes.bool.isRequired,
|
||||
items: PropTypes.instanceOf(Immutable.List).isRequired,
|
||||
itemsType: PropTypes.string.isRequired,
|
||||
itemsLabel: PropTypes.string.isRequired,
|
||||
subItemsType: PropTypes.string.isRequired,
|
||||
subItemsLabel: PropTypes.string.isRequired,
|
||||
filterText: PropTypes.string
|
||||
};
|
||||
|
||||
|
|
|
@ -68,7 +68,7 @@ class PaginationCSSIntl extends Component {
|
|||
// Push first page
|
||||
pagesButton.push(
|
||||
<li className="page-item" key={key}>
|
||||
<Link role="button" className="page-link" title={formatMessage(paginationMessages["app.pagination.goToPageWithoutMarkup"], { pageNumber: 1})} to={this.props.buildLinkToPage(1)}>
|
||||
<Link className="page-link" title={formatMessage(paginationMessages["app.pagination.goToPageWithoutMarkup"], { pageNumber: 1})} to={this.props.buildLinkToPage(1)}>
|
||||
<FormattedHTMLMessage {...paginationMessages["app.pagination.goToPage"]} values={{ pageNumber: 1 }} />
|
||||
</Link>
|
||||
</li>
|
||||
|
@ -95,7 +95,7 @@ class PaginationCSSIntl extends Component {
|
|||
const title = formatMessage(paginationMessages["app.pagination.goToPageWithoutMarkup"], { pageNumber: i });
|
||||
pagesButton.push(
|
||||
<li className={className} key={key}>
|
||||
<Link role="button" className="page-link" title={title} to={this.props.buildLinkToPage(i)}>
|
||||
<Link className="page-link" title={title} to={this.props.buildLinkToPage(i)}>
|
||||
<FormattedHTMLMessage {...paginationMessages["app.pagination.goToPage"]} values={{ pageNumber: i }} />
|
||||
{currentSpan}
|
||||
</Link>
|
||||
|
@ -117,7 +117,7 @@ class PaginationCSSIntl extends Component {
|
|||
// Push last page
|
||||
pagesButton.push(
|
||||
<li className="page-item" key={key}>
|
||||
<Link role="button" className="page-link" title={title} to={this.props.buildLinkToPage(this.props.nPages)}>
|
||||
<Link className="page-link" title={title} to={this.props.buildLinkToPage(this.props.nPages)}>
|
||||
<FormattedHTMLMessage {...paginationMessages["app.pagination.goToPage"]} values={{ pageNumber: this.props.nPages }} />
|
||||
</Link>
|
||||
</li>
|
||||
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -8,6 +8,7 @@ module.exports = {
|
|||
"app.common.close": "Close", // Close
|
||||
"app.common.go": "Go", // Go
|
||||
"app.common.loading": "Loading…", // Loading indicator
|
||||
"app.common.play": "Play", // PLay icon description
|
||||
"app.common.track": "{itemCount, plural, one {track} other {tracks}}", // Track
|
||||
"app.filter.filter": "Filter…", // Filtering input placeholder
|
||||
"app.filter.whatAreWeListeningToToday": "What are we listening to today?", // Description for the filter bar
|
||||
|
|
|
@ -8,6 +8,7 @@ module.exports = {
|
|||
"app.common.close": "Fermer", // Close
|
||||
"app.common.go": "Aller", // Go
|
||||
"app.common.loading": "Chargement…", // Loading indicator
|
||||
"app.common.play": "Jouer", // PLay icon description
|
||||
"app.common.track": "{itemCount, plural, one {piste} other {pistes}}", // Track
|
||||
"app.filter.filter": "Filtrer…", // Filtering input placeholder
|
||||
"app.filter.whatAreWeListeningToToday": "Que voulez-vous écouter aujourd'hui\u00a0?", // Description for the filter bar
|
||||
|
|
|
@ -34,6 +34,11 @@ const messages = [
|
|||
description: "Loading indicator",
|
||||
defaultMessage: "Loading…"
|
||||
},
|
||||
{
|
||||
id: "app.common.play",
|
||||
description: "PLay icon description",
|
||||
defaultMessage: "Play"
|
||||
},
|
||||
];
|
||||
|
||||
export default messages;
|
||||
|
|
|
@ -46,7 +46,7 @@ function _checkAPIErrors (jsonData) {
|
|||
return jsonData;
|
||||
}
|
||||
|
||||
function _uglyFixes (endpoint, token) {
|
||||
function _uglyFixes () {
|
||||
if (typeof _uglyFixes.artistsCount === "undefined" ) {
|
||||
_uglyFixes.artistsCount = 0;
|
||||
}
|
||||
|
@ -58,51 +58,70 @@ function _uglyFixes (endpoint, token) {
|
|||
}
|
||||
|
||||
var _uglyFixesSongs = function (songs) {
|
||||
for (var i = 0; i < songs.length; i++) {
|
||||
// Add song type
|
||||
songs[i].type = "track";
|
||||
// Fix for name becoming title in songs objects
|
||||
songs[i].name = songs[i].title;
|
||||
// Fix for length being time in songs objects
|
||||
songs[i].length = songs[i].time;
|
||||
|
||||
return songs.map(function (song) {
|
||||
// Fix for cdata left in artist and album
|
||||
songs[i].artist.name = songs[i].artist.cdata;
|
||||
songs[i].album.name = songs[i].album.cdata;
|
||||
}
|
||||
return songs;
|
||||
song.artist.name = song.artist.cdata;
|
||||
song.album.name = song.album.cdata;
|
||||
return song;
|
||||
});
|
||||
};
|
||||
|
||||
var _uglyFixesAlbums = function (albums) {
|
||||
for (var i = 0; i < albums.length; i++) {
|
||||
// Add album type
|
||||
albums[i].type = "album";
|
||||
|
||||
return albums.map(function (album) {
|
||||
// TODO
|
||||
// Fix for absence of distinction between disks in the same album
|
||||
if (albums[i].disk > 1) {
|
||||
albums[i].name = albums[i].name + " [Disk " + albums[i].disk + "]";
|
||||
if (album.disk > 1) {
|
||||
album.name = album.name + " [Disk " + album.disk + "]";
|
||||
}
|
||||
|
||||
// Move songs one node top
|
||||
if (albums[i].tracks.song) {
|
||||
albums[i].tracks = albums[i].tracks.song;
|
||||
if (album.tracks.song) {
|
||||
album.tracks = album.tracks.song;
|
||||
|
||||
// Ensure tracks is an array
|
||||
if (!Array.isArray(albums[i].tracks)) {
|
||||
albums[i].tracks = [albums[i].tracks];
|
||||
if (!Array.isArray(album.tracks)) {
|
||||
album.tracks = [album.tracks];
|
||||
}
|
||||
|
||||
// Fix tracks
|
||||
albums[i].tracks = _uglyFixesSongs(albums[i].tracks);
|
||||
album.tracks = _uglyFixesSongs(album.tracks);
|
||||
}
|
||||
}
|
||||
return albums;
|
||||
return album;
|
||||
});
|
||||
};
|
||||
|
||||
var _uglyFixesArtists = function (artists) {
|
||||
return artists.map(function (artist) {
|
||||
// Move albums one node top
|
||||
if (artist.albums.album) {
|
||||
artist.albums = artist.albums.album;
|
||||
|
||||
// Ensure albums are an array
|
||||
if (!Array.isArray(artist.albums)) {
|
||||
artist.albums = [artist.albums];
|
||||
}
|
||||
|
||||
// Fix albums
|
||||
artist.albums = _uglyFixesAlbums(artist.albums);
|
||||
}
|
||||
|
||||
// Move songs one node top
|
||||
if (artist.songs.song) {
|
||||
artist.songs = artist.songs.song;
|
||||
|
||||
// Ensure songs are an array
|
||||
if (!Array.isArray(artist.songs)) {
|
||||
artist.songs = [artist.songs];
|
||||
}
|
||||
|
||||
// Fix songs
|
||||
artist.songs = _uglyFixesSongs(artist.songs);
|
||||
}
|
||||
return artist;
|
||||
});
|
||||
};
|
||||
|
||||
return jsonData => {
|
||||
// Camelize
|
||||
jsonData = humps.camelizeKeys(jsonData);
|
||||
|
||||
// Ensure items are always wrapped in an array
|
||||
if (jsonData.artist && !Array.isArray(jsonData.artist)) {
|
||||
jsonData.artist = [jsonData.artist];
|
||||
|
@ -114,6 +133,7 @@ function _uglyFixes (endpoint, token) {
|
|||
jsonData.song = [jsonData.song];
|
||||
}
|
||||
|
||||
// TODO
|
||||
// Keep track of artists count
|
||||
if (jsonData.artists) {
|
||||
_uglyFixes.artistsCount = parseInt(jsonData.artists);
|
||||
|
@ -127,49 +147,22 @@ function _uglyFixes (endpoint, token) {
|
|||
_uglyFixes.songsCount = parseInt(jsonData.songs);
|
||||
}
|
||||
|
||||
// Fix artists
|
||||
if (jsonData.artist) {
|
||||
for (var i = 0; i < jsonData.artist.length; i++) {
|
||||
// Add artist type
|
||||
jsonData.artist[i].type = "artist";
|
||||
|
||||
// Fix for artists art not included
|
||||
jsonData.artist[i].art = endpoint.replace("/server/xml.server.php", "") + "/image.php?object_id=" + jsonData.artist[i].id + "&object_type=artist&auth=" + token;
|
||||
|
||||
// Move albums one node top
|
||||
if (jsonData.artist[i].albums.album) {
|
||||
jsonData.artist[i].albums = jsonData.artist[i].albums.album;
|
||||
|
||||
// Ensure albums are an array
|
||||
if (!Array.isArray(jsonData.artist[i].albums)) {
|
||||
jsonData.artist[i].albums = [jsonData.artist[i].albums];
|
||||
}
|
||||
|
||||
// Fix albums
|
||||
jsonData.artist[i].albums = _uglyFixesAlbums(jsonData.artist[i].albums);
|
||||
}
|
||||
|
||||
// Move songs one node top
|
||||
if (jsonData.artist[i].songs.song) {
|
||||
jsonData.artist[i].songs = jsonData.artist[i].songs.song;
|
||||
|
||||
// Ensure songs are an array
|
||||
if (!Array.isArray(jsonData.artist[i].songs)) {
|
||||
jsonData.artist[i].songs = [jsonData.artist[i].songs];
|
||||
}
|
||||
|
||||
// Fix songs
|
||||
jsonData.artist[i].songs = _uglyFixesSongs(jsonData.artist[i].songs);
|
||||
}
|
||||
}
|
||||
jsonData.artist = _uglyFixesArtists(jsonData.artist);
|
||||
// Store the total number of items
|
||||
jsonData.artists = _uglyFixes.artistsCount;
|
||||
}
|
||||
|
||||
// Fix albums
|
||||
if (jsonData.album) {
|
||||
// Fix albums
|
||||
jsonData.album = _uglyFixesAlbums(jsonData.album);
|
||||
// Store the total number of items
|
||||
jsonData.albums = _uglyFixes.albumsCount;
|
||||
}
|
||||
|
||||
// Fix songs
|
||||
if (jsonData.song) {
|
||||
// Fix songs
|
||||
jsonData.song = _uglyFixesSongs(jsonData.song);
|
||||
|
@ -177,6 +170,8 @@ function _uglyFixes (endpoint, token) {
|
|||
jsonData.songs = _uglyFixes.songsCount;
|
||||
}
|
||||
|
||||
// TODO
|
||||
// Add sessionExpire information
|
||||
if (!jsonData.sessionExpire) {
|
||||
// Fix for Ampache not returning updated sessionExpire
|
||||
jsonData.sessionExpire = (new Date(Date.now() + 3600 * 1000)).toJSON();
|
||||
|
@ -186,6 +181,11 @@ function _uglyFixes (endpoint, token) {
|
|||
};
|
||||
}
|
||||
|
||||
function _normalizeResponse(jsonData) {
|
||||
// TODO
|
||||
return jsonData;
|
||||
}
|
||||
|
||||
// Fetches an API response and normalizes the result JSON according to schema.
|
||||
// This makes every API response have the same shape, regardless of how nested it was.
|
||||
function doAPICall (endpoint, action, auth, username, extraParams) {
|
||||
|
@ -206,7 +206,9 @@ function doAPICall (endpoint, action, auth, username, extraParams) {
|
|||
.then (response => response.text())
|
||||
.then(_parseToJSON)
|
||||
.then(_checkAPIErrors)
|
||||
.then(_uglyFixes(endpoint, auth));
|
||||
.then(jsonData => humps.camelizeKeys(jsonData)) // Camelize
|
||||
.then(_uglyFixes())
|
||||
.then(_normalizeResponse);
|
||||
}
|
||||
|
||||
// Action key that carries API call info interpreted by this Redux middleware.
|
||||
|
|
Loading…
Reference in New Issue