updated plugin AudioIgniter version 1.9.0

This commit is contained in:
2022-08-19 16:15:57 +00:00
committed by Gitium
parent b49569de47
commit 8af91729f1
43 changed files with 6804 additions and 6000 deletions

View File

@ -1,7 +1,22 @@
{
"presets": [
"es2015",
"react",
"stage-2"
]
"presets": [
[
"@babel/preset-env",
{
"targets": {
"browsers": [
"last 2 versions, >1%"
]
},
"modules": false
}
],
"@babel/preset-react"
],
"plugins": [
"@babel/plugin-proposal-object-rest-spread",
"@babel/plugin-proposal-class-properties",
"@babel/plugin-proposal-optional-chaining",
"@babel/plugin-proposal-nullish-coalescing-operator"
]
}

View File

@ -1,15 +1,16 @@
{
"extends": [
"airbnb",
"plugin:prettier/recommended",
"prettier/react"
"prettier"
],
"parser": "@babel/eslint-parser",
"plugins": [
"babel",
"import"
],
"globals": {
"aiStrings": true
},
"globals": {
"aiStrings": true
},
"env": {
"browser": true
},
@ -17,22 +18,36 @@
"arrow-body-style": 0,
"no-confusing-arrow": 0,
"global-require": 0,
"import/no-extraneous-dependencies": ["error", {"devDependencies": true}],
"import/no-extraneous-dependencies": [
"error",
{
"devDependencies": true
}
],
"import/prefer-default-export": 0,
"import/no-cycle": 0,
"react/jsx-filename-extension": 0,
"react/require-default-props": 0,
"react/forbid-prop-types": 0,
"react/default-props-match-prop-types": 0,
"react/prefer-stateless-function": 0,
"react/jsx-curly-spacing": [2, {
"when": "never",
"children": true
}],
"react/jsx-curly-spacing": [
2,
{
"when": "never",
"children": true
}
],
"react/no-array-index-key": 0,
"jsx-a11y/anchor-is-valid": 0,
"jsx-a11y/no-static-element-interactions": 0,
"react/destructuring-assignment": 0,
"react/function-component-definition": 0,
"react/jsx-props-no-spreading": 0,
"react/button-has-type": 0,
"jsx-a11y/label-has-for": 0
"react/jsx-fragments": 0,
"react/jsx-no-constructed-context-values": 0,
"jsx-a11y/label-has-for": 0,
"jsx-a11y/click-events-have-key-events": 0
}
}

View File

@ -1 +1 @@
v8.12.0
16.14.2

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,66 @@
/*!
Copyright (c) 2015 Jed Watson.
Based on code that is Copyright 2013-2015, Facebook, Inc.
All rights reserved.
*/
/*!
Copyright (c) 2018 Jed Watson.
Licensed under the MIT License (MIT), see
http://jedwatson.github.io/classnames
*/
/*!
* Adapted from jQuery UI core
*
* http://jqueryui.com
*
* Copyright 2014 jQuery Foundation and other contributors
* Released under the MIT license.
* http://jquery.org/license
*
* http://api.jqueryui.com/category/ui-core/
*/
/**
* @license React
* react-dom.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/**
* @license React
* react.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/**
* @license React
* scheduler.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/** @license
*
* SoundManager 2: JavaScript Sound for the Web
* ----------------------------------------------
* http://schillmania.com/projects/soundmanager2/
*
* Copyright (c) 2007, Scott Schiller. All rights reserved.
* Code provided under the BSD License:
* http://schillmania.com/projects/soundmanager2/license.txt
*
* V2.97a.20170601
*/

View File

@ -1,100 +1,54 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>AudioIgniter</title>
<style>
body {
padding-bottom: 120px;
}
</style>
<link href="style.css" rel="stylesheet"></head>
<body>
<div
class="audioigniter-root"
data-player-type="full"
data-tracks-url="/dev-tracks.json"
data-display-active-cover="true"
data-display-tracklist-covers="true"
data-display-credits="true"
data-display-tracklist="true"
data-allow-tracklist-toggle="true"
data-allow-tracklist-loop="true"
data-allow-track-loop="true"
data-allow-playback-rate="true"
data-display-track-no="true"
data-display-artist-names="true"
data-display-buy-buttons="true"
data-buy-buttons-target="true"
data-volume="50"
data-cycle-tracks="true"
data-limit-tracklist-height="true"
data-tracklist-height="185"
data-reverse-track-order="false"
data-skip-amount="15"
data-max-width="600px"
data-initial-track="1"
data-stop-on-finish="false"
data-tracks-delay="5"
data-timer-countdown="false"
data-shuffle="true"
data-shuffle-default="false"
data-soundcloud-client-id=""
></div>
<div
class="audioigniter-root"
data-player-type="simple"
data-tracks-url="/dev-tracks.json"
data-display-credits="true"
data-display-track-no="true"
data-allow-playback-rate="true"
data-display-artist-names="true"
data-display-buy-buttons="true"
data-buy-buttons-target="true"
data-allow-track-loop="true"
data-volume="50"
data-reverse-track-order="false"
data-max-width="600px"
data-initial-track="3"
data-stop-on-finish="false"
data-tracks-delay="0"
data-timer-countdown="false"
data-shuffle="true"
data-soundcloud-client-id=""
></div>
<div
class="audioigniter-root"
data-player-type="global-footer"
data-tracks-url="/dev-tracks.json"
data-display-active-cover="true"
data-display-tracklist-covers="true"
data-display-credits="true"
data-display-tracklist="false"
data-allow-tracklist-toggle="true"
data-allow-tracklist-loop="true"
data-allow-track-loop="true"
data-display-track-no="true"
data-allow-playback-rate="true"
data-display-artist-names="true"
data-display-buy-buttons="true"
data-buy-buttons-target="true"
data-volume="50"
data-skip-amount="15"
data-cycle-tracks="false"
data-limit-tracklist-height="true"
data-tracklist-height="185"
data-reverse-track-order="false"
data-max-width="600px"
data-initial-track="1"
data-stop-on-finish="false"
data-tracks-delay="0"
data-timer-countdown="true"
data-shuffle="true"
data-soundcloud-client-id=""
></div>
<script type="text/javascript" src="app.js"></script><script type="text/javascript" src="style.js"></script></body>
</html>
<!doctype html><html><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>AudioIgniter</title><style>body {
padding-bottom: 120px;
}</style><script defer="defer" src="style.js"></script><script defer="defer" src="app.js"></script><link href="style.css" rel="stylesheet"></head><body><div id="audioigniter-2799" class="audioigniter-root" data-player-type="full" data-tracks-url="/dev-tracks.json" data-display-active-cover="true" data-display-tracklist-covers="true" data-display-credits="true" data-display-tracklist="true" data-allow-tracklist-toggle="true" data-allow-tracklist-loop="true" data-allow-track-loop="true" data-allow-playback-rate="true" data-display-track-no="true" data-display-artist-names="true" data-display-buy-buttons="true" data-buy-buttons-target="true" data-volume="50" data-cycle-tracks="true" data-limit-tracklist-height="true" data-tracklist-height="185" data-reverse-track-order="false" data-skip-amount="15" data-max-width="600px" data-initial-track="1" data-stop-on-finish="false" data-tracks-delay="5" data-timer-countdown="false" data-shuffle="true" data-shuffle-default="false" data-soundcloud-client-id="" data-remember-last="true" data-player-buttons='[
{
"title": "CSSIgniter",
"url": "https://cssigniter.com",
"icon": "<svg width=\"48\" height=\"48\" viewBox=\"0 0 48 48\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n <path d=\"M48 24C48 10.7452 37.2548 0 24 0C10.7452 0 0 10.7452 0 24C0 35.9789 8.77641 45.908 20.25 47.7084V30.9375H14.1562V24H20.25V18.7125C20.25 12.6975 23.8331 9.375 29.3152 9.375C31.9402 9.375 34.6875 9.84375 34.6875 9.84375V15.75H31.6613C28.68 15.75 27.75 17.6002 27.75 19.5V24H34.4062L33.3422 30.9375H27.75V47.7084C39.2236 45.908 48 35.9789 48 24Z\" fill=\"black\"/>\n</svg>"
},
{
"title": "",
"url": "https://cssigniter.com",
"icon": "<svg width=\"48\" height=\"40\" viewBox=\"0 0 48 40\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n <path d=\"M15.1003 39.5001C33.2091 39.5001 43.1166 24.4935 43.1166 11.4838C43.1166 11.0619 43.1072 10.6307 43.0884 10.2088C45.0157 8.81501 46.679 7.0886 48 5.11068C46.205 5.90929 44.2993 6.43085 42.3478 6.65756C44.4026 5.4259 45.9411 3.49103 46.6781 1.21162C44.7451 2.3572 42.6312 3.16531 40.4269 3.60131C38.9417 2.02321 36.978 0.97832 34.8394 0.62819C32.7008 0.278059 30.5064 0.642189 28.5955 1.66428C26.6846 2.68637 25.1636 4.3095 24.2677 6.28271C23.3718 8.25592 23.1509 10.4693 23.6391 12.5807C19.725 12.3843 15.8959 11.3675 12.4 9.59628C8.90405 7.82507 5.81939 5.33896 3.34594 2.29912C2.0888 4.46657 1.70411 7.03138 2.27006 9.47227C2.83601 11.9132 4.31013 14.047 6.39281 15.4401C4.82926 15.3904 3.29995 14.9694 1.93125 14.2119V14.3338C1.92985 16.6084 2.7162 18.8133 4.15662 20.5736C5.59704 22.334 7.60265 23.5412 9.8325 23.9901C8.38411 24.3863 6.86396 24.4441 5.38969 24.1588C6.01891 26.115 7.24315 27.8259 8.89154 29.0528C10.5399 30.2796 12.5302 30.9613 14.5847 31.0026C11.0968 33.7423 6.78835 35.2283 2.35313 35.2213C1.56657 35.2201 0.780798 35.1719 0 35.0769C4.50571 37.9676 9.74706 39.5029 15.1003 39.5001Z\" fill=\"black\"/>\n</svg>"
},
{
"title": "Another title",
"url": "https://cssigniter.com",
"icon": ""
}
]
'></div><div id="audioigniter-2999" class="audioigniter-root" data-player-type="simple" data-tracks-url="/dev-tracks.json" data-display-credits="true" data-display-track-no="true" data-allow-playback-rate="true" data-display-artist-names="true" data-display-buy-buttons="true" data-buy-buttons-target="true" data-allow-track-loop="true" data-volume="50" data-reverse-track-order="false" data-max-width="600px" data-initial-track="3" data-stop-on-finish="false" data-tracks-delay="0" data-timer-countdown="false" data-shuffle="true" data-soundcloud-client-id="" data-player-buttons='[
{
"title": "CSSIgniter",
"url": "https://cssigniter.com",
"icon": "<svg width=\"48\" height=\"48\" viewBox=\"0 0 48 48\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n <path d=\"M48 24C48 10.7452 37.2548 0 24 0C10.7452 0 0 10.7452 0 24C0 35.9789 8.77641 45.908 20.25 47.7084V30.9375H14.1562V24H20.25V18.7125C20.25 12.6975 23.8331 9.375 29.3152 9.375C31.9402 9.375 34.6875 9.84375 34.6875 9.84375V15.75H31.6613C28.68 15.75 27.75 17.6002 27.75 19.5V24H34.4062L33.3422 30.9375H27.75V47.7084C39.2236 45.908 48 35.9789 48 24Z\" fill=\"black\"/>\n</svg>"
},
{
"title": "",
"url": "https://cssigniter.com",
"icon": "<svg width=\"48\" height=\"40\" viewBox=\"0 0 48 40\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n <path d=\"M15.1003 39.5001C33.2091 39.5001 43.1166 24.4935 43.1166 11.4838C43.1166 11.0619 43.1072 10.6307 43.0884 10.2088C45.0157 8.81501 46.679 7.0886 48 5.11068C46.205 5.90929 44.2993 6.43085 42.3478 6.65756C44.4026 5.4259 45.9411 3.49103 46.6781 1.21162C44.7451 2.3572 42.6312 3.16531 40.4269 3.60131C38.9417 2.02321 36.978 0.97832 34.8394 0.62819C32.7008 0.278059 30.5064 0.642189 28.5955 1.66428C26.6846 2.68637 25.1636 4.3095 24.2677 6.28271C23.3718 8.25592 23.1509 10.4693 23.6391 12.5807C19.725 12.3843 15.8959 11.3675 12.4 9.59628C8.90405 7.82507 5.81939 5.33896 3.34594 2.29912C2.0888 4.46657 1.70411 7.03138 2.27006 9.47227C2.83601 11.9132 4.31013 14.047 6.39281 15.4401C4.82926 15.3904 3.29995 14.9694 1.93125 14.2119V14.3338C1.92985 16.6084 2.7162 18.8133 4.15662 20.5736C5.59704 22.334 7.60265 23.5412 9.8325 23.9901C8.38411 24.3863 6.86396 24.4441 5.38969 24.1588C6.01891 26.115 7.24315 27.8259 8.89154 29.0528C10.5399 30.2796 12.5302 30.9613 14.5847 31.0026C11.0968 33.7423 6.78835 35.2283 2.35313 35.2213C1.56657 35.2201 0.780798 35.1719 0 35.0769C4.50571 37.9676 9.74706 39.5029 15.1003 39.5001Z\" fill=\"black\"/>\n</svg>"
},
{
"title": "Another title",
"url": "https://cssigniter.com",
"icon": ""
}
]
'></div><div id="audioigniter-2519" class="audioigniter-root" data-track='{"title":"Sunrise","subtitle":"Thoribass","audio":"https:\/\/www.cssigniter.com\/assets\/audioigniter\/sunrise.mp3","buyUrl":"https:\/\/google.com","downloadUrl":"https:\/\/www.cssigniter.com\/assets\/audioigniter\/sunrise.mp3","cover":"https:\/\/www.cssigniter.com\/demos\/audioigniter\/wp-content\/uploads\/sites\/48\/2016\/08\/CyberSDF-Flame-and-Go.jpg","lyrics":"Some lyrics"}' data-display-active-cover="false" data-display-credits="true" data-allow-playback-rate="false" data-display-buy-buttons="true" data-volume="100" data-skip-amount="0" data-stop-on-finish="true" data-timer-countdown="false" data-player-type="full" data-tracks-url="" data-display-tracklist="false" data-allow-tracklist-toggle="true" data-allow-tracklist-loop="true" data-allow-track-loop="true" data-display-track-no="false" data-display-artist-names="true" data-buy-buttons-target="true" data-cycle-tracks="false" data-limit-tracklist-height="false" data-tracklist-height="185" data-reverse-track-order="false" data-max-width="600px" data-initial-track="1" data-tracks-delay="0" data-shuffle="false" data-shuffle-default="false" data-remember-last="false"></div><div id="audioigniter-8999" class="audioigniter-root" data-player-type="global-footer" data-tracks-url="/dev-tracks.json" data-display-active-cover="true" data-display-tracklist-covers="true" data-display-credits="true" data-display-tracklist="false" data-allow-tracklist-toggle="true" data-allow-tracklist-loop="true" data-allow-track-loop="true" data-display-track-no="true" data-allow-playback-rate="true" data-display-artist-names="true" data-display-buy-buttons="true" data-buy-buttons-target="true" data-volume="50" data-skip-amount="15" data-cycle-tracks="false" data-limit-tracklist-height="true" data-tracklist-height="185" data-reverse-track-order="false" data-max-width="600px" data-initial-track="1" data-stop-on-finish="false" data-tracks-delay="0" data-timer-countdown="true" data-shuffle="true" data-soundcloud-client-id="" data-player-buttons='[
{
"title": "CSSIgniter",
"url": "https://cssigniter.com",
"icon": "<svg width=\"48\" height=\"48\" viewBox=\"0 0 48 48\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n <path d=\"M48 24C48 10.7452 37.2548 0 24 0C10.7452 0 0 10.7452 0 24C0 35.9789 8.77641 45.908 20.25 47.7084V30.9375H14.1562V24H20.25V18.7125C20.25 12.6975 23.8331 9.375 29.3152 9.375C31.9402 9.375 34.6875 9.84375 34.6875 9.84375V15.75H31.6613C28.68 15.75 27.75 17.6002 27.75 19.5V24H34.4062L33.3422 30.9375H27.75V47.7084C39.2236 45.908 48 35.9789 48 24Z\" fill=\"black\"/>\n</svg>"
},
{
"title": "",
"url": "https://cssigniter.com",
"icon": "<svg width=\"48\" height=\"40\" viewBox=\"0 0 48 40\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n <path d=\"M15.1003 39.5001C33.2091 39.5001 43.1166 24.4935 43.1166 11.4838C43.1166 11.0619 43.1072 10.6307 43.0884 10.2088C45.0157 8.81501 46.679 7.0886 48 5.11068C46.205 5.90929 44.2993 6.43085 42.3478 6.65756C44.4026 5.4259 45.9411 3.49103 46.6781 1.21162C44.7451 2.3572 42.6312 3.16531 40.4269 3.60131C38.9417 2.02321 36.978 0.97832 34.8394 0.62819C32.7008 0.278059 30.5064 0.642189 28.5955 1.66428C26.6846 2.68637 25.1636 4.3095 24.2677 6.28271C23.3718 8.25592 23.1509 10.4693 23.6391 12.5807C19.725 12.3843 15.8959 11.3675 12.4 9.59628C8.90405 7.82507 5.81939 5.33896 3.34594 2.29912C2.0888 4.46657 1.70411 7.03138 2.27006 9.47227C2.83601 11.9132 4.31013 14.047 6.39281 15.4401C4.82926 15.3904 3.29995 14.9694 1.93125 14.2119V14.3338C1.92985 16.6084 2.7162 18.8133 4.15662 20.5736C5.59704 22.334 7.60265 23.5412 9.8325 23.9901C8.38411 24.3863 6.86396 24.4441 5.38969 24.1588C6.01891 26.115 7.24315 27.8259 8.89154 29.0528C10.5399 30.2796 12.5302 30.9613 14.5847 31.0026C11.0968 33.7423 6.78835 35.2283 2.35313 35.2213C1.56657 35.2201 0.780798 35.1719 0 35.0769C4.50571 37.9676 9.74706 39.5029 15.1003 39.5001Z\" fill=\"black\"/>\n</svg>"
},
{
"title": "Another title",
"url": "https://cssigniter.com",
"icon": ""
}
]
'></div></body></html>

File diff suppressed because one or more lines are too long

View File

@ -1 +0,0 @@
!function(n){function r(e){if(t[e])return t[e].exports;var o=t[e]={i:e,l:!1,exports:{}};return n[e].call(o.exports,o,o.exports,r),o.l=!0,o.exports}var t={};r.m=n,r.c=t,r.d=function(n,t,e){r.o(n,t)||Object.defineProperty(n,t,{configurable:!1,enumerable:!0,get:e})},r.n=function(n){var t=n&&n.__esModule?function(){return n.default}:function(){return n};return r.d(t,"a",t),t},r.o=function(n,r){return Object.prototype.hasOwnProperty.call(n,r)},r.p="",r(r.s=28)}({28:function(n,r){}});

View File

@ -5,7 +5,7 @@
"audio": "https://www.cssigniter.com/assets/audioigniter/sunrise.mp3",
"buyUrl": "https://www.cssigniter.com",
"downloadUrl": "https:\/\/www.cssigniter.com\/assets\/audioigniter\/sunrise.mp3",
"cover": "https:\/\/www.cssigniter.com\/preview\/audioigniter\/files\/2016\/08\/Thoribass-Sunrise.jpg",
"cover": "https://www.cssigniter.com/demos/audioigniter/wp-content/uploads/sites/48/2016/08/CyberSDF-Flame-and-Go.jpg",
"lyrics": "Here in my mind\nYou know you might find\nSomething that you\n\nYou thought you once knew\nBut now it's all gone\nAnd you know it's no fun\n\nYeah I know it's no fun\nOh I know it's no fun\nI'm free to be whatever I\nWhatever I choose\nAnd I'll sing the blues if I want\nI'm free to be whatever I\nWhatever I choose\nAnd I'll sing the blues if I want\nWhatever you do\nWhatever you say\nYeah I know it's alright"
},
{
@ -14,14 +14,14 @@
"audio": "https://www.cssigniter.com/assets/audioigniter/sunrise.mp3",
"buyUrl": "https://www.cssigniter.com",
"downloadUrl": "https:\/\/www.cssigniter.com\/assets\/audioigniter\/sunrise.mp3",
"cover": "https:\/\/www.cssigniter.com\/preview\/audioigniter\/files\/2016\/08\/The-Fisherman-Another-Day.jpg"
"cover": "https://www.cssigniter.com/demos/audioigniter/wp-content/uploads/sites/48/2016/08/Thoribass-Sunrise.jpg"
},
{
"title": "Remix Safety Guide",
"subtitle": "Rocavaco",
"audio": "https:\/\/www.cssigniter.com\/assets\/audioigniter\/remix.mp3",
"buyUrl": "https://www.cssigniter.com",
"cover": "https:\/\/www.cssigniter.com\/preview\/audioigniter\/files\/2016\/08\/Rocavaco-Remix-Safety-Guide.jpg",
"cover": "https://www.cssigniter.com/demos/audioigniter/wp-content/uploads/sites/48/2016/08/The-Fisherman-Another-Day.jpg",
"lyrics": "Lorem ipsum dolor sit amet, consectetur adipisicing elit. At eius hic illo natus vitae. Assumenda commodi eaque eos est eum excepturi fugiat provident, quidem saepe? Aut doloremque, unde? Delectus, dolorum."
},
{
@ -37,28 +37,28 @@
"subtitle": "Syl Johnson (SoundCloud)",
"audio": "https://soundcloud.com/enterofficial/maceo-plex-b2b-richie-hawtin-enterweek-11-sake-bar-space-ibiza-september-10th-2015",
"buyUrl": "",
"cover": "https://www.cssigniter.com/preview/audioigniter/files/2016/08/artworks-000103551140-ez6k4x-t500x500.jpg"
"cover": "https://www.cssigniter.com/demos/audioigniter/wp-content/uploads/sites/48/2016/08/Rocavaco-Remix-Safety-Guide.jpg"
},
{
"title": "Deep House Radio",
"subtitle": "",
"audio": "https://deephouseradio.radioca.st/stream/1/",
"buyUrl": "https://www.cssigniter.com",
"cover": "https://www.cssigniter.com/preview/audioigniter/files/2016/08/Rocavaco-Remix-Safety-Guide.jpg"
"cover": "https://www.cssigniter.com/demos/audioigniter/wp-content/uploads/sites/48/2016/08/MegaEnx-Tomorrow.jpg"
},
{
"title": "Flash of Light",
"subtitle": "Kxmode",
"audio": "https:\/\/www.cssigniter.com\/assets\/audioigniter\/flashlight.mp3",
"buyUrl": "https://www.cssigniter.com",
"cover": "https:\/\/www.cssigniter.com\/preview\/audioigniter\/files\/2016\/08\/Kxmode-Flash-of-Light.jpg",
"lyrics": "Lorem ipsum dolor sit amet, consectetur adipisicing elit. At eius hic illo natus vitae. Assumenda commodi eaque eos est eum excepturi fugiat provident, quidem saepe? Aut doloremque, unde? Delectus, dolorum."
"cover": "https://www.cssigniter.com/demos/audioigniter/wp-content/uploads/sites/48/2016/08/BitBurner-We-Get-Mental.jpg",
"lyrics": "Lorem ipsum dolor sit amet, consectetur adipisicing elit. At eius hic illo natus vitae. Assumenda commodi eaque eos est eum excepturi fugiat provident, quidem saepe? Aut doloremque, unde? Delectus, dolorum."
},
{
"title": "We Get Mental",
"subtitle": "BitBurner",
"audio": "https:\/\/www.cssigniter.com\/assets\/audioigniter\/mental.mp3",
"buyUrl": "",
"cover": "https:\/\/www.cssigniter.com\/preview\/audioigniter\/files\/2016\/08\/BitBurner-We-Get-Mental.jpg"
"cover": "https://www.cssigniter.com/demos/audioigniter/wp-content/uploads/sites/48/2016/08/Kxmode-Flash-of-Light.jpg"
}
]

View File

@ -7,6 +7,8 @@
"start": "webpack-dev-server",
"build": "rm -rf ./build && webpack",
"lint": "eslint ./src --ext .js --ext .jsx --cache || true",
"webpack-profile": "webpack --json > stats.json",
"babel-lala": "babel --plugins transform-react-remove-prop-types build/app.js",
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [
@ -17,46 +19,53 @@
"author": "vmasto",
"license": "ISC",
"devDependencies": {
"@babel/cli": "^7.17.10",
"@babel/core": "^7.18.5",
"@babel/eslint-parser": "^7.18.2",
"@babel/plugin-proposal-class-properties": "^7.17.12",
"@babel/plugin-proposal-nullish-coalescing-operator": "^7.17.12",
"@babel/plugin-proposal-object-rest-spread": "^7.18.0",
"@babel/plugin-proposal-optional-chaining": "^7.17.12",
"@babel/preset-env": "^7.18.2",
"@babel/preset-react": "^7.17.12",
"autoprefixer": "^7.1.2",
"babel-core": "^6.25.0",
"babel-loader": "^7.1.1",
"babel-preset-es2015": "^6.24.1",
"babel-preset-react": "^6.24.1",
"babel-preset-react-hmre": "1.1.1",
"babel-preset-stage-1": "^6.24.1",
"babel-preset-stage-2": "^6.24.1",
"css-loader": "^0.28.4",
"eslint": "3.19.0",
"eslint-config-airbnb": "15.0.2",
"eslint-config-airbnb-base": "^11.2.0",
"eslint-config-prettier": "^4.1.0",
"eslint-plugin-import": "2.7.0",
"eslint-plugin-jsx-a11y": "5.1.1",
"eslint-plugin-prettier": "^3.0.1",
"eslint-plugin-react": "7.1.0",
"extract-text-webpack-plugin": "^3.0.0",
"html-webpack-plugin": "^2.29.0",
"node-sass": "^4.5.3",
"postcss-loader": "^2.0.6",
"precss": "^2.0.0",
"babel-loader": "^8.2.5",
"babel-plugin-transform-react-remove-prop-types": "^0.4.24",
"css-loader": "^6.7.1",
"eslint": "^8.18.0",
"eslint-config-airbnb": "^19.0.4",
"eslint-config-airbnb-base": "^15.0.0",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-babel": "^5.3.1",
"eslint-plugin-import": "^2.26.0",
"eslint-plugin-jsx-a11y": "^6.5.1",
"eslint-plugin-prettier": "^3.3.1",
"eslint-plugin-react": "^7.30.0",
"extract-text-webpack-plugin": "^3.0.2",
"html-webpack-plugin": "^5.5.0",
"mini-css-extract-plugin": "^2.6.1",
"postcss-loader": "^7.0.0",
"precss": "^4.0.0",
"prettier": "^1.16.4",
"sass-loader": "^6.0.6",
"style-loader": "^0.18.2",
"webpack": "^3.2.0",
"webpack-dev-server": "^2.5.1",
"sass": "^1.52.3",
"sass-loader": "11.0.1",
"style-loader": "^3.3.1",
"terser-webpack-plugin": "^5.3.3",
"webpack": "5.72.1",
"webpack-bundle-analyzer": "^4.5.0",
"webpack-cli": "^4.10.0",
"webpack-dev-server": "^4.9.2",
"webpack-merge": "^4.1.0"
},
"dependencies": {
"classnames": "2.3.0",
"es6-promise": "^4.1.1",
"prop-types": "^15.7.2",
"react": "^16.8.3",
"react": "^18.2.0",
"react-custom-scrollbars": "^4.1.2",
"react-dom": "^16.8.3",
"react-dom": "^18.2.0",
"react-modal": "^3.8.1",
"react-sound": "^1.2.0",
"soundmanager2": "^2.97.20170602",
"sprintf-js": "1.1.1",
"whatwg-fetch": "0.11.1"
"sprintf-js": "1.1.1"
}
}

View File

@ -6,13 +6,14 @@
<title><%= htmlWebpackPlugin.options.title %></title>
<style>
body {
padding-bottom: 120px;
}
body {
padding-bottom: 120px;
}
</style>
</head>
<body>
<div
id="audioigniter-2799"
class="audioigniter-root"
data-player-type="full"
data-tracks-url="/dev-tracks.json"
@ -42,9 +43,29 @@
data-shuffle="true"
data-shuffle-default="false"
data-soundcloud-client-id=""
data-remember-last="true"
data-player-buttons='[
{
"title": "CSSIgniter",
"url": "https://cssigniter.com",
"icon": "<svg width=\"48\" height=\"48\" viewBox=\"0 0 48 48\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n <path d=\"M48 24C48 10.7452 37.2548 0 24 0C10.7452 0 0 10.7452 0 24C0 35.9789 8.77641 45.908 20.25 47.7084V30.9375H14.1562V24H20.25V18.7125C20.25 12.6975 23.8331 9.375 29.3152 9.375C31.9402 9.375 34.6875 9.84375 34.6875 9.84375V15.75H31.6613C28.68 15.75 27.75 17.6002 27.75 19.5V24H34.4062L33.3422 30.9375H27.75V47.7084C39.2236 45.908 48 35.9789 48 24Z\" fill=\"black\"/>\n</svg>"
},
{
"title": "",
"url": "https://cssigniter.com",
"icon": "<svg width=\"48\" height=\"40\" viewBox=\"0 0 48 40\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n <path d=\"M15.1003 39.5001C33.2091 39.5001 43.1166 24.4935 43.1166 11.4838C43.1166 11.0619 43.1072 10.6307 43.0884 10.2088C45.0157 8.81501 46.679 7.0886 48 5.11068C46.205 5.90929 44.2993 6.43085 42.3478 6.65756C44.4026 5.4259 45.9411 3.49103 46.6781 1.21162C44.7451 2.3572 42.6312 3.16531 40.4269 3.60131C38.9417 2.02321 36.978 0.97832 34.8394 0.62819C32.7008 0.278059 30.5064 0.642189 28.5955 1.66428C26.6846 2.68637 25.1636 4.3095 24.2677 6.28271C23.3718 8.25592 23.1509 10.4693 23.6391 12.5807C19.725 12.3843 15.8959 11.3675 12.4 9.59628C8.90405 7.82507 5.81939 5.33896 3.34594 2.29912C2.0888 4.46657 1.70411 7.03138 2.27006 9.47227C2.83601 11.9132 4.31013 14.047 6.39281 15.4401C4.82926 15.3904 3.29995 14.9694 1.93125 14.2119V14.3338C1.92985 16.6084 2.7162 18.8133 4.15662 20.5736C5.59704 22.334 7.60265 23.5412 9.8325 23.9901C8.38411 24.3863 6.86396 24.4441 5.38969 24.1588C6.01891 26.115 7.24315 27.8259 8.89154 29.0528C10.5399 30.2796 12.5302 30.9613 14.5847 31.0026C11.0968 33.7423 6.78835 35.2283 2.35313 35.2213C1.56657 35.2201 0.780798 35.1719 0 35.0769C4.50571 37.9676 9.74706 39.5029 15.1003 39.5001Z\" fill=\"black\"/>\n</svg>"
},
{
"title": "Another title",
"url": "https://cssigniter.com",
"icon": ""
}
]
'
></div>
<div
id="audioigniter-2999"
class="audioigniter-root"
data-player-type="simple"
data-tracks-url="/dev-tracks.json"
@ -64,9 +85,63 @@
data-timer-countdown="false"
data-shuffle="true"
data-soundcloud-client-id=""
data-player-buttons='[
{
"title": "CSSIgniter",
"url": "https://cssigniter.com",
"icon": "<svg width=\"48\" height=\"48\" viewBox=\"0 0 48 48\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n <path d=\"M48 24C48 10.7452 37.2548 0 24 0C10.7452 0 0 10.7452 0 24C0 35.9789 8.77641 45.908 20.25 47.7084V30.9375H14.1562V24H20.25V18.7125C20.25 12.6975 23.8331 9.375 29.3152 9.375C31.9402 9.375 34.6875 9.84375 34.6875 9.84375V15.75H31.6613C28.68 15.75 27.75 17.6002 27.75 19.5V24H34.4062L33.3422 30.9375H27.75V47.7084C39.2236 45.908 48 35.9789 48 24Z\" fill=\"black\"/>\n</svg>"
},
{
"title": "",
"url": "https://cssigniter.com",
"icon": "<svg width=\"48\" height=\"40\" viewBox=\"0 0 48 40\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n <path d=\"M15.1003 39.5001C33.2091 39.5001 43.1166 24.4935 43.1166 11.4838C43.1166 11.0619 43.1072 10.6307 43.0884 10.2088C45.0157 8.81501 46.679 7.0886 48 5.11068C46.205 5.90929 44.2993 6.43085 42.3478 6.65756C44.4026 5.4259 45.9411 3.49103 46.6781 1.21162C44.7451 2.3572 42.6312 3.16531 40.4269 3.60131C38.9417 2.02321 36.978 0.97832 34.8394 0.62819C32.7008 0.278059 30.5064 0.642189 28.5955 1.66428C26.6846 2.68637 25.1636 4.3095 24.2677 6.28271C23.3718 8.25592 23.1509 10.4693 23.6391 12.5807C19.725 12.3843 15.8959 11.3675 12.4 9.59628C8.90405 7.82507 5.81939 5.33896 3.34594 2.29912C2.0888 4.46657 1.70411 7.03138 2.27006 9.47227C2.83601 11.9132 4.31013 14.047 6.39281 15.4401C4.82926 15.3904 3.29995 14.9694 1.93125 14.2119V14.3338C1.92985 16.6084 2.7162 18.8133 4.15662 20.5736C5.59704 22.334 7.60265 23.5412 9.8325 23.9901C8.38411 24.3863 6.86396 24.4441 5.38969 24.1588C6.01891 26.115 7.24315 27.8259 8.89154 29.0528C10.5399 30.2796 12.5302 30.9613 14.5847 31.0026C11.0968 33.7423 6.78835 35.2283 2.35313 35.2213C1.56657 35.2201 0.780798 35.1719 0 35.0769C4.50571 37.9676 9.74706 39.5029 15.1003 39.5001Z\" fill=\"black\"/>\n</svg>"
},
{
"title": "Another title",
"url": "https://cssigniter.com",
"icon": ""
}
]
'
></div>
<div
id="audioigniter-2519"
class="audioigniter-root"
data-track='{"title":"Sunrise","subtitle":"Thoribass","audio":"https:\/\/www.cssigniter.com\/assets\/audioigniter\/sunrise.mp3","buyUrl":"https:\/\/google.com","downloadUrl":"https:\/\/www.cssigniter.com\/assets\/audioigniter\/sunrise.mp3","cover":"https:\/\/www.cssigniter.com\/demos\/audioigniter\/wp-content\/uploads\/sites\/48\/2016\/08\/CyberSDF-Flame-and-Go.jpg","lyrics":"Some lyrics"}'
data-display-active-cover="false"
data-display-credits="true"
data-allow-playback-rate="false"
data-display-buy-buttons="true"
data-volume="100"
data-skip-amount="0"
data-stop-on-finish="true"
data-timer-countdown="false"
data-player-type="full"
data-tracks-url=""
data-display-tracklist="false"
data-allow-tracklist-toggle="true"
data-allow-tracklist-loop="true"
data-allow-track-loop="true"
data-display-track-no="false"
data-display-artist-names="true"
data-buy-buttons-target="true"
data-cycle-tracks="false"
data-limit-tracklist-height="false"
data-tracklist-height="185"
data-reverse-track-order="false"
data-max-width="600px"
data-initial-track="1"
data-tracks-delay="0"
data-shuffle="false"
data-shuffle-default="false"
data-remember-last="false"
></div>
<div
id="audioigniter-8999"
class="audioigniter-root"
data-player-type="global-footer"
data-tracks-url="/dev-tracks.json"
@ -95,6 +170,24 @@
data-timer-countdown="true"
data-shuffle="true"
data-soundcloud-client-id=""
data-player-buttons='[
{
"title": "CSSIgniter",
"url": "https://cssigniter.com",
"icon": "<svg width=\"48\" height=\"48\" viewBox=\"0 0 48 48\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n <path d=\"M48 24C48 10.7452 37.2548 0 24 0C10.7452 0 0 10.7452 0 24C0 35.9789 8.77641 45.908 20.25 47.7084V30.9375H14.1562V24H20.25V18.7125C20.25 12.6975 23.8331 9.375 29.3152 9.375C31.9402 9.375 34.6875 9.84375 34.6875 9.84375V15.75H31.6613C28.68 15.75 27.75 17.6002 27.75 19.5V24H34.4062L33.3422 30.9375H27.75V47.7084C39.2236 45.908 48 35.9789 48 24Z\" fill=\"black\"/>\n</svg>"
},
{
"title": "",
"url": "https://cssigniter.com",
"icon": "<svg width=\"48\" height=\"40\" viewBox=\"0 0 48 40\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n <path d=\"M15.1003 39.5001C33.2091 39.5001 43.1166 24.4935 43.1166 11.4838C43.1166 11.0619 43.1072 10.6307 43.0884 10.2088C45.0157 8.81501 46.679 7.0886 48 5.11068C46.205 5.90929 44.2993 6.43085 42.3478 6.65756C44.4026 5.4259 45.9411 3.49103 46.6781 1.21162C44.7451 2.3572 42.6312 3.16531 40.4269 3.60131C38.9417 2.02321 36.978 0.97832 34.8394 0.62819C32.7008 0.278059 30.5064 0.642189 28.5955 1.66428C26.6846 2.68637 25.1636 4.3095 24.2677 6.28271C23.3718 8.25592 23.1509 10.4693 23.6391 12.5807C19.725 12.3843 15.8959 11.3675 12.4 9.59628C8.90405 7.82507 5.81939 5.33896 3.34594 2.29912C2.0888 4.46657 1.70411 7.03138 2.27006 9.47227C2.83601 11.9132 4.31013 14.047 6.39281 15.4401C4.82926 15.3904 3.29995 14.9694 1.93125 14.2119V14.3338C1.92985 16.6084 2.7162 18.8133 4.15662 20.5736C5.59704 22.334 7.60265 23.5412 9.8325 23.9901C8.38411 24.3863 6.86396 24.4441 5.38969 24.1588C6.01891 26.115 7.24315 27.8259 8.89154 29.0528C10.5399 30.2796 12.5302 30.9613 14.5847 31.0026C11.0968 33.7423 6.78835 35.2283 2.35313 35.2213C1.56657 35.2201 0.780798 35.1719 0 35.0769C4.50571 37.9676 9.74706 39.5029 15.1003 39.5001Z\" fill=\"black\"/>\n</svg>"
},
{
"title": "Another title",
"url": "https://cssigniter.com",
"icon": ""
}
]
'
></div>
</body>
</html>

View File

@ -1,7 +1,5 @@
import React from 'react';
import { render } from 'react-dom';
import 'es6-promise/auto';
import 'whatwg-fetch';
import { createRoot } from 'react-dom/client';
import App from './App';
@ -35,7 +33,9 @@ function renderApp(node) {
const type = node.getAttribute('data-player-type');
const props = {
playerId: node.getAttribute('id'),
tracksUrl: node.getAttribute('data-tracks-url'),
track: node.getAttribute('data-track'),
displayTracklistCovers: JSON.parse(
node.getAttribute('data-display-tracklist-covers'),
),
@ -82,9 +82,12 @@ function renderApp(node) {
countdownTimerByDefault: JSON.parse(
node.getAttribute('data-timer-countdown'),
),
rememberLastPosition: JSON.parse(node.getAttribute('data-remember-last')),
playerButtons: JSON.parse(node.getAttribute('data-player-buttons')),
};
render(<App type={type} {...props} />, node);
const root = createRoot(node);
root.render(<App type={type} {...props} />);
}
Array.prototype.slice.call(nodes).forEach(node => {

View File

@ -1,4 +1,4 @@
import React, { Fragment } from 'react';
import React, { Fragment, useState } from 'react';
import PropTypes from 'prop-types';
import Sound from 'react-sound';
import { sprintf } from 'sprintf-js';
@ -22,269 +22,9 @@ import {
} from './components/Icons';
import { AppContext } from '../App';
import typographyDisabled from '../utils/typography-disabled';
import PlayerButtons from './components/PlayerButtons';
class GlobalFooterPlayer extends React.Component {
constructor(props) {
super(props);
this.state = {
isTrackListOpen: this.props.displayTracklist,
};
this.toggleTracklist = this.toggleTracklist.bind(this);
}
toggleTracklist() {
this.setState(state => ({
isTrackListOpen: !state.isTrackListOpen,
}));
}
render() {
const { isTrackListOpen } = this.state;
const {
tracks,
playStatus,
activeIndex,
volume,
position,
duration,
playbackRate,
currentTrack,
playTrack,
togglePlay,
nextTrack,
prevTrack,
setPosition,
setVolume,
toggleTracklistCycling,
cycleTracks,
setTrackCycling,
setPlaybackRate,
allowPlaybackRate,
allowTracklistToggle,
allowTracklistLoop,
allowTrackLoop,
reverseTrackOrder,
displayTrackNo,
displayTracklistCovers,
displayActiveCover,
limitTracklistHeight,
tracklistHeight,
displayBuyButtons,
buyButtonsTarget,
displayArtistNames,
repeatingTrackIndex,
skipAmount,
skipPosition,
countdownTimerByDefault,
buffering,
} = this.props;
const classes = classNames({
'ai-wrap': true,
'ai-type-global-footer': true,
'ai-is-loading': !tracks.length,
'ai-with-typography': !typographyDisabled(),
});
const audioControlClasses = classNames({
'ai-audio-control': true,
'ai-audio-playing': playStatus === Sound.status.PLAYING,
'ai-audio-loading': buffering,
});
return (
<div
ref={ref => (this.root = ref)} // eslint-disable-line no-return-assign
className={classes}
>
<div className="ai-control-wrap">
{displayActiveCover && (
<Cover
className="ai-thumb ai-control-wrap-thumb"
src={currentTrack.cover}
alt={currentTrack.title}
/>
)}
<div className="ai-control-wrap-controls">
<ProgressBar
setPosition={setPosition}
duration={duration}
position={position}
/>
<div className="ai-audio-controls-main">
<Button
onClick={togglePlay}
className={audioControlClasses}
ariaLabel={
playStatus === Sound.status.PLAYING
? sprintf(aiStrings.pause_title, currentTrack.title)
: sprintf(aiStrings.play_title, currentTrack.title)
}
ariaPressed={playStatus === Sound.status.PLAYING}
>
{playStatus === Sound.status.PLAYING ? (
<PauseIcon />
) : (
<PlayIcon />
)}
<span className="ai-control-spinner" />
</Button>
<div className="ai-audio-controls-meta">
{tracks.length > 1 && (
<Button
className="ai-btn ai-tracklist-prev"
onClick={prevTrack}
ariaLabel={aiStrings.previous}
>
<PreviousIcon />
</Button>
)}
{tracks.length > 1 && (
<Button
className="ai-btn ai-tracklist-next"
onClick={nextTrack}
ariaLabel={aiStrings.next}
>
<NextIcon />
</Button>
)}
<VolumeControl
volume={volume}
// eslint-disable-next-line no-shadow
setVolume={setVolume}
/>
{allowTracklistLoop && (
<Button
className={`ai-btn ai-btn-repeat ${cycleTracks &&
'ai-btn-active'}`}
onClick={toggleTracklistCycling}
ariaLabel={aiStrings.toggle_list_repeat}
>
<RefreshIcon />
</Button>
)}
{allowPlaybackRate && (
<Button
className="ai-btn ai-btn-playback-rate"
onClick={setPlaybackRate}
ariaLabel={aiStrings.set_playback_rate}
>
<Fragment>&times;{playbackRate}</Fragment>
</Button>
)}
{skipAmount > 0 && (
<Fragment>
<Button
className="ai-btn ai-btn-skip-position"
onClick={() => skipPosition(-1)}
ariaLabel={aiStrings.skip_backward}
>
-{skipAmount}s
</Button>
<Button
className="ai-btn ai-btn-skip-position"
onClick={() => skipPosition(1)}
ariaLabel={aiStrings.skip_forward}
>
+{skipAmount}s
</Button>
</Fragment>
)}
{currentTrack && currentTrack.lyrics && !isTrackListOpen && (
<AppContext.Consumer>
{({ toggleLyricsModal }) => (
<Button
className="ai-btn ai-lyrics"
onClick={() => toggleLyricsModal(true, currentTrack)}
ariaLabel={aiStrings.open_track_lyrics}
title={aiStrings.open_track_lyrics}
>
<LyricsIcon />
</Button>
)}
</AppContext.Consumer>
)}
</div>
<div className="ai-track-info">
<p className="ai-track-title">
<span>{currentTrack.title}</span>
</p>
{(tracks.length === 0 || currentTrack.subtitle) &&
displayArtistNames && (
<p className="ai-track-subtitle">
<span>{currentTrack.subtitle}</span>
</p>
)}
</div>
<div className="ai-audio-controls-meta-right">
<Time
duration={duration}
position={position}
countdown={countdownTimerByDefault}
/>
{allowTracklistToggle && (
<Button
className="ai-btn ai-tracklist-toggle"
onClick={this.toggleTracklist}
ariaLabel={aiStrings.toggle_list_visible}
>
<PlaylistIcon />
</Button>
)}
</div>
</div>
</div>
</div>
<div
className={`ai-tracklist-wrap ${
isTrackListOpen ? 'ai-tracklist-open' : ''
}`}
style={{ display: isTrackListOpen ? 'block' : 'none' }}
>
<TracklistWrap
className="ai-tracklist"
trackClassName="ai-track"
tracks={tracks}
activeTrackIndex={activeIndex}
isOpen={isTrackListOpen}
displayTrackNo={displayTrackNo}
displayCovers={displayTracklistCovers}
displayBuyButtons={displayBuyButtons}
buyButtonsTarget={buyButtonsTarget}
displayArtistNames={displayArtistNames}
reverseTrackOrder={reverseTrackOrder}
limitTracklistHeight={limitTracklistHeight}
tracklistHeight={tracklistHeight}
onTrackClick={playTrack}
onTrackLoop={allowTrackLoop ? setTrackCycling : undefined}
repeatingTrackIndex={repeatingTrackIndex}
/>
</div>
</div>
);
}
}
GlobalFooterPlayer.propTypes = {
const propTypes = {
tracks: PropTypes.arrayOf(PropTypes.object),
playStatus: PropTypes.oneOf([
Sound.status.PLAYING,
@ -304,11 +44,11 @@ GlobalFooterPlayer.propTypes = {
setVolume: PropTypes.func.isRequired,
toggleTracklistCycling: PropTypes.func.isRequired,
cycleTracks: PropTypes.bool.isRequired,
displayTracklist: PropTypes.bool,
allowTracklistToggle: PropTypes.bool,
allowTracklistLoop: PropTypes.bool,
reverseTrackOrder: PropTypes.bool,
displayTrackNo: PropTypes.bool,
displayTracklist: PropTypes.bool,
displayActiveCover: PropTypes.bool,
displayTracklistCovers: PropTypes.bool,
limitTracklistHeight: PropTypes.bool,
@ -326,8 +66,262 @@ GlobalFooterPlayer.propTypes = {
countdownTimerByDefault: PropTypes.bool,
allowPlaybackRate: PropTypes.bool,
buffering: PropTypes.bool,
playerButtons: PropTypes.arrayOf(
PropTypes.shape({
title: PropTypes.string,
url: PropTypes.string,
icon: PropTypes.string,
}).isRequired,
),
};
const GlobalFooterPlayer = ({
tracks,
playStatus,
activeIndex,
volume,
position,
duration,
playbackRate,
currentTrack,
playTrack,
togglePlay,
nextTrack,
prevTrack,
setPosition,
setVolume,
toggleTracklistCycling,
cycleTracks,
setTrackCycling,
setPlaybackRate,
allowPlaybackRate,
allowTracklistToggle,
allowTracklistLoop,
allowTrackLoop,
reverseTrackOrder,
displayTracklist,
displayTrackNo,
displayTracklistCovers,
displayActiveCover,
limitTracklistHeight,
tracklistHeight,
displayBuyButtons,
buyButtonsTarget,
displayArtistNames,
repeatingTrackIndex,
skipAmount,
skipPosition,
countdownTimerByDefault,
buffering,
playerButtons,
}) => {
const [isTrackListOpen, setTracklistOpen] = useState(displayTracklist);
const toggleTracklist = () => {
setTracklistOpen(x => !x);
};
const classes = classNames({
'ai-wrap': true,
'ai-type-global-footer': true,
'ai-is-loading': !tracks.length,
'ai-with-typography': !typographyDisabled(),
});
const audioControlClasses = classNames({
'ai-audio-control': true,
'ai-audio-playing': playStatus === Sound.status.PLAYING,
'ai-audio-loading': buffering,
});
return (
<div className={classes}>
<div className="ai-control-wrap">
{displayActiveCover && (
<Cover
className="ai-thumb ai-control-wrap-thumb"
src={currentTrack.cover}
alt={currentTrack.title}
/>
)}
<div className="ai-control-wrap-controls">
<ProgressBar
setPosition={setPosition}
duration={duration}
position={position}
/>
<div className="ai-audio-controls-main">
<Button
onClick={togglePlay}
className={audioControlClasses}
ariaLabel={
playStatus === Sound.status.PLAYING
? sprintf(aiStrings.pause_title, currentTrack.title)
: sprintf(aiStrings.play_title, currentTrack.title)
}
ariaPressed={playStatus === Sound.status.PLAYING}
>
{playStatus === Sound.status.PLAYING ? (
<PauseIcon />
) : (
<PlayIcon />
)}
<span className="ai-control-spinner" />
</Button>
<div className="ai-audio-controls-meta">
{tracks.length > 1 && (
<Button
className="ai-btn ai-tracklist-prev"
onClick={prevTrack}
ariaLabel={aiStrings.previous}
>
<PreviousIcon />
</Button>
)}
{tracks.length > 1 && (
<Button
className="ai-btn ai-tracklist-next"
onClick={nextTrack}
ariaLabel={aiStrings.next}
>
<NextIcon />
</Button>
)}
<VolumeControl
volume={volume}
// eslint-disable-next-line no-shadow
setVolume={setVolume}
/>
{allowTracklistLoop && (
<Button
className={`ai-btn ai-btn-repeat ${cycleTracks &&
'ai-btn-active'}`}
onClick={toggleTracklistCycling}
ariaLabel={aiStrings.toggle_list_repeat}
>
<RefreshIcon />
</Button>
)}
{allowPlaybackRate && (
<Button
className="ai-btn ai-btn-playback-rate"
onClick={setPlaybackRate}
ariaLabel={aiStrings.set_playback_rate}
>
<Fragment>&times;{playbackRate}</Fragment>
</Button>
)}
{skipAmount > 0 && (
<Fragment>
<Button
className="ai-btn ai-btn-skip-position"
onClick={() => skipPosition(-1)}
ariaLabel={aiStrings.skip_backward}
>
-{skipAmount}s
</Button>
<Button
className="ai-btn ai-btn-skip-position"
onClick={() => skipPosition(1)}
ariaLabel={aiStrings.skip_forward}
>
+{skipAmount}s
</Button>
</Fragment>
)}
{currentTrack && currentTrack.lyrics && !isTrackListOpen && (
<AppContext.Consumer>
{({ toggleLyricsModal }) => (
<Button
className="ai-btn ai-lyrics"
onClick={() => toggleLyricsModal(true, currentTrack)}
ariaLabel={aiStrings.open_track_lyrics}
title={aiStrings.open_track_lyrics}
>
<LyricsIcon />
</Button>
)}
</AppContext.Consumer>
)}
</div>
<div className="ai-track-info">
<p className="ai-track-title">
<span>{currentTrack.title}</span>
</p>
{(tracks.length === 0 || currentTrack.subtitle) &&
displayArtistNames && (
<p className="ai-track-subtitle">
<span>{currentTrack.subtitle}</span>
</p>
)}
</div>
<div className="ai-audio-controls-meta-right">
<Time
duration={duration}
position={position}
countdown={countdownTimerByDefault}
/>
{allowTracklistToggle && (
<Button
className="ai-btn ai-tracklist-toggle"
onClick={toggleTracklist}
ariaLabel={aiStrings.toggle_list_visible}
>
<PlaylistIcon />
</Button>
)}
</div>
</div>
</div>
</div>
<div
className={`ai-tracklist-wrap ${
isTrackListOpen ? 'ai-tracklist-open' : ''
}`}
style={{ display: isTrackListOpen ? 'block' : 'none' }}
>
<TracklistWrap
className="ai-tracklist"
trackClassName="ai-track"
tracks={tracks}
activeTrackIndex={activeIndex}
isOpen={isTrackListOpen}
displayTrackNo={displayTrackNo}
displayCovers={displayTracklistCovers}
displayBuyButtons={displayBuyButtons}
buyButtonsTarget={buyButtonsTarget}
displayArtistNames={displayArtistNames}
reverseTrackOrder={reverseTrackOrder}
limitTracklistHeight={limitTracklistHeight}
tracklistHeight={tracklistHeight}
onTrackClick={playTrack}
onTrackLoop={allowTrackLoop ? setTrackCycling : undefined}
repeatingTrackIndex={repeatingTrackIndex}
/>
{playerButtons?.length > 0 && <PlayerButtons buttons={playerButtons} />}
</div>
</div>
);
};
GlobalFooterPlayer.propTypes = propTypes;
export default soundProvider(GlobalFooterPlayer, {
onFinishedPlaying(props) {
const {

View File

@ -1,4 +1,4 @@
import React, { Fragment } from 'react';
import React, { Fragment, useState, useRef } from 'react';
import PropTypes from 'prop-types';
import Sound from 'react-sound';
import { sprintf } from 'sprintf-js';
@ -23,309 +23,10 @@ import {
import soundProvider from './soundProvider';
import { AppContext } from '../App';
import typographyDisabled from '../utils/typography-disabled';
import useComponentSize from '../utils/useComponentSize';
import PlayerButtons from './components/PlayerButtons';
class Player extends React.Component {
constructor(props) {
super(props);
this.state = {
isTrackListOpen: this.props.displayTracklist,
};
this.toggleTracklist = this.toggleTracklist.bind(this);
this.isNarrowContext = this.isNarrowContext.bind(this);
}
isNarrowContext() {
return this.root && this.root.offsetWidth < 480 && window.innerWidth > 480;
}
toggleTracklist() {
this.setState(state => ({
isTrackListOpen: !state.isTrackListOpen,
}));
}
render() {
const { isTrackListOpen } = this.state;
const {
tracks,
playStatus,
activeIndex,
volume,
position,
duration,
playbackRate,
shuffle,
shuffleEnabled,
currentTrack,
playTrack,
togglePlay,
nextTrack,
prevTrack,
setPosition,
setVolume,
setPlaybackRate,
toggleTracklistCycling,
cycleTracks,
toggleShuffle,
allowTracklistToggle,
allowTracklistLoop,
allowPlaybackRate,
allowTrackLoop,
setTrackCycling,
reverseTrackOrder,
displayTrackNo,
displayTracklistCovers,
displayActiveCover,
displayCredits,
limitTracklistHeight,
tracklistHeight,
displayBuyButtons,
buyButtonsTarget,
displayArtistNames,
maxWidth,
repeatingTrackIndex,
skipAmount,
skipPosition,
countdownTimerByDefault,
buffering,
} = this.props;
const classes = classNames({
'ai-wrap': true,
'ai-type-full': true,
'ai-is-loading': !tracks.length,
'ai-narrow': this.isNarrowContext(),
'ai-with-typography': !typographyDisabled(),
});
const audioControlClasses = classNames({
'ai-audio-control': true,
'ai-audio-playing': playStatus === Sound.status.PLAYING,
'ai-audio-loading': buffering,
});
return (
<div
ref={ref => (this.root = ref)} // eslint-disable-line no-return-assign
className={classes}
style={{ maxWidth }}
>
<div className="ai-control-wrap">
{displayActiveCover && (
<Cover
className="ai-thumb ai-control-wrap-thumb"
src={currentTrack.cover}
alt={currentTrack.title}
/>
)}
<div className="ai-control-wrap-controls">
<div className="ai-audio-controls-main">
<Button
onClick={togglePlay}
className={audioControlClasses}
ariaLabel={
playStatus === Sound.status.PLAYING
? sprintf(aiStrings.pause_title, currentTrack.title)
: sprintf(aiStrings.play_title, currentTrack.title)
}
ariaPressed={playStatus === Sound.status.PLAYING}
>
{playStatus === Sound.status.PLAYING ? (
<PauseIcon />
) : (
<PlayIcon />
)}
<span className="ai-control-spinner" />
</Button>
<div className="ai-track-info">
<p className="ai-track-title">
<span>{currentTrack.title}</span>
</p>
{(tracks.length === 0 || currentTrack.subtitle) &&
displayArtistNames && (
<p className="ai-track-subtitle">
<span>{currentTrack.subtitle}</span>
</p>
)}
</div>
</div>
<div className="ai-audio-controls-progress">
<ProgressBar
setPosition={setPosition}
duration={duration}
position={position}
/>
<Time
duration={duration}
position={position}
countdown={countdownTimerByDefault}
/>
</div>
<div className="ai-audio-controls-meta">
{tracks.length > 1 && (
<Button
className="ai-btn ai-tracklist-prev"
onClick={prevTrack}
ariaLabel={aiStrings.previous}
title={aiStrings.previous}
>
<PreviousIcon />
</Button>
)}
{tracks.length > 1 && (
<Button
className="ai-btn ai-tracklist-next"
onClick={nextTrack}
ariaLabel={aiStrings.next}
title={aiStrings.next}
>
<NextIcon />
</Button>
)}
<VolumeControl
volume={volume}
// eslint-disable-next-line no-shadow
setVolume={setVolume}
/>
{allowTracklistLoop && (
<Button
className={`ai-btn ai-btn-repeat ${cycleTracks &&
'ai-btn-active'}`}
onClick={toggleTracklistCycling}
ariaLabel={aiStrings.toggle_list_repeat}
>
<RefreshIcon />
</Button>
)}
{shuffleEnabled && (
<Button
className={`ai-btn ai-btn-shuffle ${shuffle &&
'ai-btn-active'}`}
onClick={toggleShuffle}
ariaLabel={aiStrings.shuffle}
>
<ShuffleIcon />
</Button>
)}
{allowPlaybackRate && (
<Button
className="ai-btn ai-btn-playback-rate"
onClick={setPlaybackRate}
ariaLabel={aiStrings.set_playback_rate}
>
<Fragment>&times;{playbackRate}</Fragment>
</Button>
)}
{skipAmount > 0 && (
<Fragment>
<Button
className="ai-btn ai-btn-skip-position"
onClick={() => skipPosition(-1)}
ariaLabel={aiStrings.skip_backward}
>
-{skipAmount}s
</Button>
<Button
className="ai-btn ai-btn-skip-position"
onClick={() => skipPosition(1)}
ariaLabel={aiStrings.skip_forward}
>
+{skipAmount}s
</Button>
</Fragment>
)}
{currentTrack && currentTrack.lyrics && !isTrackListOpen && (
<AppContext.Consumer>
{({ toggleLyricsModal }) => (
<Button
className="ai-btn ai-lyrics"
onClick={() => toggleLyricsModal(true, currentTrack)}
ariaLabel={aiStrings.open_track_lyrics}
title={aiStrings.open_track_lyrics}
>
<LyricsIcon />
</Button>
)}
</AppContext.Consumer>
)}
{allowTracklistToggle && (
<Button
className="ai-btn ai-tracklist-toggle"
onClick={this.toggleTracklist}
ariaLabel={aiStrings.toggle_list_visible}
ariaExpanded={isTrackListOpen}
>
<PlaylistIcon />
</Button>
)}
</div>
</div>
</div>
<div
className={`ai-tracklist-wrap ${
isTrackListOpen ? 'ai-tracklist-open' : ''
}`}
>
<TracklistWrap
className="ai-tracklist"
trackClassName="ai-track"
tracks={tracks}
activeTrackIndex={activeIndex}
isOpen={isTrackListOpen}
displayTrackNo={displayTrackNo}
displayCovers={displayTracklistCovers}
displayBuyButtons={displayBuyButtons}
buyButtonsTarget={buyButtonsTarget}
displayArtistNames={displayArtistNames}
reverseTrackOrder={reverseTrackOrder}
limitTracklistHeight={limitTracklistHeight}
tracklistHeight={tracklistHeight}
onTrackClick={playTrack}
onTrackLoop={allowTrackLoop ? setTrackCycling : undefined}
repeatingTrackIndex={repeatingTrackIndex}
/>
</div>
{displayCredits && (
<div className="ai-footer">
<p>
Powered by{' '}
<a
href="https://www.cssigniter.com/plugins/audioigniter?utm_source=player&utm_medium=link&utm_content=audioigniter&utm_campaign=footer-link"
target="_blank"
rel="noopener noreferrer"
>
AudioIgniter
</a>
</p>
</div>
)}
</div>
);
}
}
Player.propTypes = {
const propTypes = {
tracks: PropTypes.arrayOf(PropTypes.object),
playStatus: PropTypes.oneOf([
Sound.status.PLAYING,
@ -372,8 +73,303 @@ Player.propTypes = {
shuffleEnabled: PropTypes.bool,
shuffle: PropTypes.bool,
toggleShuffle: PropTypes.func.isRequired,
playerButtons: PropTypes.arrayOf(
PropTypes.shape({
title: PropTypes.string,
url: PropTypes.string,
icon: PropTypes.string,
}).isRequired,
),
};
const Player = ({
tracks,
playStatus,
activeIndex,
volume,
position,
duration,
playbackRate,
shuffle,
shuffleEnabled,
currentTrack,
playTrack,
togglePlay,
nextTrack,
prevTrack,
setPosition,
setVolume,
setPlaybackRate,
toggleTracklistCycling,
cycleTracks,
toggleShuffle,
allowTracklistToggle,
allowTracklistLoop,
allowPlaybackRate,
allowTrackLoop,
setTrackCycling,
reverseTrackOrder,
displayTrackNo,
displayTracklist,
displayTracklistCovers,
displayActiveCover,
displayCredits,
limitTracklistHeight,
tracklistHeight,
displayBuyButtons,
buyButtonsTarget,
displayArtistNames,
maxWidth,
repeatingTrackIndex,
skipAmount,
skipPosition,
countdownTimerByDefault,
buffering,
playerButtons,
}) => {
const ref = useRef(null);
const [isTrackListOpen, setTracklistOpen] = useState(displayTracklist);
const { width } = useComponentSize(ref);
const isNarrowContext = () => {
return width != null && width < 480 && window.innerWidth > 480;
};
const toggleTracklist = () => {
setTracklistOpen(x => !x);
};
const classes = classNames({
'ai-wrap': true,
'ai-type-full': true,
'ai-is-loading': !tracks.length,
'ai-narrow': isNarrowContext(),
'ai-with-typography': !typographyDisabled(),
});
const audioControlClasses = classNames({
'ai-audio-control': true,
'ai-audio-playing': playStatus === Sound.status.PLAYING,
'ai-audio-loading': buffering,
});
return (
<div ref={ref} className={classes} style={{ maxWidth }}>
<div className="ai-control-wrap">
{displayActiveCover && (
<Cover
className="ai-thumb ai-control-wrap-thumb"
src={currentTrack.cover}
alt={currentTrack.title}
/>
)}
<div className="ai-control-wrap-controls">
<div className="ai-audio-controls-main">
<Button
onClick={togglePlay}
className={audioControlClasses}
ariaLabel={
playStatus === Sound.status.PLAYING
? sprintf(aiStrings.pause_title, currentTrack.title)
: sprintf(aiStrings.play_title, currentTrack.title)
}
ariaPressed={playStatus === Sound.status.PLAYING}
>
{playStatus === Sound.status.PLAYING ? (
<PauseIcon />
) : (
<PlayIcon />
)}
<span className="ai-control-spinner" />
</Button>
<div className="ai-track-info">
<p className="ai-track-title">
<span>{currentTrack.title}</span>
</p>
{(tracks.length === 0 || currentTrack.subtitle) &&
displayArtistNames && (
<p className="ai-track-subtitle">
<span>{currentTrack.subtitle}</span>
</p>
)}
</div>
</div>
<div className="ai-audio-controls-progress">
<ProgressBar
setPosition={setPosition}
duration={duration}
position={position}
/>
<Time
duration={duration}
position={position}
countdown={countdownTimerByDefault}
/>
</div>
<div className="ai-audio-controls-meta">
{tracks.length > 1 && (
<Button
className="ai-btn ai-tracklist-prev"
onClick={prevTrack}
ariaLabel={aiStrings.previous}
title={aiStrings.previous}
>
<PreviousIcon />
</Button>
)}
{tracks.length > 1 && (
<Button
className="ai-btn ai-tracklist-next"
onClick={nextTrack}
ariaLabel={aiStrings.next}
title={aiStrings.next}
>
<NextIcon />
</Button>
)}
<VolumeControl
volume={volume}
// eslint-disable-next-line no-shadow
setVolume={setVolume}
/>
{allowTracklistLoop && (
<Button
className={`ai-btn ai-btn-repeat ${cycleTracks &&
'ai-btn-active'}`}
onClick={toggleTracklistCycling}
ariaLabel={aiStrings.toggle_list_repeat}
>
<RefreshIcon />
</Button>
)}
{shuffleEnabled && (
<Button
className={`ai-btn ai-btn-shuffle ${shuffle &&
'ai-btn-active'}`}
onClick={toggleShuffle}
ariaLabel={aiStrings.shuffle}
>
<ShuffleIcon />
</Button>
)}
{allowPlaybackRate && (
<Button
className="ai-btn ai-btn-playback-rate"
onClick={setPlaybackRate}
ariaLabel={aiStrings.set_playback_rate}
>
<Fragment>&times;{playbackRate}</Fragment>
</Button>
)}
{skipAmount > 0 && (
<Fragment>
<Button
className="ai-btn ai-btn-skip-position"
onClick={() => skipPosition(-1)}
ariaLabel={aiStrings.skip_backward}
>
-{skipAmount}s
</Button>
<Button
className="ai-btn ai-btn-skip-position"
onClick={() => skipPosition(1)}
ariaLabel={aiStrings.skip_forward}
>
+{skipAmount}s
</Button>
</Fragment>
)}
{currentTrack && currentTrack.lyrics && !isTrackListOpen && (
<AppContext.Consumer>
{({ toggleLyricsModal }) => (
<Button
className="ai-btn ai-lyrics"
onClick={() => toggleLyricsModal(true, currentTrack)}
ariaLabel={aiStrings.open_track_lyrics}
title={aiStrings.open_track_lyrics}
>
<LyricsIcon />
</Button>
)}
</AppContext.Consumer>
)}
{allowTracklistToggle && (
<Button
className="ai-btn ai-tracklist-toggle"
onClick={toggleTracklist}
ariaLabel={aiStrings.toggle_list_visible}
ariaExpanded={isTrackListOpen}
>
<PlaylistIcon />
</Button>
)}
</div>
</div>
</div>
<div
className={`ai-tracklist-wrap ${
isTrackListOpen ? 'ai-tracklist-open' : ''
}`}
>
<TracklistWrap
className="ai-tracklist"
trackClassName="ai-track"
tracks={tracks}
activeTrackIndex={activeIndex}
isOpen={isTrackListOpen}
displayTrackNo={displayTrackNo}
displayCovers={displayTracklistCovers}
displayBuyButtons={displayBuyButtons}
buyButtonsTarget={buyButtonsTarget}
displayArtistNames={displayArtistNames}
reverseTrackOrder={reverseTrackOrder}
limitTracklistHeight={limitTracklistHeight}
tracklistHeight={tracklistHeight}
onTrackClick={playTrack}
onTrackLoop={allowTrackLoop ? setTrackCycling : undefined}
repeatingTrackIndex={repeatingTrackIndex}
/>
</div>
{playerButtons?.length > 0 && <PlayerButtons buttons={playerButtons} />}
{displayCredits && (
<div className="ai-footer">
<p>
Powered by{' '}
<a
href="https://www.cssigniter.com/plugins/audioigniter?utm_source=player&utm_medium=link&utm_content=audioigniter&utm_campaign=footer-link"
target="_blank"
rel="noopener noreferrer"
>
AudioIgniter
</a>
</p>
</div>
)}
</div>
);
};
Player.propTypes = propTypes;
export default soundProvider(Player, {
onFinishedPlaying(props) {
const {

View File

@ -6,6 +6,43 @@ import classNames from 'classnames';
import soundProvider from './soundProvider';
import Tracklist from './components/Tracklist';
import typographyDisabled from '../utils/typography-disabled';
import PlayerButtons from './components/PlayerButtons';
const propTypes = {
tracks: PropTypes.arrayOf(PropTypes.object),
playStatus: PropTypes.oneOf([
Sound.status.PLAYING,
Sound.status.PAUSED,
Sound.status.STOPPED,
]),
activeIndex: PropTypes.number,
position: PropTypes.number,
duration: PropTypes.number,
setPosition: PropTypes.func.isRequired,
togglePlay: PropTypes.func.isRequired,
setTrackCycling: PropTypes.func.isRequired,
allowTrackLoop: PropTypes.bool,
maxWidth: PropTypes.string,
reverseTrackOrder: PropTypes.bool,
displayTrackNo: PropTypes.bool,
buyButtonsTarget: PropTypes.bool,
displayArtistNames: PropTypes.bool,
displayBuyButtons: PropTypes.bool,
displayCredits: PropTypes.bool,
repeatingTrackIndex: PropTypes.number,
playbackRate: PropTypes.number,
setPlaybackRate: PropTypes.func,
allowPlaybackRate: PropTypes.bool,
buffering: PropTypes.bool,
playerButtons: PropTypes.arrayOf(
PropTypes.shape({
title: PropTypes.string,
url: PropTypes.string,
icon: PropTypes.string,
}).isRequired,
),
};
const SimplePlayer = props => {
const { playStatus } = props;
@ -48,6 +85,10 @@ const SimplePlayer = props => {
/>
</div>
{props.playerButtons?.length > 0 && (
<PlayerButtons buttons={props.playerButtons} />
)}
{props.displayCredits && (
<div className="ai-footer">
<p>
@ -66,34 +107,7 @@ const SimplePlayer = props => {
);
};
SimplePlayer.propTypes = {
tracks: PropTypes.arrayOf(PropTypes.object),
playStatus: PropTypes.oneOf([
Sound.status.PLAYING,
Sound.status.PAUSED,
Sound.status.STOPPED,
]),
activeIndex: PropTypes.number,
position: PropTypes.number,
duration: PropTypes.number,
setPosition: PropTypes.func.isRequired,
togglePlay: PropTypes.func.isRequired,
setTrackCycling: PropTypes.func.isRequired,
allowTrackLoop: PropTypes.bool,
maxWidth: PropTypes.string,
reverseTrackOrder: PropTypes.bool,
displayTrackNo: PropTypes.bool,
buyButtonsTarget: PropTypes.bool,
displayArtistNames: PropTypes.bool,
displayBuyButtons: PropTypes.bool,
displayCredits: PropTypes.bool,
repeatingTrackIndex: PropTypes.number,
playbackRate: PropTypes.number,
setPlaybackRate: PropTypes.func,
allowPlaybackRate: PropTypes.bool,
buffering: PropTypes.bool,
};
SimplePlayer.propTypes = propTypes;
export default soundProvider(SimplePlayer, {
onFinishedPlaying(props) {

View File

@ -1,6 +1,16 @@
import React from 'react';
import PropTypes from 'prop-types';
const propTypes = {
className: PropTypes.string,
onClick: PropTypes.func,
children: PropTypes.node,
ariaLabel: PropTypes.string,
ariaPressed: PropTypes.bool,
ariaExpanded: PropTypes.bool,
ariaControls: PropTypes.string,
};
const Button = ({
className,
onClick,
@ -22,14 +32,6 @@ const Button = ({
</button>
);
Button.propTypes = {
className: PropTypes.string,
onClick: PropTypes.func,
children: PropTypes.node,
ariaLabel: PropTypes.string,
ariaPressed: PropTypes.bool,
ariaExpanded: PropTypes.bool,
ariaControls: PropTypes.string,
};
Button.propTypes = propTypes;
export default Button;

View File

@ -1,7 +1,15 @@
import React from 'react';
import PropTypes from 'prop-types';
import { MusicNoteIcon } from './Icons';
const propTypes = {
className: PropTypes.string,
title: PropTypes.string,
src: PropTypes.string,
onClick: PropTypes.func,
};
const Cover = ({ className, title, src, onClick }) => (
<div
className={className + (src ? '' : ' ai-track-no-thumb')}
@ -11,11 +19,6 @@ const Cover = ({ className, title, src, onClick }) => (
</div>
);
Cover.propTypes = {
className: PropTypes.string,
title: PropTypes.string,
src: PropTypes.string,
onClick: PropTypes.func,
};
Cover.propTypes = propTypes;
export default Cover;

View File

@ -0,0 +1,44 @@
import React from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
const propTypes = {
className: PropTypes.string,
button: PropTypes.shape({
title: PropTypes.string,
url: PropTypes.string,
icon: PropTypes.string,
}).isRequired,
};
const PlayerButton = ({ className, button }) => {
const classes = classNames({
'ai-btn': true,
'ai-player-button': true,
'ai-player-button-icon-only': !button.title,
[className]: !!className,
});
return (
<a
href={button.url}
className={classes}
target="_blank"
rel="noopener noreferrer"
>
{button.icon && (
<span
className="ai-player-button-icon"
dangerouslySetInnerHTML={{ __html: button.icon }}
/>
)}
{button.title && (
<span className="ai-player-button-title">{button.title}</span>
)}
</a>
);
};
PlayerButton.propTypes = propTypes;
export default PlayerButton;

View File

@ -0,0 +1,35 @@
import React from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import PlayerButton from './PlayerButton';
const propTypes = {
className: PropTypes.string,
buttons: PropTypes.arrayOf(
PropTypes.shape({
title: PropTypes.string,
url: PropTypes.string,
icon: PropTypes.string,
}).isRequired,
).isRequired,
};
const PlayerButtons = ({ className, buttons }) => {
const classes = classNames({
'ai-player-buttons': true,
[className]: !!className,
});
return (
<div className={classes}>
{buttons.map((button, index) => {
return <PlayerButton key={index} button={button} />;
})}
</div>
);
};
PlayerButtons.propTypes = propTypes;
export default PlayerButtons;

View File

@ -1,15 +1,14 @@
import React from 'react';
import PropTypes from 'prop-types';
export default class ProgressBar extends React.Component {
constructor() {
super();
this.handleClick = this.handleClick.bind(this);
}
handleClick(event) {
const { duration, setPosition } = this.props;
const propTypes = {
setPosition: PropTypes.func,
position: PropTypes.number.isRequired,
duration: PropTypes.number.isRequired,
};
const ProgressBar = ({ position, duration, setPosition }) => {
const handleClick = event => {
if (setPosition == null) {
return;
}
@ -19,24 +18,18 @@ export default class ProgressBar extends React.Component {
const posX = offsetX / event.currentTarget.offsetWidth;
setPosition(posX * duration);
}
};
render() {
const { position, duration } = this.props;
return (
<span onClick={this.handleClick} className="ai-track-progress-bar">
<span
className="ai-track-progress"
style={{ width: `${(position * 100) / duration}%` }}
/>
</span>
);
}
}
ProgressBar.propTypes = {
setPosition: PropTypes.func,
position: PropTypes.number.isRequired,
duration: PropTypes.number.isRequired,
return (
<span onClick={handleClick} className="ai-track-progress-bar">
<span
className="ai-track-progress"
style={{ width: `${(position * 100) / duration}%` }}
/>
</span>
);
};
ProgressBar.propTypes = propTypes;
export default ProgressBar;

View File

@ -1,28 +1,21 @@
import React from 'react';
import React, { useState } from 'react';
import PropTypes from 'prop-types';
export default class Time extends React.Component {
constructor(props) {
super(props);
const propTypes = {
position: PropTypes.number.isRequired,
duration: PropTypes.number.isRequired,
countdown: PropTypes.bool.isRequired,
};
const { countdown } = this.props;
this.state = {
showRemaining: countdown || false,
};
this.handleClick = this.handleClick.bind(this);
}
const Time = ({ countdown, position, duration }) => {
const [showRemaining, setShowRemaining] = useState(countdown || false);
/**
* Pretty prints time remaining/elapsed
*
* @param {number} position - Track position in milliseconds
* @param {number} duration - Track duration in milliseconds
* @returns {string} - Time pretty formatted
*/
formatTime(position, duration) {
const { showRemaining } = this.state;
const renderFormattedTime = () => {
const positionInSeconds = showRemaining
? (duration - position) / 1000
: position / 1000;
@ -34,7 +27,7 @@ export default class Time extends React.Component {
min = min >= 10 ? min : `0${min}`;
sec = sec >= 10 ? sec : `0${sec}`;
if (!isNaN(sec)) {
if (!Number.isNaN(sec)) {
if (hours) {
time = `${hours}:${min}:${sec}`;
} else {
@ -43,26 +36,19 @@ export default class Time extends React.Component {
}
return showRemaining ? `-${time}` : time;
}
};
handleClick() {
const { showRemaining } = this.state;
this.setState({ showRemaining: !showRemaining });
}
const handleClick = () => {
setShowRemaining(x => !x);
};
render() {
const { position, duration } = this.props;
return (
<span className="ai-track-time" onClick={this.handleClick}>
{this.formatTime(position, duration)}
</span>
);
}
}
Time.propTypes = {
position: PropTypes.number.isRequired,
duration: PropTypes.number.isRequired,
countdown: PropTypes.bool.isRequired,
return (
<span className="ai-track-time" onClick={handleClick}>
{renderFormattedTime()}
</span>
);
};
Time.propTypes = propTypes;
export default Time;

View File

@ -11,6 +11,43 @@ import ProgressBar from './ProgressBar';
import { PlayIcon, PauseIcon } from './Icons';
import { AppContext } from '../../App';
const propTypes = {
track: PropTypes.shape({
audio: PropTypes.string,
buyUrl: PropTypes.string,
cover: PropTypes.string,
title: PropTypes.string,
subtitle: PropTypes.string,
lyrics: PropTypes.string,
downloadUrl: PropTypes.string,
downloadFilename: PropTypes.string,
}),
index: PropTypes.number.isRequired,
trackNo: PropTypes.number,
isActive: PropTypes.bool,
position: PropTypes.number,
duration: PropTypes.number,
setPosition: PropTypes.func,
playStatus: PropTypes.oneOf([
Sound.status.PLAYING,
Sound.status.PAUSED,
Sound.status.STOPPED,
]),
onTrackClick: PropTypes.func.isRequired,
onTrackLoop: PropTypes.func,
className: PropTypes.string.isRequired,
isStandalone: PropTypes.bool,
buyButtonsTarget: PropTypes.bool,
displayArtistNames: PropTypes.bool,
displayCovers: PropTypes.bool,
displayBuyButtons: PropTypes.bool,
isLooping: PropTypes.bool,
playbackRate: PropTypes.number,
setPlaybackRate: PropTypes.func,
allowPlaybackRate: PropTypes.bool,
buffering: PropTypes.bool,
};
const Track = ({
track,
index,
@ -114,40 +151,6 @@ const Track = ({
);
};
Track.propTypes = {
track: PropTypes.shape({
audio: PropTypes.string,
buyUrl: PropTypes.string,
cover: PropTypes.string,
title: PropTypes.string,
subtitle: PropTypes.string,
lyrics: PropTypes.string,
downloadUrl: PropTypes.string,
}),
index: PropTypes.number.isRequired,
trackNo: PropTypes.number,
isActive: PropTypes.bool,
position: PropTypes.number,
duration: PropTypes.number,
setPosition: PropTypes.func,
playStatus: PropTypes.oneOf([
Sound.status.PLAYING,
Sound.status.PAUSED,
Sound.status.STOPPED,
]),
onTrackClick: PropTypes.func.isRequired,
onTrackLoop: PropTypes.func,
className: PropTypes.string.isRequired,
isStandalone: PropTypes.bool,
buyButtonsTarget: PropTypes.bool,
displayArtistNames: PropTypes.bool,
displayCovers: PropTypes.bool,
displayBuyButtons: PropTypes.bool,
isLooping: PropTypes.bool,
playbackRate: PropTypes.number,
setPlaybackRate: PropTypes.func,
allowPlaybackRate: PropTypes.bool,
buffering: PropTypes.bool,
};
Track.propTypes = propTypes;
export default Track;

View File

@ -1,7 +1,23 @@
import React, { Fragment } from 'react';
import React from 'react';
import PropTypes from 'prop-types';
import { CartIcon, DownloadIcon, LyricsIcon, RefreshIcon } from './Icons';
const propTypes = {
buyButtonsTarget: PropTypes.bool,
buyUrl: PropTypes.string,
downloadUrl: PropTypes.string,
downloadFilename: PropTypes.string,
onTrackLoop: PropTypes.func,
isLooping: PropTypes.bool,
displayBuyButtons: PropTypes.bool,
onOpenTrackLyrics: PropTypes.func,
playbackRate: PropTypes.number,
setPlaybackRate: PropTypes.func,
allowPlaybackRate: PropTypes.bool,
isPlaying: PropTypes.bool,
};
const TrackButtons = ({
buyButtonsTarget,
buyUrl,
@ -28,6 +44,7 @@ const TrackButtons = ({
return (
<div className="ai-track-control-buttons">
{buyUrl && displayBuyButtons && (
// eslint-disable-next-line react/jsx-no-target-blank
<a
href={buyUrl}
className="ai-track-btn"
@ -83,7 +100,7 @@ const TrackButtons = ({
setPlaybackRate();
}}
>
<Fragment>&times;{playbackRate}</Fragment>
&times;{playbackRate}
</a>
)}
@ -113,19 +130,6 @@ const TrackButtons = ({
);
};
TrackButtons.propTypes = {
buyButtonsTarget: PropTypes.bool,
buyUrl: PropTypes.string,
downloadUrl: PropTypes.string,
downloadFilename: PropTypes.string,
onTrackLoop: PropTypes.func,
isLooping: PropTypes.bool,
displayBuyButtons: PropTypes.bool,
onOpenTrackLyrics: PropTypes.func,
playbackRate: PropTypes.number,
setPlaybackRate: PropTypes.func,
allowPlaybackRate: PropTypes.bool,
isPlaying: PropTypes.bool,
};
TrackButtons.propTypes = propTypes;
export default TrackButtons;

View File

@ -6,6 +6,12 @@ if (document.querySelector('.audioigniter-root')) {
Modal.setAppElement('.audioigniter-root');
}
const propTypes = {
isOpen: PropTypes.bool,
closeModal: PropTypes.func.isRequired,
children: PropTypes.any,
};
const TrackLyricsModal = ({ isOpen, closeModal, children }) => {
return (
<Modal
@ -32,12 +38,6 @@ const TrackLyricsModal = ({ isOpen, closeModal, children }) => {
);
};
const propTypes = {
isOpen: PropTypes.bool,
closeModal: PropTypes.func.isRequired,
children: PropTypes.any,
};
TrackLyricsModal.propTypes = propTypes;
export default TrackLyricsModal;

View File

@ -1,6 +1,14 @@
import React from 'react';
import PropTypes from 'prop-types';
const propTypes = {
track: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types
trackNo: PropTypes.number,
style: PropTypes.object, // eslint-disable-line react/forbid-prop-types
className: PropTypes.string,
displayArtistNames: PropTypes.bool,
};
const TrackTitle = ({
className,
style,
@ -25,12 +33,6 @@ const TrackTitle = ({
);
};
TrackTitle.propTypes = {
track: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types
trackNo: PropTypes.number,
style: PropTypes.object, // eslint-disable-line react/forbid-prop-types
className: PropTypes.string,
displayArtistNames: PropTypes.bool,
};
TrackTitle.propTypes = propTypes;
export default TrackTitle;

View File

@ -1,13 +1,43 @@
import React from 'react';
import PropTypes from 'prop-types';
import Sound from 'react-sound';
import Track from './Track';
const propTypes = {
tracks: PropTypes.arrayOf(PropTypes.object).isRequired,
playStatus: PropTypes.oneOf([
Sound.status.PLAYING,
Sound.status.PAUSED,
Sound.status.STOPPED,
]),
activeTrackIndex: PropTypes.number,
position: PropTypes.number,
duration: PropTypes.number,
setPosition: PropTypes.func,
standaloneTracks: PropTypes.bool,
onTrackClick: PropTypes.func.isRequired,
onTrackLoop: PropTypes.func,
className: PropTypes.string,
trackClassName: PropTypes.string,
reverseTrackOrder: PropTypes.bool,
displayTrackNo: PropTypes.bool,
displayBuyButtons: PropTypes.bool,
buyButtonsTarget: PropTypes.bool,
displayCovers: PropTypes.bool,
displayArtistNames: PropTypes.bool,
playbackRate: PropTypes.number,
setPlaybackRate: PropTypes.func,
allowPlaybackRate: PropTypes.bool,
buffering: PropTypes.bool,
repeatingTrackIndex: PropTypes.bool,
};
const Tracklist = ({ ...props }) => {
const { tracks } = props;
return (
<ul className={props.className} aria-expanded="true">
<ul className={props.className}>
{tracks &&
tracks.map((track, index) => {
const trackNo = props.reverseTrackOrder
@ -46,32 +76,6 @@ const Tracklist = ({ ...props }) => {
);
};
Tracklist.propTypes = {
tracks: PropTypes.arrayOf(PropTypes.object).isRequired,
playStatus: PropTypes.oneOf([
Sound.status.PLAYING,
Sound.status.PAUSED,
Sound.status.STOPPED,
]),
activeTrackIndex: PropTypes.number,
position: PropTypes.number,
duration: PropTypes.number,
setPosition: PropTypes.func,
standaloneTracks: PropTypes.bool,
onTrackClick: PropTypes.func.isRequired,
onTrackLoop: PropTypes.func,
className: PropTypes.string,
trackClassName: PropTypes.string,
reverseTrackOrder: PropTypes.bool,
displayTrackNo: PropTypes.bool,
displayBuyButtons: PropTypes.bool,
buyButtonsTarget: PropTypes.bool,
displayCovers: PropTypes.bool,
displayArtistNames: PropTypes.bool,
playbackRate: PropTypes.number,
setPlaybackRate: PropTypes.func,
allowPlaybackRate: PropTypes.bool,
buffering: PropTypes.bool,
};
Tracklist.propTypes = propTypes;
export default Tracklist;

View File

@ -1,81 +1,10 @@
import React from 'react';
import React, { useEffect, useRef } from 'react';
import PropTypes from 'prop-types';
import { Scrollbars } from 'react-custom-scrollbars';
import Tracklist from './Tracklist';
export default class TracklistWrap extends React.Component {
componentWillReceiveProps(nextProps) {
const { activeTrackIndex, limitTracklistHeight } = this.props;
if (
activeTrackIndex !== nextProps.activeTrackIndex &&
limitTracklistHeight
) {
this.scrollToTrack(nextProps.activeTrackIndex);
}
}
scrollToTrack(trackIndex) {
const { tracks } = this.props;
const trackHeight = this.scrollbarsRef.getScrollHeight() / tracks.length;
if (!this.isTrackVisible(trackIndex)) {
this.scrollbarsRef.scrollTop(trackHeight * trackIndex);
}
}
isTrackVisible(trackIndex) {
const { tracks } = this.props;
const trackHeight = this.scrollbarsRef.getScrollHeight() / tracks.length;
const trackPosition = trackHeight * trackIndex;
const scrollTop = this.scrollbarsRef.getScrollTop();
const scrollBottom = scrollTop + this.scrollbarsRef.getClientHeight();
return !(trackPosition < scrollTop || trackPosition > scrollBottom);
}
renderTracklist() {
return (
<Tracklist
tracks={this.props.tracks}
activeTrackIndex={this.props.activeTrackIndex}
onTrackClick={this.props.onTrackClick}
className={this.props.className}
trackClassName={this.props.trackClassName}
reverseTrackOrder={this.props.reverseTrackOrder}
displayTrackNo={this.props.displayTrackNo}
displayBuyButtons={this.props.displayBuyButtons}
buyButtonsTarget={this.props.buyButtonsTarget}
displayCovers={this.props.displayCovers}
displayArtistNames={this.props.displayArtistNames}
onTrackLoop={this.props.onTrackLoop}
repeatingTrackIndex={this.props.repeatingTrackIndex}
/>
);
}
render() {
const { isOpen, limitTracklistHeight, tracklistHeight } = this.props;
return (
<div id="tracklisting" style={{ display: isOpen ? 'block' : 'none' }}>
{limitTracklistHeight ? (
<Scrollbars
className="ai-scroll-wrap"
ref={ref => (this.scrollbarsRef = ref)} // eslint-disable-line no-return-assign
style={{ height: tracklistHeight }}
>
{this.renderTracklist()}
</Scrollbars>
) : (
this.renderTracklist()
)}
</div>
);
}
}
TracklistWrap.propTypes = {
const propTypes = {
tracks: PropTypes.arrayOf(PropTypes.object).isRequired,
activeTrackIndex: PropTypes.number.isRequired,
onTrackClick: PropTypes.func.isRequired,
@ -93,3 +22,87 @@ TracklistWrap.propTypes = {
onTrackLoop: PropTypes.func,
repeatingTrackIndex: PropTypes.number,
};
const TracklistWrap = ({
isOpen,
limitTracklistHeight,
tracklistHeight,
tracks,
activeTrackIndex,
onTrackClick,
onTrackLoop,
className,
reverseTrackOrder,
trackClassName,
displayTrackNo,
displayBuyButtons,
buyButtonsTarget,
displayCovers,
displayArtistNames,
repeatingTrackIndex,
}) => {
const scrollbarRef = useRef(null);
const isTrackVisible = trackIndex => {
const trackHeight = scrollbarRef.current.getScrollHeight() / tracks.length;
const trackPosition = trackHeight * trackIndex;
const scrollTop = scrollbarRef.current.getScrollTop();
const scrollBottom = scrollTop + scrollbarRef.current.getClientHeight();
return !(trackPosition < scrollTop || trackPosition > scrollBottom);
};
const scrollToTrack = trackIndex => {
const trackHeight = scrollbarRef.current.getScrollHeight() / tracks.length;
if (!isTrackVisible(trackIndex)) {
scrollbarRef.current.scrollTop(trackHeight * trackIndex);
}
};
useEffect(() => {
if (limitTracklistHeight) {
scrollToTrack(activeTrackIndex);
}
}, [activeTrackIndex, limitTracklistHeight]);
const renderTracklist = () => {
return (
<Tracklist
tracks={tracks}
activeTrackIndex={activeTrackIndex}
onTrackClick={onTrackClick}
className={className}
trackClassName={trackClassName}
reverseTrackOrder={reverseTrackOrder}
displayTrackNo={displayTrackNo}
displayBuyButtons={displayBuyButtons}
buyButtonsTarget={buyButtonsTarget}
displayCovers={displayCovers}
displayArtistNames={displayArtistNames}
onTrackLoop={onTrackLoop}
repeatingTrackIndex={repeatingTrackIndex}
/>
);
};
return (
<div id="tracklisting" style={{ display: isOpen ? 'block' : 'none' }}>
{limitTracklistHeight ? (
<Scrollbars
className="ai-scroll-wrap"
ref={scrollbarRef} // eslint-disable-line no-return-assign
style={{ height: tracklistHeight }}
>
{renderTracklist()}
</Scrollbars>
) : (
renderTracklist()
)}
</div>
);
};
TracklistWrap.propTypes = propTypes;
export default TracklistWrap;

View File

@ -1,12 +1,16 @@
import React from 'react';
import PropTypes from 'prop-types';
import Button from './Button';
import { VolumeUpIcon, VolumeDownIcon } from './Icons';
export default class VolumeControl extends React.Component {
renderVolumeBars() {
const { volume, setVolume } = this.props;
const propTypes = {
volume: PropTypes.number.isRequired,
setVolume: PropTypes.func.isRequired,
};
const VolumeControl = ({ volume, setVolume }) => {
const renderVolumeBars = () => {
return Array(...Array(11)).map((bar, i) => (
<span
key={i} // eslint-disable-line react/no-array-index-key
@ -16,37 +20,32 @@ export default class VolumeControl extends React.Component {
onClick={() => setVolume(i * 10)}
/>
));
}
};
render() {
const { volume, setVolume } = this.props;
return (
<div className="ai-audio-volume-control">
<div className="ai-audio-volume-bars">{renderVolumeBars()}</div>
return (
<div className="ai-audio-volume-control">
<div className="ai-audio-volume-bars">{this.renderVolumeBars()}</div>
<div className="ai-audio-volume-control-btns">
<Button
className="ai-btn"
onClick={() => setVolume(volume >= 100 ? volume : volume + 10)}
aria-label={aiStrings.volume_up}
>
<VolumeUpIcon />
</Button>
<Button
className="ai-btn"
onClick={() => setVolume(volume <= 0 ? volume : volume - 10)}
aria-label={aiStrings.volume_down}
>
<VolumeDownIcon />
</Button>
</div>
<div className="ai-audio-volume-control-btns">
<Button
className="ai-btn"
onClick={() => setVolume(volume >= 100 ? volume : volume + 10)}
aria-label={aiStrings.volume_up}
>
<VolumeUpIcon />
</Button>
<Button
className="ai-btn"
onClick={() => setVolume(volume <= 0 ? volume : volume - 10)}
aria-label={aiStrings.volume_down}
>
<VolumeDownIcon />
</Button>
</div>
);
}
}
VolumeControl.propTypes = {
volume: PropTypes.number.isRequired,
setVolume: PropTypes.func.isRequired,
</div>
);
};
VolumeControl.propTypes = propTypes;
export default VolumeControl;

View File

@ -5,6 +5,7 @@ import Sound from 'react-sound';
import SoundCloud from '../utils/soundcloud';
import multiSoundDisabled from '../utils/multi-sound-disabled';
import { getInitialTrackQueueAndIndex } from '../utils/getInitialTrackIndex';
import playerStorage from '../utils/playerStorage';
const PLAYBACK_RATES = [0.5, 0.75, 1, 1.25, 1.5, 2, 3];
@ -14,12 +15,15 @@ const soundProvider = (Player, events) => {
super(props);
const {
playerId,
volume,
cycleTracks,
defaultShuffle,
shuffleEnabled,
} = this.props;
const initialData = playerStorage.get(playerId);
this.state = {
tracks: [],
activeIndex: 0, // Determine active track by index
@ -30,7 +34,7 @@ const soundProvider = (Player, events) => {
// [4, 2, 0, ...] will play the 5th track first, 3rd second, then the 1st, etc.
trackQueue: [],
playStatus: Sound.status.STOPPED,
position: 0,
position: initialData?.position ?? 0,
duration: 0,
playbackRate: 1,
volume: volume == null ? 100 : volume,
@ -61,19 +65,39 @@ const soundProvider = (Player, events) => {
componentDidMount() {
const {
playerId,
tracksUrl,
soundcloudClientId,
reverseTrackOrder,
initialTrack,
rememberLastPosition,
track,
} = this.props;
const { shuffle } = this.state;
const initialData = playerStorage.get(playerId);
// We have a standalone track (from the shortcode).
if (track) {
try {
this.setState({
tracks: [JSON.parse(track)],
});
return;
// eslint-disable-next-line no-empty
} catch {}
}
const tracksPromised = fetch(tracksUrl).then(res => res.json());
if (!soundcloudClientId) {
tracksPromised.then(tracks => {
const { trackQueue, activeIndex } = getInitialTrackQueueAndIndex({
tracks,
initialTrack,
initialTrack:
initialData?.activeIndex && rememberLastPosition
? initialData.activeIndex + 1
: initialTrack,
reverseTrackOrder,
shuffle,
});
@ -139,12 +163,23 @@ const soundProvider = (Player, events) => {
// Events
onPlaying({ duration, position }) {
const { activeIndex } = this.state;
const { playerId, rememberLastPosition } = this.props;
this.setState(
() => ({ duration, position }),
() => {
if (events && events.onPlaying) {
events.onPlaying(this.getFinalProps());
}
// Store last position every 5 seconds
if (playerId && rememberLastPosition && position % 5000 < 300) {
playerStorage.set(playerId, {
position,
activeIndex,
});
}
},
);
}
@ -399,9 +434,11 @@ const soundProvider = (Player, events) => {
}
EnhancedPlayer.propTypes = {
playerId: PropTypes.string,
volume: PropTypes.number,
cycleTracks: PropTypes.bool,
tracksUrl: PropTypes.string,
track: PropTypes.string,
soundcloudClientId: PropTypes.string,
reverseTrackOrder: PropTypes.bool,
skipAmount: PropTypes.number,
@ -410,6 +447,7 @@ const soundProvider = (Player, events) => {
initialTrack: PropTypes.number,
shuffleEnabled: PropTypes.bool,
defaultShuffle: PropTypes.bool,
rememberLastPosition: PropTypes.bool,
};
return EnhancedPlayer;

View File

@ -0,0 +1,39 @@
/**
* Very basic local storage facade with the ability to store objects.
*
* @type {{set: playerStorage.set, get: ((function(*): (undefined))|*)}}
*/
const playerStorage = {
set: (key, value) => {
if (!key || !value) {
return;
}
if (typeof value === 'object') {
window.localStorage.setItem(key, JSON.stringify(value));
} else {
window.localStorage.setItem(key, value);
}
},
get: key => {
const value = localStorage.getItem(key);
if (!value) {
return undefined;
}
try {
const parsed = JSON.parse(value);
if (parsed && typeof parsed === 'object') {
return parsed;
}
} catch {
return value;
}
return value;
},
};
export default playerStorage;

View File

@ -0,0 +1,62 @@
/**
* @file React hook for determining the size of a component.
*
* Forked from https://github.com/rehooks/component-size.
*/
import { useCallback, useState, useLayoutEffect } from 'react';
const getSize = el => {
if (!el) {
return {
width: 0,
height: 0,
};
}
return {
width: el.offsetWidth,
height: el.offsetHeight,
};
};
const useComponentSize = ref => {
const [ComponentSize, setComponentSize] = useState(
getSize(ref ? ref.current : {}),
);
const handleResize = useCallback(() => {
if (ref.current) {
setComponentSize(getSize(ref.current));
}
}, [ref]);
useLayoutEffect(() => {
if (!ref.current) {
return undefined;
}
handleResize();
if (typeof ResizeObserver === 'function') {
// eslint-disable-next-line no-undef
let resizeObserver = new ResizeObserver(() => handleResize());
resizeObserver.observe(ref.current);
return () => {
resizeObserver.disconnect(ref.current);
resizeObserver = null;
};
}
window.addEventListener('resize', handleResize);
return () => {
window.removeEventListener('resize', handleResize);
};
}, [ref.current]);
return ComponentSize;
};
export default useComponentSize;

View File

@ -597,6 +597,52 @@ $screen-xs-max: 320px !default;
}
}
//
// Player buttons
//
.ai-player-buttons {
display: flex;
align-items: center;
flex-wrap: wrap;
line-height: normal;
grid-gap: calc($spacing / 2);
margin-top: $spacing;
}
.ai-player-button {
display: flex;
align-items: center;
justify-content: center;
grid-gap: 6px;
font-size: 13px;
color: currentColor;
width: auto;
padding-left: 15px;
padding-right: 15px;
}
.ai-player-button-icon-only {
padding: 0;
width: 32px;
}
.ai-player-button-icon {
width: 18px;
height: 18px;
display: block;
svg {
width: 100%;
height: 100%;
fill: currentColor;
rect,
path {
fill: currentColor;
}
}
}
//
// Footer
//
@ -979,16 +1025,16 @@ $screen-xs-max: 320px !default;
}
.ai-audio-controls-main {
padding-left: $spacing / 2;
padding-right: $spacing / 2;
padding-left: calc($spacing / 2);
padding-right: calc($spacing / 2);
}
.ai-track-info {
padding-left: $spacing / 2;
padding-left: calc($spacing / 2);
}
.ai-audio-controls-meta {
margin-left: $spacing / 2;
margin-left: calc($spacing / 2);
}
}

View File

@ -28,7 +28,9 @@ const common = {
}),
],
devServer: {
contentBase: path.resolve('assets'),
static: {
directory: path.resolve('assets'),
},
},
resolve: {
extensions: ['.js', '.jsx'],
@ -37,7 +39,8 @@ const common = {
rules: [
{
test: /\.jsx?$/,
use: ['babel-loader?cacheDirectory'],
exclude: /node_modules/,
loader: 'babel-loader',
},
],
},
@ -51,6 +54,7 @@ switch (TARGET) {
config = merge(
common,
{
mode: 'production',
resolve: {
modules: [path.resolve(__dirname), 'node_modules'],
extensions: ['.js', '.jsx'],
@ -66,6 +70,7 @@ switch (TARGET) {
config = merge(
common,
{
mode: 'development',
devtool: 'eval-source-map',
},
parts.setupSass(PATHS.style),

View File

@ -1,5 +1,6 @@
const webpack = require('webpack');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const TerserPlugin = require('terser-webpack-plugin');
const autoprefixer = require('autoprefixer');
exports.setupSass = paths => ({
@ -19,48 +20,34 @@ exports.extractCSS = paths => ({
rules: [
{
test: /\.scss$/,
use: ExtractTextPlugin.extract({
fallback: 'style-loader',
use: [
{
loader: 'css-loader',
options: {
minimize: true,
},
},
{
loader: 'postcss-loader',
options: {
plugins: () => [
use: [
MiniCssExtractPlugin.loader,
{
loader: 'css-loader',
},
{
loader: 'postcss-loader',
options: {
postcssOptions: {
plugins: [
autoprefixer({
browsers: [
'Chrome >= 46',
'Firefox ESR',
'Edge >= 12',
'Explorer >= 9',
'iOS >= 8',
'Safari >= 8',
'Android >= 4',
],
browsers: ['last 2 versions', '>1%'],
cascade: false,
}),
],
},
},
{
loader: 'sass-loader',
options: {
outputStyle: 'expanded',
},
},
],
}),
},
{
loader: 'sass-loader',
},
],
include: paths,
},
],
},
plugins: [
new ExtractTextPlugin({
new MiniCssExtractPlugin({
filename: '[name].css',
}),
],
@ -68,33 +55,25 @@ exports.extractCSS = paths => ({
exports.devServer = options => ({
devServer: {
contentBase: __dirname,
static:{
directory: __dirname,
},
historyApiFallback: true,
hot: false,
inline: true,
stats: 'errors-only',
host: options.host,
port: options.port,
overlay: {
warnings: true,
errors: true,
},
},
});
exports.minify = () => ({
plugins: [
new webpack.optimize.UglifyJsPlugin({
compress: {
warnings: false,
drop_console: true,
screw_ie8: true,
},
output: {
comments: false,
},
}),
],
optimization: {
minimize: true,
minimizer: [
new TerserPlugin({
parallel: false,
}),
],
},
});
exports.setFreeVariable = (key, value) => {

File diff suppressed because it is too large Load Diff