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
parent 4cef2c2014
commit 4d4ce6c14e
22 changed files with 373 additions and 84 deletions

View File

@ -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");

View File

@ -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
View File

@ -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
};
}

View File

@ -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
};

View File

@ -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),

View File

@ -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,

View File

@ -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
View File

@ -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()
});

View File

@ -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
View File

@ -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"));
},
});

View File

@ -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,

View File

@ -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) {

View File

@ -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} />
);
}
}

View File

@ -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);

View File

@ -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

View File

@ -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