]> git.0d.be Git - botaradio.git/blob - templates/index.html
0a2dca60ace4ef2734d418b5409e0afe4e45060b
[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>botamusique web interface</title>
110     <link 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><i class="fa fa-music" aria-hidden="true"></i> botamusique Web Interface</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                         </div>
144
145                         <div class="btn-group" style="float: right;">
146                             <button type="button" id="oneshot-btn" class="btn btn-primary btn-space"
147                                     title="One-shot Mode"
148                                     onclick="request('post', {action : 'one-shot'})" disabled>
149                                 <i class="fas fa-tasks" aria-hidden="true"></i>
150                             </button>
151
152                             <button type="button" id="random-btn" class="btn btn-primary btn-space"
153                                     title="Random Mode"
154                                     onclick="request('post', {action : 'randomize'})" disabled>
155                                 <i class="fas fa-random" aria-hidden="true"></i>
156                             </button>
157
158                             <button type="button" id="repeat-btn" class="btn btn-primary btn-space"
159                                     title="Repeat Mode"
160                                     onclick="request('post', {action : 'repeat'})" disabled>
161                                 <i class="fas fa-redo" aria-hidden="true"></i>
162                             </button>
163
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                             <button type="button" class="btn btn-warning btn-space"
171                                     onclick="request('post', {action : 'volume_down'})">
172                                 <i class="fa fa-volume-down" aria-hidden="true"></i>
173                             </button>
174                             <button type="button" class="btn btn-warning btn-space"
175                                     onclick="request('post', {action : 'volume_up'})">
176                                 <i class="fa fa-volume-up" aria-hidden="true"></i>
177                             </button>
178                         </div>
179
180                         <table class="table">
181                             <thead>
182                                 <tr>
183                                     <th scope="col">#</th>
184                                     <th scope="col" class="playlist-title-td">Title</th>
185                                     <th scope="col">Url/Path</th>
186                                     <th scope="col">Action</th>
187                                 </tr>
188                             </thead>
189                             <tbody id="playlist-table">
190                                 <tr class="table-dark">
191                                 <td colspan="4" class="text-muted" style="text-align:center;"> Fetching playlist .... </td>
192                                 </tr>
193                             </tbody>
194                         </table>
195
196                         <div class="btn-group">
197                             <button type="button" class="btn btn-danger btn-space"
198                                     onclick="request('post', {action : 'clear'})">
199                                 <i class="fas fa-trash-alt" aria-hidden="true"></i> Clear Playlist
200                             </button>
201                         </div>
202                     </div>
203                 </div>
204             </div>
205         </div>
206         </div>
207
208         <div class="bs-docs-section">
209             <div class="row">
210             <div class="col">
211                 <div class="page-header">
212                     <h1 id="forms">Music Library</h1>
213                 </div>
214                 <div class="card">
215                     <div class="card-header">
216                         <h4 class="card-title">Tags</h4>
217                     </div>
218                     <div class="card-body">
219                         {% for tag in tags_color_lookup.keys() %}
220                             <span class="tag-click badge badge-{{ tags_color_lookup[tag] }}"
221                                   onclick="request('post', {add_tag : '{{ tag }}'})">
222                                 {{ tag }}</span>
223                         {% endfor %}
224                     </div>
225                 </div>
226             </div>
227             </div>
228         </div>
229         <div class="bs-docs-section">
230             <div class="row">
231                 <div class="col">
232                 <div id="browser" class="card">
233                     <div class="card-header">
234                         <h4 class="card-title">Files</h4>
235                     </div>
236
237                     <div class="card-body">
238                         <div class="btn-group" style="margin-bottom: 5px;" role="group">
239                             <button type="submit" class="btn btn-secondary btn-space"
240                             onclick="request('/post', {action : 'rescan'}); location.reload()">
241                                 <i class="fas fa-sync-alt" aria-hidden="true"></i> Rescan Files
242                             </button>
243                             <form action="./download" method="get" class="directory form1">
244                                 <input type="text" value="./" name="directory" hidden>
245                                 <button type="submit" class="btn btn-secondary btn-space"><i class="fa fa-download" aria-hidden="true"></i> Download All</button>
246                             </form>
247                             <form method="post" class="directory form3">
248                                 <input type="text" value="./" name="add_folder_recursively" hidden>
249                                 <button type="submit" class="btn btn-secondary btn-space"><i class="fa fa-plus" aria-hidden="true"></i> Add All</button>
250                             </form>
251                         </div>
252                         <br />
253                         {{ dirlisting(music_library) }}
254                     </div>
255                 </div>
256             </div>
257         </div>
258         </div>
259     </div>
260
261     <div id="upload" class="container">
262         <div class="bs-docs-section">
263             <div class="row">
264             <div class="col">
265                 <div class="card">
266                     <div class="card-header">
267                         <h5 class="card-title">Upload File</h5>
268                     </div>
269                     <div class="card-body">
270                         <form action="./upload" method="post" enctype="multipart/form-data">
271                             <div class="row" style="margin-bottom: 5px;">
272                                 <div id="uploadBox" class="col-lg-7 input-group">
273                                     <div id="uploadField" style="display: flex; width: 100%">
274                                         <div class="custom-file btn-space">
275                                             <input type="file" name="file[]" class="custom-file-input" id="uploadSelectFile"
276                                                    aria-describedby="uploadSubmit" value="Browse Music file" multiple/>
277                                             <label class="custom-file-label" for="uploadSelectFile">Choose file</label>
278                                         </div>
279                                     </div>
280                                 </div>
281                                 <div class="col-lg-4 input-group-append">
282                                     <span class="input-group-text">Upload To</span>
283                                     <input class="form-control btn-space" list="targetdirs" id="targetdir" name="targetdir"
284                                            placeholder="uploads" />
285                                     <datalist id="targetdirs">
286                                         <option value="uploads">
287                                             {% for dir in music_library.get_subdirs_recursively() %}
288                                                 <option value="{{ dir }}">
289                                             {% endfor %}
290                                     </datalist>
291                                 </div>
292                                 <button class="btn btn-primary btn-space" type="submit"
293                                         id="uploadSubmit" style="margin-left: -5px;">Upload!</button>
294                             </div>
295                         </form>
296                     </div>
297                 </div>
298             </div>
299         </div>
300         </div>
301
302         <div class="bs-docs-section" style="margin-bottom: 150px;">
303             <div class="row">
304             <div class="col">
305                 <div class="card">
306                     <div class="card-header">
307                         <h5 class="card-title">Add URL</h5>
308                     </div>
309                     <div class="card-body">
310                         <label>Add Youtube/Soundcloud URL</label>
311                         <div class="input-group">
312                             <input class="form-control btn-space" type="text" id="add_url_input" placeholder="URL...">
313                             <button type="submit" class="btn btn-primary"
314                             onclick="var $i = $('#add_url_input')[0]; request('/post', {add_url :  $i.value }); $i.value = ''; ">Add URL</button>
315                         </div>
316                     </div>
317                 </div>
318             </div>
319             <div class="col">
320                 <div class="card">
321                     <div class="card-header">
322                         <h5 class="card-title">Add Radio</h5>
323                     </div>
324                     <div class="card-body">
325                         <label>Add Radio URL</label>
326                         <div class="input-group">
327                             <input class="form-control btn-space" type="text" id="add_radio_input" placeholder="Radio Address...">
328                             <button type="submit" class="btn btn-primary"
329                             onclick="var $i = $('#add_radio_input')[0]; request('/post', {add_radio : $i.value }); $i.value = '';">Add Radio</button>
330                         </div>
331                     </div>
332                 </div>
333             </div>
334         </div>
335         </div>
336     </div>
337
338     <script src="static/js/jquery-3.4.1.min.js" crossorigin="anonymous"></script>
339     <script src="static/js/bootstrap.bundle.min.js" crossorigin="anonymous"></script>
340     <script src="static/js/fontawesome.all.js" crossorigin="anonymous"></script>
341
342     <script>
343         $('#uploadSelectFile').on('change', function () {
344             //get the file name
345             var fileName = $(this).val().replace('C:\\fakepath\\', " ");
346             //replace the "Choose a file" label
347             $(this).next('.custom-file-label').html(fileName);
348         });
349         $('a.a-submit, button.btn-submit').on('click', function (event) {
350             $(event.target).closest('form').submit();
351         });
352
353         var playlist_ver = 0;
354
355         function request(url, _data, refresh=false){
356             $.ajax({
357                 type: 'POST',
358                 url: 'post',
359                 data : _data,
360                 statusCode : {
361                     200 : function(data) {
362                         if (data.ver !== playlist_ver) {
363                             updatePlaylist();
364                             playlist_ver = data.ver;
365                         }
366                         updateControls(data.empty, data.play, data.mode);
367                     }
368                 }
369             });
370             if(refresh){
371                 location.reload()
372             }
373         }
374
375         function displayPlaylist(data){
376             // console.info(data);
377             $("#playlist-table tr").remove();
378
379             var items = data.items;
380             $.each(items, function(index, item){
381                 $("#playlist-table").append(item);
382             });
383
384         }
385
386         function updatePlaylist(){
387             $.ajax({
388                 type: 'GET',
389                 url: 'playlist',
390                 statusCode : {
391                     200 : displayPlaylist
392                 }
393             });
394         }
395
396         function updateControls(empty, play, mode){
397             if(empty){
398                 $("#play-btn").prop("disabled", true);
399                 $("#pause-btn").prop("disabled", true);
400                 $("#stop-btn").prop("disabled", true);
401             }else{
402                 if(play){
403                     $("#play-btn").prop("disabled", true);
404                     $("#pause-btn").prop("disabled", false);
405                     $("#stop-btn").prop("disabled", false);
406                 }else{
407                     $("#play-btn").prop("disabled", false);
408                     $("#pause-btn").prop("disabled", true);
409                     $("#stop-btn").prop("disabled", true);
410                 }
411             }
412             if(mode === "one-shot"){
413                 $("#oneshot-btn").removeClass("btn-secondary").addClass("btn-primary").prop("disabled", true);
414                 $("#repeat-btn").removeClass("btn-primary").addClass("btn-secondary").prop("disabled", false);
415                 $("#random-btn").removeClass("btn-primary").addClass("btn-secondary").prop("disabled", false);
416                 $("#autoplay-btn").removeClass("btn-primary").addClass("btn-secondary").prop("disabled", false);
417             }else if(mode === "repeat"){
418                 $("#oneshot-btn").removeClass("btn-primary").addClass("btn-secondary").prop("disabled", false);
419                 $("#repeat-btn").removeClass("btn-secondary").addClass("btn-primary").prop("disabled", true);
420                 $("#random-btn").removeClass("btn-primary").addClass("btn-secondary").prop("disabled", false);
421                 $("#autoplay-btn").removeClass("btn-primary").addClass("btn-secondary").prop("disabled", false);
422             }else if(mode === "random"){
423                 $("#oneshot-btn").removeClass("btn-primary").addClass("btn-secondary").prop("disabled", false);
424                 $("#repeat-btn").removeClass("btn-primary").addClass("btn-secondary").prop("disabled", false);
425                 $("#random-btn").removeClass("btn-secondary").addClass("btn-primary").prop("disabled", false); // This is a feature.
426                 $("#autoplay-btn").removeClass("btn-primary").addClass("btn-secondary").prop("disabled", false);
427             }else if(mode === "autoplay"){
428                 $("#oneshot-btn").removeClass("btn-primary").addClass("btn-secondary").prop("disabled", false);
429                 $("#repeat-btn").removeClass("btn-primary").addClass("btn-secondary").prop("disabled", false);
430                 $("#random-btn").removeClass("btn-primary").addClass("btn-secondary").prop("disabled", false);
431                 $("#autoplay-btn").removeClass("btn-secondary").addClass("btn-primary").prop("disabled", true);
432             }
433
434         }
435
436         // Check the version of playlist to see if update is needed.
437         setInterval(function(){
438             $.ajax({
439                 type: 'POST',
440                 url : 'post',
441                 statusCode : {
442                     200 : function(data){
443                         if(data.ver !== playlist_ver){
444                             updatePlaylist();
445                             playlist_ver = data.ver;
446                         }
447                         updateControls(data.empty, data.play, data.mode);
448                     }
449                 }
450             });
451         } , 3000);
452
453         $(document).ready(updatePlaylist);
454
455     </script>
456 </body>
457
458 </html>