Basic webplayer

Now able to play a single file, in a format supported by your browser.

* Playlists not yet supported.
* Volume is a simple on/off switch.
* Repeat / Random not yet supported.
This commit is contained in:
Lucas Verney 2016-08-07 00:58:36 +02:00
rodzic 4cef2c2014
commit 4d4ce6c14e
22 zmienionych plików z 373 dodań i 84 usunięć

Wyświetl plik

@ -5,7 +5,7 @@ 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 const DEFAULT_LIMIT = 32; /** Default max number of elements to retrieve. */
export default function (action, requestType, successType, failureType) {
const itemName = action.rstrip("s");

Wyświetl plik

@ -11,3 +11,4 @@ export var { loadSongs } = APIAction("songs", API_REQUEST, API_SUCCESS, API_FAIL
export * from "./paginate";
export * from "./store";
export * from "./webplayer";

92
app/actions/webplayer.js Normal file
Wyświetl plik

@ -0,0 +1,92 @@
export const PLAY_PAUSE = "PLAY_PAUSE";
/**
* true to play, false to pause.
*/
export function togglePlaying(playPause) {
return (dispatch, getState) => {
let isPlaying = false;
if (typeof playPause !== "undefined") {
isPlaying = playPause;
} else {
isPlaying = !(getState().webplayer.isPlaying);
}
dispatch({
type: PLAY_PAUSE,
payload: {
isPlaying: isPlaying
}
});
};
}
export const PUSH_PLAYLIST = "PUSH_PLAYLIST";
export function playTrack(trackID) {
return (dispatch, getState) => {
const track = getState().api.entities.getIn(["track", trackID]);
const album = getState().api.entities.getIn(["album", track.get("album")]);
const artist = getState().api.entities.getIn(["artist", track.get("artist")]);
dispatch({
type: PUSH_PLAYLIST,
payload: {
playlist: [trackID],
tracks: [
[trackID, track]
],
albums: [
[album.get("id"), album]
],
artists: [
[artist.get("id"), artist]
]
}
});
dispatch(togglePlaying(true));
};
}
export const CHANGE_TRACK = "CHANGE_TRACK";
export function playPrevious() {
// TODO: Playlist overflow
return (dispatch, getState) => {
let { index } = getState().webplayer;
dispatch({
type: CHANGE_TRACK,
payload: {
index: index - 1
}
});
};
}
export function playNext() {
// TODO: Playlist overflow
return (dispatch, getState) => {
let { index } = getState().webplayer;
dispatch({
type: CHANGE_TRACK,
payload: {
index: index + 1
}
});
};
}
export const TOGGLE_RANDOM = "TOGGLE_RANDOM";
export function toggleRandom() {
return {
type: TOGGLE_RANDOM
};
}
export const TOGGLE_REPEAT = "TOGGLE_REPEAT";
export function toggleRepeat() {
return {
type: TOGGLE_REPEAT
};
}
export const TOGGLE_MUTE = "TOGGLE_MUTE";
export function toggleMute() {
return {
type: TOGGLE_MUTE
};
}

Wyświetl plik

@ -1,6 +1,6 @@
import React, { Component, PropTypes } from "react";
import CSSModules from "react-css-modules";
import { defineMessages, FormattedMessage } from "react-intl";
import { defineMessages, FormattedMessage, injectIntl, intlShape } from "react-intl";
import FontAwesome from "react-fontawesome";
import Immutable from "immutable";
@ -12,13 +12,14 @@ import css from "../styles/Album.scss";
const albumMessages = defineMessages(messagesMap(Array.concat([], commonMessages)));
class AlbumTrackRowCSS extends Component {
class AlbumTrackRowCSSIntl extends Component {
render () {
const { formatMessage } = this.props.intl;
const length = formatLength(this.props.track.get("time"));
return (
<tr>
<td>
<button styleName="play">
<button styleName="play" title={formatMessage(albumMessages["app.common.play"])} onClick={() => this.props.playAction(this.props.track.get("id"))}>
<span className="sr-only">
<FormattedMessage {...albumMessages["app.common.play"]} />
</span>
@ -33,18 +34,21 @@ class AlbumTrackRowCSS extends Component {
}
}
AlbumTrackRowCSS.propTypes = {
track: PropTypes.instanceOf(Immutable.Map).isRequired
AlbumTrackRowCSSIntl.propTypes = {
playAction: PropTypes.func.isRequired,
track: PropTypes.instanceOf(Immutable.Map).isRequired,
intl: intlShape.isRequired
};
export let AlbumTrackRow = CSSModules(AlbumTrackRowCSS, css);
export let AlbumTrackRow = injectIntl(CSSModules(AlbumTrackRowCSSIntl, css));
class AlbumTracksTableCSS extends Component {
render () {
let rows = [];
const playAction = this.props.playAction;
this.props.tracks.forEach(function (item) {
rows.push(<AlbumTrackRow track={item} key={item.get("id")} />);
rows.push(<AlbumTrackRow playAction={playAction} track={item} key={item.get("id")} />);
});
return (
<table className="table table-hover" styleName="songs">
@ -57,6 +61,7 @@ class AlbumTracksTableCSS extends Component {
}
AlbumTracksTableCSS.propTypes = {
playAction: PropTypes.func.isRequired,
tracks: PropTypes.instanceOf(Immutable.List).isRequired
};
@ -75,7 +80,7 @@ class AlbumRowCSS extends Component {
<div className="col-xs-9 col-sm-10 table-responsive">
{
this.props.songs.size > 0 ?
<AlbumTracksTable tracks={this.props.songs} /> :
<AlbumTracksTable playAction={this.props.playAction} tracks={this.props.songs} /> :
null
}
</div>
@ -85,6 +90,7 @@ class AlbumRowCSS extends Component {
}
AlbumRowCSS.propTypes = {
playAction: PropTypes.func.isRequired,
album: PropTypes.instanceOf(Immutable.Map).isRequired,
songs: PropTypes.instanceOf(Immutable.List).isRequired
};

Wyświetl plik

@ -26,7 +26,7 @@ class ArtistCSS extends Component {
</div>
);
if (this.props.isFetching && !this.props.artist) {
if (this.props.isFetching && !this.props.artist.size > 0) {
// Loading
return loading;
}
@ -37,7 +37,7 @@ class ArtistCSS extends Component {
}
let albumsRows = [];
const { albums, songs } = this.props;
const { albums, songs, playAction } = this.props;
const artistAlbums = this.props.artist.get("albums");
if (albums && songs && artistAlbums && artistAlbums.size > 0) {
this.props.artist.get("albums").forEach(function (album) {
@ -45,7 +45,7 @@ class ArtistCSS extends Component {
const albumSongs = album.get("tracks").map(
id => songs.get(id)
);
albumsRows.push(<AlbumRow album={album} songs={albumSongs} key={album.get("id")} />);
albumsRows.push(<AlbumRow playAction={playAction} album={album} songs={albumSongs} key={album.get("id")} />);
});
}
else {
@ -76,6 +76,7 @@ class ArtistCSS extends Component {
}
ArtistCSS.propTypes = {
playAction: PropTypes.func.isRequired,
isFetching: PropTypes.bool.isRequired,
error: PropTypes.string,
artist: PropTypes.instanceOf(Immutable.Map),

Wyświetl plik

@ -1,7 +1,7 @@
import React, { Component, PropTypes } from "react";
import { Link} from "react-router";
import CSSModules from "react-css-modules";
import { defineMessages, FormattedMessage } from "react-intl";
import { defineMessages, injectIntl, intlShape, FormattedMessage } from "react-intl";
import FontAwesome from "react-fontawesome";
import Immutable from "immutable";
import Fuse from "fuse.js";
@ -18,15 +18,16 @@ import css from "../styles/Songs.scss";
const songsMessages = defineMessages(messagesMap(Array.concat([], commonMessages, messages)));
class SongsTableRowCSS extends Component {
class SongsTableRowCSSIntl extends Component {
render () {
const { formatMessage } = this.props.intl;
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>
<button styleName="play">
<button styleName="play" title={formatMessage(songsMessages["app.common.play"])} onClick={() => this.props.playAction(this.props.song.get("id"))}>
<span className="sr-only">
<FormattedMessage {...songsMessages["app.common.play"]} />
</span>
@ -43,11 +44,13 @@ class SongsTableRowCSS extends Component {
}
}
SongsTableRowCSS.propTypes = {
song: PropTypes.instanceOf(Immutable.Map).isRequired
SongsTableRowCSSIntl.propTypes = {
playAction: PropTypes.func.isRequired,
song: PropTypes.instanceOf(Immutable.Map).isRequired,
intl: intlShape.isRequired
};
export let SongsTableRow = CSSModules(SongsTableRowCSS, css);
export let SongsTableRow = injectIntl(CSSModules(SongsTableRowCSSIntl, css));
class SongsTableCSS extends Component {
@ -67,8 +70,9 @@ class SongsTableCSS extends Component {
}
let rows = [];
const { playAction } = this.props;
displayedSongs.forEach(function (song) {
rows.push(<SongsTableRow song={song} key={song.get("id")} />);
rows.push(<SongsTableRow playAction={playAction} song={song} key={song.get("id")} />);
});
let loading = null;
if (rows.length == 0 && this.props.isFetching) {
@ -112,6 +116,7 @@ class SongsTableCSS extends Component {
}
SongsTableCSS.propTypes = {
playAction: PropTypes.func.isRequired,
songs: PropTypes.instanceOf(Immutable.List).isRequired,
filterText: PropTypes.string
};
@ -145,7 +150,7 @@ export default class FilterablePaginatedSongsTable extends Component {
<div>
{ error }
<FilterBar filterText={this.state.filterText} onUserInput={this.handleUserInput} />
<SongsTable isFetching={this.props.isFetching} songs={this.props.songs} filterText={this.state.filterText} />
<SongsTable playAction={this.props.playAction} isFetching={this.props.isFetching} songs={this.props.songs} filterText={this.state.filterText} />
<Pagination {...this.props.pagination} />
</div>
);
@ -153,6 +158,7 @@ export default class FilterablePaginatedSongsTable extends Component {
}
FilterablePaginatedSongsTable.propTypes = {
playAction: PropTypes.func.isRequired,
isFetching: PropTypes.bool.isRequired,
error: PropTypes.string,
songs: PropTypes.instanceOf(Immutable.List).isRequired,

Wyświetl plik

@ -23,15 +23,24 @@ class WebPlayerCSSIntl extends Component {
artOpacityHandler (ev) {
if (ev.type == "mouseover") {
this.refs.art.style.opacity = "1";
this.refs.artText.style.display = "none";
} else {
this.refs.art.style.opacity = "0.75";
this.refs.artText.style.display = "block";
}
}
render () {
const { formatMessage } = this.props.intl;
const song = this.props.currentTrack;
if (!song) {
return (<div></div>);
}
const playPause = this.props.isPlaying ? "pause" : "play";
const volumeMute = this.props.isMute ? "volume-off" : "volume-up";
const randomBtnStyles = ["randomBtn"];
const repeatBtnStyles = ["repeatBtn"];
if (this.props.isRandom) {
@ -46,36 +55,38 @@ class WebPlayerCSSIntl extends Component {
<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.get("art")} width="200" height="200" alt={formatMessage(webplayerMessages["app.common.art"])} ref="art" styleName="art" />
<h2>{this.props.song.get("title")}</h2>
<h3>
<span className="text-capitalize">
<FormattedMessage {...webplayerMessages["app.webplayer.by"]} />
</span> {this.props.song.get("artist")}
</h3>
<img src={song.get("art")} width="200" height="200" alt={formatMessage(webplayerMessages["app.common.art"])} ref="art" styleName="art" />
<div ref="artText">
<h2>{song.get("title")}</h2>
<h3>
<span className="text-capitalize">
<FormattedMessage {...webplayerMessages["app.webplayer.by"]} />
</span> { this.props.currentArtist.get("name") }
</h3>
</div>
</div>
</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"])}>
<button styleName="prevBtn" aria-label={formatMessage(webplayerMessages["app.webplayer.previous"])} title={formatMessage(webplayerMessages["app.webplayer.previous"])} onClick={this.props.onPrev}>
<FontAwesome name="step-backward" />
</button>
<button className="play" styleName="playPauseBtn" aria-label={formatMessage(webplayerMessages["app.common." + playPause])} title={formatMessage(webplayerMessages["app.common." + playPause])}>
<button className="play" styleName="playPauseBtn" aria-label={formatMessage(webplayerMessages["app.common." + playPause])} title={formatMessage(webplayerMessages["app.common." + playPause])} onClick={this.props.onPlayPause}>
<FontAwesome name={playPause} />
</button>
<button styleName="nextBtn" aria-label={formatMessage(webplayerMessages["app.webplayer.next"])} title={formatMessage(webplayerMessages["app.webplayer.next"])}>
<button styleName="nextBtn" aria-label={formatMessage(webplayerMessages["app.webplayer.next"])} title={formatMessage(webplayerMessages["app.webplayer.next"])} onClick={this.props.onSkip}>
<FontAwesome name="step-forward" />
</button>
</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 styleName="volumeBtn" aria-label={formatMessage(webplayerMessages["app.webplayer.volume"])} title={formatMessage(webplayerMessages["app.webplayer.volume"])} onClick={this.props.onMute}>
<FontAwesome name={volumeMute} />
</button>
<button styleName={repeatBtnStyles.join(" ")} aria-label={formatMessage(webplayerMessages["app.webplayer.repeat"])} title={formatMessage(webplayerMessages["app.webplayer.repeat"])} aria-pressed={this.props.isRepeat}>
<button styleName={repeatBtnStyles.join(" ")} aria-label={formatMessage(webplayerMessages["app.webplayer.repeat"])} title={formatMessage(webplayerMessages["app.webplayer.repeat"])} aria-pressed={this.props.isRepeat} onClick={this.props.onRepeat}>
<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}>
<button styleName={randomBtnStyles.join(" ")} aria-label={formatMessage(webplayerMessages["app.webplayer.random"])} title={formatMessage(webplayerMessages["app.webplayer.random"])} aria-pressed={this.props.isRandom} onClick={this.props.onRandom}>
<FontAwesome name="random" />
</button>
<button styleName="playlistBtn" aria-label={formatMessage(webplayerMessages["app.webplayer.playlist"])} title={formatMessage(webplayerMessages["app.webplayer.playlist"])}>
@ -90,10 +101,18 @@ class WebPlayerCSSIntl extends Component {
}
WebPlayerCSSIntl.propTypes = {
song: PropTypes.instanceOf(Immutable.Map).isRequired,
isPlaying: PropTypes.bool.isRequired,
isRandom: PropTypes.bool.isRequired,
isRepeat: PropTypes.bool.isRequired,
isMute: PropTypes.bool.isRequired,
currentTrack: PropTypes.instanceOf(Immutable.Map),
currentArtist: PropTypes.instanceOf(Immutable.Map),
onPlayPause: PropTypes.func.isRequired,
onPrev: PropTypes.func.isRequired,
onSkip: PropTypes.func.isRequired,
onRandom: PropTypes.func.isRequired,
onRepeat: PropTypes.func.isRequired,
onMute: PropTypes.func.isRequired,
intl: intlShape.isRequired
};

17
app/models/webplayer.js Normal file
Wyświetl plik

@ -0,0 +1,17 @@
import Immutable from "immutable";
export const entitiesRecord = new Immutable.Record({
artists: new Immutable.Map(),
albums: new Immutable.Map(),
tracks: new Immutable.Map()
});
export const stateRecord = new Immutable.Record({
isPlaying: false,
isRandom: false,
isRepeat: false,
isMute: false,
currentIndex: 0,
playlist: new Immutable.List(),
entities: new entitiesRecord()
});

Wyświetl plik

@ -3,6 +3,7 @@ import { combineReducers } from "redux";
import auth from "./auth";
import paginate from "./paginate";
import webplayer from "./webplayer";
import * as ActionTypes from "../actions";
@ -16,7 +17,8 @@ const api = paginate([
const rootReducer = combineReducers({
routing,
auth,
api
api,
webplayer
});
export default rootReducer;

51
app/reducers/webplayer.js Normal file
Wyświetl plik

@ -0,0 +1,51 @@
import Immutable from "immutable";
import {
PUSH_PLAYLIST,
CHANGE_TRACK,
PLAY_PAUSE,
TOGGLE_RANDOM,
TOGGLE_REPEAT,
TOGGLE_MUTE } from "../actions";
import { createReducer } from "../utils";
import { stateRecord } from "../models/webplayer";
/**
* Initial state
*/
var initialState = new stateRecord();
/**
* Reducers
*/
export default createReducer(initialState, {
[PLAY_PAUSE]: (state, payload) => {
return state.set("isPlaying", payload.isPlaying);
},
[CHANGE_TRACK]: (state, payload) => {
return state.set("currentIndex", payload.index);
},
[PUSH_PLAYLIST]: (state, payload) => {
return (
state
.set("playlist", new Immutable.List(payload.playlist))
.setIn(["entities", "artists"], new Immutable.Map(payload.artists))
.setIn(["entities", "albums"], new Immutable.Map(payload.albums))
.setIn(["entities", "tracks"], new Immutable.Map(payload.tracks))
.set("currentIndex", 0)
.set("isPlaying", true)
);
},
[TOGGLE_RANDOM]: (state) => {
return state.set("isRandom", !state.get("isRandom"));
},
[TOGGLE_REPEAT]: (state) => {
return state.set("isRepeat", !state.get("isRepeat"));
},
[TOGGLE_MUTE]: (state) => {
return state.set("isMute", !state.get("isMute"));
},
});

Wyświetl plik

@ -25,11 +25,14 @@ $controlsMarginTop: 10px;
.btn {
background: transparent;
border: none;
opacity: 0.8;
opacity: 0.4;
}
.btn:hover {
.btn:hover,
.btn:active,
.btn:focus {
opacity: 1;
outline: none;
}
.prevBtn,

Wyświetl plik

@ -27,14 +27,14 @@ class ArtistPageIntl extends Component {
const {formatMessage} = this.props.intl;
const error = handleErrorI18nObject(this.props.error, formatMessage, artistMessages);
return (
<Artist isFetching={this.props.isFetching} error={error} artist={this.props.artist} albums={this.props.albums} songs={this.props.songs} />
<Artist playAction={this.props.actions.playTrack} isFetching={this.props.isFetching} error={error} artist={this.props.artist} albums={this.props.albums} songs={this.props.songs} />
);
}
}
const mapStateToProps = (state, ownProps) => {
const artists = state.api.entities.get("artist");
let artist = undefined;
let artist = new Immutable.Map();
let albums = new Immutable.Map();
let songs = new Immutable.Map();
if (artists) {

Wyświetl plik

@ -35,7 +35,7 @@ class SongsPageIntl extends Component {
const {formatMessage} = this.props.intl;
const error = handleErrorI18nObject(this.props.error, formatMessage, songsMessages);
return (
<Songs isFetching={this.props.isFetching} error={error} songs={this.props.songsList} pagination={pagination} />
<Songs playAction={this.props.actions.playTrack} isFetching={this.props.isFetching} error={error} songs={this.props.songsList} pagination={pagination} />
);
}
}

Wyświetl plik

@ -1,23 +1,112 @@
import React, { Component } from "react";
import { bindActionCreators } from "redux";
import { connect } from "react-redux";
import { Howl } from "howler";
import Immutable from "immutable";
import * as actionCreators from "../actions";
import WebPlayerComponent from "../components/elements/WebPlayer";
class WebPlayer extends Component {
constructor (props) {
super(props);
this.play = this.play.bind(this);
this.howl = null;
}
componentDidMount () {
this.play(this.props.isPlaying);
}
componentWillUpdate (nextProps) {
// Toggle play / pause
if (nextProps.isPlaying != this.props.isPlaying) {
// This check ensure we do not start multiple times the same music.
this.play(nextProps);
}
// Toggle mute / unmute
if (this.howl) {
this.howl.mute(nextProps.isMute);
}
}
getCurrentTrackPath (props) {
return [
"tracks",
props.playlist.get(props.currentIndex)
];
}
play (props) {
if (props.isPlaying) {
if (!this.howl) {
const url = props.entities.getIn(
Array.concat([], this.getCurrentTrackPath(props), ["url"])
);
if (!url) {
// TODO: Error handling
return;
}
this.howl = new Howl({
src: [url],
html5: true,
loop: false,
mute: props.isMute,
autoplay: false,
});
}
this.howl.play();
}
else {
if (this.howl) {
this.howl.pause();
}
}
}
export default class WebPlayer extends Component {
render () {
const currentTrack = this.props.entities.getIn(this.getCurrentTrackPath(this.props));
let currentArtist = new Immutable.Map();
if (currentTrack) {
currentArtist = this.props.entities.getIn(["artists", currentTrack.get("artist")]);
}
const webplayerProps = {
song: new Immutable.Map({
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
isPlaying: this.props.isPlaying,
isRandom: this.props.isRandom,
isRepeat: this.props.isRepeat,
isMute: this.props.isMute,
currentTrack: currentTrack,
currentArtist: currentArtist,
onPlayPause: (() => this.props.actions.togglePlaying()),
onPrev: this.props.actions.playPrevious,
onSkip: this.props.actions.playNext,
onRandom: this.props.actions.toggleRandom,
onRepeat: this.props.actions.toggleRepeat,
onMute: this.props.actions.toggleMute
};
return (
<WebPlayerComponent {...webplayerProps} />
);
}
}
const mapStateToProps = (state) => ({
isPlaying: state.webplayer.isPlaying,
isRandom: state.webplayer.isRandom,
isRepeat: state.webplayer.isRepeat,
isMute: state.webplayer.isMute,
currentIndex: state.webplayer.currentIndex,
playlist: state.webplayer.playlist,
entities: state.webplayer.entities
});
const mapDispatchToProps = (dispatch) => ({
actions: bindActionCreators(actionCreators, dispatch)
});
export default connect(mapStateToProps, mapDispatchToProps)(WebPlayer);

Wyświetl plik

@ -26,6 +26,7 @@
"eslint": "^3.2.2",
"font-awesome": "^4.6.3",
"fuse.js": "^2.4.1",
"howler": "^2.0.0",
"html5shiv": "^3.7.3",
"humps": "^1.1.0",
"imagesloaded": "^4.1.0",

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Wyświetl plik

@ -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(626);Object.keys(r).forEach(function(e){"default"!==e&&Object.defineProperty(t,e,{enumerable:!0,get:function(){return r[e]}})})},626: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(630);Object.keys(r).forEach(function(e){"default"!==e&&Object.defineProperty(t,e,{enumerable:!0,get:function(){return r[e]}})})},630: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