]> git.0d.be Git - botaradio.git/blob - templates/index.html
only update every seconds when there's a sound playing
[botaradio.git] / templates / index.html
1 {% macro dirlisting(dir, path='') -%}
2 <ul class="list-group">
3     {% if dir and dir.get_subdirs().items() %}
4     {% for subdirname, subdirobj in dir.get_subdirs().items() %}
5     {% set subdirpath = os.path.relpath(subdirobj.fullpath, music_library.fullpath) %}
6     {% set subdirid = subdirpath.replace("/","-") %}
7     <li class="directory list-group-item list-group-item-primary">
8         <div class="btn-group" role="group">
9             <div class="btn-group" role="group">
10                 <button type="button" class="btn btn-success btn-sm"
11                         onclick="request('/post', {add_folder : '{{ subdirpath }}'})">
12                     <i class="fa fa-plus" aria-hidden="true"></i>
13                 </button>
14                 <div class="btn-group" role="group">
15                     <button id="btnGroupDrop2" type="button" class="btn btn-success btn-sm dropdown-toggle btn-space" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></button>
16                     <div class="dropdown-menu" aria-labelledby="btnGroupDrop2" style="">
17                         <a class="dropdown-item"
18                            onclick="request('/post', {add_folder : '{{ subdirpath }}'})">
19                             <i class="fa fa-folder" aria-hidden="true"></i> Entire folder
20                         </a>
21                         <a class="dropdown-item"
22                            onclick="request('/post', {add_folder_recursively : '{{ subdirpath }}'})">
23                             <i class="fa fa-folder" aria-hidden="true"></i> Entire folder and sub-folders
24                         </a>
25                     </div>
26                 </div>
27             </div>
28
29         </div>
30
31         <div class="btn-group lead"><div class="btn-space"><i class="fa fa-folder" aria-hidden="true"></i></div><a class="lead" data-toggle="collapse"
32                     data-target="#multiCollapse-{{ subdirid }}" aria-expanded="true"
33                     aria-controls="multiCollapse-{{ subdirid }}" href="#"> {{ subdirpath }}/</a>
34         </div>
35
36         <div class="btn-group" style="float: right;">
37             <form action="./download" method="get" class="directory">
38                 <input type="text" value="{{ subdirpath }}" name="directory" hidden>
39                 <button type="submit" class="btn btn-primary btn-sm btn-space"><i class="fa fa-download" aria-hidden="true"></i></button>
40             </form>
41             <button type="submit" class="btn btn-danger btn-sm btn-space"
42                     onclick="request('/post', {delete_folder : '{{ subdirpath }}'}, true)">
43                 <i class="fas fa-trash-alt"></i>
44             </button>
45         </div>
46     </li>
47     <div class="collapse multi-collapse" id="multiCollapse-{{ subdirid }}">
48         {{ dirlisting(subdirobj, subdirpath) -}}
49     </div>
50     {% endfor %}
51     {% endif %}
52     {% set files = dir.get_files() %}
53     {% if files %}
54     {% for file in files %}
55     {% set filepath = os.path.relpath(os.path.join(dir.fullpath, file), music_library.fullpath) %}
56     <li class="file list-group-item">
57         <div class="btn-group" role="group">
58             <div class="btn-group" role="group">
59                 <button type="button" class="btn btn-success btn-sm"
60                         onclick="request('/post', {add_file_bottom : '{{ filepath }}'})">
61                     <i class="fa fa-plus" aria-hidden="true"></i>
62                 </button>
63                 <div class="btn-group" role="group">
64                     <button id="btnGroupDrop2" type="button" class="btn btn-success btn-sm dropdown-toggle btn-space" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></button>
65                     <div class="dropdown-menu" aria-labelledby="btnGroupDrop2" style="">
66                         <a class="dropdown-item"
67                            onclick="request('/post', {add_file_bottom : '{{ filepath }}'})">
68                             <i class="fa fa-angle-down" aria-hidden="true"></i> To bottom of play list
69                         </a>
70                         <a class="dropdown-item"
71                            onclick="request('/post', {add_file_next : '{{ filepath }}'})">
72                             <i class="fa fa-angle-right" aria-hidden="true"></i> After current song
73                         </a>
74                     </div>
75                 </div>
76             </div>
77
78         </div>
79         <div class="btn-group lead">
80             <div class="btn-space"><i class="fa fa-music" aria-hidden="true"></i></div>
81             {{ filepath }}
82         </div>
83             {% if tags_lookup[filepath] %}
84             {% for tag in tags_lookup[filepath] %}
85                 <span class="badge badge-{{ tags_color_lookup[tag] }}">{{ tag }}</span>
86             {% endfor %}
87             {% endif %}
88
89         <div class="btn-group" style="float: right;">
90             <form action="./download" method="get" class="file file_download">
91                 <input type="text" value="{{ filepath }}" name="file" hidden>
92                 <button type="submit" class="btn btn-primary btn-sm btn-space"><i class="fa fa-download" aria-hidden="true"></i></button>
93             </form>
94             <button type="submit" class="btn btn-danger btn-sm btn-space"
95                     onclick="request('/post', {delete_music_file : '{{ filepath }}'}, true)">
96                 <i class="fas fa-trash-alt"></i>
97             </button>
98         </div>
99     </li>
100     {% endfor %}
101     {% endif %}
102 </ul>
103 {%- endmacro %}
104
105 <!DOCTYPE html>
106
107 <head>
108     <meta charset="UTF-8">
109     <title>Live ðŸ“», don't ðŸ¤§</title>
110     <link id="pagestyle" rel="stylesheet" href="static/css/bootstrap.min.css">
111     <link rel="stylesheet" href="static/css/custom.css">
112     <META HTTP-EQUIV="Pragma" CONTENT="no-cache">
113     <META HTTP-EQUIV="Expires" CONTENT="-1">
114 </head>
115
116 <body>
117     <div class="container">
118         <div class="bs-docs-section">
119             <div class="page-header" id="banner">
120                 <h1>Live ðŸ“», don't ðŸ¤§</h1>
121             </div>
122         </div>
123         <div class="bs-docs-section">
124             <div class="row">
125             <div class="col">
126                 <div id="playlist" class="col-lg-12">
127                     <div>
128                         <div class="btn-group" style="margin-bottom: 10px;">
129                             <button type="button" id="play-btn" class="btn btn-info btn-space"
130                                     onclick="request('post', {action : 'resume'})" disabled>
131                                 <i class="fas fa-play" aria-hidden="true"></i>
132                             </button>
133
134                             <button type="button" id="pause-btn" class="btn btn-warning btn-space"
135                                     onclick="request('post', {action : 'pause'})" disabled>
136                                 <i class="fas fa-pause" aria-hidden="true"></i>
137                             </button>
138
139                             <button type="button" id="stop-btn" class="btn btn-danger btn-space"
140                                     onclick="request('post', {action : 'stop'})" disabled>
141                                 <i class="fas fa-stop" aria-hidden="true"></i>
142                             </button>
143                             <span id="current-time" style="line-height: 200%">
144                             </span>
145                         </div>
146
147                         <div class="btn-group" style="float: right;">
148                              <!--
149                             <button type="button" id="oneshot-btn" class="btn btn-primary btn-space"
150                                     title="One-shot Mode"
151                                     onclick="request('post', {action : 'one-shot'})" disabled>
152                                 <i class="fas fa-tasks" aria-hidden="true"></i>
153                             </button>
154                             <button type="button" id="random-btn" class="btn btn-primary btn-space"
155                                     title="Random Mode"
156                                     onclick="request('post', {action : 'randomize'})" disabled>
157                                 <i class="fas fa-random" aria-hidden="true"></i>
158                             </button>
159                             <button type="button" id="repeat-btn" class="btn btn-primary btn-space"
160                                     title="Repeat Mode"
161                                     onclick="request('post', {action : 'repeat'})" disabled>
162                                 <i class="fas fa-redo" aria-hidden="true"></i>
163                             </button>
164                             <button type="button" id="autoplay-btn" class="btn btn-primary btn-space"
165                                     title="Autoplay Mode"
166                                     onclick="request('post', {action : 'autoplay'})" disabled>
167                                 <i class="fas fa-robot" aria-hidden="true"></i>
168                             </button>
169                             -->
170
171                             <button type="button" class="btn btn-warning btn-space"
172                                     onclick="request('post', {action : 'volume_down'})">
173                                 <i class="fa fa-volume-down" aria-hidden="true"></i>
174                             </button>
175                             <button type="button" class="btn btn-warning btn-space"
176                                     onclick="request('post', {action : 'volume_up'})">
177                                 <i class="fa fa-volume-up" aria-hidden="true"></i>
178                             </button>
179                         </div>
180
181                         <table class="table">
182                             <thead>
183                                 <tr>
184                                     <th scope="col">#</th>
185                                     <th scope="col" class="playlist-title-td">Title</th>
186                                     <th scope="col">Url/Path</th>
187                                     <th scope="col">Action</th>
188                                 </tr>
189                             </thead>
190                             <tbody id="playlist-table">
191                                 <tr class="table-dark">
192                                 <td colspan="4" class="text-muted" style="text-align:center;"> Fetching playlist .... </td>
193                                 </tr>
194                             </tbody>
195                         </table>
196
197                         <div class="btn-group">
198                             <button type="button" class="btn btn-danger btn-space"
199                                     onclick="request('post', {action : 'clear'})">
200                                 <i class="fas fa-trash-alt" aria-hidden="true"></i> Clear Playlist
201                             </button>
202                         </div>
203                     </div>
204                 </div>
205             </div>
206         </div>
207         </div>
208
209         <div class="bs-docs-section">
210             <div class="row">
211                 <div class="col">
212                 <div id="browser" class="card">
213                     <div class="card-header">
214                         <h4 class="card-title">Files</h4>
215                     </div>
216
217                     <div class="card-body">
218                             <!--
219                         <div class="btn-group" style="margin-bottom: 5px;" role="group">
220                             <button type="submit" class="btn btn-secondary btn-space"
221                             onclick="request('/post', {action : 'rescan'}); location.reload()">
222                                 <i class="fas fa-sync-alt" aria-hidden="true"></i> Rescan Files
223                             </button>
224                             <form action="./download" method="get" class="directory form1">
225                                 <input type="text" value="./" name="directory" hidden>
226                                 <button type="submit" class="btn btn-secondary btn-space"><i class="fa fa-download" aria-hidden="true"></i> Download All</button>
227                             </form>
228                             <form method="post" class="directory form3">
229                                 <input type="text" value="./" name="add_folder_recursively" hidden>
230                                 <button type="submit" class="btn btn-secondary btn-space"><i class="fa fa-plus" aria-hidden="true"></i> Add All</button>
231                             </form>
232                         </div>
233                         <br />
234                             -->
235                         {{ dirlisting(music_library) }}
236                     </div>
237                 </div>
238             </div>
239         </div>
240         </div>
241     </div>
242
243     <div id="upload" class="container">
244         <div class="bs-docs-section">
245             <div class="row">
246             <div class="col">
247                 <div class="card">
248                     <div class="card-header">
249                         <h5 class="card-title">Upload File</h5>
250                     </div>
251                     <div class="card-body">
252                         <form action="./upload" method="post" enctype="multipart/form-data">
253                             <div class="row" style="margin-bottom: 5px;">
254                                 <div id="uploadBox" class="col-lg-7 input-group">
255                                     <div id="uploadField" style="display: flex; width: 100%">
256                                         <div class="custom-file btn-space">
257                                             <input type="file" name="file[]" class="custom-file-input" id="uploadSelectFile"
258                                                    aria-describedby="uploadSubmit" value="Browse Music file" multiple/>
259                                             <label class="custom-file-label" for="uploadSelectFile">Choose file</label>
260                                         </div>
261                                     </div>
262                                 </div>
263                                 <div class="col-lg-4 input-group-append">
264                                     <span class="input-group-text">Upload To</span>
265                                     <input class="form-control btn-space" list="targetdirs" id="targetdir" name="targetdir"
266                                            placeholder="uploads" />
267                                     <datalist id="targetdirs">
268                                         <option value="uploads">
269                                             {% for dir in music_library.get_subdirs_recursively() %}
270                                                 <option value="{{ dir }}">
271                                             {% endfor %}
272                                     </datalist>
273                                 </div>
274                                 <button class="btn btn-primary btn-space" type="submit"
275                                         id="uploadSubmit" style="margin-left: -5px;">Upload!</button>
276                             </div>
277                         </form>
278                     </div>
279                 </div>
280             </div>
281         </div>
282         </div>
283
284         <div class="bs-docs-section" style="margin-bottom: 150px;">
285             <div class="row">
286             <div class="col">
287                 <div class="card">
288                     <div class="card-header">
289                         <h5 class="card-title">Add URL</h5>
290                     </div>
291                     <div class="card-body">
292                         <label>Add Youtube/Soundcloud URL</label>
293                         <div class="input-group">
294                             <input class="form-control btn-space" type="text" id="add_url_input" placeholder="URL...">
295                             <button type="submit" class="btn btn-primary"
296                             onclick="var $i = $('#add_url_input')[0]; request('/post', {add_url :  $i.value }); $i.value = ''; ">Add URL</button>
297                         </div>
298                     </div>
299                 </div>
300             </div>
301             <div class="col">
302                 <div class="card">
303                     <div class="card-header">
304                         <h5 class="card-title">Add Radio</h5>
305                     </div>
306                     <div class="card-body">
307                         <label>Add Radio URL</label>
308                         <div class="input-group">
309                             <input class="form-control btn-space" type="text" id="add_radio_input" placeholder="Radio Address...">
310                             <button type="submit" class="btn btn-primary"
311                             onclick="var $i = $('#add_radio_input')[0]; request('/post', {add_radio : $i.value }); $i.value = '';">Add Radio</button>
312                         </div>
313                     </div>
314                 </div>
315             </div>
316         </div>
317         </div>
318     </div>
319
320     <div class="floating-button" onclick="switchTheme()"> <i class="fas fa-lightbulb" aria-hidden="true"></i> </div>
321
322     <script src="static/js/jquery-3.4.1.min.js" crossorigin="anonymous"></script>
323     <script src="static/js/bootstrap.bundle.min.js" crossorigin="anonymous"></script>
324     <script src="static/js/fontawesome.all.js" crossorigin="anonymous"></script>
325
326     <script>
327         $('#uploadSelectFile').on('change', function () {
328             //get the file name
329             var fileName = $(this).val().replace('C:\\fakepath\\', " ");
330             //replace the "Choose a file" label
331             $(this).next('.custom-file-label').html(fileName);
332         });
333         $('a.a-submit, button.btn-submit').on('click', function (event) {
334             $(event.target).closest('form').submit();
335         });
336
337         var playlist_ver = 0;
338
339         function format_duration(secs) {
340             return parseInt(secs/60) + ':' + ('0' + parseInt(secs%60)).slice(-2);
341         }
342
343         function request(url, _data, refresh=false){
344             $.ajax({
345                 type: 'POST',
346                 url: 'post',
347                 data : _data,
348                 statusCode : {
349                     200 : function(data) {
350                         if (data.ver !== playlist_ver) {
351                             updatePlaylist();
352                             playlist_ver = data.ver;
353                         }
354                         updateControls(data);
355                     }
356                 }
357             });
358             if(refresh){
359                 location.reload()
360             }
361         }
362
363         function displayPlaylist(data){
364             // console.info(data);
365             $("#playlist-table tr").remove();
366
367             var items = data.items;
368             $.each(items, function(index, item){
369                 $("#playlist-table").append(item);
370             });
371
372         }
373
374         function updatePlaylist(){
375             $.ajax({
376                 type: 'GET',
377                 url: 'playlist',
378                 statusCode : {
379                     200 : displayPlaylist
380                 }
381             });
382         }
383
384         function updateControls(data) {
385             var empty = data.empty;
386             var play = data.play;
387             var mode = data.mode;
388             if(empty){
389                 $("#play-btn").prop("disabled", true);
390                 $("#pause-btn").prop("disabled", true);
391                 $("#stop-btn").prop("disabled", true);
392             }else{
393                 if(play){
394                     $("#play-btn").prop("disabled", true);
395                     $("#pause-btn").prop("disabled", false);
396                     $("#stop-btn").prop("disabled", false);
397                 }else{
398                     $("#play-btn").prop("disabled", false);
399                     $("#pause-btn").prop("disabled", true);
400                     $("#stop-btn").prop("disabled", true);
401                 }
402             }
403             if(mode === "one-shot"){
404                 $("#oneshot-btn").removeClass("btn-secondary").addClass("btn-primary").prop("disabled", true);
405                 $("#repeat-btn").removeClass("btn-primary").addClass("btn-secondary").prop("disabled", false);
406                 $("#random-btn").removeClass("btn-primary").addClass("btn-secondary").prop("disabled", false);
407                 $("#autoplay-btn").removeClass("btn-primary").addClass("btn-secondary").prop("disabled", false);
408             }else if(mode === "repeat"){
409                 $("#oneshot-btn").removeClass("btn-primary").addClass("btn-secondary").prop("disabled", false);
410                 $("#repeat-btn").removeClass("btn-secondary").addClass("btn-primary").prop("disabled", true);
411                 $("#random-btn").removeClass("btn-primary").addClass("btn-secondary").prop("disabled", false);
412                 $("#autoplay-btn").removeClass("btn-primary").addClass("btn-secondary").prop("disabled", false);
413             }else if(mode === "random"){
414                 $("#oneshot-btn").removeClass("btn-primary").addClass("btn-secondary").prop("disabled", false);
415                 $("#repeat-btn").removeClass("btn-primary").addClass("btn-secondary").prop("disabled", false);
416                 $("#random-btn").removeClass("btn-secondary").addClass("btn-primary").prop("disabled", false); // This is a feature.
417                 $("#autoplay-btn").removeClass("btn-primary").addClass("btn-secondary").prop("disabled", false);
418             }else if(mode === "autoplay"){
419                 $("#oneshot-btn").removeClass("btn-primary").addClass("btn-secondary").prop("disabled", false);
420                 $("#repeat-btn").removeClass("btn-primary").addClass("btn-secondary").prop("disabled", false);
421                 $("#random-btn").removeClass("btn-primary").addClass("btn-secondary").prop("disabled", false);
422                 $("#autoplay-btn").removeClass("btn-secondary").addClass("btn-primary").prop("disabled", true);
423             }
424             var current_time = "";
425             if (data.playhead != -1) {
426                 var duration = $('#playlist-table .table-active').data('duration');
427                 if (duration) {
428                     var elapsed = data.playhead;
429                     current_time = format_duration(elapsed) + '/' + format_duration(duration);
430                 }
431             }
432             $('#current-time').text(current_time);
433         }
434
435         function themeInit(){
436             var theme = localStorage.getItem("theme");
437             if(theme !== null){
438                 setPageTheme(theme);
439             }
440         }
441
442         function switchTheme(){
443             var theme = localStorage.getItem("theme");
444             if(theme === "light" || theme === null){
445                 setPageTheme("dark");
446                 localStorage.setItem("theme", "dark");
447             }else{
448                 setPageTheme("light");
449                 localStorage.setItem("theme", "light");
450             }
451         }
452
453         function setPageTheme(theme) {
454             if(theme === "light")
455                 document.getElementById("pagestyle").setAttribute("href", "static/css/bootstrap.min.css");
456             else if(theme === "dark")
457                 document.getElementById("pagestyle").setAttribute("href", "static/css/bootstrap.darkly.min.css");
458         }
459
460         // Check the version of playlist to see if update is needed.
461         var update_interval_id = null;
462         function update() {
463             $.ajax({
464                 type: 'POST',
465                 url : 'post',
466                 statusCode : {
467                     200 : function(data){
468                         if(data.ver !== playlist_ver){
469                             updatePlaylist();
470                             playlist_ver = data.ver;
471                         }
472                         updateControls(data);
473                         if (data.play) {
474                             clearInterval(update_interval_id);
475                             update_interval_id = setInterval(update, 1000);
476                         } else {
477                             clearInterval(update_interval_id);
478                             update_interval_id = setInterval(update, 3000);
479                         }
480                     }
481                 }
482             });
483         }
484         update_interval_id = setInterval(update, 3000);
485
486         themeInit();
487         $(document).ready(updatePlaylist);
488
489     </script>
490 </body>
491
492 </html>