Finish API refactor, use normalizr + immutable
This commit is contained in:
parent
288039e732
commit
b73b4ba200
6
TODO
6
TODO
@ -1,9 +1,7 @@
|
||||
4. Refactor API
|
||||
* PropTypes.object
|
||||
|
||||
5. Web player
|
||||
6. Homepage
|
||||
7. Settings
|
||||
8. Search
|
||||
9. Discover
|
||||
|
||||
# API middleware
|
||||
* Immutable.js : entities in API
|
||||
|
@ -1,17 +1,42 @@
|
||||
import { normalize, arrayOf } from "normalizr";
|
||||
import humps from "humps";
|
||||
|
||||
import { CALL_API } from "../middleware/api";
|
||||
|
||||
import { artist, track, album } from "../models/api";
|
||||
|
||||
export const DEFAULT_LIMIT = 30; /** Default max number of elements to retrieve. */
|
||||
|
||||
export default function (action, requestType, successType, failureType) {
|
||||
const itemName = action.rstrip("s");
|
||||
const fetchItemsSuccess = function (itemsList, itemsCount, pageNumber) {
|
||||
const nPages = Math.ceil(itemsCount / DEFAULT_LIMIT);
|
||||
|
||||
const fetchItemsSuccess = function (jsonData, pageNumber) {
|
||||
// Normalize data
|
||||
jsonData = normalize(
|
||||
jsonData,
|
||||
{
|
||||
artist: arrayOf(artist),
|
||||
album: arrayOf(album),
|
||||
song: arrayOf(track)
|
||||
},
|
||||
{
|
||||
assignEntity: function (output, key, value) {
|
||||
// Delete useless fields
|
||||
if (key == "sessionExpire") {
|
||||
delete output.sessionExpire;
|
||||
} else {
|
||||
output[key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
const nPages = Math.ceil(jsonData.result[itemName].length / DEFAULT_LIMIT);
|
||||
return {
|
||||
type: successType,
|
||||
payload: {
|
||||
items: itemsList,
|
||||
result: jsonData.result,
|
||||
entities: jsonData.entities,
|
||||
nPages: nPages,
|
||||
currentPage: pageNumber
|
||||
}
|
||||
@ -34,7 +59,7 @@ export default function (action, requestType, successType, failureType) {
|
||||
};
|
||||
const fetchItems = function (endpoint, username, passphrase, filter, pageNumber, include = [], limit=DEFAULT_LIMIT) {
|
||||
const offset = (pageNumber - 1) * DEFAULT_LIMIT;
|
||||
var extraParams = {
|
||||
let extraParams = {
|
||||
offset: offset,
|
||||
limit: limit
|
||||
};
|
||||
@ -51,7 +76,7 @@ export default function (action, requestType, successType, failureType) {
|
||||
dispatch: [
|
||||
fetchItemsRequest,
|
||||
jsonData => dispatch => {
|
||||
dispatch(fetchItemsSuccess(jsonData[itemName], jsonData.totalCount, pageNumber));
|
||||
dispatch(fetchItemsSuccess(jsonData, pageNumber));
|
||||
},
|
||||
fetchItemsFailure
|
||||
],
|
||||
|
@ -27,7 +27,7 @@ function _buildHMAC (password) {
|
||||
// Handle Ampache HMAC generation
|
||||
const time = Math.floor(Date.now() / 1000);
|
||||
|
||||
var shaObj = new jsSHA("SHA-256", "TEXT");
|
||||
let shaObj = new jsSHA("SHA-256", "TEXT");
|
||||
shaObj.update(password);
|
||||
const key = shaObj.getHash("HEX");
|
||||
|
||||
@ -120,8 +120,8 @@ export function logoutAndRedirect() {
|
||||
|
||||
export function loginUser(username, passwordOrToken, endpoint, rememberMe, redirect="/", isToken=false) {
|
||||
endpoint = _cleanEndpoint(endpoint);
|
||||
var time = 0;
|
||||
var passphrase = passwordOrToken;
|
||||
let time = 0;
|
||||
let passphrase = passwordOrToken;
|
||||
|
||||
if (!isToken) {
|
||||
// Standard password connection
|
||||
|
@ -2,20 +2,12 @@ export * from "./auth";
|
||||
|
||||
import APIAction from "./APIActions";
|
||||
|
||||
export const ARTISTS_SUCCESS = "ARTISTS_SUCCESS";
|
||||
export const ARTISTS_REQUEST = "ARTISTS_REQUEST";
|
||||
export const ARTISTS_FAILURE = "ARTISTS_FAILURE";
|
||||
export var { loadArtists } = APIAction("artists", ARTISTS_REQUEST, ARTISTS_SUCCESS, ARTISTS_FAILURE);
|
||||
|
||||
export const ALBUMS_SUCCESS = "ALBUMS_SUCCESS";
|
||||
export const ALBUMS_REQUEST = "ALBUMS_REQUEST";
|
||||
export const ALBUMS_FAILURE = "ALBUMS_FAILURE";
|
||||
export var { loadAlbums } = APIAction("albums", ALBUMS_REQUEST, ALBUMS_SUCCESS, ALBUMS_FAILURE);
|
||||
|
||||
export const SONGS_SUCCESS = "SONGS_SUCCESS";
|
||||
export const SONGS_REQUEST = "SONGS_REQUEST";
|
||||
export const SONGS_FAILURE = "SONGS_FAILURE";
|
||||
export var { loadSongs } = APIAction("songs", SONGS_REQUEST, SONGS_SUCCESS, SONGS_FAILURE);
|
||||
export const API_SUCCESS = "API_SUCCESS";
|
||||
export const API_REQUEST = "API_REQUEST";
|
||||
export const API_FAILURE = "API_FAILURE";
|
||||
export var { loadArtists } = APIAction("artists", API_REQUEST, API_SUCCESS, API_FAILURE);
|
||||
export var { loadAlbums } = APIAction("albums", API_REQUEST, API_SUCCESS, API_FAILURE);
|
||||
export var { loadSongs } = APIAction("songs", API_REQUEST, API_SUCCESS, API_FAILURE);
|
||||
|
||||
export * from "./paginate";
|
||||
export * from "./store";
|
||||
|
@ -13,7 +13,7 @@ const albumMessages = defineMessages(messagesMap(commonMessages));
|
||||
|
||||
class AlbumTrackRowCSS extends Component {
|
||||
render () {
|
||||
const length = formatLength(this.props.track.time);
|
||||
const length = formatLength(this.props.track.get("time"));
|
||||
return (
|
||||
<tr>
|
||||
<td>
|
||||
@ -24,14 +24,15 @@ class AlbumTrackRowCSS extends Component {
|
||||
<FontAwesome name="play-circle-o" aria-hidden="true" />
|
||||
</button>
|
||||
</td>
|
||||
<td>{this.props.track.track}</td>
|
||||
<td>{this.props.track.name}</td>
|
||||
<td>{this.props.track.get("track")}</td>
|
||||
<td>{this.props.track.get("name")}</td>
|
||||
<td>{length}</td>
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Not object
|
||||
AlbumTrackRowCSS.propTypes = {
|
||||
track: PropTypes.object.isRequired
|
||||
};
|
||||
@ -41,9 +42,9 @@ export let AlbumTrackRow = CSSModules(AlbumTrackRowCSS, css);
|
||||
|
||||
class AlbumTracksTableCSS extends Component {
|
||||
render () {
|
||||
var rows = [];
|
||||
let rows = [];
|
||||
this.props.tracks.forEach(function (item) {
|
||||
rows.push(<AlbumTrackRow track={item} key={item.id} />);
|
||||
rows.push(<AlbumTrackRow track={item} key={item.get("id")} />);
|
||||
});
|
||||
return (
|
||||
<table className="table table-hover" styleName="songs">
|
||||
@ -55,8 +56,9 @@ class AlbumTracksTableCSS extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Not object
|
||||
AlbumTracksTableCSS.propTypes = {
|
||||
tracks: PropTypes.array.isRequired
|
||||
tracks: PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
export let AlbumTracksTable = CSSModules(AlbumTracksTableCSS, css);
|
||||
@ -66,15 +68,15 @@ class AlbumRowCSS extends Component {
|
||||
return (
|
||||
<div className="row" styleName="row">
|
||||
<div className="col-sm-offset-2 col-xs-9 col-sm-10" styleName="nameRow">
|
||||
<h2>{this.props.album.name}</h2>
|
||||
<h2>{this.props.album.get("name")}</h2>
|
||||
</div>
|
||||
<div className="col-xs-3 col-sm-2" styleName="artRow">
|
||||
<p className="text-center"><img src={this.props.album.art} width="200" height="200" className="img-responsive img-circle" styleName="art" alt={this.props.album.name} /></p>
|
||||
<p className="text-center"><img src={this.props.album.get("art")} width="200" height="200" className="img-responsive img-circle" styleName="art" alt={this.props.album.get("name")} /></p>
|
||||
</div>
|
||||
<div className="col-xs-9 col-sm-10 table-responsive">
|
||||
{
|
||||
Array.isArray(this.props.album.tracks) ?
|
||||
<AlbumTracksTable tracks={this.props.album.tracks} /> :
|
||||
this.props.songs.size > 0 ?
|
||||
<AlbumTracksTable tracks={this.props.songs} /> :
|
||||
null
|
||||
}
|
||||
</div>
|
||||
@ -83,8 +85,10 @@ class AlbumRowCSS extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Not object
|
||||
AlbumRowCSS.propTypes = {
|
||||
album: PropTypes.object.isRequired
|
||||
album: PropTypes.object.isRequired,
|
||||
songs: PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
export let AlbumRow = CSSModules(AlbumRowCSS, css);
|
||||
@ -92,11 +96,13 @@ export let AlbumRow = CSSModules(AlbumRowCSS, css);
|
||||
export default class Album extends Component {
|
||||
render () {
|
||||
return (
|
||||
<AlbumRow album={this.props.album} />
|
||||
<AlbumRow album={this.props.album} songs={this.props.songs} />
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Not object
|
||||
Album.propTypes = {
|
||||
album: PropTypes.object.isRequired
|
||||
album: PropTypes.object.isRequired,
|
||||
songs: PropTypes.object.isRequired
|
||||
};
|
||||
|
@ -7,26 +7,32 @@ import css from "../styles/Artist.scss";
|
||||
|
||||
class ArtistCSS extends Component {
|
||||
render () {
|
||||
var albumsRows = [];
|
||||
if (Array.isArray(this.props.artist.albums)) {
|
||||
this.props.artist.albums.forEach(function (item) {
|
||||
albumsRows.push(<AlbumRow album={item} key={item.id} />);
|
||||
let albumsRows = [];
|
||||
if (this.props.artist.get("albums").size > 0) {
|
||||
const artistAlbums = this.props.albums;
|
||||
const artistSongs = this.props.songs;
|
||||
this.props.artist.get("albums").forEach(function (album) {
|
||||
album = artistAlbums.get(album);
|
||||
const songs = album.get("tracks").map(
|
||||
id => artistSongs.get(id)
|
||||
);
|
||||
albumsRows.push(<AlbumRow album={album} songs={songs} key={album.get("id")} />);
|
||||
});
|
||||
}
|
||||
return (
|
||||
<div>
|
||||
<div className="row" styleName="name">
|
||||
<div className="col-sm-12">
|
||||
<h1>{this.props.artist.name}</h1>
|
||||
<h1>{this.props.artist.get("name")}</h1>
|
||||
<hr/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="row">
|
||||
<div className="col-sm-9">
|
||||
<p>{this.props.artist.summary}</p>
|
||||
<p>{this.props.artist.get("summary")}</p>
|
||||
</div>
|
||||
<div className="col-sm-3 text-center">
|
||||
<p><img src={this.props.artist.art} width="200" height="200" className="img-responsive img-circle" styleName="art" alt={this.props.artist.name}/></p>
|
||||
<p><img src={this.props.artist.get("art")} width="200" height="200" className="img-responsive img-circle" styleName="art" alt={this.props.artist.get("name")}/></p>
|
||||
</div>
|
||||
</div>
|
||||
{ albumsRows }
|
||||
@ -35,8 +41,11 @@ class ArtistCSS extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Not object
|
||||
ArtistCSS.propTypes = {
|
||||
artist: PropTypes.object.isRequired
|
||||
artist: PropTypes.object.isRequired,
|
||||
albums: PropTypes.object.isRequired,
|
||||
songs: PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
export default CSSModules(ArtistCSS, css);
|
||||
|
@ -41,7 +41,7 @@ class LoginFormCSSIntl extends Component {
|
||||
const endpoint = this.refs.endpoint.value.trim();
|
||||
const rememberMe = this.refs.rememberMe.checked;
|
||||
|
||||
var hasError = this.setError(this.refs.usernameFormGroup, !username);
|
||||
let hasError = this.setError(this.refs.usernameFormGroup, !username);
|
||||
hasError |= this.setError(this.refs.passwordFormGroup, !password);
|
||||
hasError |= this.setError(this.refs.endpointFormGroup, !endpoint);
|
||||
|
||||
@ -61,13 +61,13 @@ class LoginFormCSSIntl extends Component {
|
||||
|
||||
render () {
|
||||
const {formatMessage} = this.props.intl;
|
||||
var infoMessage = this.props.info;
|
||||
let infoMessage = this.props.info;
|
||||
if (this.props.info && this.props.info instanceof i18nRecord) {
|
||||
infoMessage = (
|
||||
<FormattedMessage {...loginMessages[this.props.info.id]} values={ this.props.info.values} />
|
||||
);
|
||||
}
|
||||
var errorMessage = this.props.error;
|
||||
let errorMessage = this.props.error;
|
||||
if (this.props.error && this.props.error instanceof i18nRecord) {
|
||||
errorMessage = (
|
||||
<FormattedMessage {...loginMessages[this.props.error.id]} values={ this.props.error.values} />
|
||||
|
@ -19,9 +19,9 @@ const songsMessages = defineMessages(messagesMap(Array.concat([], commonMessages
|
||||
|
||||
class SongsTableRowCSS extends Component {
|
||||
render () {
|
||||
const length = formatLength(this.props.song.time);
|
||||
const linkToArtist = "/artist/" + this.props.song.artist.id;
|
||||
const linkToAlbum = "/album/" + this.props.song.album.id;
|
||||
const length = formatLength(this.props.song.get("time"));
|
||||
const linkToArtist = "/artist/" + this.props.song.getIn(["artist", "id"]);
|
||||
const linkToAlbum = "/album/" + this.props.song.getIn(["album", "id"]);
|
||||
return (
|
||||
<tr>
|
||||
<td>
|
||||
@ -32,10 +32,10 @@ class SongsTableRowCSS extends Component {
|
||||
<FontAwesome name="play-circle-o" aria-hidden="true" />
|
||||
</button>
|
||||
</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>
|
||||
<td className="genre">{this.props.song.genre}</td>
|
||||
<td className="title">{this.props.song.get("name")}</td>
|
||||
<td className="artist"><Link to={linkToArtist}>{this.props.song.getIn(["artist", "name"])}</Link></td>
|
||||
<td className="album"><Link to={linkToAlbum}>{this.props.song.getIn(["album", "name"])}</Link></td>
|
||||
<td className="genre">{this.props.song.get("genre")}</td>
|
||||
<td className="length">{length}</td>
|
||||
</tr>
|
||||
);
|
||||
@ -51,7 +51,7 @@ export let SongsTableRow = CSSModules(SongsTableRowCSS, css);
|
||||
|
||||
class SongsTableCSS extends Component {
|
||||
render () {
|
||||
var displayedSongs = this.props.songs;
|
||||
let displayedSongs = this.props.songs;
|
||||
if (this.props.filterText) {
|
||||
// Use Fuse for the filter
|
||||
displayedSongs = new Fuse(
|
||||
@ -65,11 +65,11 @@ class SongsTableCSS extends Component {
|
||||
displayedSongs = displayedSongs.map(function (item) { return item.item; });
|
||||
}
|
||||
|
||||
var rows = [];
|
||||
let rows = [];
|
||||
displayedSongs.forEach(function (song) {
|
||||
rows.push(<SongsTableRow song={song} key={song.id} />);
|
||||
rows.push(<SongsTableRow song={song} key={song.get("id")} />);
|
||||
});
|
||||
var loading = null;
|
||||
let loading = null;
|
||||
if (rows.length == 0 && this.props.isFetching) {
|
||||
// If we are fetching and there is nothing to show
|
||||
loading = (
|
||||
|
@ -24,22 +24,22 @@ class GridItemCSSIntl extends Component {
|
||||
render () {
|
||||
const {formatMessage} = this.props.intl;
|
||||
|
||||
var nSubItems = this.props.item[this.props.subItemsType];
|
||||
let nSubItems = this.props.item.get(this.props.subItemsType);
|
||||
if (Array.isArray(nSubItems)) {
|
||||
nSubItems = nSubItems.length;
|
||||
}
|
||||
|
||||
var subItemsLabel = formatMessage(gridMessages[this.props.subItemsLabel], { itemCount: nSubItems });
|
||||
let subItemsLabel = formatMessage(gridMessages[this.props.subItemsLabel], { itemCount: nSubItems });
|
||||
|
||||
const to = "/" + this.props.itemsType + "/" + this.props.item.id;
|
||||
const id = "grid-item-" + this.props.itemsType + "/" + this.props.item.id;
|
||||
const to = "/" + this.props.itemsType + "/" + this.props.item.get("id");
|
||||
const id = "grid-item-" + this.props.itemsType + "/" + this.props.item.get("id");
|
||||
|
||||
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">
|
||||
<Link title={title} to={to}><img src={this.props.item.art} width="200" height="200" className="img-responsive img-circle art" styleName="art" alt={this.props.item.name}/></Link>
|
||||
<h4 className="name" styleName="name">{this.props.item.name}</h4>
|
||||
<Link title={title} to={to}><img src={this.props.item.get("art")} width="200" height="200" className="img-responsive img-circle art" styleName="art" alt={this.props.item.get("name")}/></Link>
|
||||
<h4 className="name" styleName="name">{this.props.item.get("name")}</h4>
|
||||
<span className="sub-items text-muted"><span className="n-sub-items">{nSubItems}</span> <span className="sub-items-type">{subItemsLabel}</span></span>
|
||||
</div>
|
||||
</div>
|
||||
@ -97,7 +97,7 @@ export class Grid extends Component {
|
||||
return this.iso.arrange(ISOTOPE_OPTIONS);
|
||||
}
|
||||
// Use Fuse for the filter
|
||||
var result = new Fuse(
|
||||
let result = new Fuse(
|
||||
props.items.toArray(),
|
||||
{
|
||||
"keys": ["name"],
|
||||
@ -108,13 +108,13 @@ export class Grid extends Component {
|
||||
// Apply filter on grid
|
||||
this.iso.arrange({
|
||||
filter: function (item) {
|
||||
var name = $(item).find(".name").text();
|
||||
let name = $(item).find(".name").text();
|
||||
return result.find(function (i) { return i.item.name == name; });
|
||||
},
|
||||
transitionDuration: "0.4s",
|
||||
getSortData: {
|
||||
relevance: function (item) {
|
||||
var name = $(item).find(".name").text();
|
||||
let name = $(item).find(".name").text();
|
||||
return result.reduce(function (p, c) {
|
||||
if (c.item.name == name) {
|
||||
return c.score + p;
|
||||
@ -152,12 +152,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-" + prevProps.itemsType + "/" + n.id);
|
||||
(n) => "grid-item-" + prevProps.itemsType + "/" + n.get("id"));
|
||||
|
||||
// The latest list of keys that have been rendered
|
||||
const {itemsType} = this.props;
|
||||
let newKeys = this.props.items.map(
|
||||
(n) => "grid-item-" + itemsType + "/" + n.id);
|
||||
(n) => "grid-item-" + itemsType + "/" + n.get("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);
|
||||
@ -165,7 +165,7 @@ 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;
|
||||
let iso = this.iso;
|
||||
if (removeKeys.count() > 0) {
|
||||
removeKeys.forEach(removeKey => iso.remove(document.getElementById(removeKey)));
|
||||
iso.arrange();
|
||||
@ -187,12 +187,12 @@ export class Grid extends Component {
|
||||
}
|
||||
|
||||
render () {
|
||||
var gridItems = [];
|
||||
let gridItems = [];
|
||||
const { itemsType, itemsLabel, subItemsType, subItemsLabel } = this.props;
|
||||
this.props.items.forEach(function (item) {
|
||||
gridItems.push(<GridItem item={item} itemsType={itemsType} itemsLabel={itemsLabel} subItemsType={subItemsType} subItemsLabel={subItemsLabel} key={item.id} />);
|
||||
gridItems.push(<GridItem item={item} itemsType={itemsType} itemsLabel={itemsLabel} subItemsType={subItemsType} subItemsLabel={subItemsLabel} key={item.get("id")} />);
|
||||
});
|
||||
var loading = null;
|
||||
let loading = null;
|
||||
if (gridItems.length == 0 && this.props.isFetching) {
|
||||
loading = (
|
||||
<div className="row text-center">
|
||||
|
@ -14,8 +14,8 @@ const paginationMessages = defineMessages(messagesMap(Array.concat([], commonMes
|
||||
class PaginationCSSIntl extends Component {
|
||||
computePaginationBounds(currentPage, nPages, maxNumberPagesShown=5) {
|
||||
// Taken from http://stackoverflow.com/a/8608998/2626416
|
||||
var lowerLimit = currentPage;
|
||||
var upperLimit = currentPage;
|
||||
let lowerLimit = currentPage;
|
||||
let upperLimit = currentPage;
|
||||
|
||||
for (let b = 1; b < maxNumberPagesShown && b < nPages;) {
|
||||
if (lowerLimit > 1 ) {
|
||||
@ -62,8 +62,8 @@ class PaginationCSSIntl extends Component {
|
||||
render () {
|
||||
const { formatMessage } = this.props.intl;
|
||||
const { lowerLimit, upperLimit } = this.computePaginationBounds(this.props.currentPage, this.props.nPages);
|
||||
var pagesButton = [];
|
||||
var key = 0; // key increment to ensure correct ordering
|
||||
let pagesButton = [];
|
||||
let key = 0; // key increment to ensure correct ordering
|
||||
if (lowerLimit > 1) {
|
||||
// Push first page
|
||||
pagesButton.push(
|
||||
@ -85,8 +85,8 @@ class PaginationCSSIntl extends Component {
|
||||
}
|
||||
}
|
||||
for (let i = lowerLimit; i < upperLimit; i++) {
|
||||
var className = "page-item";
|
||||
var currentSpan = null;
|
||||
let className = "page-item";
|
||||
let currentSpan = null;
|
||||
if (this.props.currentPage == i) {
|
||||
className += " active";
|
||||
currentSpan = <span className="sr-only">(<FormattedMessage {...paginationMessages["app.pagination.current"]} />)</span>;
|
||||
|
@ -1,54 +1,86 @@
|
||||
import React, { Component, PropTypes } from "react";
|
||||
import CSSModules from "react-css-modules";
|
||||
import { defineMessages, injectIntl, intlShape, FormattedMessage } from "react-intl";
|
||||
import FontAwesome from "react-fontawesome";
|
||||
|
||||
import { messagesMap } from "../../utils";
|
||||
|
||||
import css from "../../styles/elements/WebPlayer.scss";
|
||||
|
||||
class WebPlayerCSS extends Component {
|
||||
componentDidMount () {
|
||||
// TODO: Should be in the container mounting WebPlayer
|
||||
$(".sidebar").css("bottom", "15vh");
|
||||
$(".main-panel").css("margin-bottom", "15vh");
|
||||
import commonMessages from "../../locales/messagesDescriptors/common";
|
||||
import messages from "../../locales/messagesDescriptors/elements/WebPlayer";
|
||||
|
||||
const webplayerMessages = defineMessages(messagesMap(Array.concat([], commonMessages, messages)));
|
||||
|
||||
class WebPlayerCSSIntl extends Component {
|
||||
constructor (props) {
|
||||
super(props);
|
||||
|
||||
this.artOpacityHandler = this.artOpacityHandler.bind(this);
|
||||
}
|
||||
|
||||
artOpacityHandler (ev) {
|
||||
if (ev.type == "mouseover") {
|
||||
this.refs.art.style.opacity = "1";
|
||||
} else {
|
||||
this.refs.art.style.opacity = "0.75";
|
||||
}
|
||||
}
|
||||
|
||||
render () {
|
||||
const { formatMessage } = this.props.intl;
|
||||
|
||||
const playPause = this.props.isPlaying ? "pause" : "play";
|
||||
const randomBtnStyles = ["randomBtn"];
|
||||
const repeatBtnStyles = ["repeatBtn"];
|
||||
if (this.props.isRandom) {
|
||||
randomBtnStyles.push("active");
|
||||
}
|
||||
if (this.props.isRepeat) {
|
||||
repeatBtnStyles.push("active");
|
||||
}
|
||||
|
||||
return (
|
||||
<div id="row">
|
||||
<div id="webplayer" className="col-xs-12" styleName="body">
|
||||
{ /* Top Info */ }
|
||||
<div id="title" styleName="title">
|
||||
<span id="track">Foobar</span>
|
||||
<div id="timer" styleName="timer">0:00</div>
|
||||
<div id="duration" styleName="duration">0:00</div>
|
||||
<div id="row" styleName="webplayer">
|
||||
<div className="col-xs-12">
|
||||
<div className="row" styleName="artRow" onMouseOver={this.artOpacityHandler} onMouseOut={this.artOpacityHandler}>
|
||||
<div className="col-xs-12">
|
||||
<img src={this.props.song.art} width="200" height="200" ref="art" styleName="art" />
|
||||
<h2>{this.props.song.title}</h2>
|
||||
<h3>
|
||||
<span className="text-capitalize">
|
||||
<FormattedMessage {...webplayerMessages["app.webplayer.by"]} />
|
||||
</span> {this.props.song.artist}
|
||||
</h3>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{ /* Controls */ }
|
||||
<div styleName="controlsOuter">
|
||||
<div styleName="controlsInner">
|
||||
<div id="loading" styleName="loading"></div>
|
||||
<div id="playBtn" styleName="playBtn"></div>
|
||||
<div id="pauseBtn" styleName="pauseBtn"></div>
|
||||
<div id="prevBtn" styleName="prevBtn"></div>
|
||||
<div id="nextBtn" styleName="nextBtn"></div>
|
||||
<div className="row text-center" styleName="controls">
|
||||
<div className="col-xs-12">
|
||||
<button styleName="prevBtn" aria-label={formatMessage(webplayerMessages["app.webplayer.previous"])} title={formatMessage(webplayerMessages["app.webplayer.previous"])}>
|
||||
<FontAwesome name="step-backward" />
|
||||
</button>
|
||||
<button className="play" styleName="playPauseBtn" aria-label={formatMessage(webplayerMessages["app.common." + playPause])} title={formatMessage(webplayerMessages["app.common." + playPause])}>
|
||||
<FontAwesome name={playPause} />
|
||||
</button>
|
||||
<button styleName="nextBtn" aria-label={formatMessage(webplayerMessages["app.webplayer.next"])} title={formatMessage(webplayerMessages["app.webplayer.next"])}>
|
||||
<FontAwesome name="step-forward" />
|
||||
</button>
|
||||
</div>
|
||||
<div id="playlistBtn" styleName="playlistBtn"></div>
|
||||
<div id="volumeBtn" styleName="volumeBtn"></div>
|
||||
<div className="col-xs-12">
|
||||
<button styleName="volumeBtn" aria-label={formatMessage(webplayerMessages["app.webplayer.volume"])} title={formatMessage(webplayerMessages["app.webplayer.volume"])}>
|
||||
<FontAwesome name="volume-up" />
|
||||
</button>
|
||||
<button styleName={repeatBtnStyles.join(" ")} aria-label={formatMessage(webplayerMessages["app.webplayer.repeat"])} title={formatMessage(webplayerMessages["app.webplayer.repeat"])} aria-pressed={this.props.isRepeat}>
|
||||
<FontAwesome name="repeat" />
|
||||
</button>
|
||||
<button styleName={randomBtnStyles.join(" ")} aria-label={formatMessage(webplayerMessages["app.webplayer.random"])} title={formatMessage(webplayerMessages["app.webplayer.random"])} aria-pressed={this.props.isRandom}>
|
||||
<FontAwesome name="random" />
|
||||
</button>
|
||||
<button styleName="playlistBtn" aria-label={formatMessage(webplayerMessages["app.webplayer.playlist"])} title={formatMessage(webplayerMessages["app.webplayer.playlist"])}>
|
||||
<FontAwesome name="list" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{ /* Progress */ }
|
||||
<div id="waveform" styleName="waveform"></div>
|
||||
<div id="bar" styleName="progressBar"></div>
|
||||
<div id="progress" styleName="progress"></div>
|
||||
|
||||
{ /* Playlist */ }
|
||||
<div id="playlist" styleName="playlist">
|
||||
<div id="list" styleName="list"></div>
|
||||
</div>
|
||||
|
||||
{ /* Volume */ }
|
||||
<div id="volume" styleName="volume-fadeout">
|
||||
<div id="barFull" styleName="barFull"></div>
|
||||
<div id="barEmpty" styleName="barEmpty"></div>
|
||||
<div id="sliderBtn" styleName="sliderBtn"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -56,4 +88,12 @@ class WebPlayerCSS extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
export default CSSModules(WebPlayerCSS, css);
|
||||
WebPlayerCSSIntl.propTypes = {
|
||||
song: PropTypes.object.isRequired,
|
||||
isPlaying: PropTypes.bool.isRequired,
|
||||
isRandom: PropTypes.bool.isRequired,
|
||||
isRepeat: PropTypes.bool.isRequired,
|
||||
intl: intlShape.isRequired
|
||||
};
|
||||
|
||||
export default injectIntl(CSSModules(WebPlayerCSSIntl, css, { allowMultiple: true }));
|
||||
|
@ -7,7 +7,7 @@ import { messagesMap } from "../../utils";
|
||||
import commonMessages from "../../locales/messagesDescriptors/common";
|
||||
import messages from "../../locales/messagesDescriptors/layouts/Sidebar";
|
||||
|
||||
import WebPlayer from "../elements/WebPlayer";
|
||||
import WebPlayer from "../../containers/WebPlayer";
|
||||
|
||||
import css from "../../styles/layouts/Sidebar.scss";
|
||||
|
||||
@ -138,13 +138,13 @@ class SidebarLayoutIntl extends Component {
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
<WebPlayer />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="col-xs-12 col-md-11 col-md-offset-1 col-lg-10 col-lg-offset-2 main-panel" styleName="main-panel" onClick={collapseHamburger} role="main">
|
||||
{this.props.children}
|
||||
</div>
|
||||
{ /* TODO <WebPlayer /> */ }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
21
app/containers/WebPlayer.jsx
Normal file
21
app/containers/WebPlayer.jsx
Normal file
@ -0,0 +1,21 @@
|
||||
import React, { Component } from "react";
|
||||
import WebPlayerComponent from "../components/elements/WebPlayer";
|
||||
|
||||
|
||||
export default class WebPlayer extends Component {
|
||||
render () {
|
||||
const webplayerProps = {
|
||||
song: {
|
||||
art: "http://albumartcollection.com/wp-content/uploads/2011/07/summer-album-art.jpg",
|
||||
title: "Tel-ho",
|
||||
artist: "Lapso Laps",
|
||||
},
|
||||
isPlaying: false,
|
||||
isRandom: false,
|
||||
isRepeat: true
|
||||
};
|
||||
return (
|
||||
<WebPlayerComponent {...webplayerProps} />
|
||||
);
|
||||
}
|
||||
}
|
@ -8,7 +8,8 @@ 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.pause": "Pause", // Pause icon description
|
||||
"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
|
||||
@ -43,4 +44,11 @@ module.exports = {
|
||||
"app.songs.genre": "Genre", // Genre (song)
|
||||
"app.songs.length": "Length", // Length (song)
|
||||
"app.songs.title": "Title", // Title (song)
|
||||
"app.webplayer.by": "by", // Artist affiliation of a song
|
||||
"app.webplayer.next": "Next", // Next button description
|
||||
"app.webplayer.playlist": "Playlist", // Playlist button description
|
||||
"app.webplayer.previous": "Previous", // Previous button description
|
||||
"app.webplayer.random": "Random", // Random button description
|
||||
"app.webplayer.repeat": "Repeat", // Repeat button description
|
||||
"app.webplayer.volume": "Volume", // Volume button description
|
||||
};
|
||||
|
@ -8,6 +8,7 @@ module.exports = {
|
||||
"app.common.close": "Fermer", // Close
|
||||
"app.common.go": "Aller", // Go
|
||||
"app.common.loading": "Chargement…", // Loading indicator
|
||||
"app.common.pause": "Pause", // Pause icon description
|
||||
"app.common.play": "Jouer", // PLay icon description
|
||||
"app.common.track": "{itemCount, plural, one {piste} other {pistes}}", // Track
|
||||
"app.filter.filter": "Filtrer…", // Filtering input placeholder
|
||||
@ -43,4 +44,11 @@ module.exports = {
|
||||
"app.songs.genre": "Genre", // Genre (song)
|
||||
"app.songs.length": "Durée", // Length (song)
|
||||
"app.songs.title": "Titre", // Title (song)
|
||||
"app.webplayer.by": "par", // Artist affiliation of a song
|
||||
"app.webplayer.next": "Suivant", // Next button description
|
||||
"app.webplayer.playlist": "Liste de lecture", // Playlist button description
|
||||
"app.webplayer.previous": "Précédent", // Previous button description
|
||||
"app.webplayer.random": "Aléatoire", // Random button description
|
||||
"app.webplayer.repeat": "Répéter", // Repeat button description
|
||||
"app.webplayer.volume": "Volume", // Volume button description
|
||||
};
|
||||
|
@ -36,9 +36,14 @@ const messages = [
|
||||
},
|
||||
{
|
||||
id: "app.common.play",
|
||||
description: "PLay icon description",
|
||||
description: "Play icon description",
|
||||
defaultMessage: "Play"
|
||||
},
|
||||
{
|
||||
id: "app.common.pause",
|
||||
description: "Pause icon description",
|
||||
defaultMessage: "Pause"
|
||||
},
|
||||
];
|
||||
|
||||
export default messages;
|
||||
|
39
app/locales/messagesDescriptors/elements/WebPlayer.js
Normal file
39
app/locales/messagesDescriptors/elements/WebPlayer.js
Normal file
@ -0,0 +1,39 @@
|
||||
const messages = [
|
||||
{
|
||||
id: "app.webplayer.by",
|
||||
defaultMessage: "by",
|
||||
description: "Artist affiliation of a song"
|
||||
},
|
||||
{
|
||||
id: "app.webplayer.previous",
|
||||
defaultMessage: "Previous",
|
||||
description: "Previous button description"
|
||||
},
|
||||
{
|
||||
id: "app.webplayer.next",
|
||||
defaultMessage: "Next",
|
||||
description: "Next button description"
|
||||
},
|
||||
{
|
||||
id: "app.webplayer.volume",
|
||||
defaultMessage: "Volume",
|
||||
description: "Volume button description"
|
||||
},
|
||||
{
|
||||
id: "app.webplayer.repeat",
|
||||
defaultMessage: "Repeat",
|
||||
description: "Repeat button description"
|
||||
},
|
||||
{
|
||||
id: "app.webplayer.random",
|
||||
defaultMessage: "Random",
|
||||
description: "Random button description"
|
||||
},
|
||||
{
|
||||
id: "app.webplayer.playlist",
|
||||
defaultMessage: "Playlist",
|
||||
description: "Playlist button description"
|
||||
}
|
||||
];
|
||||
|
||||
export default messages;
|
@ -20,7 +20,7 @@ function _checkHTTPStatus (response) {
|
||||
}
|
||||
|
||||
function _parseToJSON (responseText) {
|
||||
var x2js = new X2JS({
|
||||
let x2js = new X2JS({
|
||||
attributePrefix: "",
|
||||
keepCData: false
|
||||
});
|
||||
@ -47,7 +47,7 @@ function _checkAPIErrors (jsonData) {
|
||||
}
|
||||
|
||||
function _uglyFixes (jsonData) {
|
||||
var _uglyFixesSongs = function (songs) {
|
||||
let _uglyFixesSongs = function (songs) {
|
||||
return songs.map(function (song) {
|
||||
// Fix for cdata left in artist and album
|
||||
song.artist.name = song.artist.cdata;
|
||||
@ -56,7 +56,7 @@ function _uglyFixes (jsonData) {
|
||||
});
|
||||
};
|
||||
|
||||
var _uglyFixesAlbums = function (albums) {
|
||||
let _uglyFixesAlbums = function (albums) {
|
||||
return albums.map(function (album) {
|
||||
// TODO
|
||||
// Fix for absence of distinction between disks in the same album
|
||||
@ -80,7 +80,7 @@ function _uglyFixes (jsonData) {
|
||||
});
|
||||
};
|
||||
|
||||
var _uglyFixesArtists = function (artists) {
|
||||
let _uglyFixesArtists = function (artists) {
|
||||
return artists.map(function (artist) {
|
||||
// Move albums one node top
|
||||
if (artist.albums.album) {
|
||||
@ -149,11 +149,6 @@ function _uglyFixes (jsonData) {
|
||||
return jsonData;
|
||||
}
|
||||
|
||||
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) {
|
||||
@ -175,8 +170,7 @@ function doAPICall (endpoint, action, auth, username, extraParams) {
|
||||
.then(_parseToJSON)
|
||||
.then(_checkAPIErrors)
|
||||
.then(jsonData => humps.camelizeKeys(jsonData)) // Camelize
|
||||
.then(_uglyFixes)
|
||||
.then(_normalizeResponse);
|
||||
.then(_uglyFixes);
|
||||
}
|
||||
|
||||
// Action key that carries API call info interpreted by this Redux middleware.
|
||||
|
22
app/models/api.js
Normal file
22
app/models/api.js
Normal file
@ -0,0 +1,22 @@
|
||||
import { Schema, arrayOf } from "normalizr";
|
||||
|
||||
export const artist = new Schema("artist");
|
||||
export const album = new Schema("album");
|
||||
export const track = new Schema("track");
|
||||
export const tag = new Schema("tag");
|
||||
|
||||
artist.define({
|
||||
albums: arrayOf(album),
|
||||
songs: arrayOf(track)
|
||||
});
|
||||
|
||||
album.define({
|
||||
artist: artist,
|
||||
tracks: arrayOf(track),
|
||||
tag: arrayOf(tag)
|
||||
});
|
||||
|
||||
track.define({
|
||||
artist: artist,
|
||||
album: album
|
||||
});
|
@ -2,7 +2,8 @@ import Immutable from "immutable";
|
||||
|
||||
export const stateRecord = new Immutable.Record({
|
||||
isFetching: false,
|
||||
items: new Immutable.List(),
|
||||
result: new Immutable.Map(),
|
||||
entities: new Immutable.Map(),
|
||||
error: null,
|
||||
currentPage: 1,
|
||||
nPages: 1
|
||||
|
@ -7,28 +7,16 @@ import paginate from "./paginate";
|
||||
import * as ActionTypes from "../actions";
|
||||
|
||||
// Updates the pagination data for different actions.
|
||||
const pagination = combineReducers({
|
||||
artists: paginate([
|
||||
ActionTypes.ARTISTS_REQUEST,
|
||||
ActionTypes.ARTISTS_SUCCESS,
|
||||
ActionTypes.ARTISTS_FAILURE
|
||||
]),
|
||||
albums: paginate([
|
||||
ActionTypes.ALBUMS_REQUEST,
|
||||
ActionTypes.ALBUMS_SUCCESS,
|
||||
ActionTypes.ALBUMS_FAILURE
|
||||
]),
|
||||
songs: paginate([
|
||||
ActionTypes.SONGS_REQUEST,
|
||||
ActionTypes.SONGS_SUCCESS,
|
||||
ActionTypes.SONGS_FAILURE
|
||||
])
|
||||
});
|
||||
const api = paginate([
|
||||
ActionTypes.API_REQUEST,
|
||||
ActionTypes.API_SUCCESS,
|
||||
ActionTypes.API_FAILURE
|
||||
]);
|
||||
|
||||
const rootReducer = combineReducers({
|
||||
routing,
|
||||
auth,
|
||||
pagination
|
||||
api
|
||||
});
|
||||
|
||||
export default rootReducer;
|
||||
|
@ -30,7 +30,8 @@ export default function paginate(types) {
|
||||
return (
|
||||
state
|
||||
.set("isFetching", false)
|
||||
.set("items", new Immutable.List(payload.items))
|
||||
.set("result", Immutable.fromJS(payload.result))
|
||||
.set("entities", Immutable.fromJS(payload.entities))
|
||||
.set("error", null)
|
||||
.set("nPages", payload.nPages)
|
||||
.set("currentPage", payload.currentPage)
|
||||
|
@ -1,340 +1,51 @@
|
||||
.body {
|
||||
height: 15vh;
|
||||
background: #bb71f3;
|
||||
background: linear-gradient(135deg, #bb71f3 0%, #3d4d91 100%);
|
||||
font-family: "Helvetica Neue", "Futura", "Trebuchet MS", Arial;
|
||||
user-select: none;
|
||||
-webkit-tap-highlight-color: rgba(255, 255, 255, 0);
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
$controlsMarginTop: 10px;
|
||||
|
||||
.webplayer {
|
||||
margin-top: 1em;
|
||||
}
|
||||
|
||||
/* Top Info */
|
||||
.title {
|
||||
.art {
|
||||
opacity: 0.75;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
top: 3%;
|
||||
line-height: 34px;
|
||||
height: 34px;
|
||||
text-align: center;
|
||||
font-size: 34px;
|
||||
opacity: 0.9;
|
||||
font-weight: 300;
|
||||
color: #fff;
|
||||
text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.33);
|
||||
z-index: -10;
|
||||
}
|
||||
|
||||
.timer {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 3%;
|
||||
text-align: left;
|
||||
font-size: 26px;
|
||||
opacity: 0.9;
|
||||
font-weight: 300;
|
||||
color: #fff;
|
||||
text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.33);
|
||||
.artRow {
|
||||
min-height: 200px;
|
||||
}
|
||||
|
||||
.duration {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 3%;
|
||||
text-align: right;
|
||||
font-size: 26px;
|
||||
opacity: 0.5;
|
||||
font-weight: 300;
|
||||
color: #fff;
|
||||
text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.33);
|
||||
}
|
||||
/**
|
||||
* Controls
|
||||
*/
|
||||
|
||||
/* Controls */
|
||||
.controlsOuter {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 70px;
|
||||
bottom: 3%;
|
||||
}
|
||||
|
||||
.controlsInner {
|
||||
position: absolute;
|
||||
width: 340px;
|
||||
height: 70px;
|
||||
left: 50%;
|
||||
margin: 0 -170px;
|
||||
.controls {
|
||||
margin-top: $controlsMarginTop;
|
||||
}
|
||||
|
||||
.btn {
|
||||
position: absolute;
|
||||
cursor: pointer;
|
||||
opacity: 0.9;
|
||||
-webkit-filter: drop-shadow(1px 1px 2px rgba(0, 0, 0, 0.33));
|
||||
filter: drop-shadow(1px 1px 2px rgba(0, 0, 0, 0.33));
|
||||
-webkit-user-select: none;
|
||||
user-select: none;
|
||||
background: transparent;
|
||||
border: none;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.playBtn {
|
||||
composes: btn;
|
||||
background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEUAAABGCAYAAACaGVmHAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyhpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNi1jMTExIDc5LjE1ODMyNSwgMjAxNS8wOS8xMC0wMToxMDoyMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIDIwMTUgKE1hY2ludG9zaCkiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6QThERThENUFDNkJEMTFFNTk2RTBDMTRBMkYyNjVGMzQiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6QThERThENUJDNkJEMTFFNTk2RTBDMTRBMkYyNjVGMzQiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDpBOERFOEQ1OEM2QkQxMUU1OTZFMEMxNEEyRjI2NUYzNCIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDpBOERFOEQ1OUM2QkQxMUU1OTZFMEMxNEEyRjI2NUYzNCIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PnFM7yIAAAFWSURBVHja7NRJDoMwDEDRcPL2ZoVeLEUtCyoCZPBsf8mS10+Wp5zzK6X0XGdJ0bdpRcnbPgfOESUFzjmKe5wrFLc4NSjucFpQ3OD0oJjHGUExiwOBYg4HEsUMDgaKehxMFLU4FCjqcChR1OBwoIjH4UQRiyMBRRyOJBQxOBJR2HEko7DhaEAhx9GEQoajEQUdRzMKGo4FFHAcSyhgOBZRhnEso3TjeEBpxvGEUo3jEeUWxzPKKU6gFHACpYATKMfegbLDWOcRl/Jr2X7JHI+2gOEZ5RTDI8othieUagwPKM0YllG6MSyiDGNYQgHDsIACjqEZBQ1DIwo6hiYUMgwNKOQYklHYMCSisGNIQhGDIQFFHAYnilgMDhTxGJQoajAoUNRhYKKoxcBAUY8BiWIGAwLFHMYIilmMHhTzGC0objBqUNxhXKG4xSihuMfYo8yB8d9HgAEATyefBJWpw70AAAAASUVORK5CYII=');
|
||||
width: 69px;
|
||||
height: 70px;
|
||||
left: 50%;
|
||||
margin: auto -34.5px;
|
||||
}
|
||||
|
||||
.pauseBtn {
|
||||
composes: btn;
|
||||
background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEUAAABGCAYAAACaGVmHAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyhpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNi1jMTExIDc5LjE1ODMyNSwgMjAxNS8wOS8xMC0wMToxMDoyMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIDIwMTUgKE1hY2ludG9zaCkiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6MDE4MjkzRjNDNzI0MTFFNTk2RTBDMTRBMkYyNjVGMzQiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6MDE4MjkzRjRDNzI0MTFFNTk2RTBDMTRBMkYyNjVGMzQiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDpBOERFOEQ1Q0M2QkQxMUU1OTZFMEMxNEEyRjI2NUYzNCIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDowMTgyOTNGMkM3MjQxMUU1OTZFMEMxNEEyRjI2NUYzNCIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/Pne5d1AAAAB8SURBVHja7NCxCQAwAALBuP/QZoEUlinua0G4tD1j83Asv/4GChQoUKBAgQIFChQoUKBAgQIFChQoUKBAgQIFChQoUKBAgQIFChQoUKBAgQIFChQoUKBAgQIFChQoUKBAgQIFChQoUKBAgQIFChQoUKBAgQIFCpRXV4ABAOgpXYTDYwEwAAAAAElFTkSuQmCC');
|
||||
width: 69px;
|
||||
height: 70px;
|
||||
left: 50%;
|
||||
margin: auto -34.5px;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.prevBtn {
|
||||
composes: btn;
|
||||
background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACMAAAAjCAYAAAAe2bNZAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyhpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNi1jMTExIDc5LjE1ODMyNSwgMjAxNS8wOS8xMC0wMToxMDoyMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIDIwMTUgKE1hY2ludG9zaCkiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6MDE4MjkzRkJDNzI0MTFFNTk2RTBDMTRBMkYyNjVGMzQiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6MDE4MjkzRkNDNzI0MTFFNTk2RTBDMTRBMkYyNjVGMzQiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDowMTgyOTNGOUM3MjQxMUU1OTZFMEMxNEEyRjI2NUYzNCIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDowMTgyOTNGQUM3MjQxMUU1OTZFMEMxNEEyRjI2NUYzNCIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PndeAH8AAACtSURBVHja7NXJCcAgEAVQEevK0lk6M1aW6CGQQBBn+ephBv7F0wNn8Vd7OWCWnOjd2FpzYk7K2cJAxFEA78cwA6I3porohWlCoDEkBArDQmhjRAgtjApCilFFcDEQBAcTUYinKLcJCqFinGEMYxjDgDFpJkzZwDsSRf2mE4ni9gwEJW1gVZTWNKmgtEdbhELtGRYKvfRIqF4buAnV+xxUUaNu0y9q9KH8oG4BBgDjdpk0jMoz1gAAAABJRU5ErkJggg==');
|
||||
width: 35px;
|
||||
height: 35px;
|
||||
left: 0;
|
||||
top: 50%;
|
||||
margin: -17.5px auto;
|
||||
}
|
||||
|
||||
.nextBtn {
|
||||
composes: btn;
|
||||
background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACMAAAAjCAYAAAAe2bNZAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyhpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNi1jMTExIDc5LjE1ODMyNSwgMjAxNS8wOS8xMC0wMToxMDoyMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIDIwMTUgKE1hY2ludG9zaCkiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6MDE4MjkzRjdDNzI0MTFFNTk2RTBDMTRBMkYyNjVGMzQiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6MDE4MjkzRjhDNzI0MTFFNTk2RTBDMTRBMkYyNjVGMzQiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDowMTgyOTNGNUM3MjQxMUU1OTZFMEMxNEEyRjI2NUYzNCIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDowMTgyOTNGNkM3MjQxMUU1OTZFMEMxNEEyRjI2NUYzNCIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PiFbkXsAAACvSURBVHja7NZBDoAgDARA9GPiz/yZ8DKsxgMHY1rYBQ5sshdCwiQcWpdSOqWb1BGrSn6RiTJjmKhiDANVjUGiYBgECo6pQdEwJSg6xoJqhtGgVFkeETZBekhjdqZ6g4H5QnXH5Cg/CkadiZmYiZkYdtYGb8QRMDdi144C1jfFd0AG69RG7jNB6nsvV3+IZhgNgo6xIGiYEgQcU4OAYRCIagwSUYxhIMwYJsKEuQQYABTs+qTMuF1rAAAAAElFTkSuQmCC');
|
||||
width: 35px;
|
||||
height: 35px;
|
||||
right: 0;
|
||||
top: 50%;
|
||||
margin: -17.5px auto;
|
||||
}
|
||||
|
||||
.prevBtn,
|
||||
.playPauseBtn,
|
||||
.nextBtn,
|
||||
.volumeBtn,
|
||||
.repeatBtn,
|
||||
.randomBtn,
|
||||
.playlistBtn {
|
||||
composes: btn;
|
||||
background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACMAAAAjCAYAAAAe2bNZAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyhpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNi1jMTExIDc5LjE1ODMyNSwgMjAxNS8wOS8xMC0wMToxMDoyMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIDIwMTUgKE1hY2ludG9zaCkiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6MUVBQzMwNzRDNzI0MTFFNTk2RTBDMTRBMkYyNjVGMzQiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6MUVBQzMwNzVDNzI0MTFFNTk2RTBDMTRBMkYyNjVGMzQiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDoxRUFDMzA3MkM3MjQxMUU1OTZFMEMxNEEyRjI2NUYzNCIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDoxRUFDMzA3M0M3MjQxMUU1OTZFMEMxNEEyRjI2NUYzNCIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PnWBPa0AAADGSURBVHjaYvz//z/DYAFMDIMIjDpm1DHDwjH/0fDugXAMCxK7AojPAnEHmpo0IFaisTsqIEECLPT+Q4ALlL0bihmQ+LQGYLsYoSXwf2iovAdiYyjbdaCiaQ+S2FkopjtgHK2bCESTC5r4+4GIKuQEjAz2DGQCHhTlDAtaLtoDjSJkoATN7iMzmjrRxO+NljODsZzpwBJNs0bLGShwhToCvWHVQYes7YruGJiFgljKmpEbTa5YKsrRcmbUMaOOGXUMNQBAgAEAm2XFJrn+jrsAAAAASUVORK5CYII=');
|
||||
width: 35px;
|
||||
height: 35px;
|
||||
top: 50%;
|
||||
left: 3%;
|
||||
margin: -17.5px auto;
|
||||
}
|
||||
|
||||
.volumeBtn {
|
||||
composes: btn;
|
||||
background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACMAAAAjCAYAAAAe2bNZAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyhpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNi1jMTExIDc5LjE1ODMyNSwgMjAxNS8wOS8xMC0wMToxMDoyMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIDIwMTUgKE1hY2ludG9zaCkiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6MUVBQzMwNzBDNzI0MTFFNTk2RTBDMTRBMkYyNjVGMzQiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6MUVBQzMwNzFDNzI0MTFFNTk2RTBDMTRBMkYyNjVGMzQiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDoxRUFDMzA2RUM3MjQxMUU1OTZFMEMxNEEyRjI2NUYzNCIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDoxRUFDMzA2RkM3MjQxMUU1OTZFMEMxNEEyRjI2NUYzNCIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PtAZ+FgAAAHRSURBVHja7FjRbYMwEIWqC7CCO4IzgjMCHYEV6AjuCHSEMAKMkIwAI5ARXFt9rox1phDA5KMnPSGwOZ4fx/mOVCmVPIu9JE9ke5GRGpejyeQanUapkXlj5lzEIMM1GqjRAyG1eNCLCeAVyDQq9WOdRoHrDeDP7YCM8reGiNQYQER6D6DIGHDc02xFJsfqjF00GDEnRCaBegp+RmMp8oyYERcmAAvMvWl8aLSBuQ2O58D4Ff7eqJiZa4MTF1OYUsZAwF9BKaOw0tsf6pjx+wwVXWU41Pwk1DF2opQRK7+skDIyECMlrv8GfoztwMaW9K7XTqKMujeZV8SQma31eO0iNpkW8L/a3t02Yu7aITL8CDL2S2VE/jqsnnHJ3I8urnpKldhkOEFmdB6TTO7kFveVRScjoExLqBWVTIbsax765RFh7n74GoFMiQefCLXcbWFEhs/MFfeFZGon9fsx1I78PVDPyA3qmXyqnhEzV1lgRT124/rBSq+Dj/Pa7sDUPVesrEGRvUSZMlQ/rSmgCqc7qBZ0B7aQ37RVsb2QJOIp1DcNUDXbg4wFw2qV06j5ZCqQYSE/yYZ1LxVPvjJ86v50p/8ztr96X3JT+v+zKGDfAgwAZkMcn0x+AsMAAAAASUVORK5CYII=');
|
||||
width: 35px;
|
||||
height: 35px;
|
||||
top: 50%;
|
||||
right: 3%;
|
||||
margin: -17.5px auto;
|
||||
.playPauseBtn {
|
||||
font-size: $font-size-h2;
|
||||
}
|
||||
|
||||
/* Progress */
|
||||
.waveform {
|
||||
width: 100%;
|
||||
height: 30%;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 50%;
|
||||
margin: -15% auto;
|
||||
display: none;
|
||||
cursor: pointer;
|
||||
opacity: 0.8;
|
||||
-webkit-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.waveform:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.progressBar {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 2px;
|
||||
background-color: rgba(255, 255, 255, 0.9);
|
||||
box-shadow: 1px 1px 2px rgba(0, 0, 0, 0.33);
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.progress {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 0%;
|
||||
height: 100%;
|
||||
background-color: rgba(0, 0, 0, 0.1);
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
/* Loading */
|
||||
.loading {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
margin: -35px;
|
||||
width: 70px;
|
||||
height: 70px;
|
||||
background-color: #fff;
|
||||
border-radius: 100%;
|
||||
-webkit-animation: sk-scaleout 1.0s infinite ease-in-out;
|
||||
animation: sk-scaleout 1.0s infinite ease-in-out;
|
||||
display: none;
|
||||
}
|
||||
@-webkit-keyframes sk-scaleout {
|
||||
0% { -webkit-transform: scale(0) }
|
||||
100% {
|
||||
-webkit-transform: scale(1.0);
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
@keyframes sk-scaleout {
|
||||
0% {
|
||||
-webkit-transform: scale(0);
|
||||
transform: scale(0);
|
||||
} 100% {
|
||||
-webkit-transform: scale(1.0);
|
||||
transform: scale(1.0);
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* Plylist */
|
||||
.playlist {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
display: none;
|
||||
}
|
||||
|
||||
.list {
|
||||
width: 100%;
|
||||
height: 360px;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 0;
|
||||
margin: -180px auto;
|
||||
}
|
||||
|
||||
.list-song {
|
||||
width: 100%;
|
||||
height: 120px;
|
||||
font-size: 50px;
|
||||
line-height: 120px;
|
||||
text-align: center;
|
||||
font-weight: bold;
|
||||
color: #fff;
|
||||
text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.33);
|
||||
}
|
||||
|
||||
.list-song:hover {
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* Volume */
|
||||
.volume {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
touch-action: none;
|
||||
-webkit-user-select: none;
|
||||
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
|
||||
display: none;
|
||||
}
|
||||
|
||||
.bar {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 5%;
|
||||
margin: -5px auto;
|
||||
height: 10px;
|
||||
background-color: rgba(255, 255, 255, 0.9);
|
||||
box-shadow: 1px 1px 2px rgba(0, 0, 0, 0.33);
|
||||
}
|
||||
|
||||
.barEmpty {
|
||||
composes: bar;
|
||||
width: 90%;
|
||||
opacity: 0.5;
|
||||
box-shadow: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.barFull {
|
||||
composes: bar;
|
||||
width: 90%;
|
||||
}
|
||||
|
||||
.sliderBtn {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 93.25%;
|
||||
margin: -25px auto;
|
||||
background-color: rgba(255, 255, 255, 0.8);
|
||||
box-shadow: 1px 1px 5px rgba(0, 0, 0, 0.33);
|
||||
border-radius: 25px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* Fade-In */
|
||||
.fadeout {
|
||||
webkit-animation: fadeout 0.5s;
|
||||
-ms-animation: fadeout 0.5s;
|
||||
animation: fadeout 0.5s;
|
||||
}
|
||||
.fadein {
|
||||
webkit-animation: fadein 0.5s;
|
||||
-ms-animation: fadein 0.5s;
|
||||
animation: fadein 0.5s;
|
||||
}
|
||||
@keyframes fadein {
|
||||
from { opacity: 0; }
|
||||
to { opacity: 1; }
|
||||
}
|
||||
@-webkit-keyframes fadein {
|
||||
from { opacity: 0; }
|
||||
to { opacity: 1; }
|
||||
}
|
||||
@-ms-keyframes fadein {
|
||||
from { opacity: 0; }
|
||||
to { opacity: 1; }
|
||||
}
|
||||
@keyframes fadeout {
|
||||
from { opacity: 1; }
|
||||
to { opacity: 0; }
|
||||
}
|
||||
@-webkit-keyframes fadeout {
|
||||
from { opacity: 1; }
|
||||
to { opacity: 0; }
|
||||
}
|
||||
@-ms-keyframes fadeout {
|
||||
from { opacity: 1; }
|
||||
to { opacity: 0; }
|
||||
}
|
||||
|
||||
/** Composed classes */
|
||||
.volume-fadeout {
|
||||
composes: volume;
|
||||
composes: fadeout;
|
||||
.active {
|
||||
color: $blue;
|
||||
}
|
||||
|
@ -1,3 +1,6 @@
|
||||
// Make variables and mixins available when using CSS modules.
|
||||
@import "node_modules/bootstrap-sass/assets/stylesheets/bootstrap/_variables";
|
||||
@import "node_modules/bootstrap-sass/assets/stylesheets/bootstrap/_mixins";
|
||||
|
||||
$blue: #3e90fa;
|
||||
$orange: #faa83e;
|
||||
|
@ -11,6 +11,6 @@ String.prototype.capitalize = function () {
|
||||
* @param chars A regex-like element to strip from the end.
|
||||
*/
|
||||
String.prototype.rstrip = function (chars) {
|
||||
var regex = new RegExp(chars + "$");
|
||||
let regex = new RegExp(chars + "$");
|
||||
return this.replace(regex, "");
|
||||
};
|
||||
|
@ -1,5 +1,5 @@
|
||||
export function getBrowserLocales () {
|
||||
var langs;
|
||||
let langs;
|
||||
|
||||
if (navigator.languages) {
|
||||
// chrome does not currently set navigator.language correctly https://code.google.com/p/chromium/issues/detail?id=101138
|
||||
@ -14,8 +14,8 @@ export function getBrowserLocales () {
|
||||
}
|
||||
|
||||
// Some browsers does not return uppercase for second part
|
||||
var locales = langs.map(function (lang) {
|
||||
var locale = lang.split("-");
|
||||
let locales = langs.map(function (lang) {
|
||||
let locale = lang.split("-");
|
||||
return locale[1] ? `${locale[0]}-${locale[1].toUpperCase()}` : lang;
|
||||
});
|
||||
|
||||
@ -23,7 +23,7 @@ export function getBrowserLocales () {
|
||||
}
|
||||
|
||||
export function messagesMap(messagesDescriptorsArray) {
|
||||
var messagesDescriptorsMap = {};
|
||||
let messagesDescriptorsMap = {};
|
||||
|
||||
messagesDescriptorsArray.forEach(function (item) {
|
||||
messagesDescriptorsMap[item.id] = item;
|
||||
|
@ -18,7 +18,7 @@ export function filterInt (value) {
|
||||
*/
|
||||
export function formatLength (time) {
|
||||
const min = Math.floor(time / 60);
|
||||
var sec = (time - 60 * min);
|
||||
let sec = (time - 60 * min);
|
||||
if (sec < 10) {
|
||||
sec = "0" + sec;
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
export function assembleURLAndParams (endpoint, params) {
|
||||
var url = endpoint + "?";
|
||||
let url = endpoint + "?";
|
||||
Object.keys(params).forEach(
|
||||
key => {
|
||||
if (Array.isArray(params[key])) {
|
||||
|
@ -1,6 +1,7 @@
|
||||
import React, { Component } from "react";
|
||||
import { bindActionCreators } from "redux";
|
||||
import { connect } from "react-redux";
|
||||
import Immutable from "immutable";
|
||||
|
||||
import * as actionCreators from "../actions";
|
||||
|
||||
@ -19,21 +20,41 @@ export class AlbumPage extends Component {
|
||||
}
|
||||
|
||||
render () {
|
||||
const album = this.props.albums.find(
|
||||
item => item.id == this.props.params.id
|
||||
);
|
||||
if (album) {
|
||||
if (this.props.album) {
|
||||
return (
|
||||
<Album album={album} />
|
||||
<Album album={this.props.album} songs={this.props.songs} />
|
||||
);
|
||||
}
|
||||
return null; // Loading
|
||||
return (
|
||||
<div></div>
|
||||
); // TODO: Loading
|
||||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = (state) => ({
|
||||
albums: state.pagination.albums.items
|
||||
});
|
||||
const mapStateToProps = (state, ownProps) => {
|
||||
const albums = state.api.entities.get("album");
|
||||
let album = undefined;
|
||||
let songs = new Immutable.List();
|
||||
if (albums) {
|
||||
// Get artist
|
||||
album = albums.find(
|
||||
item => item.get("id") == ownProps.params.id
|
||||
);
|
||||
// Get songs
|
||||
const tracks = album.get("tracks");
|
||||
if (Immutable.List.isList(tracks)) {
|
||||
songs = new Immutable.Map(
|
||||
tracks.map(
|
||||
id => [id, state.api.entities.getIn(["track", id])]
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
return {
|
||||
album: album,
|
||||
songs: songs
|
||||
};
|
||||
};
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
actions: bindActionCreators(actionCreators, dispatch)
|
||||
|
@ -2,6 +2,7 @@ import React, { Component, PropTypes } from "react";
|
||||
import { bindActionCreators } from "redux";
|
||||
import { connect } from "react-redux";
|
||||
import { defineMessages, injectIntl, intlShape } from "react-intl";
|
||||
import Immutable from "immutable";
|
||||
|
||||
import * as actionCreators from "../actions";
|
||||
import { i18nRecord } from "../models/i18n";
|
||||
@ -32,13 +33,13 @@ class AlbumsPageIntl extends Component {
|
||||
render () {
|
||||
const {formatMessage} = this.props.intl;
|
||||
if (this.props.error) {
|
||||
var errorMessage = this.props.error;
|
||||
let errorMessage = this.props.error;
|
||||
if (this.props.error instanceof i18nRecord) {
|
||||
errorMessage = formatMessage(albumsMessages[this.props.error.id], this.props.error.values);
|
||||
}
|
||||
alert(errorMessage);
|
||||
this.context.router.replace("/");
|
||||
return null;
|
||||
return (<div></div>);
|
||||
}
|
||||
const pagination = buildPaginationObject(this.props.location, this.props.currentPage, this.props.nPages, this.props.actions.goToPageAction);
|
||||
return (
|
||||
@ -55,13 +56,22 @@ AlbumsPageIntl.propTypes = {
|
||||
intl: intlShape.isRequired,
|
||||
};
|
||||
|
||||
const mapStateToProps = (state) => ({
|
||||
isFetching: state.pagination.albums.isFetching,
|
||||
error: state.pagination.albums.error,
|
||||
albumsList: state.pagination.albums.items,
|
||||
currentPage: state.pagination.albums.currentPage,
|
||||
nPages: state.pagination.albums.nPages
|
||||
});
|
||||
const mapStateToProps = (state) => {
|
||||
let albumsList = new Immutable.List();
|
||||
let albums = state.api.result.get("album");
|
||||
if (albums) {
|
||||
albumsList = albums.map(
|
||||
id => state.api.entities.getIn(["album", id])
|
||||
);
|
||||
}
|
||||
return {
|
||||
isFetching: state.api.isFetching,
|
||||
error: state.api.error,
|
||||
albumsList: albumsList,
|
||||
currentPage: state.api.currentPage,
|
||||
nPages: state.api.nPages
|
||||
};
|
||||
};
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
actions: bindActionCreators(actionCreators, dispatch)
|
||||
|
@ -1,6 +1,7 @@
|
||||
import React, { Component } from "react";
|
||||
import { bindActionCreators } from "redux";
|
||||
import { connect } from "react-redux";
|
||||
import Immutable from "immutable";
|
||||
|
||||
import * as actionCreators from "../actions";
|
||||
|
||||
@ -17,21 +18,52 @@ export class ArtistPage extends Component {
|
||||
}
|
||||
|
||||
render () {
|
||||
const artist = this.props.artists.find(
|
||||
item => item.id == this.props.params.id
|
||||
);
|
||||
if (artist) {
|
||||
if (this.props.artist) {
|
||||
return (
|
||||
<Artist artist={artist} />
|
||||
<Artist artist={this.props.artist} albums={this.props.albums} songs={this.props.songs} />
|
||||
);
|
||||
}
|
||||
return null; // Loading
|
||||
return (
|
||||
<div></div>
|
||||
); // TODO: Loading
|
||||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = (state) => ({
|
||||
artists: state.pagination.artists.items
|
||||
});
|
||||
const mapStateToProps = (state, ownProps) => {
|
||||
const artists = state.api.entities.get("artist");
|
||||
let artist = undefined;
|
||||
let albums = new Immutable.List();
|
||||
let songs = new Immutable.List();
|
||||
if (artists) {
|
||||
// Get artist
|
||||
artist = artists.find(
|
||||
item => item.get("id") == ownProps.params.id
|
||||
);
|
||||
// Get albums
|
||||
const artistAlbums = artist.get("albums");
|
||||
if (Immutable.List.isList(artistAlbums)) {
|
||||
albums = new Immutable.Map(
|
||||
artistAlbums.map(
|
||||
id => [id, state.api.entities.getIn(["album", id])]
|
||||
)
|
||||
);
|
||||
}
|
||||
// Get songs
|
||||
const artistSongs = artist.get("songs");
|
||||
if (Immutable.List.isList(artistSongs)) {
|
||||
songs = new Immutable.Map(
|
||||
artistSongs.map(
|
||||
id => [id, state.api.entities.getIn(["track", id])]
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
return {
|
||||
artist: artist,
|
||||
albums: albums,
|
||||
songs: songs
|
||||
};
|
||||
};
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
actions: bindActionCreators(actionCreators, dispatch)
|
||||
|
@ -2,6 +2,7 @@ import React, { Component, PropTypes } from "react";
|
||||
import { bindActionCreators } from "redux";
|
||||
import { connect } from "react-redux";
|
||||
import { defineMessages, injectIntl, intlShape } from "react-intl";
|
||||
import Immutable from "immutable";
|
||||
|
||||
import * as actionCreators from "../actions";
|
||||
import { i18nRecord } from "../models/i18n";
|
||||
@ -32,13 +33,13 @@ class ArtistsPageIntl extends Component {
|
||||
render () {
|
||||
const {formatMessage} = this.props.intl;
|
||||
if (this.props.error) {
|
||||
var errorMessage = this.props.error;
|
||||
let errorMessage = this.props.error;
|
||||
if (this.props.error instanceof i18nRecord) {
|
||||
errorMessage = formatMessage(artistsMessages[this.props.error.id], this.props.error.values);
|
||||
}
|
||||
alert(errorMessage);
|
||||
this.context.router.replace("/");
|
||||
return null;
|
||||
return (<div></div>);
|
||||
}
|
||||
const pagination = buildPaginationObject(this.props.location, this.props.currentPage, this.props.nPages, this.props.actions.goToPageAction);
|
||||
return (
|
||||
@ -55,13 +56,21 @@ ArtistsPageIntl.propTypes = {
|
||||
intl: intlShape.isRequired,
|
||||
};
|
||||
|
||||
const mapStateToProps = (state) => ({
|
||||
isFetching: state.pagination.artists.isFetching,
|
||||
error: state.pagination.artists.error,
|
||||
artistsList: state.pagination.artists.items,
|
||||
currentPage: state.pagination.artists.currentPage,
|
||||
nPages: state.pagination.artists.nPages,
|
||||
});
|
||||
const mapStateToProps = (state) => {
|
||||
let artistsList = new Immutable.List();
|
||||
if (state.api.result.get("artist")) {
|
||||
artistsList = state.api.result.get("artist").map(
|
||||
id => state.api.entities.getIn(["artist", id])
|
||||
);
|
||||
}
|
||||
return {
|
||||
isFetching: state.api.isFetching,
|
||||
error: state.api.error,
|
||||
artistsList: artistsList,
|
||||
currentPage: state.api.currentPage,
|
||||
nPages: state.api.nPages,
|
||||
};
|
||||
};
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
actions: bindActionCreators(actionCreators, dispatch)
|
||||
|
@ -7,8 +7,8 @@ import * as actionCreators from "../actions";
|
||||
import Login from "../components/Login";
|
||||
|
||||
function _getRedirectTo(props) {
|
||||
var redirectPathname = "/";
|
||||
var redirectQuery = {};
|
||||
let redirectPathname = "/";
|
||||
let redirectQuery = {};
|
||||
const { location } = props;
|
||||
if (location.state && location.state.nextPathname) {
|
||||
redirectPathname = location.state.nextPathname;
|
||||
|
@ -11,7 +11,7 @@ export class LogoutPage extends Component {
|
||||
|
||||
render () {
|
||||
return (
|
||||
null
|
||||
<div></div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ import React, { Component, PropTypes } from "react";
|
||||
import { bindActionCreators } from "redux";
|
||||
import { connect } from "react-redux";
|
||||
import { defineMessages, injectIntl, intlShape } from "react-intl";
|
||||
import Immutable from "immutable";
|
||||
|
||||
import * as actionCreators from "../actions";
|
||||
import { i18nRecord } from "../models/i18n";
|
||||
@ -32,13 +33,13 @@ class SongsPageIntl extends Component {
|
||||
render () {
|
||||
const {formatMessage} = this.props.intl;
|
||||
if (this.props.error) {
|
||||
var errorMessage = this.props.error;
|
||||
let errorMessage = this.props.error;
|
||||
if (this.props.error instanceof i18nRecord) {
|
||||
errorMessage = formatMessage(songsMessages[this.props.error.id], this.props.error.values);
|
||||
}
|
||||
alert(errorMessage);
|
||||
this.context.router.replace("/");
|
||||
return null;
|
||||
return (<div></div>);
|
||||
}
|
||||
const pagination = buildPaginationObject(this.props.location, this.props.currentPage, this.props.nPages, this.props.actions.goToPageAction);
|
||||
return (
|
||||
@ -55,13 +56,29 @@ SongsPageIntl.propTypes = {
|
||||
intl: intlShape.isRequired,
|
||||
};
|
||||
|
||||
const mapStateToProps = (state) => ({
|
||||
isFetching: state.pagination.songs.isFetching,
|
||||
error: state.pagination.songs.error,
|
||||
songsList: state.pagination.songs.items,
|
||||
currentPage: state.pagination.songs.currentPage,
|
||||
nPages: state.pagination.songs.nPages
|
||||
const mapStateToProps = (state) => {
|
||||
let songsList = new Immutable.List();
|
||||
if (state.api.result.get("song")) {
|
||||
songsList = state.api.result.get("song").map(function (id) {
|
||||
let song = state.api.entities.getIn(["track", id]);
|
||||
// Add artist and album infos
|
||||
const artist = state.api.entities.getIn(["artist", song.get("artist")]);
|
||||
const album = state.api.entities.getIn(["album", song.get("album")]);
|
||||
song = song.set("artist", new Immutable.Map({id: artist.get("id"), name: artist.get("name")}));
|
||||
song = song.set("album", new Immutable.Map({id: album.get("id"), name: album.get("name")}));
|
||||
return song;
|
||||
});
|
||||
}
|
||||
return {
|
||||
isFetching: state.api.isFetching,
|
||||
error: state.api.error,
|
||||
artistsList: state.api.entities.get("artist"),
|
||||
albumsList: state.api.entities.get("album"),
|
||||
songsList: songsList,
|
||||
currentPage: state.api.currentPage,
|
||||
nPages: state.api.nPages
|
||||
};
|
||||
};
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
actions: bindActionCreators(actionCreators, dispatch)
|
||||
|
@ -36,6 +36,7 @@
|
||||
"jquery": "^3.1.0",
|
||||
"js-cookie": "^2.1.2",
|
||||
"jssha": "^2.1.0",
|
||||
"normalizr": "^2.2.1",
|
||||
"react": "^15.3.0",
|
||||
"react-addons-shallow-compare": "^15.3.0",
|
||||
"react-css-modules": "^3.7.9",
|
||||
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -1,2 +1,2 @@
|
||||
!function(e){function t(r){if(n[r])return n[r].exports;var a=n[r]={exports:{},id:r,loaded:!1};return e[r].call(a.exports,a,a.exports,t),a.loaded=!0,a.exports}var n={};return t.m=e,t.c=n,t.p="./",t(0)}({0:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var r=n(620);Object.keys(r).forEach(function(e){"default"!==e&&Object.defineProperty(t,e,{enumerable:!0,get:function(){return r[e]}})})},620:function(e,t){!function(t,n){function r(e,t){var n=e.createElement("p"),r=e.getElementsByTagName("head")[0]||e.documentElement;return n.innerHTML="x<style>"+t+"</style>",r.insertBefore(n.lastChild,r.firstChild)}function a(){var e=b.elements;return"string"==typeof e?e.split(" "):e}function o(e,t){var n=b.elements;"string"!=typeof n&&(n=n.join(" ")),"string"!=typeof e&&(e=e.join(" ")),b.elements=n+" "+e,s(t)}function c(e){var t=E[e[v]];return t||(t={},y++,e[v]=y,E[y]=t),t}function i(e,t,r){if(t||(t=n),f)return t.createElement(e);r||(r=c(t));var a;return a=r.cache[e]?r.cache[e].cloneNode():g.test(e)?(r.cache[e]=r.createElem(e)).cloneNode():r.createElem(e),!a.canHaveChildren||p.test(e)||a.tagUrn?a:r.frag.appendChild(a)}function l(e,t){if(e||(e=n),f)return e.createDocumentFragment();t=t||c(e);for(var r=t.frag.cloneNode(),o=0,i=a(),l=i.length;o<l;o++)r.createElement(i[o]);return r}function u(e,t){t.cache||(t.cache={},t.createElem=e.createElement,t.createFrag=e.createDocumentFragment,t.frag=t.createFrag()),e.createElement=function(n){return b.shivMethods?i(n,e,t):t.createElem(n)},e.createDocumentFragment=Function("h,f","return function(){var n=f.cloneNode(),c=n.createElement;h.shivMethods&&("+a().join().replace(/[\w\-:]+/g,function(e){return t.createElem(e),t.frag.createElement(e),'c("'+e+'")'})+");return n}")(b,t.frag)}function s(e){e||(e=n);var t=c(e);return!b.shivCSS||d||t.hasCSS||(t.hasCSS=!!r(e,"article,aside,dialog,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}mark{background:#FF0;color:#000}template{display:none}")),f||u(e,t),e}var d,f,m="3.7.3-pre",h=t.html5||{},p=/^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i,g=/^(?:a|b|code|div|fieldset|h1|h2|h3|h4|h5|h6|i|label|li|ol|p|q|span|strong|style|table|tbody|td|th|tr|ul)$/i,v="_html5shiv",y=0,E={};!function(){try{var e=n.createElement("a");e.innerHTML="<xyz></xyz>",d="hidden"in e,f=1==e.childNodes.length||function(){n.createElement("a");var e=n.createDocumentFragment();return"undefined"==typeof e.cloneNode||"undefined"==typeof e.createDocumentFragment||"undefined"==typeof e.createElement}()}catch(t){d=!0,f=!0}}();var b={elements:h.elements||"abbr article aside audio bdi canvas data datalist details dialog figcaption figure footer header hgroup main mark meter nav output picture progress section summary template time video",version:m,shivCSS:h.shivCSS!==!1,supportsUnknownElements:f,shivMethods:h.shivMethods!==!1,type:"default",shivDocument:s,createElement:i,createDocumentFragment:l,addElements:o};t.html5=b,s(n),"object"==typeof e&&e.exports&&(e.exports=b)}("undefined"!=typeof window?window:this,document)}});
|
||||
!function(e){function t(r){if(n[r])return n[r].exports;var a=n[r]={exports:{},id:r,loaded:!1};return e[r].call(a.exports,a,a.exports,t),a.loaded=!0,a.exports}var n={};return t.m=e,t.c=n,t.p="./",t(0)}({0:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var r=n(625);Object.keys(r).forEach(function(e){"default"!==e&&Object.defineProperty(t,e,{enumerable:!0,get:function(){return r[e]}})})},625:function(e,t){!function(t,n){function r(e,t){var n=e.createElement("p"),r=e.getElementsByTagName("head")[0]||e.documentElement;return n.innerHTML="x<style>"+t+"</style>",r.insertBefore(n.lastChild,r.firstChild)}function a(){var e=b.elements;return"string"==typeof e?e.split(" "):e}function o(e,t){var n=b.elements;"string"!=typeof n&&(n=n.join(" ")),"string"!=typeof e&&(e=e.join(" ")),b.elements=n+" "+e,s(t)}function c(e){var t=E[e[v]];return t||(t={},y++,e[v]=y,E[y]=t),t}function i(e,t,r){if(t||(t=n),f)return t.createElement(e);r||(r=c(t));var a;return a=r.cache[e]?r.cache[e].cloneNode():g.test(e)?(r.cache[e]=r.createElem(e)).cloneNode():r.createElem(e),!a.canHaveChildren||p.test(e)||a.tagUrn?a:r.frag.appendChild(a)}function l(e,t){if(e||(e=n),f)return e.createDocumentFragment();t=t||c(e);for(var r=t.frag.cloneNode(),o=0,i=a(),l=i.length;o<l;o++)r.createElement(i[o]);return r}function u(e,t){t.cache||(t.cache={},t.createElem=e.createElement,t.createFrag=e.createDocumentFragment,t.frag=t.createFrag()),e.createElement=function(n){return b.shivMethods?i(n,e,t):t.createElem(n)},e.createDocumentFragment=Function("h,f","return function(){var n=f.cloneNode(),c=n.createElement;h.shivMethods&&("+a().join().replace(/[\w\-:]+/g,function(e){return t.createElem(e),t.frag.createElement(e),'c("'+e+'")'})+");return n}")(b,t.frag)}function s(e){e||(e=n);var t=c(e);return!b.shivCSS||d||t.hasCSS||(t.hasCSS=!!r(e,"article,aside,dialog,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}mark{background:#FF0;color:#000}template{display:none}")),f||u(e,t),e}var d,f,m="3.7.3-pre",h=t.html5||{},p=/^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i,g=/^(?:a|b|code|div|fieldset|h1|h2|h3|h4|h5|h6|i|label|li|ol|p|q|span|strong|style|table|tbody|td|th|tr|ul)$/i,v="_html5shiv",y=0,E={};!function(){try{var e=n.createElement("a");e.innerHTML="<xyz></xyz>",d="hidden"in e,f=1==e.childNodes.length||function(){n.createElement("a");var e=n.createDocumentFragment();return"undefined"==typeof e.cloneNode||"undefined"==typeof e.createDocumentFragment||"undefined"==typeof e.createElement}()}catch(t){d=!0,f=!0}}();var b={elements:h.elements||"abbr article aside audio bdi canvas data datalist details dialog figcaption figure footer header hgroup main mark meter nav output picture progress section summary template time video",version:m,shivCSS:h.shivCSS!==!1,supportsUnknownElements:f,shivMethods:h.shivMethods!==!1,type:"default",shivDocument:s,createElement:i,createDocumentFragment:l,addElements:o};t.html5=b,s(n),"object"==typeof e&&e.exports&&(e.exports=b)}("undefined"!=typeof window?window:this,document)}});
|
||||
//# sourceMappingURL=fix.ie9.js.map
|
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
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue
Block a user