pifi 0.4.11 → 0.4.12

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b4dc79fc2366bf2fa58a43af2c9a5771f47407bf57c3935a293fb947417cc8a3
4
- data.tar.gz: 5de137d51ec40045814c8f9e58cd4ae41b5e8bb52009d1030fffc606ec3eb8c3
3
+ metadata.gz: 0cc958e39e24a421db5d4844d545788ca48b8fcc641c7798be51f351b5a5fb07
4
+ data.tar.gz: b1e0423a339d9b0c33d4bb8054e8b8cab2069535f601e1c688cfacd385979062
5
5
  SHA512:
6
- metadata.gz: 6a97c702c8d9fd8819bf7b9e375096043520b9a16e279a2274621f5406cf0fae701e1f486e68ac9857cc118962507a816392f47099ca1bcaa4042064356181b6
7
- data.tar.gz: bbdee92c38748951cd8f4106b709fc1c17d2c40e9933145c13d9f69e34acc23a44edbeeb4dfbb4d35c00982a0a933808607526b9a6267c7ff519323d43929c84
6
+ metadata.gz: ded815507746674af611e5f97f4d49481863b5fd834973879ac4a7278bd1f79cfeaf4116cdf5fad645286e9c1017d358c1e2f2eb7f0f1ce774954ceb275014cb
7
+ data.tar.gz: b77605e7f9455a36d098c5227dc4430cfcc6adb44fe5b61ff060ee3a58502e3e715127bebedc6f41ea5950435da7ed64c8c7c2949c198f8a7b1aca437fd10db6
data/README.md CHANGED
@@ -17,6 +17,7 @@
17
17
  - [PiFi configuration](#pifi-configuration)
18
18
  - [Usage](#usage)
19
19
  - [Help me translate](#help-me-translate)
20
+ - [Some projects using PiFi Radio](#some-projects-using-pifi-radio)
20
21
  - [Credits](#credits)
21
22
  - [Buy me a coffee](#buy-me-a-coffee)
22
23
  - [License](#license)
@@ -141,7 +142,7 @@ You can help me adding a new language to PiFi or improving an existing translati
141
142
 
142
143
  The default language for PiFi is English, so you should use it as a reference.
143
144
 
144
- 1. Open [the translation file](https://github.com/rccavalcanti/pifi-radio/tree/master/frontend/public/locales) and [the English file](https://github.com/rccavalcanti/pifi-radio/blob/master/frontend/public/locales/en/translation.json).
145
+ 1. Open [the translation file](https://github.com/rccavalcanti/pifi-radio/tree/master/frontend/public/locales) and [the English file](https://github.com/rccavalcanti/pifi-radio/blob/master/frontend/public/locales/en-US/translation.json).
145
146
  2. Fill empty strings and fix any bad translations.
146
147
  3. Send a pull request.
147
148
 
@@ -151,6 +152,10 @@ The default language for PiFi is English, so you should use it as a reference.
151
152
  2. On `translation.json`, translate the strings. If you aren't sure of some, please make them empty.
152
153
  3. Send a pull request.
153
154
 
155
+ ## Some projects using PiFi Radio
156
+
157
+ - [Internet Radio from broken Bose Dockstation](https://piamble.wordpress.com/2021/01/30/internet-radio-from-broken-bose-dockstation-part-1/)
158
+
154
159
  ## Credits
155
160
 
156
161
  - de-de: [cedege](https://github.com/cedege)
@@ -170,4 +175,4 @@ If you find PiFi useful, you can show your support here:
170
175
 
171
176
  Licensed under [GPLv3](LICENSE)
172
177
 
173
- Copyright (C) 2017-2020 [Rafael Cavalcanti](https://rafaelc.org/)
178
+ Copyright (C) 2017-2021 [Rafael Cavalcanti](https://rafaelc.org/)
@@ -1,15 +1,15 @@
1
1
  {
2
2
  "files": {
3
3
  "main.css": "/static/css/main.48912641.chunk.css",
4
- "main.js": "/static/js/main.21a62959.chunk.js",
5
- "main.js.map": "/static/js/main.21a62959.chunk.js.map",
4
+ "main.js": "/static/js/main.11609572.chunk.js",
5
+ "main.js.map": "/static/js/main.11609572.chunk.js.map",
6
6
  "runtime-main.js": "/static/js/runtime-main.5aae5a31.js",
7
7
  "runtime-main.js.map": "/static/js/runtime-main.5aae5a31.js.map",
8
8
  "static/css/2.1781c263.chunk.css": "/static/css/2.1781c263.chunk.css",
9
9
  "static/js/2.6c0ded8a.chunk.js": "/static/js/2.6c0ded8a.chunk.js",
10
10
  "static/js/2.6c0ded8a.chunk.js.map": "/static/js/2.6c0ded8a.chunk.js.map",
11
11
  "index.html": "/index.html",
12
- "precache-manifest.7164537a626dea8757b9c96b06c8c33d.js": "/precache-manifest.7164537a626dea8757b9c96b06c8c33d.js",
12
+ "precache-manifest.7acd7ef058033a1ded72e03e499bd33f.js": "/precache-manifest.7acd7ef058033a1ded72e03e499bd33f.js",
13
13
  "service-worker.js": "/service-worker.js",
14
14
  "static/css/2.1781c263.chunk.css.map": "/static/css/2.1781c263.chunk.css.map",
15
15
  "static/css/main.48912641.chunk.css.map": "/static/css/main.48912641.chunk.css.map",
@@ -22,6 +22,6 @@
22
22
  "static/css/2.1781c263.chunk.css",
23
23
  "static/js/2.6c0ded8a.chunk.js",
24
24
  "static/css/main.48912641.chunk.css",
25
- "static/js/main.21a62959.chunk.js"
25
+ "static/js/main.11609572.chunk.js"
26
26
  ]
27
27
  }
@@ -1 +1 @@
1
- <!doctype html><html><head><meta charset="utf-8"/><link rel="icon" href="/favicon.ico"/><meta name="viewport" content="width=device-width,initial-scale=1"><meta name="description" content="MPD web client to listen to radio"/><link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png"><link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png"><link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png"><link rel="manifest" href="/manifest.json"><link rel="mask-icon" href="/safari-pinned-tab.svg" color="#5bbad5"><meta name="apple-mobile-web-app-title" content="PiFi Radio"><meta name="application-name" content="PiFi Radio"><meta name="msapplication-TileColor" content="#2b5797"><meta name="theme-color" content="#375a7f"><title>PiFi Radio</title><link rel="preload" href="https://stackpath.bootstrapcdn.com/bootswatch/4.4.1/darkly/bootstrap.min.css" as="style"><link href="https://stackpath.bootstrapcdn.com/bootswatch/4.4.1/darkly/bootstrap.min.css" rel="stylesheet" title="theme"><link href="/static/css/2.1781c263.chunk.css" rel="stylesheet"><link href="/static/css/main.48912641.chunk.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div><script>var isIE=window.document.documentMode,unsupportedMsg='<img src="/assets/logo80.png" class="mx-auto d-block my-5">';if(unsupportedMsg+='<h5 class="text-center my-5">This browser is not supported</h5>',isIE){var root=document.getElementById("root");root.innerHTML=unsupportedMsg,root.setAttribute("id","")}</script><script>!function(e){function r(r){for(var n,f,i=r[0],l=r[1],p=r[2],c=0,s=[];c<i.length;c++)f=i[c],Object.prototype.hasOwnProperty.call(o,f)&&o[f]&&s.push(o[f][0]),o[f]=0;for(n in l)Object.prototype.hasOwnProperty.call(l,n)&&(e[n]=l[n]);for(a&&a(r);s.length;)s.shift()();return u.push.apply(u,p||[]),t()}function t(){for(var e,r=0;r<u.length;r++){for(var t=u[r],n=!0,i=1;i<t.length;i++){var l=t[i];0!==o[l]&&(n=!1)}n&&(u.splice(r--,1),e=f(f.s=t[0]))}return e}var n={},o={1:0},u=[];function f(r){if(n[r])return n[r].exports;var t=n[r]={i:r,l:!1,exports:{}};return e[r].call(t.exports,t,t.exports,f),t.l=!0,t.exports}f.m=e,f.c=n,f.d=function(e,r,t){f.o(e,r)||Object.defineProperty(e,r,{enumerable:!0,get:t})},f.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},f.t=function(e,r){if(1&r&&(e=f(e)),8&r)return e;if(4&r&&"object"==typeof e&&e&&e.__esModule)return e;var t=Object.create(null);if(f.r(t),Object.defineProperty(t,"default",{enumerable:!0,value:e}),2&r&&"string"!=typeof e)for(var n in e)f.d(t,n,function(r){return e[r]}.bind(null,n));return t},f.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return f.d(r,"a",r),r},f.o=function(e,r){return Object.prototype.hasOwnProperty.call(e,r)},f.p="/";var i=this.webpackJsonppifi_frontend=this.webpackJsonppifi_frontend||[],l=i.push.bind(i);i.push=r,i=i.slice();for(var p=0;p<i.length;p++)r(i[p]);var a=l;t()}([])</script><script src="/static/js/2.6c0ded8a.chunk.js"></script><script src="/static/js/main.21a62959.chunk.js"></script></body></html>
1
+ <!doctype html><html><head><meta charset="utf-8"/><link rel="icon" href="/favicon.ico"/><meta name="viewport" content="width=device-width,initial-scale=1"><meta name="description" content="MPD web client to listen to radio"/><link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png"><link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png"><link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png"><link rel="manifest" href="/manifest.json"><link rel="mask-icon" href="/safari-pinned-tab.svg" color="#5bbad5"><meta name="apple-mobile-web-app-title" content="PiFi Radio"><meta name="application-name" content="PiFi Radio"><meta name="msapplication-TileColor" content="#2b5797"><meta name="theme-color" content="#375a7f"><title>PiFi Radio</title><link rel="preload" href="https://stackpath.bootstrapcdn.com/bootswatch/4.4.1/darkly/bootstrap.min.css" as="style"><link href="https://stackpath.bootstrapcdn.com/bootswatch/4.4.1/darkly/bootstrap.min.css" rel="stylesheet" title="theme"><link href="/static/css/2.1781c263.chunk.css" rel="stylesheet"><link href="/static/css/main.48912641.chunk.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div><script>var isIE=window.document.documentMode,unsupportedMsg='<img src="/assets/logo80.png" class="mx-auto d-block my-5">';if(unsupportedMsg+='<h5 class="text-center my-5">This browser is not supported</h5>',isIE){var root=document.getElementById("root");root.innerHTML=unsupportedMsg,root.setAttribute("id","")}</script><script>!function(e){function r(r){for(var n,f,i=r[0],l=r[1],p=r[2],c=0,s=[];c<i.length;c++)f=i[c],Object.prototype.hasOwnProperty.call(o,f)&&o[f]&&s.push(o[f][0]),o[f]=0;for(n in l)Object.prototype.hasOwnProperty.call(l,n)&&(e[n]=l[n]);for(a&&a(r);s.length;)s.shift()();return u.push.apply(u,p||[]),t()}function t(){for(var e,r=0;r<u.length;r++){for(var t=u[r],n=!0,i=1;i<t.length;i++){var l=t[i];0!==o[l]&&(n=!1)}n&&(u.splice(r--,1),e=f(f.s=t[0]))}return e}var n={},o={1:0},u=[];function f(r){if(n[r])return n[r].exports;var t=n[r]={i:r,l:!1,exports:{}};return e[r].call(t.exports,t,t.exports,f),t.l=!0,t.exports}f.m=e,f.c=n,f.d=function(e,r,t){f.o(e,r)||Object.defineProperty(e,r,{enumerable:!0,get:t})},f.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},f.t=function(e,r){if(1&r&&(e=f(e)),8&r)return e;if(4&r&&"object"==typeof e&&e&&e.__esModule)return e;var t=Object.create(null);if(f.r(t),Object.defineProperty(t,"default",{enumerable:!0,value:e}),2&r&&"string"!=typeof e)for(var n in e)f.d(t,n,function(r){return e[r]}.bind(null,n));return t},f.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return f.d(r,"a",r),r},f.o=function(e,r){return Object.prototype.hasOwnProperty.call(e,r)},f.p="/";var i=this.webpackJsonppifi_frontend=this.webpackJsonppifi_frontend||[],l=i.push.bind(i);i.push=r,i=i.slice();for(var p=0;p<i.length;p++)r(i[p]);var a=l;t()}([])</script><script src="/static/js/2.6c0ded8a.chunk.js"></script><script src="/static/js/main.11609572.chunk.js"></script></body></html>
@@ -1,6 +1,6 @@
1
1
  self.__precacheManifest = (self.__precacheManifest || []).concat([
2
2
  {
3
- "revision": "19b492bc2ecc8c8ce1fcaa3d2d7ce285",
3
+ "revision": "7f9522f73b77875baa1b55e081156d27",
4
4
  "url": "/index.html"
5
5
  },
6
6
  {
@@ -8,7 +8,7 @@ self.__precacheManifest = (self.__precacheManifest || []).concat([
8
8
  "url": "/static/css/2.1781c263.chunk.css"
9
9
  },
10
10
  {
11
- "revision": "ab3bef23cc7a2926b1ca",
11
+ "revision": "d7bddd229628dd8638a5",
12
12
  "url": "/static/css/main.48912641.chunk.css"
13
13
  },
14
14
  {
@@ -20,8 +20,8 @@ self.__precacheManifest = (self.__precacheManifest || []).concat([
20
20
  "url": "/static/js/2.6c0ded8a.chunk.js.LICENSE.txt"
21
21
  },
22
22
  {
23
- "revision": "ab3bef23cc7a2926b1ca",
24
- "url": "/static/js/main.21a62959.chunk.js"
23
+ "revision": "d7bddd229628dd8638a5",
24
+ "url": "/static/js/main.11609572.chunk.js"
25
25
  },
26
26
  {
27
27
  "revision": "af7ed8886109d8666769",
@@ -14,7 +14,7 @@
14
14
  importScripts("https://storage.googleapis.com/workbox-cdn/releases/4.3.1/workbox-sw.js");
15
15
 
16
16
  importScripts(
17
- "/precache-manifest.7164537a626dea8757b9c96b06c8c33d.js"
17
+ "/precache-manifest.7acd7ef058033a1ded72e03e499bd33f.js"
18
18
  );
19
19
 
20
20
  self.addEventListener('message', (event) => {
@@ -0,0 +1,2 @@
1
+ (this.webpackJsonppifi_frontend=this.webpackJsonppifi_frontend||[]).push([[0],{13:function(e){e.exports=JSON.parse('{"b":["en-US","de-DE","fr-FR","nl-NL","pl-PL","pt-BR"],"c":1000,"a":2500,"d":3000}')},30:function(e,t,a){e.exports=a.p+"static/media/logo.91554ce9.svg"},50:function(e,t,a){e.exports=a(94)},56:function(e,t,a){},79:function(e,t,a){},80:function(e,t,a){},83:function(e,t,a){},84:function(e,t,a){},85:function(e,t,a){},86:function(e,t,a){},87:function(e,t,a){},89:function(e,t,a){},94:function(e,t,a){"use strict";a.r(t);var n=a(0),r=a.n(n),l=a(12),o=a.n(l),c=a(2),s=a.n(c),i=a(8),u=a(9),m=a(10),d=a(19),p=a(20),b=a(3),f=a(98),v=a(26),h=(a(56),a(30)),g=a.n(h),y=function(){var e=Object(v.a)().t;return r.a.createElement("nav",{className:"navbar navbar-expand-sm navbar-dark bg-primary"},r.a.createElement("div",{className:"container"},r.a.createElement("a",{className:"navbar-brand",href:"/"},r.a.createElement("img",{src:g.a,width:"30",height:"30",className:"d-inline-block align-text-bottom mr-2",alt:""}),"PiFi Radio"),r.a.createElement("button",{className:"navbar-toggler",type:"button","data-toggle":"collapse","data-target":"#navbarNavAltMarkup","aria-controls":"navbarNavAltMarkup","aria-expanded":"false","aria-label":"Toggle navigation"},r.a.createElement("span",{className:"navbar-toggler-icon"})),r.a.createElement("div",{className:"collapse navbar-collapse",id:"navbarNavAltMarkup"},r.a.createElement("div",{className:"navbar-nav pt-1 ml-2"},r.a.createElement("button",{className:"btn btn-link nav-link","data-toggle":"modal","data-target":"#url-dialog"},e("playURL")),r.a.createElement("button",{className:"btn btn-link nav-link","data-toggle":"modal","data-target":"#settings"},e("settings")),r.a.createElement("button",{className:"btn btn-link nav-link","data-toggle":"modal","data-target":"#about"},e("about"))))))},E=a(15),k=a(48),N=a(44),S=a.n(N),w=function(e){var t=e.id,a=e.title,n=e.footer,l=e.children,o=Object(v.a)().t,c=r.a.createElement("button",{className:"btn btn-secondary","data-dismiss":"modal"},o("close"));return r.a.createElement("div",{className:"modal fade",id:t,tabIndex:"-1",role:"dialog","aria-labelledby":"staticBackdropLabel","aria-hidden":"true"},r.a.createElement("div",{className:"modal-dialog",role:"document"},r.a.createElement("div",{className:"modal-content"},r.a.createElement("div",{className:"modal-header"},r.a.createElement("h5",{className:"modal-title",id:"staticBackdropLabel"},a),r.a.createElement("button",{type:"button",className:"close","data-dismiss":"modal","aria-label":"Close"},r.a.createElement("span",{"aria-hidden":"true"},"\xd7"))),r.a.createElement("div",{className:"modal-body"},l),r.a.createElement("div",{className:"modal-footer"},n||c))))},O=a(49),j=function(e){var t=e.id,a=e.label,n=e.row,l=e.data,o=Object(O.a)(e,["id","label","row","data"]);return r.a.createElement("div",{className:n?"form-group row":"form-group"},r.a.createElement("label",{htmlFor:t,className:"col-sm-2 col-form-label"},a),r.a.createElement("div",{className:"col-sm-10"},r.a.createElement("select",Object.assign({id:t,className:"form-control"},o),l.map((function(e){return r.a.createElement("option",{key:e.id||e,value:e.id||e},e.name||e)})))))},C=[{id:"darkly",name:"Darkly",themeColor:"#375a7f"},{id:"lux",name:"Lux",themeColor:"#1a1a1a"}];function _(){var e=x(),t=function(e){return"https://stackpath.bootstrapcdn.com/bootswatch/4.4.1/".concat(e,"/bootstrap.min.css")}(e),a=C.find((function(t){return t.id===e})).themeColor;document.querySelector('link[title="theme"]').setAttribute("href",t),document.querySelector('meta[name="theme-color"]').setAttribute("content",a)}function x(){var e=localStorage.getItem("theme");return""===e?"darkly":0===C.filter((function(t){return t.id===e})).length?(localStorage.removeItem("theme"),"darkly"):e}var L={availableThemes:C,getCurrentId:x,change:function(e){localStorage.setItem("theme",e),_()},apply:_},T=a(13),P=function(){var e=Object(v.a)(),t=e.t,a=e.i18n,l=Object(n.useState)(""),o=Object(E.a)(l,2),c=o[0],s=o[1],i=T.b.sort().map((function(e){var t=e.slice(0,2);return{id:e,name:"".concat(S()(e)," ").concat(k.a.getNativeName(t))}}));return Object(n.useEffect)((function(){s(L.getCurrentId())}),[]),r.a.createElement(w,{id:"settings",title:t("settings")},r.a.createElement(j,{id:"theme-select",label:t("theme"),data:L.availableThemes,row:!0,value:c,onChange:function(e){return function(e){var t=e.target.value;L.change(t),s(t)}(e)}}),r.a.createElement(j,{id:"language-select",label:t("language"),data:i,row:!0,value:T.b.includes(a.language)?a.language:a.options.fallbackLng[0],onChange:function(e){return a.changeLanguage(e.target.value)}}))},R=a(24),A=a.n(R),B=a(97);A.a.defaults.baseURL="/api",A.a.interceptors.response.use(null,(function(e){var t=e.response&&e.response.status>=400&&e.response.status<500;return document.querySelector(".Toastify")&&(t||b.b.error(r.a.createElement(B.a,null,(function(e){return e("errorUnexpected")}))),e.response&&403===e.response.status&&b.b.error(r.a.createElement(B.a,null,(function(e){return e("errorForbidden")})))),Promise.reject(e)}));var D={get:A.a.get,post:A.a.post};function I(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:null,a=new FormData;return a.set("method",e),t&&a.set("params",t),a}function U(){return D.post("/player",I("play"))}function F(){return D.post("/player",I("stop"))}function M(e){return D.post("/player",I("change_vol",e))}function q(e){return D.post("/player",I("play_radios",e))}function W(e){return D.post("/player",I("play_urls",e))}var Y=function(){var e=Object(n.useState)(""),t=Object(E.a)(e,2),a=t[0],l=t[1],o=Object(v.a)().t,c=function(){""!==a&&(u(a),l(""))},u=function(){var e=Object(i.a)(s.a.mark((function e(t){var a,n;return s.a.wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return a={type:b.b.TYPE.ERROR,render:o("errorNotFound")},n=Object(b.b)(o("tryingURL")),e.prev=2,e.next=5,W(t);case 5:e.next=10;break;case 7:e.prev=7,e.t0=e.catch(2),e.t0.response&&400===e.t0.response.status&&b.b.update(n,a);case 10:case"end":return e.stop()}}),e,null,[[2,7]])})));return function(t){return e.apply(this,arguments)}}();return r.a.createElement(w,{id:"url-dialog",title:o("playURL"),footer:r.a.createElement(r.a.Fragment,null,r.a.createElement("button",{className:"btn btn-primary","data-dismiss":"modal",onClick:c},o("ok")),r.a.createElement("button",{className:"btn btn-secondary",onClick:function(){return l("")},"data-dismiss":"modal"},o("cancel")))},r.a.createElement("input",{className:"form-control mb-4",type:"text",placeholder:"URL",value:a,onChange:function(e){var t=e.target;l(t.value)},onKeyDown:function(e){"Enter"===e.key&&c()}}))};function K(){return D.get("/config")}a(79);var V=function(){return r.a.createElement("div",{className:"loader"},r.a.createElement("div",{className:"spinner-border",role:"status"},r.a.createElement("span",{className:"sr-only"},"Loading...")))},H=function(){var e=Object(v.a)().t,t=Object(n.useState)({}),a=Object(E.a)(t,2),l=a[0],o=a[1],c=Object(n.useState)(!0),u=Object(E.a)(c,2),m=u[0],d=u[1];Object(n.useEffect)((function(){function e(){return(e=Object(i.a)(s.a.mark((function e(){var t,a;return s.a.wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return e.next=2,K();case 2:t=e.sent,a=t.data,o(a),d(!1);case 6:case"end":return e.stop()}}),e)})))).apply(this,arguments)}!function(){e.apply(this,arguments)}()}),[]);var p=[{label:e("mpdHost"),value:l.mpd_host},{label:e("mpdPort"),value:l.mpd_port},{label:e("environment"),value:l.environment},{label:e("version"),value:l.version}],b=r.a.createElement("div",{className:"copyright small mt-5"},r.a.createElement("p",null,r.a.createElement("a",{href:"https://rafaelc.org/pifi",target:"_blank",rel:"noopener noreferrer",style:{color:"inherit"}},e("goToDocs"))),r.a.createElement("p",null,"Copyright \xa9 2017-2021\xa0",r.a.createElement("a",{href:"https://rafaelc.org/",target:"_blank",rel:"noopener noreferrer",style:{color:"inherit"}},"Rafael Cavalcanti"))),f=r.a.createElement("div",{className:"coffee mt-5"},r.a.createElement("p",null,e("coffee")),r.a.createElement("a",{href:"https://rafaelc.org/coffee",target:"_blank",rel:"noopener noreferrer"},r.a.createElement("img",{src:"https://cdn.buymeacoffee.com/buttons/default-blue.png",alt:"Buy Me A Coffee",style:{height:51,width:217}})));return r.a.createElement(w,{id:"about",title:e("about")},m?r.a.createElement(V,null):r.a.createElement("table",{className:"table"},r.a.createElement("tbody",null,p.map((function(e){return r.a.createElement("tr",{key:e.label},r.a.createElement("th",{scope:"row"},e.label),r.a.createElement("td",null,e.value))})))),f,b)},J=(a(80),function(){return window.innerWidth-document.documentElement.clientWidth}),G=function(e){var t=e.title,a=e.body,n=t,l=document.body.classList.contains("body--backdrop");n&&!l?(document.body.style.paddingRight=J()+"px",document.body.classList.add("body--backdrop")):!n&&l&&(document.body.style.paddingRight=0,document.body.classList.remove("body--backdrop"));var o="backdrop p-2 text-white"+(n?" backdrop--visible":"");return r.a.createElement("div",{className:o},r.a.createElement("h3",{className:"text-white"},t),r.a.createElement("h5",{className:"text-white"},a))},$=function(e){var t=e.playerStatus,a=t.playing,n=t.title,l=Object(v.a)().t;return r.a.createElement("div",{className:"text-center w-100"},r.a.createElement("h5",{className:"small text-uppercase"},l(a?"playing":"stopped")),r.a.createElement("h3",{className:"ellipsis"},n))},z=a(14),Q=a(11),X=function(e){return e.playing?r.a.createElement("button",{className:"btn btn-danger",onClick:F,"aria-label":"Stop"},r.a.createElement(z.a,{icon:Q.d})):r.a.createElement("button",{className:"btn btn-dark",onClick:U,"aria-label":"Play"},r.a.createElement(z.a,{icon:Q.c}))},Z=function(e){var t=e.playerStatus,a=Object(v.a)().t,n=null==t.vol,l=function(){var e=Object(i.a)(s.a.mark((function e(t){var n,r,l,o;return s.a.wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return e.next=2,M(t);case 2:n=e.sent,r=n.data,"vol",l="".concat(a("volume"),": ").concat(r,"%"),o={toastId:"vol",autoClose:T.d},b.b.isActive("vol")?b.b.update("vol",{render:l}):b.b.info(l,o);case 8:case"end":return e.stop()}}),e)})));return function(t){return e.apply(this,arguments)}}(),o=function(e,t,a){return r.a.createElement("button",{className:"btn btn-dark p-3",disabled:n,onClick:function(){return l(e)},"aria-label":a},r.a.createElement(z.a,{icon:t}))};return r.a.createElement("div",{className:"player-controls btn-group w-100 m-2",onClick:function(e){return e.stopPropagation()}},o("-5",Q.e,"Volume down"),o("+5",Q.f,"Volume up"),r.a.createElement(X,{playing:t.playing}))},ee=(a(83),function(e){var t=e.playerStatus;return r.a.createElement("div",{className:"player"},r.a.createElement($,{playerStatus:t}),r.a.createElement("img",{src:a(30),alt:"Logo",className:"player-logo m-4"}),r.a.createElement(Z,{playerStatus:t}))}),te=function(e){var t=e.value,a=e.onChange,n=Object(v.a)().t;return r.a.createElement("input",{className:"form-control mb-4",type:"text",id:"query",placeholder:n("search"),"aria-label":n("search"),autoComplete:"off",value:t,onChange:function(e){return a(e.target.value)},onFocus:function(){return a("")}})};a(84);var ae=function(e){Object(p.a)(a,e);var t=Object(d.a)(a);function a(){var e;Object(u.a)(this,a);for(var n=arguments.length,r=new Array(n),l=0;l<n;l++)r[l]=arguments[l];return(e=t.call.apply(t,[this].concat(r))).state={streams:{},loading:!0,query:""},e.isPlaying=function(t){return t===e.props.playerStatus.title&&e.props.playerStatus.playing},e.handleItemClick=function(){var t=Object(i.a)(s.a.mark((function t(a){var n,r,l;return s.a.wrap((function(t){for(;;)switch(t.prev=t.next){case 0:if(n=e.props,r=n.t,l=n.onBackdrop,!e.isPlaying(a)){t.next=3;break}return t.abrupt("return");case 3:return l(r("tuning"),a),t.prev=4,t.next=7,q(a);case 7:t.next=12;break;case 9:t.prev=9,t.t0=t.catch(4),t.t0.response&&400===t.t0.response.status&&b.b.error(r("errorNotFound"));case 12:case"end":return t.stop()}}),t,null,[[4,9]])})));return function(e){return t.apply(this,arguments)}}(),e.getItemClasses=function(t){var a="streams__item list-group-item ";return e.isPlaying(t)?a+"active":a+"list-group-item-action"},e.handleSearch=function(t){e.setState({query:t})},e}return Object(m.a)(a,[{key:"componentDidMount",value:function(){var e=Object(i.a)(s.a.mark((function e(){var t,a;return s.a.wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return e.next=2,D.get("/streams");case 2:t=e.sent,a=t.data,this.setState({streams:a,loading:!1});case 5:case"end":return e.stop()}}),e,this)})));return function(){return e.apply(this,arguments)}}()},{key:"filteredStreams",value:function(){var e=this.state,t=e.streams,a=e.query;if(""===a)return t;var n={};for(var r in t)r.toLowerCase().includes(a.toLowerCase())&&!t[r]&&(n[r]=t[r]);return n}},{key:"renderList",value:function(){var e=this,t=this.filteredStreams();return 0===Object.keys(t).length?r.a.createElement("h4",{className:"p-4"},this.props.t("noStreams")):r.a.createElement("ul",{className:"list-group list-group-flush"},Object.keys(t).map((function(a){return t[a]?r.a.createElement("li",{className:"streams__header ellipsis",key:a},a):r.a.createElement("li",{className:e.getItemClasses(a),key:a},r.a.createElement("button",{className:"streams__btn btn btn-outline ellipsis",onClick:function(){return e.handleItemClick(a)},disabled:e.isPlaying(a)},a))})))}},{key:"render",value:function(){return this.state.loading?r.a.createElement(V,null):r.a.createElement("div",{className:"streams"},r.a.createElement(te,{value:this.state.query,onChange:this.handleSearch}),this.renderList())}}]),a}(n.Component),ne=Object(f.a)()(ae),re=(a(85),function(e){var t=e.playerStatus,a=t.title,n=t.playing;return r.a.createElement("div",{className:"mini-player"},r.a.createElement("div",{className:"mini-player__left ellipsis"},a),r.a.createElement("div",{className:"mini-player__right",onClick:function(e){return e.stopPropagation()}},r.a.createElement(X,{playing:n})))}),le=(a(86),function(e){Object(p.a)(a,e);var t=Object(d.a)(a);function a(){var e;Object(u.a)(this,a);for(var n=arguments.length,l=new Array(n),o=0;o<n;o++)l[o]=arguments[o];return(e=t.call.apply(t,[this].concat(l))).state={open:!1},e.toggle=function(){e.setState({open:!e.state.open})},e.handleClick=function(){e.toggle()},e.handleTouchStart=function(t){if(t.touches.length>1)return e.setState({touchStartY:null});e.setState({touchStartY:t.touches[0].clientY})},e.handleTouchMove=function(t){var a=e.state,n=a.open,r=a.touchStartY;if(!(null===r||t.touches.length>1)){var l=t.changedTouches[0].clientY;(l>r&&n||l<r&&!n)&&e.toggle()}},e.renderToggleButton=function(){return r.a.createElement("button",{className:"drawer__toggler btn btn-primary-outline","aria-label":"Toggle player"},r.a.createElement(z.a,{icon:e.state.open?Q.a:Q.b,className:"fa-lg"}))},e}return Object(m.a)(a,[{key:"render",value:function(){var e=this.props.playerStatus,t=this.state.open,a="drawer fixed-bottom bg-secondary shadow-lg p-2";return t?(a+=" drawer--open",document.body.classList.add("body--drawer")):document.body.classList.remove("body--drawer"),r.a.createElement("div",{className:a,onClick:this.handleClick,onTouchStart:this.handleTouchStart,onTouchMove:this.handleTouchMove},this.renderToggleButton(),t?r.a.createElement(ee,{playerStatus:e}):r.a.createElement(re,{playerStatus:e}))}}]),a}(n.Component)),oe=(a(87),function(e){var t=e.playerStatus,a=e.onBackdrop;return r.a.createElement("main",{className:"main"},r.a.createElement(le,{playerStatus:t}),r.a.createElement("div",{className:"main__primary container"},r.a.createElement(ne,{onBackdrop:a,playerStatus:t}),r.a.createElement(ee,{playerStatus:t})))}),ce=(a(88),function(e){Object(p.a)(a,e);var t=Object(d.a)(a);function a(){var e;Object(u.a)(this,a);for(var n=arguments.length,r=new Array(n),l=0;l<n;l++)r[l]=arguments[l];return(e=t.call.apply(t,[this].concat(r))).state={playerStatus:{},loading:!0,networkError:!1,backdrop:{}},e.handleBackdrop=function(t){var a=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"";e.setState({backdrop:{title:t,body:a}}),setTimeout((function(){return e.setState({backdrop:{}})}),T.a)},e.setDocumentTitle=function(){var t=e.state,a=t.playerStatus;!t.networkError&&a.playing?document.title="PiFi \ud83d\udd0a "+a.title:document.title="PiFi Radio"},e}return Object(m.a)(a,[{key:"componentDidMount",value:function(){this.updatePlayerStatus()}},{key:"componentDidUpdate",value:function(){this.setDocumentTitle()}},{key:"updatePlayerStatus",value:function(){var e=Object(i.a)(s.a.mark((function e(){var t,a,n=this;return s.a.wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return e.prev=0,e.next=3,D.get("/player");case 3:t=e.sent,a=t.data,this.setState({playerStatus:a,loading:!1,networkError:!1}),e.next=11;break;case 8:e.prev=8,e.t0=e.catch(0),this.setState({loading:!1,networkError:!0});case 11:setTimeout((function(){return n.updatePlayerStatus()}),T.c);case 12:case"end":return e.stop()}}),e,this,[[0,8]])})));return function(){return e.apply(this,arguments)}}()},{key:"render",value:function(){var e=this.state,t=e.loading,a=e.networkError,n=e.backdrop,l=e.playerStatus,o=this.props.t;return a?r.a.createElement(G,{title:o("errorNetwork")}):t?r.a.createElement(V,null):l.con_mpd?r.a.createElement("div",{className:"app"},r.a.createElement(b.a,{pauseOnFocusLoss:!1}),r.a.createElement(G,{title:n.title,body:n.body}),r.a.createElement(y,null),r.a.createElement(oe,{onBackdrop:this.handleBackdrop,playerStatus:l}),r.a.createElement(Y,null),r.a.createElement(P,null),r.a.createElement(H,null)):r.a.createElement(G,{title:o("disconnectedMPD")})}}]),a}(n.Component)),se=Object(f.a)()(ce);Boolean("localhost"===window.location.hostname||"[::1]"===window.location.hostname||window.location.hostname.match(/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/));var ie=a(29),ue=a(21),me=a(47),de=a(46);ie.a.use(me.a).use(de.a).use(ue.f).init({fallbackLng:"en-US",debug:Object({NODE_ENV:"production",PUBLIC_URL:"",WDS_SOCKET_HOST:void 0,WDS_SOCKET_PATH:void 0,WDS_SOCKET_PORT:void 0,REACT_APP_API_URL:"/api"}).REACT_APP_I18N_DEBUG,load:"currentOnly",returnEmptyString:!1,interpolation:{escapeValue:!1}}),ie.a.on("languageChanged",(function(e){document.documentElement.setAttribute("lang",e)}));ie.a,a(89),a(90),a(91);L.apply(),o.a.render(r.a.createElement(n.Suspense,{fallback:r.a.createElement(V,null)},r.a.createElement(se,null)),document.getElementById("root")),"serviceWorker"in navigator&&navigator.serviceWorker.ready.then((function(e){e.unregister()}))}},[[50,1,2]]]);
2
+ //# sourceMappingURL=main.11609572.chunk.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["assets/logo.svg","components/navBar.jsx","components/common/modal.jsx","components/common/select.jsx","theme.js","components/modals/settings.jsx","services/httpService.js","services/playerService.js","components/modals/urlDialog.jsx","services/configService.js","components/common/loader.jsx","components/modals/about.jsx","components/common/backdrop.jsx","components/player/playerStatus.jsx","components/player/playStopControl.jsx","components/player/playerControls.jsx","components/player/player.jsx","components/common/searchBox.jsx","components/streams.jsx","services/streamsService.js","components/player/miniPlayer.jsx","components/drawer.jsx","components/main.jsx","App.js","serviceWorker.js","i18n.js","index.js"],"names":["module","exports","NavBar","t","useTranslation","className","href","src","logo","width","height","alt","type","data-toggle","data-target","aria-controls","aria-expanded","aria-label","id","Modal","title","footer","children","defaultFooter","data-dismiss","tabIndex","role","aria-labelledby","aria-hidden","Select","label","row","data","rest","htmlFor","map","d","key","value","name","availableThemes","themeColor","apply","themeId","getCurrentId","themePath","getThemePath","find","document","querySelector","setAttribute","localId","localStorage","getItem","filter","length","removeItem","change","setItem","Settings","i18n","useState","setThemeId","languagesData","languages","sort","lang","isoCode","slice","localeEmoji","ISO6391","getNativeName","useEffect","theme","onChange","e","newThemeId","target","handleThemeChange","includes","language","options","fallbackLng","changeLanguage","axios","defaults","baseURL","process","interceptors","response","use","error","expectedError","status","toast","Translation","Promise","reject","get","post","body","method","params","FormData","set","play","http","stop","changeVol","delta","playRadio","playURL","url","URLDialog","setURL","handleOK","doPlayURL","a","errorToastOpts","TYPE","ERROR","render","toastId","update","Fragment","onClick","placeholder","input","onKeyDown","getConfig","Loader","About","config","setConfig","loading","setLoading","fetchData","tableData","mpd_host","mpd_port","environment","version","copyright","rel","style","color","coffee","item","scope","getScrollbarWidth","window","innerWidth","documentElement","clientWidth","Backdrop","visible","bodyStyled","classList","contains","paddingRight","add","remove","classes","PlayerStatus","playerStatus","playing","PlayStopControl","icon","faStop","faPlay","PlayerControls","volDisabled","vol","handleVolChange","toastMsg","toastOpts","autoClose","volTimeout","isActive","info","renderVolButton","disabled","stopPropagation","faVolumeDown","faVolumeUp","Player","require","SearchBox","autoComplete","onFocus","Streams","state","streams","query","isPlaying","props","handleItemClick","onBackdrop","getItemClasses","handleSearch","setState","this","filtered","k","toLowerCase","filteredStreams","Object","keys","renderList","Component","withTranslation","MiniPlayer","Drawer","open","toggle","handleClick","handleTouchStart","touches","touchStartY","clientY","handleTouchMove","touchEndY","changedTouches","renderToggleButton","faChevronDown","faChevronUp","onTouchStart","onTouchMove","Main","App","networkError","backdrop","handleBackdrop","setTimeout","backdropTimeout","setDocumentTitle","updatePlayerStatus","updateInterval","con_mpd","pauseOnFocusLoss","Boolean","location","hostname","match","Backend","LanguageDetector","initReactI18next","init","debug","REACT_APP_I18N_DEBUG","load","returnEmptyString","interpolation","escapeValue","on","ReactDOM","fallback","getElementById","navigator","serviceWorker","ready","then","registration","unregister"],"mappings":"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","file":"static/js/main.11609572.chunk.js","sourcesContent":["module.exports = __webpack_public_path__ + \"static/media/logo.91554ce9.svg\";","import React from 'react';\nimport { useTranslation } from 'react-i18next';\nimport './navBar.scss';\nimport logo from '../assets/logo.svg';\n\nconst NavBar = () => {\n const { t } = useTranslation();\n\n return (\n <nav className=\"navbar navbar-expand-sm navbar-dark bg-primary\">\n <div className=\"container\">\n <a className=\"navbar-brand\" href=\"/\">\n <img\n src={logo}\n width=\"30\"\n height=\"30\"\n className=\"d-inline-block align-text-bottom mr-2\"\n alt=\"\"\n />\n PiFi Radio\n </a>\n <button\n className=\"navbar-toggler\"\n type=\"button\"\n data-toggle=\"collapse\"\n data-target=\"#navbarNavAltMarkup\"\n aria-controls=\"navbarNavAltMarkup\"\n aria-expanded=\"false\"\n aria-label=\"Toggle navigation\"\n >\n <span className=\"navbar-toggler-icon\"></span>\n </button>\n <div className=\"collapse navbar-collapse\" id=\"navbarNavAltMarkup\">\n <div className=\"navbar-nav pt-1 ml-2\">\n {/* We use buttons and different class names instead of Bootstrap's\n anchor, so we don't get a warning for href=\"#\". */}\n <button\n className=\"btn btn-link nav-link\"\n data-toggle=\"modal\"\n data-target=\"#url-dialog\"\n >\n {t('playURL')}\n </button>\n\n <button\n className=\"btn btn-link nav-link\"\n data-toggle=\"modal\"\n data-target=\"#settings\"\n >\n {t('settings')}\n </button>\n\n <button\n className=\"btn btn-link nav-link\"\n data-toggle=\"modal\"\n data-target=\"#about\"\n >\n {t('about')}\n </button>\n </div>\n </div>\n </div>\n </nav>\n );\n};\nexport default NavBar;\n","import React from 'react';\nimport { useTranslation } from 'react-i18next';\n\nconst Modal = ({ id, title, footer, children }) => {\n const { t } = useTranslation();\n\n const defaultFooter = (\n <button className=\"btn btn-secondary\" data-dismiss=\"modal\">\n {t('close')}\n </button>\n );\n\n return (\n <div\n className=\"modal fade\"\n id={id}\n tabIndex=\"-1\"\n role=\"dialog\"\n aria-labelledby=\"staticBackdropLabel\"\n aria-hidden=\"true\"\n >\n <div className=\"modal-dialog\" role=\"document\">\n <div className=\"modal-content\">\n <div className=\"modal-header\">\n <h5 className=\"modal-title\" id=\"staticBackdropLabel\">\n {title}\n </h5>\n <button\n type=\"button\"\n className=\"close\"\n data-dismiss=\"modal\"\n aria-label=\"Close\"\n >\n <span aria-hidden=\"true\">&times;</span>\n </button>\n </div>\n <div className=\"modal-body\">{children}</div>\n <div className=\"modal-footer\">{footer ? footer : defaultFooter}</div>\n </div>\n </div>\n </div>\n );\n};\n\nexport default Modal;\n","import React from 'react';\n\nconst Select = ({ id, label, row, data, ...rest }) => (\n <div className={row ? 'form-group row' : 'form-group'}>\n <label htmlFor={id} className=\"col-sm-2 col-form-label\">\n {label}\n </label>\n <div className=\"col-sm-10\">\n <select id={id} className=\"form-control\" {...rest}>\n {data.map(d => (\n <option key={d.id || d} value={d.id || d}>\n {d.name || d}\n </option>\n ))}\n </select>\n </div>\n </div>\n);\n\nexport default Select;\n","const DEFAULT_THEME_ID = 'darkly';\n\nconst STORAGE_KEY = 'theme';\n\nconst getThemePath = themeId =>\n `https://stackpath.bootstrapcdn.com/bootswatch/4.4.1/${themeId}/bootstrap.min.css`;\n\nconst availableThemes = [\n { id: 'darkly', name: 'Darkly', themeColor: '#375a7f' },\n { id: 'lux', name: 'Lux', themeColor: '#1a1a1a' }\n];\n\nfunction change(themeId) {\n localStorage.setItem(STORAGE_KEY, themeId);\n apply();\n}\n\nfunction apply() {\n const themeId = getCurrentId();\n const themePath = getThemePath(themeId);\n const themeColor = availableThemes.find(t => t.id === themeId).themeColor;\n\n const linkEl = document.querySelector('link[title=\"theme\"]');\n linkEl.setAttribute('href', themePath);\n\n const metaEl = document.querySelector('meta[name=\"theme-color\"]');\n metaEl.setAttribute('content', themeColor);\n}\n\nfunction getCurrentId() {\n const localId = localStorage.getItem(STORAGE_KEY);\n\n if (localId === '') return DEFAULT_THEME_ID;\n if (availableThemes.filter(t => t.id === localId).length === 0) {\n localStorage.removeItem(STORAGE_KEY);\n return DEFAULT_THEME_ID;\n }\n\n return localId;\n}\n\nexport default {\n availableThemes,\n getCurrentId,\n change,\n apply\n};\n","import React, { useState, useEffect } from 'react';\nimport { useTranslation } from 'react-i18next';\nimport ISO6391 from 'iso-639-1';\nimport localeEmoji from 'locale-emoji';\nimport Modal from '../common/modal';\nimport Select from '../common/select';\nimport theme from '../../theme';\nimport { languages } from '../../config.json';\n\nconst Settings = () => {\n const { t, i18n } = useTranslation();\n\n const [themeId, setThemeId] = useState('');\n\n const languagesData = languages.sort().map(lang => {\n const isoCode = lang.slice(0, 2);\n return {\n id: lang,\n name: `${localeEmoji(lang)} ${ISO6391.getNativeName(isoCode)}`\n };\n });\n\n const handleThemeChange = ({ target }) => {\n const newThemeId = target.value;\n theme.change(newThemeId);\n setThemeId(newThemeId);\n };\n\n useEffect(() => {\n setThemeId(theme.getCurrentId());\n }, []);\n\n return (\n <Modal id=\"settings\" title={t('settings')}>\n <Select\n id=\"theme-select\"\n label={t('theme')}\n data={theme.availableThemes}\n row\n value={themeId}\n onChange={e => handleThemeChange(e)}\n />\n <Select\n id=\"language-select\"\n label={t('language')}\n data={languagesData}\n row\n value={\n languages.includes(i18n.language)\n ? i18n.language\n : i18n.options.fallbackLng[0]\n }\n onChange={e => i18n.changeLanguage(e.target.value)}\n />\n </Modal>\n );\n};\n\nexport default Settings;\n","import React from 'react';\nimport axios from 'axios';\nimport { toast } from 'react-toastify';\nimport { Translation } from 'react-i18next';\n\naxios.defaults.baseURL = process.env.REACT_APP_API_URL;\n\naxios.interceptors.response.use(null, error => {\n const expectedError =\n error.response &&\n error.response.status >= 400 &&\n error.response.status < 500;\n\n // Some errors cause our app to only render a backdrop warning.\n // Queuing toasts would be redundant.\n const hasToastify = document.querySelector('.Toastify');\n\n if (hasToastify) {\n if (!expectedError)\n toast.error(<Translation>{t => t('errorUnexpected')}</Translation>);\n\n // Universal expected error\n if (error.response && error.response.status === 403)\n toast.error(<Translation>{t => t('errorForbidden')}</Translation>);\n }\n\n return Promise.reject(error);\n});\n\nexport default {\n get: axios.get,\n post: axios.post\n};\n","import http from './httpService.js';\n\nconst apiEndpoint = '/player';\n\nfunction body(method, params = null) {\n const body = new FormData();\n body.set('method', method);\n if (params) body.set('params', params);\n return body;\n}\n\nexport function getStatus() {\n return http.get(apiEndpoint);\n}\n\nexport function play() {\n return http.post(apiEndpoint, body('play'));\n}\n\nexport function stop() {\n return http.post(apiEndpoint, body('stop'));\n}\n\nexport function changeVol(delta) {\n return http.post(apiEndpoint, body('change_vol', delta));\n}\n\nexport function playRadio(name) {\n return http.post(apiEndpoint, body('play_radios', name));\n}\n\nexport function playURL(url) {\n return http.post(apiEndpoint, body('play_urls', url));\n}\n","import React, { useState } from 'react';\nimport { toast } from 'react-toastify';\nimport { useTranslation } from 'react-i18next';\nimport Modal from '../common/modal';\nimport { playURL } from '../../services/playerService';\n\nconst URLDialog = () => {\n const [url, setURL] = useState('');\n\n const { t } = useTranslation();\n\n const handleOK = () => {\n if (url === '') return;\n doPlayURL(url);\n setURL('');\n };\n\n const doPlayURL = async url => {\n const errorToastOpts = {\n type: toast.TYPE.ERROR,\n render: t('errorNotFound')\n };\n\n const toastId = toast(t('tryingURL'));\n\n try {\n await playURL(url);\n } catch (ex) {\n if (ex.response && ex.response.status === 400)\n toast.update(toastId, errorToastOpts);\n }\n };\n\n const handleChange = ({ target: input }) => {\n setURL(input.value);\n };\n\n const handleKeyDown = e => {\n if (e.key === 'Enter') handleOK();\n };\n\n const renderFooter = () => (\n <React.Fragment>\n <button\n className=\"btn btn-primary\"\n data-dismiss=\"modal\"\n onClick={handleOK}\n >\n {t('ok')}\n </button>\n <button\n className=\"btn btn-secondary\"\n onClick={() => setURL('')}\n data-dismiss=\"modal\"\n >\n {t('cancel')}\n </button>\n </React.Fragment>\n );\n\n return (\n <Modal id=\"url-dialog\" title={t('playURL')} footer={renderFooter()}>\n <input\n className=\"form-control mb-4\"\n type=\"text\"\n placeholder=\"URL\"\n value={url}\n onChange={handleChange}\n onKeyDown={handleKeyDown}\n />\n </Modal>\n );\n};\n\nexport default URLDialog;\n","import http from './httpService';\n\nconst apiEndpoint = '/config';\n\nexport function getConfig() {\n return http.get(apiEndpoint);\n}\n","import React from 'react';\nimport './loader.scss';\n\nconst Loader = () => (\n <div className=\"loader\">\n <div className=\"spinner-border\" role=\"status\">\n <span className=\"sr-only\">Loading...</span>\n </div>\n </div>\n);\n\nexport default Loader;\n","import React from 'react';\nimport { useEffect, useState } from 'react';\nimport { useTranslation } from 'react-i18next';\nimport Modal from '../common/modal';\nimport { getConfig } from '../../services/configService';\nimport Loader from '../common/loader';\n\nconst About = () => {\n const { t } = useTranslation();\n\n const [config, setConfig] = useState({});\n const [loading, setLoading] = useState(true);\n\n useEffect(() => {\n async function fetchData() {\n const { data: config } = await getConfig();\n setConfig(config);\n setLoading(false);\n }\n fetchData();\n }, []);\n\n const tableData = [\n { label: t('mpdHost'), value: config.mpd_host },\n { label: t('mpdPort'), value: config.mpd_port },\n { label: t('environment'), value: config.environment },\n { label: t('version'), value: config.version },\n ];\n\n const renderTable = () => {\n if (loading) return <Loader />;\n\n return (\n <table className=\"table\">\n <tbody>\n {tableData.map((item) => (\n <tr key={item.label}>\n <th scope=\"row\">{item.label}</th>\n <td>{item.value}</td>\n </tr>\n ))}\n </tbody>\n </table>\n );\n };\n\n const copyright = (\n <div className=\"copyright small mt-5\">\n <p>\n <a\n href=\"https://rafaelc.org/pifi\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n style={{ color: 'inherit' }}\n >\n {t('goToDocs')}\n </a>\n </p>\n <p>\n Copyright &copy; 2017-2021&nbsp;\n {/* Use the default color, as some themes give a different color for links. */}\n <a\n href=\"https://rafaelc.org/\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n style={{ color: 'inherit' }}\n >\n Rafael Cavalcanti\n </a>\n </p>\n </div>\n );\n\n const coffee = (\n <div className=\"coffee mt-5\">\n <p>{t('coffee')}</p>\n <a\n href=\"https://rafaelc.org/coffee\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n >\n <img\n src=\"https://cdn.buymeacoffee.com/buttons/default-blue.png\"\n alt=\"Buy Me A Coffee\"\n style={{ height: 51, width: 217 }}\n />\n </a>\n </div>\n );\n\n return (\n <Modal id=\"about\" title={t('about')}>\n {renderTable()}\n {coffee}\n {copyright}\n </Modal>\n );\n};\n\nexport default About;\n","import React from 'react';\nimport './backdrop.scss';\n\nconst Backdrop = ({ title, body }) => {\n const visible = title;\n const bodyStyled = document.body.classList.contains('body--backdrop');\n\n if (visible && !bodyStyled) {\n // Calculate this BEFORE adding the class.\n document.body.style.paddingRight = getScrollbarWidth() + 'px';\n document.body.classList.add('body--backdrop');\n // We need this check because Bootstrap modals also style paddingRight\n } else if (!visible && bodyStyled) {\n document.body.style.paddingRight = 0;\n document.body.classList.remove('body--backdrop');\n }\n\n const classes =\n 'backdrop p-2 text-white' + (visible ? ' backdrop--visible' : '');\n\n // We need text-white on h* tags because some themes override it\n return (\n <div className={classes}>\n <h3 className=\"text-white\">{title}</h3>\n <h5 className=\"text-white\">{body}</h5>\n </div>\n );\n};\n\nconst getScrollbarWidth = () =>\n window.innerWidth - document.documentElement.clientWidth;\n\nexport default Backdrop;\n","import React from 'react';\nimport { useTranslation } from 'react-i18next';\n\nconst PlayerStatus = ({ playerStatus }) => {\n const { playing, title } = playerStatus;\n\n const { t } = useTranslation();\n\n return (\n <div className=\"text-center w-100\">\n <h5 className=\"small text-uppercase\">\n {playing ? t('playing') : t('stopped')}\n </h5>\n <h3 className=\"ellipsis\">{title}</h3>\n </div>\n );\n};\n\nexport default PlayerStatus;\n","import React from 'react';\nimport { FontAwesomeIcon } from '@fortawesome/react-fontawesome';\nimport { faStop, faPlay } from '@fortawesome/free-solid-svg-icons';\nimport { play, stop } from '../../services/playerService';\n\nconst PlayStopControl = ({ playing }) =>\n playing ? (\n <button className=\"btn btn-danger\" onClick={stop} aria-label=\"Stop\">\n <FontAwesomeIcon icon={faStop} />\n </button>\n ) : (\n <button className=\"btn btn-dark\" onClick={play} aria-label=\"Play\">\n <FontAwesomeIcon icon={faPlay} />\n </button>\n );\n\nexport default PlayStopControl;\n","import React from 'react';\nimport PlayStopControl from './playStopControl';\nimport { FontAwesomeIcon } from '@fortawesome/react-fontawesome';\nimport { faVolumeDown, faVolumeUp } from '@fortawesome/free-solid-svg-icons';\nimport { toast } from 'react-toastify';\nimport { useTranslation } from 'react-i18next';\nimport { changeVol } from '../../services/playerService';\nimport { volTimeout } from '../../config.json';\n\nconst PlayerControls = ({ playerStatus }) => {\n const { t } = useTranslation();\n\n const volDisabled = playerStatus.vol == null;\n\n const handleVolChange = async delta => {\n const { data: vol } = await changeVol(delta);\n\n const toastId = 'vol';\n const toastMsg = `${t('volume')}: ${vol}%`;\n const toastOpts = { toastId, autoClose: volTimeout };\n if (toast.isActive(toastId)) toast.update(toastId, { render: toastMsg });\n else toast.info(toastMsg, toastOpts);\n };\n\n const renderVolButton = (delta, icon, label) => (\n <button\n className=\"btn btn-dark p-3\"\n disabled={volDisabled}\n onClick={() => handleVolChange(delta)}\n aria-label={label}\n >\n <FontAwesomeIcon icon={icon} />\n </button>\n );\n\n return (\n <div\n className=\"player-controls btn-group w-100 m-2\"\n onClick={e => e.stopPropagation()}\n >\n {renderVolButton('-5', faVolumeDown, 'Volume down')}\n {renderVolButton('+5', faVolumeUp, 'Volume up')}\n <PlayStopControl playing={playerStatus.playing} />\n </div>\n );\n};\n\nexport default PlayerControls;\n","import React from 'react';\nimport PlayerStatus from './playerStatus';\nimport PlayerControls from './playerControls';\nimport './player.scss';\n\nconst Player = ({ playerStatus }) => {\n const renderLogo = () => (\n <img\n src={require('../../assets/logo.svg')}\n alt=\"Logo\"\n className=\"player-logo m-4\"\n />\n );\n\n return (\n <div className=\"player\">\n <PlayerStatus playerStatus={playerStatus} />\n {renderLogo()}\n <PlayerControls playerStatus={playerStatus} />\n </div>\n );\n};\n\nexport default Player;\n","import React from 'react';\nimport { useTranslation } from 'react-i18next';\n\nconst SearchBox = ({ value, onChange }) => {\n const { t } = useTranslation();\n\n return (\n <input\n className=\"form-control mb-4\"\n type=\"text\"\n id=\"query\"\n placeholder={t('search')}\n aria-label={t('search')}\n autoComplete=\"off\"\n value={value}\n onChange={e => onChange(e.target.value)}\n onFocus={() => onChange('')}\n ></input>\n );\n};\n\nexport default SearchBox;\n","import React, { Component } from 'react';\nimport { withTranslation } from 'react-i18next';\nimport { toast } from 'react-toastify';\nimport Loader from './common/loader';\nimport SearchBox from './common/searchBox';\nimport { playRadio } from '../services/playerService';\nimport { getStreams } from '../services/streamsService';\nimport './streams.scss';\n\nclass Streams extends Component {\n state = { streams: {}, loading: true, query: '' };\n\n async componentDidMount() {\n const { data: streams } = await getStreams();\n this.setState({ streams, loading: false });\n }\n\n isPlaying = name => {\n return (\n name === this.props.playerStatus.title && this.props.playerStatus.playing\n );\n };\n\n handleItemClick = async name => {\n const { t, onBackdrop } = this.props;\n\n if (this.isPlaying(name)) return;\n\n onBackdrop(t('tuning'), name);\n try {\n await playRadio(name);\n } catch (ex) {\n if (ex.response && ex.response.status === 400)\n toast.error(t('errorNotFound'));\n }\n };\n\n getItemClasses = name => {\n const classes = 'streams__item list-group-item ';\n return this.isPlaying(name)\n ? classes + 'active'\n : classes + 'list-group-item-action';\n };\n\n handleSearch = query => {\n this.setState({ query });\n };\n\n filteredStreams() {\n const { streams, query } = this.state;\n\n if (query === '') return streams;\n\n let filtered = {};\n for (let k in streams) {\n if (k.toLowerCase().includes(query.toLowerCase()) && !streams[k]) {\n filtered[k] = streams[k];\n }\n }\n return filtered;\n }\n\n renderList() {\n const streams = this.filteredStreams();\n\n if (Object.keys(streams).length === 0)\n return <h4 className=\"p-4\">{this.props.t('noStreams')}</h4>;\n\n return (\n <ul className=\"list-group list-group-flush\">\n {Object.keys(streams).map(name =>\n streams[name] ? (\n <li className=\"streams__header ellipsis\" key={name}>\n {name}\n </li>\n ) : (\n <li className={this.getItemClasses(name)} key={name}>\n <button\n className=\"streams__btn btn btn-outline ellipsis\"\n onClick={() => this.handleItemClick(name)}\n disabled={this.isPlaying(name)}\n >\n {name}\n </button>\n </li>\n )\n )}\n </ul>\n );\n }\n\n render() {\n if (this.state.loading) return <Loader />;\n\n return (\n <div className=\"streams\">\n <SearchBox value={this.state.query} onChange={this.handleSearch} />\n {this.renderList()}\n </div>\n );\n }\n}\n\nexport default withTranslation()(Streams);\n","import http from './httpService';\n\nconst apiEndpoint = '/streams';\n\nexport function getStreams() {\n return http.get(apiEndpoint);\n}\n","import React from 'react';\nimport PlayStopControl from './playStopControl';\nimport './miniPlayer.scss';\n\nconst MiniPlayer = ({ playerStatus }) => {\n const { title, playing } = playerStatus;\n\n return (\n <div className=\"mini-player\">\n <div className=\"mini-player__left ellipsis\">\n {title}\n </div>\n <div\n className=\"mini-player__right\"\n onClick={e => e.stopPropagation()}\n >\n <PlayStopControl playing={playing} />\n </div>\n </div>\n );\n};\n\nexport default MiniPlayer;\n","import React, { Component } from 'react';\nimport { FontAwesomeIcon } from '@fortawesome/react-fontawesome';\nimport { faChevronDown, faChevronUp } from '@fortawesome/free-solid-svg-icons';\nimport MiniPlayer from './player/miniPlayer';\nimport Player from './player/player';\nimport './drawer.scss';\n\nclass Drawer extends Component {\n state = { open: false };\n\n toggle = () => {\n this.setState({ open: !this.state.open });\n };\n\n handleClick = () => {\n this.toggle();\n };\n\n handleTouchStart = (e) => {\n // Ignore multitouch\n if (e.touches.length > 1) return this.setState({ touchStartY: null });\n\n this.setState({ touchStartY: e.touches[0].clientY });\n };\n\n handleTouchMove = (e) => {\n const { open, touchStartY } = this.state;\n\n // Ignore multitouch\n if (touchStartY === null || e.touches.length > 1) return;\n\n const touchEndY = e.changedTouches[0].clientY;\n const touchUp = touchEndY < touchStartY;\n const touchDown = touchEndY > touchStartY;\n\n if (touchDown && open) this.toggle();\n else if (touchUp && !open) this.toggle();\n };\n\n renderToggleButton = () => (\n <button\n className=\"drawer__toggler btn btn-primary-outline\"\n aria-label=\"Toggle player\"\n >\n <FontAwesomeIcon\n icon={this.state.open ? faChevronDown : faChevronUp}\n className=\"fa-lg\"\n />\n </button>\n );\n\n render() {\n const { playerStatus } = this.props;\n const { open } = this.state;\n\n let classes = 'drawer fixed-bottom bg-secondary shadow-lg p-2';\n\n if (open) {\n classes += ' drawer--open';\n document.body.classList.add('body--drawer');\n } else document.body.classList.remove('body--drawer');\n\n return (\n <div\n className={classes}\n onClick={this.handleClick}\n onTouchStart={this.handleTouchStart}\n onTouchMove={this.handleTouchMove}\n >\n {this.renderToggleButton()}\n {open ? (\n <Player playerStatus={playerStatus} />\n ) : (\n <MiniPlayer playerStatus={playerStatus} />\n )}\n </div>\n );\n }\n}\n\nexport default Drawer;\n","import React from 'react';\nimport Player from './player/player';\nimport Streams from './streams';\nimport Drawer from './drawer';\nimport './main.scss';\n\nconst Main = ({ playerStatus, onBackdrop }) => {\n return (\n <main className=\"main\">\n <Drawer playerStatus={playerStatus} />\n <div className=\"main__primary container\">\n <Streams onBackdrop={onBackdrop} playerStatus={playerStatus} />\n <Player playerStatus={playerStatus} />\n </div>\n </main>\n );\n};\n\nexport default Main;\n","import React, { Component } from 'react';\nimport { ToastContainer } from 'react-toastify';\nimport { withTranslation } from 'react-i18next';\nimport NavBar from './components/navBar';\nimport Settings from './components/modals/settings';\nimport URLDialog from './components/modals/urlDialog';\nimport About from './components/modals/about';\nimport Loader from './components/common/loader';\nimport Backdrop from './components/common/backdrop';\nimport Main from './components/main';\nimport { getStatus } from './services/playerService';\nimport { updateInterval, backdropTimeout } from './config.json';\nimport 'react-toastify/dist/ReactToastify.css';\n\nclass App extends Component {\n state = {\n playerStatus: {},\n loading: true,\n networkError: false,\n backdrop: {},\n };\n\n componentDidMount() {\n this.updatePlayerStatus();\n }\n\n componentDidUpdate() {\n this.setDocumentTitle();\n }\n\n async updatePlayerStatus() {\n try {\n const { data: playerStatus } = await getStatus();\n this.setState({ playerStatus, loading: false, networkError: false });\n } catch (ex) {\n this.setState({ loading: false, networkError: true });\n }\n\n setTimeout(() => this.updatePlayerStatus(), updateInterval);\n }\n\n handleBackdrop = (title, body = '') => {\n this.setState({ backdrop: { title, body } });\n setTimeout(() => this.setState({ backdrop: {} }), backdropTimeout);\n };\n\n setDocumentTitle = () => {\n const { playerStatus: status, networkError } = this.state;\n\n if (!networkError && status.playing)\n document.title = 'PiFi 🔊 ' + status.title;\n else document.title = 'PiFi Radio';\n };\n\n render() {\n const { loading, networkError, backdrop, playerStatus } = this.state;\n const { t } = this.props;\n\n if (networkError) return <Backdrop title={t('errorNetwork')} />;\n if (loading) return <Loader />;\n if (!playerStatus.con_mpd) return <Backdrop title={t('disconnectedMPD')} />;\n\n return (\n <div className=\"app\">\n <ToastContainer pauseOnFocusLoss={false} />\n <Backdrop title={backdrop.title} body={backdrop.body} />\n <NavBar />\n <Main onBackdrop={this.handleBackdrop} playerStatus={playerStatus} />\n <URLDialog />\n <Settings />\n <About />\n </div>\n );\n }\n}\n\nexport default withTranslation()(App);\n","// This optional code is used to register a service worker.\n// register() is not called by default.\n\n// This lets the app load faster on subsequent visits in production, and gives\n// it offline capabilities. However, it also means that developers (and users)\n// will only see deployed updates on subsequent visits to a page, after all the\n// existing tabs open on the page have been closed, since previously cached\n// resources are updated in the background.\n\n// To learn more about the benefits of this model and instructions on how to\n// opt-in, read https://bit.ly/CRA-PWA\n\nconst isLocalhost = Boolean(\n window.location.hostname === 'localhost' ||\n // [::1] is the IPv6 localhost address.\n window.location.hostname === '[::1]' ||\n // 127.0.0.0/8 are considered localhost for IPv4.\n window.location.hostname.match(\n /^127(?:\\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/\n )\n);\n\nexport function register(config) {\n if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {\n // The URL constructor is available in all browsers that support SW.\n const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href);\n if (publicUrl.origin !== window.location.origin) {\n // Our service worker won't work if PUBLIC_URL is on a different origin\n // from what our page is served on. This might happen if a CDN is used to\n // serve assets; see https://github.com/facebook/create-react-app/issues/2374\n return;\n }\n\n window.addEventListener('load', () => {\n const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;\n\n if (isLocalhost) {\n // This is running on localhost. Let's check if a service worker still exists or not.\n checkValidServiceWorker(swUrl, config);\n\n // Add some additional logging to localhost, pointing developers to the\n // service worker/PWA documentation.\n navigator.serviceWorker.ready.then(() => {\n console.log(\n 'This web app is being served cache-first by a service ' +\n 'worker. To learn more, visit https://bit.ly/CRA-PWA'\n );\n });\n } else {\n // Is not localhost. Just register service worker\n registerValidSW(swUrl, config);\n }\n });\n }\n}\n\nfunction registerValidSW(swUrl, config) {\n navigator.serviceWorker\n .register(swUrl)\n .then(registration => {\n registration.onupdatefound = () => {\n const installingWorker = registration.installing;\n if (installingWorker == null) {\n return;\n }\n installingWorker.onstatechange = () => {\n if (installingWorker.state === 'installed') {\n if (navigator.serviceWorker.controller) {\n // At this point, the updated precached content has been fetched,\n // but the previous service worker will still serve the older\n // content until all client tabs are closed.\n console.log(\n 'New content is available and will be used when all ' +\n 'tabs for this page are closed. See https://bit.ly/CRA-PWA.'\n );\n\n // Execute callback\n if (config && config.onUpdate) {\n config.onUpdate(registration);\n }\n } else {\n // At this point, everything has been precached.\n // It's the perfect time to display a\n // \"Content is cached for offline use.\" message.\n console.log('Content is cached for offline use.');\n\n // Execute callback\n if (config && config.onSuccess) {\n config.onSuccess(registration);\n }\n }\n }\n };\n };\n })\n .catch(error => {\n console.error('Error during service worker registration:', error);\n });\n}\n\nfunction checkValidServiceWorker(swUrl, config) {\n // Check if the service worker can be found. If it can't reload the page.\n fetch(swUrl, {\n headers: { 'Service-Worker': 'script' }\n })\n .then(response => {\n // Ensure service worker exists, and that we really are getting a JS file.\n const contentType = response.headers.get('content-type');\n if (\n response.status === 404 ||\n (contentType != null && contentType.indexOf('javascript') === -1)\n ) {\n // No service worker found. Probably a different app. Reload the page.\n navigator.serviceWorker.ready.then(registration => {\n registration.unregister().then(() => {\n window.location.reload();\n });\n });\n } else {\n // Service worker found. Proceed as normal.\n registerValidSW(swUrl, config);\n }\n })\n .catch(() => {\n console.log(\n 'No internet connection found. App is running in offline mode.'\n );\n });\n}\n\nexport function unregister() {\n if ('serviceWorker' in navigator) {\n navigator.serviceWorker.ready.then(registration => {\n registration.unregister();\n });\n }\n}\n","import i18n from 'i18next';\nimport { initReactI18next } from 'react-i18next';\n\nimport Backend from 'i18next-http-backend';\nimport LanguageDetector from 'i18next-browser-languagedetector';\n// not like to use this?\n// have a look at the Quick start guide\n// for passing in lng and translations on init\n\ni18n\n // load translation using xhr -> see /public/locales\n // learn more: https://github.com/i18next/i18next-xhr-backend\n .use(Backend)\n // detect user language\n // learn more: https://github.com/i18next/i18next-browser-languageDetector\n .use(LanguageDetector)\n // pass the i18n instance to react-i18next.\n .use(initReactI18next)\n // init i18next\n // for all options read: https://www.i18next.com/overview/configuration-options\n .init({\n fallbackLng: 'en-US',\n debug: process.env.REACT_APP_I18N_DEBUG,\n load: 'currentOnly',\n returnEmptyString: false,\n\n interpolation: {\n escapeValue: false // not needed for react as it escapes by default\n }\n });\n\n// Set language on html tag\ni18n.on('languageChanged', lang => {\n document.documentElement.setAttribute('lang', lang);\n});\n\nexport default i18n;\n","import React, { Suspense } from 'react';\nimport ReactDOM from 'react-dom';\nimport App from './App';\nimport Loader from './components/common/loader';\nimport * as serviceWorker from './serviceWorker';\nimport './i18n';\nimport theme from './theme';\nimport './index.scss';\n// Bootstrap dependencies\nimport 'jquery/dist/jquery.min.js';\nimport 'bootstrap/dist/js/bootstrap.js';\n\ntheme.apply();\n\nReactDOM.render(\n // Suspense needed for i18n\n <Suspense fallback={<Loader />}>\n <App />\n </Suspense>,\n document.getElementById('root')\n);\n\n// If you want your app to work offline and load faster, you can change\n// unregister() to register() below. Note this comes with some pitfalls.\n// Learn more about service workers: https://bit.ly/CRA-PWA\nserviceWorker.unregister();\n"],"sourceRoot":""}
data/lib/pifi/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module PiFi
2
- VERSION = "0.4.11".freeze
2
+ VERSION = "0.4.12".freeze
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pifi
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.11
4
+ version: 0.4.12
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rafael Cavalcanti
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-11-28 00:00:00.000000000 Z
11
+ date: 2021-04-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: sinatra
@@ -115,7 +115,7 @@ files:
115
115
  - lib/pifi/public/favicon.ico
116
116
  - lib/pifi/public/index.html
117
117
  - lib/pifi/public/locales/de-DE/translation.json
118
- - lib/pifi/public/locales/en/translation.json
118
+ - lib/pifi/public/locales/en-US/translation.json
119
119
  - lib/pifi/public/locales/fr-FR/translation.json
120
120
  - lib/pifi/public/locales/nl-NL/translation.json
121
121
  - lib/pifi/public/locales/pl-PL/translation.json
@@ -126,7 +126,7 @@ files:
126
126
  - lib/pifi/public/mstile-310x150.png
127
127
  - lib/pifi/public/mstile-310x310.png
128
128
  - lib/pifi/public/mstile-70x70.png
129
- - lib/pifi/public/precache-manifest.7164537a626dea8757b9c96b06c8c33d.js
129
+ - lib/pifi/public/precache-manifest.7acd7ef058033a1ded72e03e499bd33f.js
130
130
  - lib/pifi/public/robots.txt
131
131
  - lib/pifi/public/safari-pinned-tab.svg
132
132
  - lib/pifi/public/service-worker.js
@@ -137,8 +137,8 @@ files:
137
137
  - lib/pifi/public/static/js/2.6c0ded8a.chunk.js
138
138
  - lib/pifi/public/static/js/2.6c0ded8a.chunk.js.LICENSE.txt
139
139
  - lib/pifi/public/static/js/2.6c0ded8a.chunk.js.map
140
- - lib/pifi/public/static/js/main.21a62959.chunk.js
141
- - lib/pifi/public/static/js/main.21a62959.chunk.js.map
140
+ - lib/pifi/public/static/js/main.11609572.chunk.js
141
+ - lib/pifi/public/static/js/main.11609572.chunk.js.map
142
142
  - lib/pifi/public/static/js/runtime-main.5aae5a31.js
143
143
  - lib/pifi/public/static/js/runtime-main.5aae5a31.js.map
144
144
  - lib/pifi/public/static/media/getFetch.5e98861f.cjs
@@ -1,2 +0,0 @@
1
- (this.webpackJsonppifi_frontend=this.webpackJsonppifi_frontend||[]).push([[0],{13:function(e){e.exports=JSON.parse('{"b":["en","de-DE","fr-FR","nl-NL","pl-PL","pt-BR"],"c":1000,"a":2500,"d":3000}')},30:function(e,t,a){e.exports=a.p+"static/media/logo.91554ce9.svg"},50:function(e,t,a){e.exports=a(94)},56:function(e,t,a){},79:function(e,t,a){},80:function(e,t,a){},83:function(e,t,a){},84:function(e,t,a){},85:function(e,t,a){},86:function(e,t,a){},87:function(e,t,a){},89:function(e,t,a){},94:function(e,t,a){"use strict";a.r(t);var n=a(0),r=a.n(n),l=a(12),o=a.n(l),c=a(2),s=a.n(c),i=a(8),u=a(9),m=a(10),d=a(19),p=a(20),b=a(3),f=a(98),v=a(26),h=(a(56),a(30)),g=a.n(h),y=function(){var e=Object(v.a)().t;return r.a.createElement("nav",{className:"navbar navbar-expand-sm navbar-dark bg-primary"},r.a.createElement("div",{className:"container"},r.a.createElement("a",{className:"navbar-brand",href:"/"},r.a.createElement("img",{src:g.a,width:"30",height:"30",className:"d-inline-block align-text-bottom mr-2",alt:""}),"PiFi Radio"),r.a.createElement("button",{className:"navbar-toggler",type:"button","data-toggle":"collapse","data-target":"#navbarNavAltMarkup","aria-controls":"navbarNavAltMarkup","aria-expanded":"false","aria-label":"Toggle navigation"},r.a.createElement("span",{className:"navbar-toggler-icon"})),r.a.createElement("div",{className:"collapse navbar-collapse",id:"navbarNavAltMarkup"},r.a.createElement("div",{className:"navbar-nav pt-1 ml-2"},r.a.createElement("button",{className:"btn btn-link nav-link","data-toggle":"modal","data-target":"#url-dialog"},e("playURL")),r.a.createElement("button",{className:"btn btn-link nav-link","data-toggle":"modal","data-target":"#settings"},e("settings")),r.a.createElement("button",{className:"btn btn-link nav-link","data-toggle":"modal","data-target":"#about"},e("about"))))))},E=a(15),k=a(48),N=a(44),S=a.n(N),w=function(e){var t=e.id,a=e.title,n=e.footer,l=e.children,o=Object(v.a)().t,c=r.a.createElement("button",{className:"btn btn-secondary","data-dismiss":"modal"},o("close"));return r.a.createElement("div",{className:"modal fade",id:t,tabIndex:"-1",role:"dialog","aria-labelledby":"staticBackdropLabel","aria-hidden":"true"},r.a.createElement("div",{className:"modal-dialog",role:"document"},r.a.createElement("div",{className:"modal-content"},r.a.createElement("div",{className:"modal-header"},r.a.createElement("h5",{className:"modal-title",id:"staticBackdropLabel"},a),r.a.createElement("button",{type:"button",className:"close","data-dismiss":"modal","aria-label":"Close"},r.a.createElement("span",{"aria-hidden":"true"},"\xd7"))),r.a.createElement("div",{className:"modal-body"},l),r.a.createElement("div",{className:"modal-footer"},n||c))))},O=a(49),j=function(e){var t=e.id,a=e.label,n=e.row,l=e.data,o=Object(O.a)(e,["id","label","row","data"]);return r.a.createElement("div",{className:n?"form-group row":"form-group"},r.a.createElement("label",{htmlFor:t,className:"col-sm-2 col-form-label"},a),r.a.createElement("div",{className:"col-sm-10"},r.a.createElement("select",Object.assign({id:t,className:"form-control"},o),l.map((function(e){return r.a.createElement("option",{key:e.id||e,value:e.id||e},e.name||e)})))))},C=[{id:"darkly",name:"Darkly",themeColor:"#375a7f"},{id:"lux",name:"Lux",themeColor:"#1a1a1a"}];function _(){var e=x(),t=function(e){return"https://stackpath.bootstrapcdn.com/bootswatch/4.4.1/".concat(e,"/bootstrap.min.css")}(e),a=C.find((function(t){return t.id===e})).themeColor;document.querySelector('link[title="theme"]').setAttribute("href",t),document.querySelector('meta[name="theme-color"]').setAttribute("content",a)}function x(){var e=localStorage.getItem("theme");return""===e?"darkly":0===C.filter((function(t){return t.id===e})).length?(localStorage.removeItem("theme"),"darkly"):e}var T={availableThemes:C,getCurrentId:x,change:function(e){localStorage.setItem("theme",e),_()},apply:_},L=a(13),P=function(){var e=Object(v.a)(),t=e.t,a=e.i18n,l=Object(n.useState)(""),o=Object(E.a)(l,2),c=o[0],s=o[1],i=L.b.sort().map((function(e){var t=e.slice(0,2);return{id:e,name:"".concat(S()(e)," ").concat(k.a.getNativeName(t))}}));return Object(n.useEffect)((function(){s(T.getCurrentId())}),[]),r.a.createElement(w,{id:"settings",title:t("settings")},r.a.createElement(j,{id:"theme-select",label:t("theme"),data:T.availableThemes,row:!0,value:c,onChange:function(e){return function(e){var t=e.target.value;T.change(t),s(t)}(e)}}),r.a.createElement(j,{id:"language-select",label:t("language"),data:i,row:!0,value:a.language,onChange:function(e){return a.changeLanguage(e.target.value)}}))},R=a(24),A=a.n(R),B=a(97);A.a.defaults.baseURL="/api",A.a.interceptors.response.use(null,(function(e){var t=e.response&&e.response.status>=400&&e.response.status<500;return document.querySelector(".Toastify")&&(t||b.b.error(r.a.createElement(B.a,null,(function(e){return e("errorUnexpected")}))),e.response&&403===e.response.status&&b.b.error(r.a.createElement(B.a,null,(function(e){return e("errorForbidden")})))),Promise.reject(e)}));var D={get:A.a.get,post:A.a.post};function I(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:null,a=new FormData;return a.set("method",e),t&&a.set("params",t),a}function F(){return D.post("/player",I("play"))}function U(){return D.post("/player",I("stop"))}function M(e){return D.post("/player",I("change_vol",e))}function q(e){return D.post("/player",I("play_radios",e))}function W(e){return D.post("/player",I("play_urls",e))}var Y=function(){var e=Object(n.useState)(""),t=Object(E.a)(e,2),a=t[0],l=t[1],o=Object(v.a)().t,c=function(){""!==a&&(u(a),l(""))},u=function(){var e=Object(i.a)(s.a.mark((function e(t){var a,n;return s.a.wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return a={type:b.b.TYPE.ERROR,render:o("errorNotFound")},n=Object(b.b)(o("tryingURL")),e.prev=2,e.next=5,W(t);case 5:e.next=10;break;case 7:e.prev=7,e.t0=e.catch(2),e.t0.response&&400===e.t0.response.status&&b.b.update(n,a);case 10:case"end":return e.stop()}}),e,null,[[2,7]])})));return function(t){return e.apply(this,arguments)}}();return r.a.createElement(w,{id:"url-dialog",title:o("playURL"),footer:r.a.createElement(r.a.Fragment,null,r.a.createElement("button",{className:"btn btn-primary","data-dismiss":"modal",onClick:c},o("ok")),r.a.createElement("button",{className:"btn btn-secondary",onClick:function(){return l("")},"data-dismiss":"modal"},o("cancel")))},r.a.createElement("input",{className:"form-control mb-4",type:"text",placeholder:"URL",value:a,onChange:function(e){var t=e.target;l(t.value)},onKeyDown:function(e){"Enter"===e.key&&c()}}))};function K(){return D.get("/config")}a(79);var V=function(){return r.a.createElement("div",{className:"loader"},r.a.createElement("div",{className:"spinner-border",role:"status"},r.a.createElement("span",{className:"sr-only"},"Loading...")))},H=function(){var e=Object(v.a)().t,t=Object(n.useState)({}),a=Object(E.a)(t,2),l=a[0],o=a[1],c=Object(n.useState)(!0),u=Object(E.a)(c,2),m=u[0],d=u[1];Object(n.useEffect)((function(){function e(){return(e=Object(i.a)(s.a.mark((function e(){var t,a;return s.a.wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return e.next=2,K();case 2:t=e.sent,a=t.data,o(a),d(!1);case 6:case"end":return e.stop()}}),e)})))).apply(this,arguments)}!function(){e.apply(this,arguments)}()}),[]);var p=[{label:e("mpdHost"),value:l.mpd_host},{label:e("mpdPort"),value:l.mpd_port},{label:e("environment"),value:l.environment},{label:e("version"),value:l.version}],b=r.a.createElement("div",{className:"copyright small mt-5"},r.a.createElement("p",null,r.a.createElement("a",{href:"https://rafaelc.org/pifi",target:"_blank",rel:"noopener noreferrer",style:{color:"inherit"}},e("goToDocs"))),r.a.createElement("p",null,"Copyright \xa9 2017-2020\xa0",r.a.createElement("a",{href:"https://rafaelc.org/",target:"_blank",rel:"noopener noreferrer",style:{color:"inherit"}},"Rafael Cavalcanti"))),f=r.a.createElement("div",{className:"coffee mt-5"},r.a.createElement("p",null,e("coffee")),r.a.createElement("a",{href:"https://rafaelc.org/coffee",target:"_blank",rel:"noopener noreferrer"},r.a.createElement("img",{src:"https://cdn.buymeacoffee.com/buttons/default-blue.png",alt:"Buy Me A Coffee",style:{height:51,width:217}})));return r.a.createElement(w,{id:"about",title:e("about")},m?r.a.createElement(V,null):r.a.createElement("table",{className:"table"},r.a.createElement("tbody",null,p.map((function(e){return r.a.createElement("tr",{key:e.label},r.a.createElement("th",{scope:"row"},e.label),r.a.createElement("td",null,e.value))})))),f,b)},J=(a(80),function(){return window.innerWidth-document.documentElement.clientWidth}),G=function(e){var t=e.title,a=e.body,n=t,l=document.body.classList.contains("body--backdrop");n&&!l?(document.body.style.paddingRight=J()+"px",document.body.classList.add("body--backdrop")):!n&&l&&(document.body.style.paddingRight=0,document.body.classList.remove("body--backdrop"));var o="backdrop p-2 text-white"+(n?" backdrop--visible":"");return r.a.createElement("div",{className:o},r.a.createElement("h3",{className:"text-white"},t),r.a.createElement("h5",{className:"text-white"},a))},$=function(e){var t=e.playerStatus,a=t.playing,n=t.title,l=Object(v.a)().t;return r.a.createElement("div",{className:"text-center w-100"},r.a.createElement("h5",{className:"small text-uppercase"},l(a?"playing":"stopped")),r.a.createElement("h3",{className:"ellipsis"},n))},z=a(14),Q=a(11),X=function(e){return e.playing?r.a.createElement("button",{className:"btn btn-danger",onClick:U,"aria-label":"Stop"},r.a.createElement(z.a,{icon:Q.d})):r.a.createElement("button",{className:"btn btn-dark",onClick:F,"aria-label":"Play"},r.a.createElement(z.a,{icon:Q.c}))},Z=function(e){var t=e.playerStatus,a=Object(v.a)().t,n=null==t.vol,l=function(){var e=Object(i.a)(s.a.mark((function e(t){var n,r,l,o;return s.a.wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return e.next=2,M(t);case 2:n=e.sent,r=n.data,"vol",l="".concat(a("volume"),": ").concat(r,"%"),o={toastId:"vol",autoClose:L.d},b.b.isActive("vol")?b.b.update("vol",{render:l}):b.b.info(l,o);case 8:case"end":return e.stop()}}),e)})));return function(t){return e.apply(this,arguments)}}(),o=function(e,t,a){return r.a.createElement("button",{className:"btn btn-dark p-3",disabled:n,onClick:function(){return l(e)},"aria-label":a},r.a.createElement(z.a,{icon:t}))};return r.a.createElement("div",{className:"player-controls btn-group w-100 m-2",onClick:function(e){return e.stopPropagation()}},o("-5",Q.e,"Volume down"),o("+5",Q.f,"Volume up"),r.a.createElement(X,{playing:t.playing}))},ee=(a(83),function(e){var t=e.playerStatus;return r.a.createElement("div",{className:"player"},r.a.createElement($,{playerStatus:t}),r.a.createElement("img",{src:a(30),alt:"Logo",className:"player-logo m-4"}),r.a.createElement(Z,{playerStatus:t}))}),te=function(e){var t=e.value,a=e.onChange,n=Object(v.a)().t;return r.a.createElement("input",{className:"form-control mb-4",type:"text",id:"query",placeholder:n("search"),"aria-label":n("search"),autoComplete:"off",value:t,onChange:function(e){return a(e.target.value)},onFocus:function(){return a("")}})};a(84);var ae=function(e){Object(p.a)(a,e);var t=Object(d.a)(a);function a(){var e;Object(u.a)(this,a);for(var n=arguments.length,r=new Array(n),l=0;l<n;l++)r[l]=arguments[l];return(e=t.call.apply(t,[this].concat(r))).state={streams:{},loading:!0,query:""},e.isPlaying=function(t){return t===e.props.playerStatus.title&&e.props.playerStatus.playing},e.handleItemClick=function(){var t=Object(i.a)(s.a.mark((function t(a){var n,r,l;return s.a.wrap((function(t){for(;;)switch(t.prev=t.next){case 0:if(n=e.props,r=n.t,l=n.onBackdrop,!e.isPlaying(a)){t.next=3;break}return t.abrupt("return");case 3:return l(r("tuning"),a),t.prev=4,t.next=7,q(a);case 7:t.next=12;break;case 9:t.prev=9,t.t0=t.catch(4),t.t0.response&&400===t.t0.response.status&&b.b.error(r("errorNotFound"));case 12:case"end":return t.stop()}}),t,null,[[4,9]])})));return function(e){return t.apply(this,arguments)}}(),e.getItemClasses=function(t){var a="streams__item list-group-item ";return e.isPlaying(t)?a+"active":a+"list-group-item-action"},e.handleSearch=function(t){e.setState({query:t})},e}return Object(m.a)(a,[{key:"componentDidMount",value:function(){var e=Object(i.a)(s.a.mark((function e(){var t,a;return s.a.wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return e.next=2,D.get("/streams");case 2:t=e.sent,a=t.data,this.setState({streams:a,loading:!1});case 5:case"end":return e.stop()}}),e,this)})));return function(){return e.apply(this,arguments)}}()},{key:"filteredStreams",value:function(){var e=this.state,t=e.streams,a=e.query;if(""===a)return t;var n={};for(var r in t)r.toLowerCase().includes(a.toLowerCase())&&!t[r]&&(n[r]=t[r]);return n}},{key:"renderList",value:function(){var e=this,t=this.filteredStreams();return 0===Object.keys(t).length?r.a.createElement("h4",{className:"p-4"},this.props.t("noStreams")):r.a.createElement("ul",{className:"list-group list-group-flush"},Object.keys(t).map((function(a){return t[a]?r.a.createElement("li",{className:"streams__header ellipsis",key:a},a):r.a.createElement("li",{className:e.getItemClasses(a),key:a},r.a.createElement("button",{className:"streams__btn btn btn-outline ellipsis",onClick:function(){return e.handleItemClick(a)},disabled:e.isPlaying(a)},a))})))}},{key:"render",value:function(){return this.state.loading?r.a.createElement(V,null):r.a.createElement("div",{className:"streams"},r.a.createElement(te,{value:this.state.query,onChange:this.handleSearch}),this.renderList())}}]),a}(n.Component),ne=Object(f.a)()(ae),re=(a(85),function(e){var t=e.playerStatus,a=t.title,n=t.playing;return r.a.createElement("div",{className:"mini-player"},r.a.createElement("div",{className:"mini-player__left ellipsis"},a),r.a.createElement("div",{className:"mini-player__right",onClick:function(e){return e.stopPropagation()}},r.a.createElement(X,{playing:n})))}),le=(a(86),function(e){Object(p.a)(a,e);var t=Object(d.a)(a);function a(){var e;Object(u.a)(this,a);for(var n=arguments.length,l=new Array(n),o=0;o<n;o++)l[o]=arguments[o];return(e=t.call.apply(t,[this].concat(l))).state={open:!1},e.toggle=function(){e.setState({open:!e.state.open})},e.handleClick=function(){e.toggle()},e.handleTouchStart=function(t){if(t.touches.length>1)return e.setState({touchStartY:null});e.setState({touchStartY:t.touches[0].clientY})},e.handleTouchMove=function(t){var a=e.state,n=a.open,r=a.touchStartY;if(!(null===r||t.touches.length>1)){var l=t.changedTouches[0].clientY;(l>r&&n||l<r&&!n)&&e.toggle()}},e.renderToggleButton=function(){return r.a.createElement("button",{className:"drawer__toggler btn btn-primary-outline","aria-label":"Toggle player"},r.a.createElement(z.a,{icon:e.state.open?Q.a:Q.b,className:"fa-lg"}))},e}return Object(m.a)(a,[{key:"render",value:function(){var e=this.props.playerStatus,t=this.state.open,a="drawer fixed-bottom bg-secondary shadow-lg p-2";return t?(a+=" drawer--open",document.body.classList.add("body--drawer")):document.body.classList.remove("body--drawer"),r.a.createElement("div",{className:a,onClick:this.handleClick,onTouchStart:this.handleTouchStart,onTouchMove:this.handleTouchMove},this.renderToggleButton(),t?r.a.createElement(ee,{playerStatus:e}):r.a.createElement(re,{playerStatus:e}))}}]),a}(n.Component)),oe=(a(87),function(e){var t=e.playerStatus,a=e.onBackdrop;return r.a.createElement("main",{className:"main"},r.a.createElement(le,{playerStatus:t}),r.a.createElement("div",{className:"main__primary container"},r.a.createElement(ne,{onBackdrop:a,playerStatus:t}),r.a.createElement(ee,{playerStatus:t})))}),ce=(a(88),function(e){Object(p.a)(a,e);var t=Object(d.a)(a);function a(){var e;Object(u.a)(this,a);for(var n=arguments.length,r=new Array(n),l=0;l<n;l++)r[l]=arguments[l];return(e=t.call.apply(t,[this].concat(r))).state={playerStatus:{},loading:!0,networkError:!1,backdrop:{}},e.handleBackdrop=function(t){var a=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"";e.setState({backdrop:{title:t,body:a}}),setTimeout((function(){return e.setState({backdrop:{}})}),L.a)},e.setDocumentTitle=function(){var t=e.state,a=t.playerStatus;!t.networkError&&a.playing?document.title="PiFi \ud83d\udd0a "+a.title:document.title="PiFi Radio"},e}return Object(m.a)(a,[{key:"componentDidMount",value:function(){this.updatePlayerStatus()}},{key:"componentDidUpdate",value:function(){this.setDocumentTitle()}},{key:"updatePlayerStatus",value:function(){var e=Object(i.a)(s.a.mark((function e(){var t,a,n=this;return s.a.wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return e.prev=0,e.next=3,D.get("/player");case 3:t=e.sent,a=t.data,this.setState({playerStatus:a,loading:!1,networkError:!1}),e.next=11;break;case 8:e.prev=8,e.t0=e.catch(0),this.setState({loading:!1,networkError:!0});case 11:setTimeout((function(){return n.updatePlayerStatus()}),L.c);case 12:case"end":return e.stop()}}),e,this,[[0,8]])})));return function(){return e.apply(this,arguments)}}()},{key:"render",value:function(){var e=this.state,t=e.loading,a=e.networkError,n=e.backdrop,l=e.playerStatus,o=this.props.t;return a?r.a.createElement(G,{title:o("errorNetwork")}):t?r.a.createElement(V,null):l.con_mpd?r.a.createElement("div",{className:"app"},r.a.createElement(b.a,{pauseOnFocusLoss:!1}),r.a.createElement(G,{title:n.title,body:n.body}),r.a.createElement(y,null),r.a.createElement(oe,{onBackdrop:this.handleBackdrop,playerStatus:l}),r.a.createElement(Y,null),r.a.createElement(P,null),r.a.createElement(H,null)):r.a.createElement(G,{title:o("disconnectedMPD")})}}]),a}(n.Component)),se=Object(f.a)()(ce);Boolean("localhost"===window.location.hostname||"[::1]"===window.location.hostname||window.location.hostname.match(/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/));var ie=a(29),ue=a(21),me=a(47),de=a(46);ie.a.use(me.a).use(de.a).use(ue.f).init({fallbackLng:"en",debug:Object({NODE_ENV:"production",PUBLIC_URL:"",WDS_SOCKET_HOST:void 0,WDS_SOCKET_PATH:void 0,WDS_SOCKET_PORT:void 0,REACT_APP_API_URL:"/api"}).REACT_APP_I18N_DEBUG,load:"currentOnly",returnEmptyString:!1,interpolation:{escapeValue:!1}}),ie.a.on("languageChanged",(function(e){document.documentElement.setAttribute("lang",e)}));ie.a,a(89),a(90),a(91);T.apply(),o.a.render(r.a.createElement(n.Suspense,{fallback:r.a.createElement(V,null)},r.a.createElement(se,null)),document.getElementById("root")),"serviceWorker"in navigator&&navigator.serviceWorker.ready.then((function(e){e.unregister()}))}},[[50,1,2]]]);
2
- //# sourceMappingURL=main.21a62959.chunk.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["assets/logo.svg","components/navBar.jsx","components/common/modal.jsx","components/common/select.jsx","theme.js","components/modals/settings.jsx","services/httpService.js","services/playerService.js","components/modals/urlDialog.jsx","services/configService.js","components/common/loader.jsx","components/modals/about.jsx","components/common/backdrop.jsx","components/player/playerStatus.jsx","components/player/playStopControl.jsx","components/player/playerControls.jsx","components/player/player.jsx","components/common/searchBox.jsx","components/streams.jsx","services/streamsService.js","components/player/miniPlayer.jsx","components/drawer.jsx","components/main.jsx","App.js","serviceWorker.js","i18n.js","index.js"],"names":["module","exports","NavBar","t","useTranslation","className","href","src","logo","width","height","alt","type","data-toggle","data-target","aria-controls","aria-expanded","aria-label","id","Modal","title","footer","children","defaultFooter","data-dismiss","tabIndex","role","aria-labelledby","aria-hidden","Select","label","row","data","rest","htmlFor","map","d","key","value","name","availableThemes","themeColor","apply","themeId","getCurrentId","themePath","getThemePath","find","document","querySelector","setAttribute","localId","localStorage","getItem","filter","length","removeItem","change","setItem","Settings","i18n","useState","setThemeId","languagesData","languages","sort","lang","isoCode","slice","localeEmoji","ISO6391","getNativeName","useEffect","theme","onChange","e","newThemeId","target","handleThemeChange","language","changeLanguage","axios","defaults","baseURL","process","interceptors","response","use","error","expectedError","status","toast","Translation","Promise","reject","get","post","body","method","params","FormData","set","play","http","stop","changeVol","delta","playRadio","playURL","url","URLDialog","setURL","handleOK","doPlayURL","a","errorToastOpts","TYPE","ERROR","render","toastId","update","Fragment","onClick","placeholder","input","onKeyDown","getConfig","Loader","About","config","setConfig","loading","setLoading","fetchData","tableData","mpd_host","mpd_port","environment","version","copyright","rel","style","color","coffee","item","scope","getScrollbarWidth","window","innerWidth","documentElement","clientWidth","Backdrop","visible","bodyStyled","classList","contains","paddingRight","add","remove","classes","PlayerStatus","playerStatus","playing","PlayStopControl","icon","faStop","faPlay","PlayerControls","volDisabled","vol","handleVolChange","toastMsg","toastOpts","autoClose","volTimeout","isActive","info","renderVolButton","disabled","stopPropagation","faVolumeDown","faVolumeUp","Player","require","SearchBox","autoComplete","onFocus","Streams","state","streams","query","isPlaying","props","handleItemClick","onBackdrop","getItemClasses","handleSearch","setState","this","filtered","k","toLowerCase","includes","filteredStreams","Object","keys","renderList","Component","withTranslation","MiniPlayer","Drawer","open","toggle","handleClick","handleTouchStart","touches","touchStartY","clientY","handleTouchMove","touchEndY","changedTouches","renderToggleButton","faChevronDown","faChevronUp","onTouchStart","onTouchMove","Main","App","networkError","backdrop","handleBackdrop","setTimeout","backdropTimeout","setDocumentTitle","updatePlayerStatus","updateInterval","con_mpd","pauseOnFocusLoss","Boolean","location","hostname","match","Backend","LanguageDetector","initReactI18next","init","fallbackLng","debug","REACT_APP_I18N_DEBUG","load","returnEmptyString","interpolation","escapeValue","on","ReactDOM","fallback","getElementById","navigator","serviceWorker","ready","then","registration","unregister"],"mappings":"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","file":"static/js/main.21a62959.chunk.js","sourcesContent":["module.exports = __webpack_public_path__ + \"static/media/logo.91554ce9.svg\";","import React from 'react';\nimport { useTranslation } from 'react-i18next';\nimport './navBar.scss';\nimport logo from '../assets/logo.svg';\n\nconst NavBar = () => {\n const { t } = useTranslation();\n\n return (\n <nav className=\"navbar navbar-expand-sm navbar-dark bg-primary\">\n <div className=\"container\">\n <a className=\"navbar-brand\" href=\"/\">\n <img\n src={logo}\n width=\"30\"\n height=\"30\"\n className=\"d-inline-block align-text-bottom mr-2\"\n alt=\"\"\n />\n PiFi Radio\n </a>\n <button\n className=\"navbar-toggler\"\n type=\"button\"\n data-toggle=\"collapse\"\n data-target=\"#navbarNavAltMarkup\"\n aria-controls=\"navbarNavAltMarkup\"\n aria-expanded=\"false\"\n aria-label=\"Toggle navigation\"\n >\n <span className=\"navbar-toggler-icon\"></span>\n </button>\n <div className=\"collapse navbar-collapse\" id=\"navbarNavAltMarkup\">\n <div className=\"navbar-nav pt-1 ml-2\">\n {/* We use buttons and different class names instead of Bootstrap's\n anchor, so we don't get a warning for href=\"#\". */}\n <button\n className=\"btn btn-link nav-link\"\n data-toggle=\"modal\"\n data-target=\"#url-dialog\"\n >\n {t('playURL')}\n </button>\n\n <button\n className=\"btn btn-link nav-link\"\n data-toggle=\"modal\"\n data-target=\"#settings\"\n >\n {t('settings')}\n </button>\n\n <button\n className=\"btn btn-link nav-link\"\n data-toggle=\"modal\"\n data-target=\"#about\"\n >\n {t('about')}\n </button>\n </div>\n </div>\n </div>\n </nav>\n );\n};\nexport default NavBar;\n","import React from 'react';\nimport { useTranslation } from 'react-i18next';\n\nconst Modal = ({ id, title, footer, children }) => {\n const { t } = useTranslation();\n\n const defaultFooter = (\n <button className=\"btn btn-secondary\" data-dismiss=\"modal\">\n {t('close')}\n </button>\n );\n\n return (\n <div\n className=\"modal fade\"\n id={id}\n tabIndex=\"-1\"\n role=\"dialog\"\n aria-labelledby=\"staticBackdropLabel\"\n aria-hidden=\"true\"\n >\n <div className=\"modal-dialog\" role=\"document\">\n <div className=\"modal-content\">\n <div className=\"modal-header\">\n <h5 className=\"modal-title\" id=\"staticBackdropLabel\">\n {title}\n </h5>\n <button\n type=\"button\"\n className=\"close\"\n data-dismiss=\"modal\"\n aria-label=\"Close\"\n >\n <span aria-hidden=\"true\">&times;</span>\n </button>\n </div>\n <div className=\"modal-body\">{children}</div>\n <div className=\"modal-footer\">{footer ? footer : defaultFooter}</div>\n </div>\n </div>\n </div>\n );\n};\n\nexport default Modal;\n","import React from 'react';\n\nconst Select = ({ id, label, row, data, ...rest }) => (\n <div className={row ? 'form-group row' : 'form-group'}>\n <label htmlFor={id} className=\"col-sm-2 col-form-label\">\n {label}\n </label>\n <div className=\"col-sm-10\">\n <select id={id} className=\"form-control\" {...rest}>\n {data.map(d => (\n <option key={d.id || d} value={d.id || d}>\n {d.name || d}\n </option>\n ))}\n </select>\n </div>\n </div>\n);\n\nexport default Select;\n","const DEFAULT_THEME_ID = 'darkly';\n\nconst STORAGE_KEY = 'theme';\n\nconst getThemePath = themeId =>\n `https://stackpath.bootstrapcdn.com/bootswatch/4.4.1/${themeId}/bootstrap.min.css`;\n\nconst availableThemes = [\n { id: 'darkly', name: 'Darkly', themeColor: '#375a7f' },\n { id: 'lux', name: 'Lux', themeColor: '#1a1a1a' }\n];\n\nfunction change(themeId) {\n localStorage.setItem(STORAGE_KEY, themeId);\n apply();\n}\n\nfunction apply() {\n const themeId = getCurrentId();\n const themePath = getThemePath(themeId);\n const themeColor = availableThemes.find(t => t.id === themeId).themeColor;\n\n const linkEl = document.querySelector('link[title=\"theme\"]');\n linkEl.setAttribute('href', themePath);\n\n const metaEl = document.querySelector('meta[name=\"theme-color\"]');\n metaEl.setAttribute('content', themeColor);\n}\n\nfunction getCurrentId() {\n const localId = localStorage.getItem(STORAGE_KEY);\n\n if (localId === '') return DEFAULT_THEME_ID;\n if (availableThemes.filter(t => t.id === localId).length === 0) {\n localStorage.removeItem(STORAGE_KEY);\n return DEFAULT_THEME_ID;\n }\n\n return localId;\n}\n\nexport default {\n availableThemes,\n getCurrentId,\n change,\n apply\n};\n","import React, { useState, useEffect } from 'react';\nimport { useTranslation } from 'react-i18next';\nimport ISO6391 from 'iso-639-1';\nimport localeEmoji from 'locale-emoji';\nimport Modal from '../common/modal';\nimport Select from '../common/select';\nimport theme from '../../theme';\nimport { languages } from '../../config.json';\n\nconst Settings = () => {\n const { t, i18n } = useTranslation();\n\n const [themeId, setThemeId] = useState('');\n\n const languagesData = languages.sort().map(lang => {\n const isoCode = lang.slice(0, 2);\n return {\n id: lang,\n name: `${localeEmoji(lang)} ${ISO6391.getNativeName(isoCode)}`\n };\n });\n\n const handleThemeChange = ({ target }) => {\n const newThemeId = target.value;\n theme.change(newThemeId);\n setThemeId(newThemeId);\n };\n\n useEffect(() => {\n setThemeId(theme.getCurrentId());\n }, []);\n\n return (\n <Modal id=\"settings\" title={t('settings')}>\n <Select\n id=\"theme-select\"\n label={t('theme')}\n data={theme.availableThemes}\n row\n value={themeId}\n onChange={e => handleThemeChange(e)}\n />\n <Select\n id=\"language-select\"\n label={t('language')}\n data={languagesData}\n row\n value={i18n.language}\n onChange={e => i18n.changeLanguage(e.target.value)}\n />\n </Modal>\n );\n};\n\nexport default Settings;\n","import React from 'react';\nimport axios from 'axios';\nimport { toast } from 'react-toastify';\nimport { Translation } from 'react-i18next';\n\naxios.defaults.baseURL = process.env.REACT_APP_API_URL;\n\naxios.interceptors.response.use(null, error => {\n const expectedError =\n error.response &&\n error.response.status >= 400 &&\n error.response.status < 500;\n\n // Some errors cause our app to only render a backdrop warning.\n // Queuing toasts would be redundant.\n const hasToastify = document.querySelector('.Toastify');\n\n if (hasToastify) {\n if (!expectedError)\n toast.error(<Translation>{t => t('errorUnexpected')}</Translation>);\n\n // Universal expected error\n if (error.response && error.response.status === 403)\n toast.error(<Translation>{t => t('errorForbidden')}</Translation>);\n }\n\n return Promise.reject(error);\n});\n\nexport default {\n get: axios.get,\n post: axios.post\n};\n","import http from './httpService.js';\n\nconst apiEndpoint = '/player';\n\nfunction body(method, params = null) {\n const body = new FormData();\n body.set('method', method);\n if (params) body.set('params', params);\n return body;\n}\n\nexport function getStatus() {\n return http.get(apiEndpoint);\n}\n\nexport function play() {\n return http.post(apiEndpoint, body('play'));\n}\n\nexport function stop() {\n return http.post(apiEndpoint, body('stop'));\n}\n\nexport function changeVol(delta) {\n return http.post(apiEndpoint, body('change_vol', delta));\n}\n\nexport function playRadio(name) {\n return http.post(apiEndpoint, body('play_radios', name));\n}\n\nexport function playURL(url) {\n return http.post(apiEndpoint, body('play_urls', url));\n}\n","import React, { useState } from 'react';\nimport { toast } from 'react-toastify';\nimport { useTranslation } from 'react-i18next';\nimport Modal from '../common/modal';\nimport { playURL } from '../../services/playerService';\n\nconst URLDialog = () => {\n const [url, setURL] = useState('');\n\n const { t } = useTranslation();\n\n const handleOK = () => {\n if (url === '') return;\n doPlayURL(url);\n setURL('');\n };\n\n const doPlayURL = async url => {\n const errorToastOpts = {\n type: toast.TYPE.ERROR,\n render: t('errorNotFound')\n };\n\n const toastId = toast(t('tryingURL'));\n\n try {\n await playURL(url);\n } catch (ex) {\n if (ex.response && ex.response.status === 400)\n toast.update(toastId, errorToastOpts);\n }\n };\n\n const handleChange = ({ target: input }) => {\n setURL(input.value);\n };\n\n const handleKeyDown = e => {\n if (e.key === 'Enter') handleOK();\n };\n\n const renderFooter = () => (\n <React.Fragment>\n <button\n className=\"btn btn-primary\"\n data-dismiss=\"modal\"\n onClick={handleOK}\n >\n {t('ok')}\n </button>\n <button\n className=\"btn btn-secondary\"\n onClick={() => setURL('')}\n data-dismiss=\"modal\"\n >\n {t('cancel')}\n </button>\n </React.Fragment>\n );\n\n return (\n <Modal id=\"url-dialog\" title={t('playURL')} footer={renderFooter()}>\n <input\n className=\"form-control mb-4\"\n type=\"text\"\n placeholder=\"URL\"\n value={url}\n onChange={handleChange}\n onKeyDown={handleKeyDown}\n />\n </Modal>\n );\n};\n\nexport default URLDialog;\n","import http from './httpService';\n\nconst apiEndpoint = '/config';\n\nexport function getConfig() {\n return http.get(apiEndpoint);\n}\n","import React from 'react';\nimport './loader.scss';\n\nconst Loader = () => (\n <div className=\"loader\">\n <div className=\"spinner-border\" role=\"status\">\n <span className=\"sr-only\">Loading...</span>\n </div>\n </div>\n);\n\nexport default Loader;\n","import React from 'react';\nimport { useEffect, useState } from 'react';\nimport { useTranslation } from 'react-i18next';\nimport Modal from '../common/modal';\nimport { getConfig } from '../../services/configService';\nimport Loader from '../common/loader';\n\nconst About = () => {\n const { t } = useTranslation();\n\n const [config, setConfig] = useState({});\n const [loading, setLoading] = useState(true);\n\n useEffect(() => {\n async function fetchData() {\n const { data: config } = await getConfig();\n setConfig(config);\n setLoading(false);\n }\n fetchData();\n }, []);\n\n const tableData = [\n { label: t('mpdHost'), value: config.mpd_host },\n { label: t('mpdPort'), value: config.mpd_port },\n { label: t('environment'), value: config.environment },\n { label: t('version'), value: config.version },\n ];\n\n const renderTable = () => {\n if (loading) return <Loader />;\n\n return (\n <table className=\"table\">\n <tbody>\n {tableData.map((item) => (\n <tr key={item.label}>\n <th scope=\"row\">{item.label}</th>\n <td>{item.value}</td>\n </tr>\n ))}\n </tbody>\n </table>\n );\n };\n\n const copyright = (\n <div className=\"copyright small mt-5\">\n <p>\n <a\n href=\"https://rafaelc.org/pifi\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n style={{ color: 'inherit' }}\n >\n {t('goToDocs')}\n </a>\n </p>\n <p>\n Copyright &copy; 2017-2020&nbsp;\n {/* Use the default color, as some themes give a different color for links. */}\n <a\n href=\"https://rafaelc.org/\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n style={{ color: 'inherit' }}\n >\n Rafael Cavalcanti\n </a>\n </p>\n </div>\n );\n\n const coffee = (\n <div className=\"coffee mt-5\">\n <p>{t('coffee')}</p>\n <a\n href=\"https://rafaelc.org/coffee\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n >\n <img\n src=\"https://cdn.buymeacoffee.com/buttons/default-blue.png\"\n alt=\"Buy Me A Coffee\"\n style={{ height: 51, width: 217 }}\n />\n </a>\n </div>\n );\n\n return (\n <Modal id=\"about\" title={t('about')}>\n {renderTable()}\n {coffee}\n {copyright}\n </Modal>\n );\n};\n\nexport default About;\n","import React from 'react';\nimport './backdrop.scss';\n\nconst Backdrop = ({ title, body }) => {\n const visible = title;\n const bodyStyled = document.body.classList.contains('body--backdrop');\n\n if (visible && !bodyStyled) {\n // Calculate this BEFORE adding the class.\n document.body.style.paddingRight = getScrollbarWidth() + 'px';\n document.body.classList.add('body--backdrop');\n // We need this check because Bootstrap modals also style paddingRight\n } else if (!visible && bodyStyled) {\n document.body.style.paddingRight = 0;\n document.body.classList.remove('body--backdrop');\n }\n\n const classes =\n 'backdrop p-2 text-white' + (visible ? ' backdrop--visible' : '');\n\n // We need text-white on h* tags because some themes override it\n return (\n <div className={classes}>\n <h3 className=\"text-white\">{title}</h3>\n <h5 className=\"text-white\">{body}</h5>\n </div>\n );\n};\n\nconst getScrollbarWidth = () =>\n window.innerWidth - document.documentElement.clientWidth;\n\nexport default Backdrop;\n","import React from 'react';\nimport { useTranslation } from 'react-i18next';\n\nconst PlayerStatus = ({ playerStatus }) => {\n const { playing, title } = playerStatus;\n\n const { t } = useTranslation();\n\n return (\n <div className=\"text-center w-100\">\n <h5 className=\"small text-uppercase\">\n {playing ? t('playing') : t('stopped')}\n </h5>\n <h3 className=\"ellipsis\">{title}</h3>\n </div>\n );\n};\n\nexport default PlayerStatus;\n","import React from 'react';\nimport { FontAwesomeIcon } from '@fortawesome/react-fontawesome';\nimport { faStop, faPlay } from '@fortawesome/free-solid-svg-icons';\nimport { play, stop } from '../../services/playerService';\n\nconst PlayStopControl = ({ playing }) =>\n playing ? (\n <button className=\"btn btn-danger\" onClick={stop} aria-label=\"Stop\">\n <FontAwesomeIcon icon={faStop} />\n </button>\n ) : (\n <button className=\"btn btn-dark\" onClick={play} aria-label=\"Play\">\n <FontAwesomeIcon icon={faPlay} />\n </button>\n );\n\nexport default PlayStopControl;\n","import React from 'react';\nimport PlayStopControl from './playStopControl';\nimport { FontAwesomeIcon } from '@fortawesome/react-fontawesome';\nimport { faVolumeDown, faVolumeUp } from '@fortawesome/free-solid-svg-icons';\nimport { toast } from 'react-toastify';\nimport { useTranslation } from 'react-i18next';\nimport { changeVol } from '../../services/playerService';\nimport { volTimeout } from '../../config.json';\n\nconst PlayerControls = ({ playerStatus }) => {\n const { t } = useTranslation();\n\n const volDisabled = playerStatus.vol == null;\n\n const handleVolChange = async delta => {\n const { data: vol } = await changeVol(delta);\n\n const toastId = 'vol';\n const toastMsg = `${t('volume')}: ${vol}%`;\n const toastOpts = { toastId, autoClose: volTimeout };\n if (toast.isActive(toastId)) toast.update(toastId, { render: toastMsg });\n else toast.info(toastMsg, toastOpts);\n };\n\n const renderVolButton = (delta, icon, label) => (\n <button\n className=\"btn btn-dark p-3\"\n disabled={volDisabled}\n onClick={() => handleVolChange(delta)}\n aria-label={label}\n >\n <FontAwesomeIcon icon={icon} />\n </button>\n );\n\n return (\n <div\n className=\"player-controls btn-group w-100 m-2\"\n onClick={e => e.stopPropagation()}\n >\n {renderVolButton('-5', faVolumeDown, 'Volume down')}\n {renderVolButton('+5', faVolumeUp, 'Volume up')}\n <PlayStopControl playing={playerStatus.playing} />\n </div>\n );\n};\n\nexport default PlayerControls;\n","import React from 'react';\nimport PlayerStatus from './playerStatus';\nimport PlayerControls from './playerControls';\nimport './player.scss';\n\nconst Player = ({ playerStatus }) => {\n const renderLogo = () => (\n <img\n src={require('../../assets/logo.svg')}\n alt=\"Logo\"\n className=\"player-logo m-4\"\n />\n );\n\n return (\n <div className=\"player\">\n <PlayerStatus playerStatus={playerStatus} />\n {renderLogo()}\n <PlayerControls playerStatus={playerStatus} />\n </div>\n );\n};\n\nexport default Player;\n","import React from 'react';\nimport { useTranslation } from 'react-i18next';\n\nconst SearchBox = ({ value, onChange }) => {\n const { t } = useTranslation();\n\n return (\n <input\n className=\"form-control mb-4\"\n type=\"text\"\n id=\"query\"\n placeholder={t('search')}\n aria-label={t('search')}\n autoComplete=\"off\"\n value={value}\n onChange={e => onChange(e.target.value)}\n onFocus={() => onChange('')}\n ></input>\n );\n};\n\nexport default SearchBox;\n","import React, { Component } from 'react';\nimport { withTranslation } from 'react-i18next';\nimport { toast } from 'react-toastify';\nimport Loader from './common/loader';\nimport SearchBox from './common/searchBox';\nimport { playRadio } from '../services/playerService';\nimport { getStreams } from '../services/streamsService';\nimport './streams.scss';\n\nclass Streams extends Component {\n state = { streams: {}, loading: true, query: '' };\n\n async componentDidMount() {\n const { data: streams } = await getStreams();\n this.setState({ streams, loading: false });\n }\n\n isPlaying = name => {\n return (\n name === this.props.playerStatus.title && this.props.playerStatus.playing\n );\n };\n\n handleItemClick = async name => {\n const { t, onBackdrop } = this.props;\n\n if (this.isPlaying(name)) return;\n\n onBackdrop(t('tuning'), name);\n try {\n await playRadio(name);\n } catch (ex) {\n if (ex.response && ex.response.status === 400)\n toast.error(t('errorNotFound'));\n }\n };\n\n getItemClasses = name => {\n const classes = 'streams__item list-group-item ';\n return this.isPlaying(name)\n ? classes + 'active'\n : classes + 'list-group-item-action';\n };\n\n handleSearch = query => {\n this.setState({ query });\n };\n\n filteredStreams() {\n const { streams, query } = this.state;\n\n if (query === '') return streams;\n\n let filtered = {};\n for (let k in streams) {\n if (k.toLowerCase().includes(query.toLowerCase()) && !streams[k]) {\n filtered[k] = streams[k];\n }\n }\n return filtered;\n }\n\n renderList() {\n const streams = this.filteredStreams();\n\n if (Object.keys(streams).length === 0)\n return <h4 className=\"p-4\">{this.props.t('noStreams')}</h4>;\n\n return (\n <ul className=\"list-group list-group-flush\">\n {Object.keys(streams).map(name =>\n streams[name] ? (\n <li className=\"streams__header ellipsis\" key={name}>\n {name}\n </li>\n ) : (\n <li className={this.getItemClasses(name)} key={name}>\n <button\n className=\"streams__btn btn btn-outline ellipsis\"\n onClick={() => this.handleItemClick(name)}\n disabled={this.isPlaying(name)}\n >\n {name}\n </button>\n </li>\n )\n )}\n </ul>\n );\n }\n\n render() {\n if (this.state.loading) return <Loader />;\n\n return (\n <div className=\"streams\">\n <SearchBox value={this.state.query} onChange={this.handleSearch} />\n {this.renderList()}\n </div>\n );\n }\n}\n\nexport default withTranslation()(Streams);\n","import http from './httpService';\n\nconst apiEndpoint = '/streams';\n\nexport function getStreams() {\n return http.get(apiEndpoint);\n}\n","import React from 'react';\nimport PlayStopControl from './playStopControl';\nimport './miniPlayer.scss';\n\nconst MiniPlayer = ({ playerStatus }) => {\n const { title, playing } = playerStatus;\n\n return (\n <div className=\"mini-player\">\n <div className=\"mini-player__left ellipsis\">\n {title}\n </div>\n <div\n className=\"mini-player__right\"\n onClick={e => e.stopPropagation()}\n >\n <PlayStopControl playing={playing} />\n </div>\n </div>\n );\n};\n\nexport default MiniPlayer;\n","import React, { Component } from 'react';\nimport { FontAwesomeIcon } from '@fortawesome/react-fontawesome';\nimport { faChevronDown, faChevronUp } from '@fortawesome/free-solid-svg-icons';\nimport MiniPlayer from './player/miniPlayer';\nimport Player from './player/player';\nimport './drawer.scss';\n\nclass Drawer extends Component {\n state = { open: false };\n\n toggle = () => {\n this.setState({ open: !this.state.open });\n };\n\n handleClick = () => {\n this.toggle();\n };\n\n handleTouchStart = (e) => {\n // Ignore multitouch\n if (e.touches.length > 1) return this.setState({ touchStartY: null });\n\n this.setState({ touchStartY: e.touches[0].clientY });\n };\n\n handleTouchMove = (e) => {\n const { open, touchStartY } = this.state;\n\n // Ignore multitouch\n if (touchStartY === null || e.touches.length > 1) return;\n\n const touchEndY = e.changedTouches[0].clientY;\n const touchUp = touchEndY < touchStartY;\n const touchDown = touchEndY > touchStartY;\n\n if (touchDown && open) this.toggle();\n else if (touchUp && !open) this.toggle();\n };\n\n renderToggleButton = () => (\n <button\n className=\"drawer__toggler btn btn-primary-outline\"\n aria-label=\"Toggle player\"\n >\n <FontAwesomeIcon\n icon={this.state.open ? faChevronDown : faChevronUp}\n className=\"fa-lg\"\n />\n </button>\n );\n\n render() {\n const { playerStatus } = this.props;\n const { open } = this.state;\n\n let classes = 'drawer fixed-bottom bg-secondary shadow-lg p-2';\n\n if (open) {\n classes += ' drawer--open';\n document.body.classList.add('body--drawer');\n } else document.body.classList.remove('body--drawer');\n\n return (\n <div\n className={classes}\n onClick={this.handleClick}\n onTouchStart={this.handleTouchStart}\n onTouchMove={this.handleTouchMove}\n >\n {this.renderToggleButton()}\n {open ? (\n <Player playerStatus={playerStatus} />\n ) : (\n <MiniPlayer playerStatus={playerStatus} />\n )}\n </div>\n );\n }\n}\n\nexport default Drawer;\n","import React from 'react';\nimport Player from './player/player';\nimport Streams from './streams';\nimport Drawer from './drawer';\nimport './main.scss';\n\nconst Main = ({ playerStatus, onBackdrop }) => {\n return (\n <main className=\"main\">\n <Drawer playerStatus={playerStatus} />\n <div className=\"main__primary container\">\n <Streams onBackdrop={onBackdrop} playerStatus={playerStatus} />\n <Player playerStatus={playerStatus} />\n </div>\n </main>\n );\n};\n\nexport default Main;\n","import React, { Component } from 'react';\nimport { ToastContainer } from 'react-toastify';\nimport { withTranslation } from 'react-i18next';\nimport NavBar from './components/navBar';\nimport Settings from './components/modals/settings';\nimport URLDialog from './components/modals/urlDialog';\nimport About from './components/modals/about';\nimport Loader from './components/common/loader';\nimport Backdrop from './components/common/backdrop';\nimport Main from './components/main';\nimport { getStatus } from './services/playerService';\nimport { updateInterval, backdropTimeout } from './config.json';\nimport 'react-toastify/dist/ReactToastify.css';\n\nclass App extends Component {\n state = {\n playerStatus: {},\n loading: true,\n networkError: false,\n backdrop: {},\n };\n\n componentDidMount() {\n this.updatePlayerStatus();\n }\n\n componentDidUpdate() {\n this.setDocumentTitle();\n }\n\n async updatePlayerStatus() {\n try {\n const { data: playerStatus } = await getStatus();\n this.setState({ playerStatus, loading: false, networkError: false });\n } catch (ex) {\n this.setState({ loading: false, networkError: true });\n }\n\n setTimeout(() => this.updatePlayerStatus(), updateInterval);\n }\n\n handleBackdrop = (title, body = '') => {\n this.setState({ backdrop: { title, body } });\n setTimeout(() => this.setState({ backdrop: {} }), backdropTimeout);\n };\n\n setDocumentTitle = () => {\n const { playerStatus: status, networkError } = this.state;\n\n if (!networkError && status.playing)\n document.title = 'PiFi 🔊 ' + status.title;\n else document.title = 'PiFi Radio';\n };\n\n render() {\n const { loading, networkError, backdrop, playerStatus } = this.state;\n const { t } = this.props;\n\n if (networkError) return <Backdrop title={t('errorNetwork')} />;\n if (loading) return <Loader />;\n if (!playerStatus.con_mpd) return <Backdrop title={t('disconnectedMPD')} />;\n\n return (\n <div className=\"app\">\n <ToastContainer pauseOnFocusLoss={false} />\n <Backdrop title={backdrop.title} body={backdrop.body} />\n <NavBar />\n <Main onBackdrop={this.handleBackdrop} playerStatus={playerStatus} />\n <URLDialog />\n <Settings />\n <About />\n </div>\n );\n }\n}\n\nexport default withTranslation()(App);\n","// This optional code is used to register a service worker.\n// register() is not called by default.\n\n// This lets the app load faster on subsequent visits in production, and gives\n// it offline capabilities. However, it also means that developers (and users)\n// will only see deployed updates on subsequent visits to a page, after all the\n// existing tabs open on the page have been closed, since previously cached\n// resources are updated in the background.\n\n// To learn more about the benefits of this model and instructions on how to\n// opt-in, read https://bit.ly/CRA-PWA\n\nconst isLocalhost = Boolean(\n window.location.hostname === 'localhost' ||\n // [::1] is the IPv6 localhost address.\n window.location.hostname === '[::1]' ||\n // 127.0.0.0/8 are considered localhost for IPv4.\n window.location.hostname.match(\n /^127(?:\\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/\n )\n);\n\nexport function register(config) {\n if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {\n // The URL constructor is available in all browsers that support SW.\n const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href);\n if (publicUrl.origin !== window.location.origin) {\n // Our service worker won't work if PUBLIC_URL is on a different origin\n // from what our page is served on. This might happen if a CDN is used to\n // serve assets; see https://github.com/facebook/create-react-app/issues/2374\n return;\n }\n\n window.addEventListener('load', () => {\n const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;\n\n if (isLocalhost) {\n // This is running on localhost. Let's check if a service worker still exists or not.\n checkValidServiceWorker(swUrl, config);\n\n // Add some additional logging to localhost, pointing developers to the\n // service worker/PWA documentation.\n navigator.serviceWorker.ready.then(() => {\n console.log(\n 'This web app is being served cache-first by a service ' +\n 'worker. To learn more, visit https://bit.ly/CRA-PWA'\n );\n });\n } else {\n // Is not localhost. Just register service worker\n registerValidSW(swUrl, config);\n }\n });\n }\n}\n\nfunction registerValidSW(swUrl, config) {\n navigator.serviceWorker\n .register(swUrl)\n .then(registration => {\n registration.onupdatefound = () => {\n const installingWorker = registration.installing;\n if (installingWorker == null) {\n return;\n }\n installingWorker.onstatechange = () => {\n if (installingWorker.state === 'installed') {\n if (navigator.serviceWorker.controller) {\n // At this point, the updated precached content has been fetched,\n // but the previous service worker will still serve the older\n // content until all client tabs are closed.\n console.log(\n 'New content is available and will be used when all ' +\n 'tabs for this page are closed. See https://bit.ly/CRA-PWA.'\n );\n\n // Execute callback\n if (config && config.onUpdate) {\n config.onUpdate(registration);\n }\n } else {\n // At this point, everything has been precached.\n // It's the perfect time to display a\n // \"Content is cached for offline use.\" message.\n console.log('Content is cached for offline use.');\n\n // Execute callback\n if (config && config.onSuccess) {\n config.onSuccess(registration);\n }\n }\n }\n };\n };\n })\n .catch(error => {\n console.error('Error during service worker registration:', error);\n });\n}\n\nfunction checkValidServiceWorker(swUrl, config) {\n // Check if the service worker can be found. If it can't reload the page.\n fetch(swUrl, {\n headers: { 'Service-Worker': 'script' }\n })\n .then(response => {\n // Ensure service worker exists, and that we really are getting a JS file.\n const contentType = response.headers.get('content-type');\n if (\n response.status === 404 ||\n (contentType != null && contentType.indexOf('javascript') === -1)\n ) {\n // No service worker found. Probably a different app. Reload the page.\n navigator.serviceWorker.ready.then(registration => {\n registration.unregister().then(() => {\n window.location.reload();\n });\n });\n } else {\n // Service worker found. Proceed as normal.\n registerValidSW(swUrl, config);\n }\n })\n .catch(() => {\n console.log(\n 'No internet connection found. App is running in offline mode.'\n );\n });\n}\n\nexport function unregister() {\n if ('serviceWorker' in navigator) {\n navigator.serviceWorker.ready.then(registration => {\n registration.unregister();\n });\n }\n}\n","import i18n from 'i18next';\nimport { initReactI18next } from 'react-i18next';\n\nimport Backend from 'i18next-http-backend';\nimport LanguageDetector from 'i18next-browser-languagedetector';\n// not like to use this?\n// have a look at the Quick start guide\n// for passing in lng and translations on init\n\ni18n\n // load translation using xhr -> see /public/locales\n // learn more: https://github.com/i18next/i18next-xhr-backend\n .use(Backend)\n // detect user language\n // learn more: https://github.com/i18next/i18next-browser-languageDetector\n .use(LanguageDetector)\n // pass the i18n instance to react-i18next.\n .use(initReactI18next)\n // init i18next\n // for all options read: https://www.i18next.com/overview/configuration-options\n .init({\n fallbackLng: 'en',\n debug: process.env.REACT_APP_I18N_DEBUG,\n load: 'currentOnly',\n returnEmptyString: false,\n\n interpolation: {\n escapeValue: false // not needed for react as it escapes by default\n }\n });\n\n// Set language on html tag\ni18n.on('languageChanged', lang => {\n document.documentElement.setAttribute('lang', lang);\n});\n\nexport default i18n;\n","import React, { Suspense } from 'react';\nimport ReactDOM from 'react-dom';\nimport App from './App';\nimport Loader from './components/common/loader';\nimport * as serviceWorker from './serviceWorker';\nimport './i18n';\nimport theme from './theme';\nimport './index.scss';\n// Bootstrap dependencies\nimport 'jquery/dist/jquery.min.js';\nimport 'bootstrap/dist/js/bootstrap.js';\n\ntheme.apply();\n\nReactDOM.render(\n // Suspense needed for i18n\n <Suspense fallback={<Loader />}>\n <App />\n </Suspense>,\n document.getElementById('root')\n);\n\n// If you want your app to work offline and load faster, you can change\n// unregister() to register() below. Note this comes with some pitfalls.\n// Learn more about service workers: https://bit.ly/CRA-PWA\nserviceWorker.unregister();\n"],"sourceRoot":""}