Accessibility enhancements: catching a11y errors with WAVE (#180)
* Added alt text for all images * Added aria labels for most buttons that needed them. Added html language label * Added remaining button labels * Added remaining missing labels. Tagged <input> where labels made no sense as "aria-hidden=true" * Fixed some of the low color contrast issues for Light theme * Replaced broken ARIA link with a new ARIA label * Fixed skipped heading levels * Fixed missing fieldset and one orphaned label (combined the two) * Changed the other orphaned label to a fieldset legend * Removed color changes * Added dynamic ARIA label for main Play/Pause button. Removed the two FIXMEs in index.html * Added default content to table header * Changed input type from text to hidden for hidden inputs * Added proper alt text for cover art * Final version of the template row * Added semantic markup to Page Regions * Missing alt text + truncated alt text - Added missing alt text for cover images in library and floating player. - Added JS to truncate alt text for cover images that was too long. * Sanitize the code Co-authored-by: Terry Geng <terry@terriex.com>
This commit is contained in:
		
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										216
									
								
								web/js/main.mjs
									
									
									
									
									
								
							
							
						
						
									
										216
									
								
								web/js/main.mjs
									
									
									
									
									
								
							@@ -10,9 +10,10 @@ import {
 | 
				
			|||||||
  isOverflown,
 | 
					  isOverflown,
 | 
				
			||||||
  setProgressBar,
 | 
					  setProgressBar,
 | 
				
			||||||
  secondsToStr,
 | 
					  secondsToStr,
 | 
				
			||||||
 | 
					  coverArtString,
 | 
				
			||||||
} from './util';
 | 
					} from './util';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
$('#uploadSelectFile').on('change', function() {
 | 
					$('#uploadSelectFile').on('change', function () {
 | 
				
			||||||
  // get the file name
 | 
					  // get the file name
 | 
				
			||||||
  const fileName = $(this).val().replace('C:\\fakepath\\', ' ');
 | 
					  const fileName = $(this).val().replace('C:\\fakepath\\', ' ');
 | 
				
			||||||
  // replace the "Choose a file" label
 | 
					  // replace the "Choose a file" label
 | 
				
			||||||
@@ -83,7 +84,7 @@ fastForwardBtn.on('click', () => {
 | 
				
			|||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
document.getElementById('clear-playlist-btn').addEventListener('click', () => {
 | 
					document.getElementById('clear-playlist-btn').addEventListener('click', () => {
 | 
				
			||||||
  request('post', {action: 'clear'});
 | 
					  request('post', { action: 'clear' });
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// eslint-disable-next-line guard-for-in
 | 
					// eslint-disable-next-line guard-for-in
 | 
				
			||||||
@@ -100,14 +101,14 @@ function request(_url, _data, refresh = false) {
 | 
				
			|||||||
    url: _url,
 | 
					    url: _url,
 | 
				
			||||||
    data: _data,
 | 
					    data: _data,
 | 
				
			||||||
    statusCode: {
 | 
					    statusCode: {
 | 
				
			||||||
      200: function(data) {
 | 
					      200: function (data) {
 | 
				
			||||||
        if (data.ver !== playlist_ver) {
 | 
					        if (data.ver !== playlist_ver) {
 | 
				
			||||||
          checkForPlaylistUpdate();
 | 
					          checkForPlaylistUpdate();
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        updateControls(data.empty, data.play, data.mode, data.volume);
 | 
					        updateControls(data.empty, data.play, data.mode, data.volume);
 | 
				
			||||||
        updatePlayerPlayhead(data.playhead);
 | 
					        updatePlayerPlayhead(data.playhead);
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
      403: function() {
 | 
					      403: function () {
 | 
				
			||||||
        location.reload(true);
 | 
					        location.reload(true);
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
@@ -123,6 +124,7 @@ function addPlaylistItem(item) {
 | 
				
			|||||||
  pl_title_element.html(item.title);
 | 
					  pl_title_element.html(item.title);
 | 
				
			||||||
  pl_artist_element.html(item.artist);
 | 
					  pl_artist_element.html(item.artist);
 | 
				
			||||||
  pl_thumb_element.attr('src', item.thumbnail);
 | 
					  pl_thumb_element.attr('src', item.thumbnail);
 | 
				
			||||||
 | 
					  pl_thumb_element.attr('alt', coverArtString(item.title));
 | 
				
			||||||
  pl_type_element.html(item.type);
 | 
					  pl_type_element.html(item.type);
 | 
				
			||||||
  pl_path_element.html(item.path);
 | 
					  pl_path_element.html(item.path);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -134,13 +136,13 @@ function addPlaylistItem(item) {
 | 
				
			|||||||
  tags.empty();
 | 
					  tags.empty();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const tag_edit_copy = pl_tag_edit_element.clone();
 | 
					  const tag_edit_copy = pl_tag_edit_element.clone();
 | 
				
			||||||
  tag_edit_copy.click(function() {
 | 
					  tag_edit_copy.click(function () {
 | 
				
			||||||
    addTagModalShow(item.id, item.title, item.tags);
 | 
					    addTagModalShow(item.id, item.title, item.tags);
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
  tag_edit_copy.appendTo(tags);
 | 
					  tag_edit_copy.appendTo(tags);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  if (item.tags.length > 0) {
 | 
					  if (item.tags.length > 0) {
 | 
				
			||||||
    item.tags.forEach(function(tag_tuple) {
 | 
					    item.tags.forEach(function (tag_tuple) {
 | 
				
			||||||
      const tag_copy = tag_element.clone();
 | 
					      const tag_copy = tag_element.clone();
 | 
				
			||||||
      tag_copy.html(tag_tuple[0]);
 | 
					      tag_copy.html(tag_tuple[0]);
 | 
				
			||||||
      tag_copy.addClass('badge-' + tag_tuple[1]);
 | 
					      tag_copy.addClass('badge-' + tag_tuple[1]);
 | 
				
			||||||
@@ -157,7 +159,7 @@ function addPlaylistItem(item) {
 | 
				
			|||||||
function displayPlaylist(data) {
 | 
					function displayPlaylist(data) {
 | 
				
			||||||
  playlist_table.animate({
 | 
					  playlist_table.animate({
 | 
				
			||||||
    opacity: 0,
 | 
					    opacity: 0,
 | 
				
			||||||
  }, 200, function() {
 | 
					  }, 200, function () {
 | 
				
			||||||
    playlist_loading.hide();
 | 
					    playlist_loading.hide();
 | 
				
			||||||
    $('.playlist-item').remove();
 | 
					    $('.playlist-item').remove();
 | 
				
			||||||
    const items = data.items;
 | 
					    const items = data.items;
 | 
				
			||||||
@@ -180,9 +182,9 @@ function displayPlaylist(data) {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    items.forEach(
 | 
					    items.forEach(
 | 
				
			||||||
        function(item) {
 | 
					      function (item) {
 | 
				
			||||||
          addPlaylistItem(item);
 | 
					        addPlaylistItem(item);
 | 
				
			||||||
        },
 | 
					      },
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (items.length < length && start_from + items.length < length) {
 | 
					    if (items.length < length && start_from + items.length < length) {
 | 
				
			||||||
@@ -221,7 +223,7 @@ function insertExpandPrompt(real_from, real_to, display_from, display_to, total_
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  expand_copy.addClass('playlist-item');
 | 
					  expand_copy.addClass('playlist-item');
 | 
				
			||||||
  expand_copy.appendTo(playlist_table);
 | 
					  expand_copy.appendTo(playlist_table);
 | 
				
			||||||
  expand_copy.click(function() {
 | 
					  expand_copy.click(function () {
 | 
				
			||||||
    playlist_range_from = real_from;
 | 
					    playlist_range_from = real_from;
 | 
				
			||||||
    playlist_range_to = real_to;
 | 
					    playlist_range_to = real_to;
 | 
				
			||||||
    updatePlaylist();
 | 
					    updatePlaylist();
 | 
				
			||||||
@@ -231,7 +233,7 @@ function insertExpandPrompt(real_from, real_to, display_from, display_to, total_
 | 
				
			|||||||
function updatePlaylist() {
 | 
					function updatePlaylist() {
 | 
				
			||||||
  playlist_table.animate({
 | 
					  playlist_table.animate({
 | 
				
			||||||
    opacity: 0,
 | 
					    opacity: 0,
 | 
				
			||||||
  }, 200, function() {
 | 
					  }, 200, function () {
 | 
				
			||||||
    playlist_empty.addClass('d-none');
 | 
					    playlist_empty.addClass('d-none');
 | 
				
			||||||
    playlist_loading.show();
 | 
					    playlist_loading.show();
 | 
				
			||||||
    playlist_table.find('.playlist-item').css('opacity', 0);
 | 
					    playlist_table.find('.playlist-item').css('opacity', 0);
 | 
				
			||||||
@@ -248,7 +250,7 @@ function updatePlaylist() {
 | 
				
			|||||||
      data: data,
 | 
					      data: data,
 | 
				
			||||||
      statusCode: {
 | 
					      statusCode: {
 | 
				
			||||||
        200: displayPlaylist,
 | 
					        200: displayPlaylist,
 | 
				
			||||||
        204: function() {
 | 
					        204: function () {
 | 
				
			||||||
          playlist_loading.hide();
 | 
					          playlist_loading.hide();
 | 
				
			||||||
          playlist_empty.removeClass('d-none');
 | 
					          playlist_empty.removeClass('d-none');
 | 
				
			||||||
          $('.playlist-item').remove();
 | 
					          $('.playlist-item').remove();
 | 
				
			||||||
@@ -266,7 +268,7 @@ function checkForPlaylistUpdate() {
 | 
				
			|||||||
    type: 'POST',
 | 
					    type: 'POST',
 | 
				
			||||||
    url: 'post',
 | 
					    url: 'post',
 | 
				
			||||||
    statusCode: {
 | 
					    statusCode: {
 | 
				
			||||||
      200: function(data) {
 | 
					      200: function (data) {
 | 
				
			||||||
        if (data.ver !== playlist_ver) {
 | 
					        if (data.ver !== playlist_ver) {
 | 
				
			||||||
          playlist_ver = data.ver;
 | 
					          playlist_ver = data.ver;
 | 
				
			||||||
          playlist_range_from = 0;
 | 
					          playlist_range_from = 0;
 | 
				
			||||||
@@ -297,18 +299,18 @@ function checkForPlaylistUpdate() {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
function bindPlaylistEvent() {
 | 
					function bindPlaylistEvent() {
 | 
				
			||||||
  $('.playlist-item-play').unbind().click(
 | 
					  $('.playlist-item-play').unbind().click(
 | 
				
			||||||
      function(e) {
 | 
					    function (e) {
 | 
				
			||||||
        request('post', {
 | 
					      request('post', {
 | 
				
			||||||
          'play_music': ($(e.currentTarget).parent().parent().parent().find('.playlist-item-index').html() - 1),
 | 
					        'play_music': ($(e.currentTarget).parent().parent().parent().find('.playlist-item-index').html() - 1),
 | 
				
			||||||
        });
 | 
					      });
 | 
				
			||||||
      },
 | 
					    },
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
  $('.playlist-item-trash').unbind().click(
 | 
					  $('.playlist-item-trash').unbind().click(
 | 
				
			||||||
      function(e) {
 | 
					    function (e) {
 | 
				
			||||||
        request('post', {
 | 
					      request('post', {
 | 
				
			||||||
          'delete_music': ($(e.currentTarget).parent().parent().parent().find('.playlist-item-index').html() - 1),
 | 
					        'delete_music': ($(e.currentTarget).parent().parent().parent().find('.playlist-item-index').html() - 1),
 | 
				
			||||||
        });
 | 
					      });
 | 
				
			||||||
      },
 | 
					    },
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -323,9 +325,15 @@ function updateControls(empty, play, mode, volume) {
 | 
				
			|||||||
    if (play) {
 | 
					    if (play) {
 | 
				
			||||||
      playing = true;
 | 
					      playing = true;
 | 
				
			||||||
      playPauseBtn.find('[data-fa-i2svg]').removeClass('fa-play').addClass('fa-pause');
 | 
					      playPauseBtn.find('[data-fa-i2svg]').removeClass('fa-play').addClass('fa-pause');
 | 
				
			||||||
 | 
					      // PR #180: Since this button changes behavior dynamically, we change its
 | 
				
			||||||
 | 
					      // ARIA labels in JS instead of only adding them statically in the HTML
 | 
				
			||||||
 | 
					      playPauseBtn.attr('aria-label', 'Pause');
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
      playing = false;
 | 
					      playing = false;
 | 
				
			||||||
      playPauseBtn.find('[data-fa-i2svg]').removeClass('fa-pause').addClass('fa-play');
 | 
					      playPauseBtn.find('[data-fa-i2svg]').removeClass('fa-pause').addClass('fa-play');
 | 
				
			||||||
 | 
					      // PR #180: Since this button changes behavior dynamically, we change its
 | 
				
			||||||
 | 
					      // ARIA labels in JS instead of only adding them statically in the HTML
 | 
				
			||||||
 | 
					      playPauseBtn.attr('aria-label', 'Play');
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -409,7 +417,7 @@ function setFilterType(event, type) {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Bind Event
 | 
					// Bind Event
 | 
				
			||||||
$('.filter-tag').click(function(e) {
 | 
					$('.filter-tag').click(function (e) {
 | 
				
			||||||
  const tag = $(e.currentTarget);
 | 
					  const tag = $(e.currentTarget);
 | 
				
			||||||
  if (!tag.hasClass('tag-clicked')) {
 | 
					  if (!tag.hasClass('tag-clicked')) {
 | 
				
			||||||
    tag.addClass('tag-clicked');
 | 
					    tag.addClass('tag-clicked');
 | 
				
			||||||
@@ -421,10 +429,10 @@ $('.filter-tag').click(function(e) {
 | 
				
			|||||||
  updateResults();
 | 
					  updateResults();
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
filter_dir.change(function() {
 | 
					filter_dir.change(function () {
 | 
				
			||||||
  updateResults();
 | 
					  updateResults();
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
filter_keywords.change(function() {
 | 
					filter_keywords.change(function () {
 | 
				
			||||||
  updateResults();
 | 
					  updateResults();
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -432,64 +440,64 @@ const item_template = $('#library-item');
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
function bindLibraryResultEvent() {
 | 
					function bindLibraryResultEvent() {
 | 
				
			||||||
  $('.library-thumb-col').unbind().hover(
 | 
					  $('.library-thumb-col').unbind().hover(
 | 
				
			||||||
      function(e) {
 | 
					    function (e) {
 | 
				
			||||||
        $(e.currentTarget).find('.library-thumb-grp').addClass('library-thumb-grp-hover');
 | 
					      $(e.currentTarget).find('.library-thumb-grp').addClass('library-thumb-grp-hover');
 | 
				
			||||||
      },
 | 
					    },
 | 
				
			||||||
      function(e) {
 | 
					    function (e) {
 | 
				
			||||||
        $(e.currentTarget).find('.library-thumb-grp').removeClass('library-thumb-grp-hover');
 | 
					      $(e.currentTarget).find('.library-thumb-grp').removeClass('library-thumb-grp-hover');
 | 
				
			||||||
      },
 | 
					    },
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  $('.library-info-title').unbind().hover(
 | 
					  $('.library-info-title').unbind().hover(
 | 
				
			||||||
      function(e) {
 | 
					    function (e) {
 | 
				
			||||||
        $(e.currentTarget).parent().find('.library-thumb-grp').addClass('library-thumb-grp-hover');
 | 
					      $(e.currentTarget).parent().find('.library-thumb-grp').addClass('library-thumb-grp-hover');
 | 
				
			||||||
      },
 | 
					    },
 | 
				
			||||||
      function(e) {
 | 
					    function (e) {
 | 
				
			||||||
        $(e.currentTarget).parent().find('.library-thumb-grp').removeClass('library-thumb-grp-hover');
 | 
					      $(e.currentTarget).parent().find('.library-thumb-grp').removeClass('library-thumb-grp-hover');
 | 
				
			||||||
      },
 | 
					    },
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  $('.library-item-play').unbind().click(
 | 
					  $('.library-item-play').unbind().click(
 | 
				
			||||||
      function(e) {
 | 
					    function (e) {
 | 
				
			||||||
        request('post', {
 | 
					      request('post', {
 | 
				
			||||||
          'add_item_at_once': $(e.currentTarget).parent().parent().parent().find('.library-item-id').val(),
 | 
					        'add_item_at_once': $(e.currentTarget).parent().parent().parent().find('.library-item-id').val(),
 | 
				
			||||||
        });
 | 
					      });
 | 
				
			||||||
      },
 | 
					    },
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  $('.library-item-trash').unbind().click(
 | 
					  $('.library-item-trash').unbind().click(
 | 
				
			||||||
      function(e) {
 | 
					    function (e) {
 | 
				
			||||||
        request('post', {
 | 
					      request('post', {
 | 
				
			||||||
          'delete_item_from_library': $(e.currentTarget).parent().parent().find('.library-item-id').val(),
 | 
					        'delete_item_from_library': $(e.currentTarget).parent().parent().find('.library-item-id').val(),
 | 
				
			||||||
        });
 | 
					      });
 | 
				
			||||||
        updateResults(active_page);
 | 
					      updateResults(active_page);
 | 
				
			||||||
      },
 | 
					    },
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  $('.library-item-download').unbind().click(
 | 
					  $('.library-item-download').unbind().click(
 | 
				
			||||||
      function(e) {
 | 
					    function (e) {
 | 
				
			||||||
        const id = $(e.currentTarget).parent().parent().find('.library-item-id').val();
 | 
					      const id = $(e.currentTarget).parent().parent().find('.library-item-id').val();
 | 
				
			||||||
        // window.open('/download?id=' + id);
 | 
					      // window.open('/download?id=' + id);
 | 
				
			||||||
        downloadId(id);
 | 
					      downloadId(id);
 | 
				
			||||||
      },
 | 
					    },
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  $('.library-item-add-next').unbind().click(
 | 
					  $('.library-item-add-next').unbind().click(
 | 
				
			||||||
      function(e) {
 | 
					    function (e) {
 | 
				
			||||||
        const id = $(e.currentTarget).parent().parent().find('.library-item-id').val();
 | 
					      const id = $(e.currentTarget).parent().parent().find('.library-item-id').val();
 | 
				
			||||||
        request('post', {
 | 
					      request('post', {
 | 
				
			||||||
          'add_item_next': id,
 | 
					        'add_item_next': id,
 | 
				
			||||||
        });
 | 
					      });
 | 
				
			||||||
      },
 | 
					    },
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  $('.library-item-add-bottom').unbind().click(
 | 
					  $('.library-item-add-bottom').unbind().click(
 | 
				
			||||||
      function(e) {
 | 
					    function (e) {
 | 
				
			||||||
        const id = $(e.currentTarget).parent().parent().find('.library-item-id').val();
 | 
					      const id = $(e.currentTarget).parent().parent().find('.library-item-id').val();
 | 
				
			||||||
        request('post', {
 | 
					      request('post', {
 | 
				
			||||||
          'add_item_bottom': id,
 | 
					        'add_item_bottom': id,
 | 
				
			||||||
        });
 | 
					      });
 | 
				
			||||||
      },
 | 
					    },
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -510,6 +518,7 @@ function addResultItem(item) {
 | 
				
			|||||||
  title_element.html(item.title);
 | 
					  title_element.html(item.title);
 | 
				
			||||||
  artist_element.html(item.artist ? ('- ' + item.artist) : '');
 | 
					  artist_element.html(item.artist ? ('- ' + item.artist) : '');
 | 
				
			||||||
  thumb_element.attr('src', item.thumb);
 | 
					  thumb_element.attr('src', item.thumb);
 | 
				
			||||||
 | 
					  thumb_element.attr('alt', coverArtString(item.title));
 | 
				
			||||||
  type_element.html('[' + item.type + ']');
 | 
					  type_element.html('[' + item.type + ']');
 | 
				
			||||||
  path_element.html(item.path);
 | 
					  path_element.html(item.path);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -520,13 +529,13 @@ function addResultItem(item) {
 | 
				
			|||||||
  tags.empty();
 | 
					  tags.empty();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const tag_edit_copy = tag_edit_element.clone();
 | 
					  const tag_edit_copy = tag_edit_element.clone();
 | 
				
			||||||
  tag_edit_copy.click(function() {
 | 
					  tag_edit_copy.click(function () {
 | 
				
			||||||
    addTagModalShow(item.id, item.title, item.tags);
 | 
					    addTagModalShow(item.id, item.title, item.tags);
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
  tag_edit_copy.appendTo(tags);
 | 
					  tag_edit_copy.appendTo(tags);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  if (item.tags.length > 0) {
 | 
					  if (item.tags.length > 0) {
 | 
				
			||||||
    item.tags.forEach(function(tag_tuple) {
 | 
					    item.tags.forEach(function (tag_tuple) {
 | 
				
			||||||
      const tag_copy = tag_element.clone();
 | 
					      const tag_copy = tag_element.clone();
 | 
				
			||||||
      tag_copy.html(tag_tuple[0]);
 | 
					      tag_copy.html(tag_tuple[0]);
 | 
				
			||||||
      tag_copy.addClass('badge-' + tag_tuple[1]);
 | 
					      tag_copy.addClass('badge-' + tag_tuple[1]);
 | 
				
			||||||
@@ -544,7 +553,7 @@ function addResultItem(item) {
 | 
				
			|||||||
function getFilters(dest_page = 1) {
 | 
					function getFilters(dest_page = 1) {
 | 
				
			||||||
  const tags = $('.tag-clicked');
 | 
					  const tags = $('.tag-clicked');
 | 
				
			||||||
  const tags_list = [];
 | 
					  const tags_list = [];
 | 
				
			||||||
  tags.each(function(index, tag) {
 | 
					  tags.each(function (index, tag) {
 | 
				
			||||||
    tags_list.push(tag.innerHTML);
 | 
					    tags_list.push(tag.innerHTML);
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -575,19 +584,19 @@ function updateResults(dest_page = 1) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  lib_group.animate({
 | 
					  lib_group.animate({
 | 
				
			||||||
    opacity: 0,
 | 
					    opacity: 0,
 | 
				
			||||||
  }, 200, function() {
 | 
					  }, 200, function () {
 | 
				
			||||||
    $.ajax({
 | 
					    $.ajax({
 | 
				
			||||||
      type: 'POST',
 | 
					      type: 'POST',
 | 
				
			||||||
      url: 'library',
 | 
					      url: 'library',
 | 
				
			||||||
      data: data,
 | 
					      data: data,
 | 
				
			||||||
      statusCode: {
 | 
					      statusCode: {
 | 
				
			||||||
        200: processResults,
 | 
					        200: processResults,
 | 
				
			||||||
        204: function() {
 | 
					        204: function () {
 | 
				
			||||||
          lib_loading.hide();
 | 
					          lib_loading.hide();
 | 
				
			||||||
          lib_empty.show();
 | 
					          lib_empty.show();
 | 
				
			||||||
          page_ul.empty();
 | 
					          page_ul.empty();
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        403: function() {
 | 
					        403: function () {
 | 
				
			||||||
          location.reload(true);
 | 
					          location.reload(true);
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
@@ -651,7 +660,7 @@ document.getElementById('library-download-btn').addEventListener('click', () =>
 | 
				
			|||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
document.getElementById('library-rescan-btn').addEventListener('click', () => {
 | 
					document.getElementById('library-rescan-btn').addEventListener('click', () => {
 | 
				
			||||||
  request('post', {action: 'rescan'});
 | 
					  request('post', { action: 'rescan' });
 | 
				
			||||||
  updateResults();
 | 
					  updateResults();
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -671,16 +680,16 @@ const page_no = $('.library-page-no');
 | 
				
			|||||||
function processResults(data) {
 | 
					function processResults(data) {
 | 
				
			||||||
  lib_group.animate({
 | 
					  lib_group.animate({
 | 
				
			||||||
    opacity: 0,
 | 
					    opacity: 0,
 | 
				
			||||||
  }, 200, function() {
 | 
					  }, 200, function () {
 | 
				
			||||||
    lib_loading.hide();
 | 
					    lib_loading.hide();
 | 
				
			||||||
    const total_pages = data.total_pages;
 | 
					    const total_pages = data.total_pages;
 | 
				
			||||||
    const active_page = data.active_page;
 | 
					    const active_page = data.active_page;
 | 
				
			||||||
    const items = data.items;
 | 
					    const items = data.items;
 | 
				
			||||||
    items.forEach(
 | 
					    items.forEach(
 | 
				
			||||||
        function(item) {
 | 
					      function (item) {
 | 
				
			||||||
          addResultItem(item);
 | 
					        addResultItem(item);
 | 
				
			||||||
          bindLibraryResultEvent();
 | 
					        bindLibraryResultEvent();
 | 
				
			||||||
        },
 | 
					      },
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    page_ul.empty();
 | 
					    page_ul.empty();
 | 
				
			||||||
@@ -698,7 +707,7 @@ function processResults(data) {
 | 
				
			|||||||
      page_no_copy = page_no.clone();
 | 
					      page_no_copy = page_no.clone();
 | 
				
			||||||
      page_no_copy.html('«');
 | 
					      page_no_copy.html('«');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      page_no_copy.click(function(e) {
 | 
					      page_no_copy.click(function (e) {
 | 
				
			||||||
        updateResults(1);
 | 
					        updateResults(1);
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -714,7 +723,7 @@ function processResults(data) {
 | 
				
			|||||||
      if (active_page === i) {
 | 
					      if (active_page === i) {
 | 
				
			||||||
        page_li_copy.addClass('active');
 | 
					        page_li_copy.addClass('active');
 | 
				
			||||||
      } else {
 | 
					      } else {
 | 
				
			||||||
        page_no_copy.click(function(e) {
 | 
					        page_no_copy.click(function (e) {
 | 
				
			||||||
          const _page_no = $(e.currentTarget).html();
 | 
					          const _page_no = $(e.currentTarget).html();
 | 
				
			||||||
          updateResults(_page_no);
 | 
					          updateResults(_page_no);
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
@@ -728,7 +737,7 @@ function processResults(data) {
 | 
				
			|||||||
      page_no_copy = page_no.clone();
 | 
					      page_no_copy = page_no.clone();
 | 
				
			||||||
      page_no_copy.html('»');
 | 
					      page_no_copy.html('»');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      page_no_copy.click(function(e) {
 | 
					      page_no_copy.click(function (e) {
 | 
				
			||||||
        updateResults(total_pages);
 | 
					        updateResults(total_pages);
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -757,11 +766,11 @@ function addTagModalShow(_id, _title, _tag_tuples) {
 | 
				
			|||||||
  add_tag_modal_title.html('Edit tags for ' + _title);
 | 
					  add_tag_modal_title.html('Edit tags for ' + _title);
 | 
				
			||||||
  add_tag_modal_item_id.val(_id);
 | 
					  add_tag_modal_item_id.val(_id);
 | 
				
			||||||
  add_tag_modal_tags.empty();
 | 
					  add_tag_modal_tags.empty();
 | 
				
			||||||
  _tag_tuples.forEach(function(tag_tuple) {
 | 
					  _tag_tuples.forEach(function (tag_tuple) {
 | 
				
			||||||
    modal_tag_text.html(tag_tuple[0]);
 | 
					    modal_tag_text.html(tag_tuple[0]);
 | 
				
			||||||
    const tag_copy = modal_tag.clone();
 | 
					    const tag_copy = modal_tag.clone();
 | 
				
			||||||
    const modal_tag_remove = tag_copy.find('.modal-tag-remove');
 | 
					    const modal_tag_remove = tag_copy.find('.modal-tag-remove');
 | 
				
			||||||
    modal_tag_remove.click(function(e) {
 | 
					    modal_tag_remove.click(function (e) {
 | 
				
			||||||
      $(e.currentTarget).parent().remove();
 | 
					      $(e.currentTarget).parent().remove();
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
    tag_copy.show();
 | 
					    tag_copy.show();
 | 
				
			||||||
@@ -772,14 +781,14 @@ function addTagModalShow(_id, _title, _tag_tuples) {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
document.getElementById('addTagModalAddBtn').addEventListener('click', () => {
 | 
					document.getElementById('addTagModalAddBtn').addEventListener('click', () => {
 | 
				
			||||||
  const new_tags = add_tag_modal_input.val().split(',').map(function(str) {
 | 
					  const new_tags = add_tag_modal_input.val().split(',').map(function (str) {
 | 
				
			||||||
    return str.trim();
 | 
					    return str.trim();
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
  new_tags.forEach(function(tag) {
 | 
					  new_tags.forEach(function (tag) {
 | 
				
			||||||
    modal_tag_text.html(tag);
 | 
					    modal_tag_text.html(tag);
 | 
				
			||||||
    const tag_copy = modal_tag.clone();
 | 
					    const tag_copy = modal_tag.clone();
 | 
				
			||||||
    const modal_tag_remove = tag_copy.find('.modal-tag-remove');
 | 
					    const modal_tag_remove = tag_copy.find('.modal-tag-remove');
 | 
				
			||||||
    modal_tag_remove.click(function(e) {
 | 
					    modal_tag_remove.click(function (e) {
 | 
				
			||||||
      $(e.currentTarget).parent().remove();
 | 
					      $(e.currentTarget).parent().remove();
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
    tag_copy.show();
 | 
					    tag_copy.show();
 | 
				
			||||||
@@ -792,7 +801,7 @@ document.getElementById('addTagModalAddBtn').addEventListener('click', () => {
 | 
				
			|||||||
document.getElementById('addTagModalSubmit').addEventListener('click', () => {
 | 
					document.getElementById('addTagModalSubmit').addEventListener('click', () => {
 | 
				
			||||||
  const all_tags = $('.modal-tag-text');
 | 
					  const all_tags = $('.modal-tag-text');
 | 
				
			||||||
  const tags = [];
 | 
					  const tags = [];
 | 
				
			||||||
  all_tags.each(function(i, element) {
 | 
					  all_tags.each(function (i, element) {
 | 
				
			||||||
    if (element.innerHTML) {
 | 
					    if (element.innerHTML) {
 | 
				
			||||||
      tags.push(element.innerHTML);
 | 
					      tags.push(element.innerHTML);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@@ -820,7 +829,7 @@ let volume_popover_instance = null;
 | 
				
			|||||||
let volume_popover_show = false;
 | 
					let volume_popover_show = false;
 | 
				
			||||||
let volume_update_timer;
 | 
					let volume_update_timer;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
volumePopoverBtn.addEventListener('click', function(e) {
 | 
					volumePopoverBtn.addEventListener('click', function (e) {
 | 
				
			||||||
  e.stopPropagation();
 | 
					  e.stopPropagation();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  if (!volume_popover_show) {
 | 
					  if (!volume_popover_show) {
 | 
				
			||||||
@@ -842,7 +851,7 @@ volumePopoverBtn.addEventListener('click', function(e) {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
  volume_popover_show = !volume_popover_show;
 | 
					  volume_popover_show = !volume_popover_show;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  document.addEventListener('click', function() {
 | 
					  document.addEventListener('click', function () {
 | 
				
			||||||
    volumePopoverDiv.removeAttribute('data-show');
 | 
					    volumePopoverDiv.removeAttribute('data-show');
 | 
				
			||||||
    if (volume_popover_instance) {
 | 
					    if (volume_popover_instance) {
 | 
				
			||||||
      volume_popover_instance.destroy();
 | 
					      volume_popover_instance.destroy();
 | 
				
			||||||
@@ -854,7 +863,7 @@ volumePopoverBtn.addEventListener('click', function(e) {
 | 
				
			|||||||
  });
 | 
					  });
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
volumePopoverBtn.addEventListener('click', function(e) {
 | 
					volumePopoverBtn.addEventListener('click', function (e) {
 | 
				
			||||||
  e.stopPropagation();
 | 
					  e.stopPropagation();
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -870,11 +879,11 @@ volumeSlider.addEventListener('change', (e) => {
 | 
				
			|||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
document.getElementById('volume-down-btn').addEventListener('click', () => {
 | 
					document.getElementById('volume-down-btn').addEventListener('click', () => {
 | 
				
			||||||
  request('post', {action: 'volume_down'});
 | 
					  request('post', { action: 'volume_down' });
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
document.getElementById('volume-up-btn').addEventListener('click', () => {
 | 
					document.getElementById('volume-up-btn').addEventListener('click', () => {
 | 
				
			||||||
  request('post', {action: 'volume_up'});
 | 
					  request('post', { action: 'volume_up' });
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// ---------------------
 | 
					// ---------------------
 | 
				
			||||||
@@ -970,7 +979,7 @@ function uploadNextFile() {
 | 
				
			|||||||
  const file = filesToProceed.shift();
 | 
					  const file = filesToProceed.shift();
 | 
				
			||||||
  const file_progress_item = filesProgressItem[file.name];
 | 
					  const file_progress_item = filesProgressItem[file.name];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  req.addEventListener('load', function() {
 | 
					  req.addEventListener('load', function () {
 | 
				
			||||||
    if (this.status === 200) {
 | 
					    if (this.status === 200) {
 | 
				
			||||||
      setProgressBar(file_progress_item.progress, 1);
 | 
					      setProgressBar(file_progress_item.progress, 1);
 | 
				
			||||||
      file_progress_item.progress.classList.add('bg-success');
 | 
					      file_progress_item.progress.classList.add('bg-success');
 | 
				
			||||||
@@ -1003,7 +1012,7 @@ function uploadNextFile() {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  req.upload.addEventListener('progress', function(e) {
 | 
					  req.upload.addEventListener('progress', function (e) {
 | 
				
			||||||
    if (e.lengthComputable) {
 | 
					    if (e.lengthComputable) {
 | 
				
			||||||
      const percent = e.loaded / e.total;
 | 
					      const percent = e.loaded / e.total;
 | 
				
			||||||
      setProgressBar(file_progress_item.progress, percent, Math.floor(percent * 100) + '%');
 | 
					      setProgressBar(file_progress_item.progress, percent, Math.floor(percent * 100) + '%');
 | 
				
			||||||
@@ -1050,12 +1059,12 @@ const musicUrlInput = document.getElementById('music-url-input');
 | 
				
			|||||||
const radioUrlInput = document.getElementById('radio-url-input');
 | 
					const radioUrlInput = document.getElementById('radio-url-input');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
document.getElementById('add-music-url').querySelector('button').addEventListener('click', () => {
 | 
					document.getElementById('add-music-url').querySelector('button').addEventListener('click', () => {
 | 
				
			||||||
  request('post', {add_url: musicUrlInput.value});
 | 
					  request('post', { add_url: musicUrlInput.value });
 | 
				
			||||||
  musicUrlInput.value = '';
 | 
					  musicUrlInput.value = '';
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
document.getElementById('add-radio-url').querySelector('button').addEventListener('click', () => {
 | 
					document.getElementById('add-radio-url').querySelector('button').addEventListener('click', () => {
 | 
				
			||||||
  request('post', {add_radio: radioUrlInput.value});
 | 
					  request('post', { add_radio: radioUrlInput.value });
 | 
				
			||||||
  radioUrlInput.value = '';
 | 
					  radioUrlInput.value = '';
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -1077,15 +1086,15 @@ const playerSkipBtn = document.getElementById('playerSkipBtn');
 | 
				
			|||||||
let currentPlayingItem = null;
 | 
					let currentPlayingItem = null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
playerPlayBtn.addEventListener('click', () => {
 | 
					playerPlayBtn.addEventListener('click', () => {
 | 
				
			||||||
  request('post', {action: 'resume'});
 | 
					  request('post', { action: 'resume' });
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
playerPauseBtn.addEventListener('click', () => {
 | 
					playerPauseBtn.addEventListener('click', () => {
 | 
				
			||||||
  request('post', {action: 'pause'});
 | 
					  request('post', { action: 'pause' });
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
playerSkipBtn.addEventListener('click', () => {
 | 
					playerSkipBtn.addEventListener('click', () => {
 | 
				
			||||||
  request('post', {action: 'next'});
 | 
					  request('post', { action: 'next' });
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
document.getElementById('player-toast').addEventListener('click', () => {
 | 
					document.getElementById('player-toast').addEventListener('click', () => {
 | 
				
			||||||
@@ -1111,6 +1120,7 @@ function updatePlayerInfo(item) {
 | 
				
			|||||||
  playerTitle.textContent = item.title;
 | 
					  playerTitle.textContent = item.title;
 | 
				
			||||||
  playerArtist.textContent = item.artist;
 | 
					  playerArtist.textContent = item.artist;
 | 
				
			||||||
  playerArtwork.setAttribute('src', item.thumbnail);
 | 
					  playerArtwork.setAttribute('src', item.thumbnail);
 | 
				
			||||||
 | 
					  playerArtwork.setAttribute('alt', coverArtString(item.title));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  if (isOverflown(playerTitle)) {
 | 
					  if (isOverflown(playerTitle)) {
 | 
				
			||||||
    playerTitle.classList.add('scrolling');
 | 
					    playerTitle.classList.add('scrolling');
 | 
				
			||||||
@@ -1159,7 +1169,7 @@ function updatePlayerPlayhead(playhead) {
 | 
				
			|||||||
    player_playhead_position = playhead;
 | 
					    player_playhead_position = playhead;
 | 
				
			||||||
    setProgressBar(playerBar, player_playhead_position / currentPlayingItem.duration, secondsToStr(player_playhead_position));
 | 
					    setProgressBar(playerBar, player_playhead_position / currentPlayingItem.duration, secondsToStr(player_playhead_position));
 | 
				
			||||||
    if (playing) {
 | 
					    if (playing) {
 | 
				
			||||||
      playhead_timer = setInterval(function() {
 | 
					      playhead_timer = setInterval(function () {
 | 
				
			||||||
        player_playhead_position += 0.3;
 | 
					        player_playhead_position += 0.3;
 | 
				
			||||||
        setProgressBar(playerBar, player_playhead_position / currentPlayingItem.duration, secondsToStr(player_playhead_position));
 | 
					        setProgressBar(playerBar, player_playhead_position / currentPlayingItem.duration, secondsToStr(player_playhead_position));
 | 
				
			||||||
      }, 300); // delay in milliseconds
 | 
					      }, 300); // delay in milliseconds
 | 
				
			||||||
@@ -1174,7 +1184,7 @@ function updatePlayerPlayhead(playhead) {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
playerBarBox.addEventListener('mousedown', function() {
 | 
					playerBarBox.addEventListener('mousedown', function () {
 | 
				
			||||||
  if (currentPlayingItem && currentPlayingItem.duration > 0) {
 | 
					  if (currentPlayingItem && currentPlayingItem.duration > 0) {
 | 
				
			||||||
    playerBarBox.addEventListener('mousemove', playheadDragged);
 | 
					    playerBarBox.addEventListener('mousemove', playheadDragged);
 | 
				
			||||||
    clearInterval(playhead_timer);
 | 
					    clearInterval(playhead_timer);
 | 
				
			||||||
@@ -1182,7 +1192,7 @@ playerBarBox.addEventListener('mousedown', function() {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
playerBarBox.addEventListener('mouseup', function(event) {
 | 
					playerBarBox.addEventListener('mouseup', function (event) {
 | 
				
			||||||
  playerBarBox.removeEventListener('mousemove', playheadDragged);
 | 
					  playerBarBox.removeEventListener('mousemove', playheadDragged);
 | 
				
			||||||
  const percent = (event.clientX - playerBarBox.getBoundingClientRect().x) / playerBarBox.clientWidth;
 | 
					  const percent = (event.clientX - playerBarBox.getBoundingClientRect().x) / playerBarBox.clientWidth;
 | 
				
			||||||
  request('post', {
 | 
					  request('post', {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -16,3 +16,20 @@ export function secondsToStr(seconds) {
 | 
				
			|||||||
  const secs = seconds % 60;
 | 
					  const secs = seconds % 60;
 | 
				
			||||||
  return ('00' + mins).slice(-2) + ':' + ('00' + secs).slice(-2);
 | 
					  return ('00' + mins).slice(-2) + ':' + ('00' + secs).slice(-2);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function coverArtString(title) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  let nameOfSong = "";
 | 
				
			||||||
 | 
					  // The maximum length before we start truncating
 | 
				
			||||||
 | 
					  const maxLength = 50;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (title.length > maxLength) {
 | 
				
			||||||
 | 
					    // Name = longTitleTooLongToBeAGoodAltTex...
 | 
				
			||||||
 | 
					    nameOfSong = title.substr(0, maxLength) + "\u2026";
 | 
				
			||||||
 | 
					  } else {
 | 
				
			||||||
 | 
					    // Name = shortTitle
 | 
				
			||||||
 | 
					    nameOfSong = title;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return 'Cover art for ' + nameOfSong;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -1,117 +1,141 @@
 | 
				
			|||||||
.btn-space {margin-right: 5px;}
 | 
					.btn-space {
 | 
				
			||||||
 | 
					    margin-right: 5px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/* Playlist */
 | 
					/* Playlist */
 | 
				
			||||||
.playlist-item {transition: all 0.2s ease-in-out;}
 | 
					.playlist-item {
 | 
				
			||||||
.playlist-artwork {
 | 
					    transition: all 0.2s ease-in-out;
 | 
				
			||||||
   float: left;
 | 
					 | 
				
			||||||
   margin-left: 10px;
 | 
					 | 
				
			||||||
   white-space: nowrap;
 | 
					 | 
				
			||||||
   overflow: hidden;
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.playlist-artwork {
 | 
				
			||||||
 | 
					    float: left;
 | 
				
			||||||
 | 
					    margin-left: 10px;
 | 
				
			||||||
 | 
					    white-space: nowrap;
 | 
				
			||||||
 | 
					    overflow: hidden;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.tag-space {
 | 
					.tag-space {
 | 
				
			||||||
    margin-right: 3px;
 | 
					    margin-right: 3px;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.tag-click {
 | 
					.tag-click {
 | 
				
			||||||
   cursor: pointer;
 | 
					    cursor: pointer;
 | 
				
			||||||
   transition: 400ms;
 | 
					    transition: 400ms;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.tag-unclicked {
 | 
					.tag-unclicked {
 | 
				
			||||||
   opacity: 0.6;
 | 
					    opacity: 0.6;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.tag-clicked {
 | 
					.tag-clicked {
 | 
				
			||||||
   box-shadow: 2px 4px 10px #777777;
 | 
					    box-shadow: 2px 4px 10px #777777;
 | 
				
			||||||
   transform: scale(1.2);
 | 
					    transform: scale(1.2);
 | 
				
			||||||
   opacity: 1;
 | 
					    opacity: 1;
 | 
				
			||||||
   margin: 5px;
 | 
					    margin: 5px;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.library-item {
 | 
					.library-item {
 | 
				
			||||||
   display: flex;
 | 
					    display: flex;
 | 
				
			||||||
   padding: .5rem .5rem .5rem 0;
 | 
					    padding: .5rem .5rem .5rem 0;
 | 
				
			||||||
   height: 72px;
 | 
					    height: 72px;
 | 
				
			||||||
   transition: ease-in-out 200ms;
 | 
					    transition: ease-in-out 200ms;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.library-thumb-img {
 | 
					.library-thumb-img {
 | 
				
			||||||
   width: 70px;
 | 
					    width: 70px;
 | 
				
			||||||
   height: 70px;
 | 
					    height: 70px;
 | 
				
			||||||
   border-radius: 5px;
 | 
					    border-radius: 5px;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.library-thumb-col {
 | 
					.library-thumb-col {
 | 
				
			||||||
   position: relative;
 | 
					    position: relative;
 | 
				
			||||||
   padding-left: 0;
 | 
					    padding-left: 0;
 | 
				
			||||||
   overflow: hidden;
 | 
					    overflow: hidden;
 | 
				
			||||||
   margin: -0.5rem 1rem -0.5rem 0;
 | 
					    margin: -0.5rem 1rem -0.5rem 0;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.library-thumb-grp {
 | 
					.library-thumb-grp {
 | 
				
			||||||
   position: absolute;
 | 
					    position: absolute;
 | 
				
			||||||
   top: 0;
 | 
					    top: 0;
 | 
				
			||||||
   left: -95px;
 | 
					    left: -95px;
 | 
				
			||||||
   width: 70px;
 | 
					    width: 70px;
 | 
				
			||||||
   margin-left: 15px;
 | 
					    margin-left: 15px;
 | 
				
			||||||
   transition: left 300ms;
 | 
					    transition: left 300ms;
 | 
				
			||||||
   border-radius: 5px;
 | 
					    border-radius: 5px;
 | 
				
			||||||
   opacity: 0.7;
 | 
					    opacity: 0.7;
 | 
				
			||||||
   font-weight: 300;
 | 
					    font-weight: 300;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.library-thumb-grp-hover {
 | 
					.library-thumb-grp-hover {
 | 
				
			||||||
   left: -15px;
 | 
					    left: -15px;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.library-thumb-btn-up {
 | 
					.library-thumb-btn-up {
 | 
				
			||||||
   position: absolute !important;
 | 
					    position: absolute !important;
 | 
				
			||||||
   top: 0;
 | 
					    top: 0;
 | 
				
			||||||
   height: 70px;
 | 
					    height: 70px;
 | 
				
			||||||
   font-size: 2em;
 | 
					    font-size: 2em;
 | 
				
			||||||
   padding-top: 10px;
 | 
					    padding-top: 10px;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.library-btn-svg {
 | 
					.library-btn-svg {
 | 
				
			||||||
   width: 1rem;
 | 
					    width: 1rem;
 | 
				
			||||||
   fill: currentColor;
 | 
					    fill: currentColor;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.library-info-col {
 | 
					.library-info-col {
 | 
				
			||||||
   margin-right: 1rem;
 | 
					    margin-right: 1rem;
 | 
				
			||||||
   padding: 3px 0;
 | 
					    padding: 3px 0;
 | 
				
			||||||
   display: flex;
 | 
					    display: flex;
 | 
				
			||||||
   flex-direction: column;
 | 
					    flex-direction: column;
 | 
				
			||||||
   justify-content: center;
 | 
					    justify-content: center;
 | 
				
			||||||
   white-space: nowrap;
 | 
					    white-space: nowrap;
 | 
				
			||||||
   overflow: hidden;
 | 
					    overflow: hidden;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.library-info-col .small {
 | 
					.library-info-col .small {
 | 
				
			||||||
    font-weight: 300;
 | 
					    font-weight: 300;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.library-action {
 | 
					.library-action {
 | 
				
			||||||
    margin-left: auto;
 | 
					    margin-left: auto;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.library-info-col .path {
 | 
					.library-info-col .path {
 | 
				
			||||||
   font-style: italic !important;
 | 
					    font-style: italic !important;
 | 
				
			||||||
   font-weight: 300;
 | 
					    font-weight: 300;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/* Theme changer and player button */
 | 
					/* Theme changer and player button */
 | 
				
			||||||
.floating-button {
 | 
					.floating-button {
 | 
				
			||||||
   width: 50px;
 | 
					    width: 50px;
 | 
				
			||||||
   height: 50px;
 | 
					    height: 50px;
 | 
				
			||||||
   background-color: #aaaaaa40;
 | 
					    background-color: #aaaaaa40;
 | 
				
			||||||
   border-radius: 50%;
 | 
					    border-radius: 50%;
 | 
				
			||||||
   box-shadow: 0 6px 10px 0 #66666647;
 | 
					    box-shadow: 0 6px 10px 0 #66666647;
 | 
				
			||||||
   transition: all 0.1s ease-in-out;
 | 
					    transition: all 0.1s ease-in-out;
 | 
				
			||||||
   font-size: 25px;
 | 
					    font-size: 25px;
 | 
				
			||||||
   color: #9896967a;
 | 
					    color: #9896967a;
 | 
				
			||||||
   text-align: center;
 | 
					    text-align: center;
 | 
				
			||||||
   line-height: 52px;
 | 
					    line-height: 52px;
 | 
				
			||||||
   position: fixed;
 | 
					    position: fixed;
 | 
				
			||||||
   right: 50px;
 | 
					    right: 50px;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.floating-button:hover {
 | 
					.floating-button:hover {
 | 
				
			||||||
    background-color: hsl(0, 0%, 43%);
 | 
					    background-color: hsl(0, 0%, 43%);
 | 
				
			||||||
    color: white;
 | 
					    color: white;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#volume-slider {
 | 
					#volume-slider {
 | 
				
			||||||
    margin-top: 4px;
 | 
					    margin-top: 4px;
 | 
				
			||||||
    margin-right: 5px;
 | 
					    margin-right: 5px;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.dropdown {
 | 
					.dropdown {
 | 
				
			||||||
    display: inline-block;
 | 
					    display: inline-block;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#volume-popover {
 | 
					#volume-popover {
 | 
				
			||||||
    position: relative;
 | 
					    position: relative;
 | 
				
			||||||
    background: #333;
 | 
					    background: #333;
 | 
				
			||||||
@@ -122,45 +146,53 @@
 | 
				
			|||||||
    border-radius: 4px;
 | 
					    border-radius: 4px;
 | 
				
			||||||
    display: none;
 | 
					    display: none;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#volume-popover[data-show] {
 | 
					#volume-popover[data-show] {
 | 
				
			||||||
    display: flex;
 | 
					    display: flex;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#volume-popover a {
 | 
					#volume-popover a {
 | 
				
			||||||
    cursor: pointer;
 | 
					    cursor: pointer;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#volume-popover-arrow,
 | 
					#volume-popover-arrow,
 | 
				
			||||||
#volume-popover-arrow::before {
 | 
					#volume-popover-arrow::before {
 | 
				
			||||||
  position: absolute;
 | 
					    position: absolute;
 | 
				
			||||||
  width: 10px;
 | 
					    width: 10px;
 | 
				
			||||||
  height: 10px;
 | 
					    height: 10px;
 | 
				
			||||||
  z-index: -1;
 | 
					    z-index: -1;
 | 
				
			||||||
  top: 16px;
 | 
					    top: 16px;
 | 
				
			||||||
  left: 46px;
 | 
					    left: 46px;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#volume-popover-arrow::before {
 | 
					#volume-popover-arrow::before {
 | 
				
			||||||
  content: '';
 | 
					    content: '';
 | 
				
			||||||
  transform: rotate(45deg);
 | 
					    transform: rotate(45deg);
 | 
				
			||||||
  background: #333;
 | 
					    background: #333;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
#volume-popover[data-popper-placement^='top'] > #volume-popover-arrow {
 | 
					
 | 
				
			||||||
  bottom: -4px;
 | 
					#volume-popover[data-popper-placement^='top']>#volume-popover-arrow {
 | 
				
			||||||
 | 
					    bottom: -4px;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#playerToast {
 | 
					#playerToast {
 | 
				
			||||||
    position: fixed;
 | 
					    position: fixed;
 | 
				
			||||||
    right: 20px;
 | 
					    right: 20px;
 | 
				
			||||||
    top: 20px;
 | 
					    top: 20px;
 | 
				
			||||||
    max-width: 800px;
 | 
					    max-width: 800px;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#playerContainer {
 | 
					#playerContainer {
 | 
				
			||||||
    display: flex;
 | 
					    display: flex;
 | 
				
			||||||
    height: 105px;
 | 
					    height: 105px;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#playerArtwork {
 | 
					#playerArtwork {
 | 
				
			||||||
    width: 80px;
 | 
					    width: 80px;
 | 
				
			||||||
    height: 80px;
 | 
					    height: 80px;
 | 
				
			||||||
    border-radius: 5px;
 | 
					    border-radius: 5px;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#playerArtworkIdle {
 | 
					#playerArtworkIdle {
 | 
				
			||||||
    width: 80px;
 | 
					    width: 80px;
 | 
				
			||||||
    height: 80px;
 | 
					    height: 80px;
 | 
				
			||||||
@@ -168,6 +200,7 @@
 | 
				
			|||||||
    margin: auto;
 | 
					    margin: auto;
 | 
				
			||||||
    padding: 15px;
 | 
					    padding: 15px;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#playerInfo {
 | 
					#playerInfo {
 | 
				
			||||||
    position: relative;
 | 
					    position: relative;
 | 
				
			||||||
    padding-top: 6px;
 | 
					    padding-top: 6px;
 | 
				
			||||||
@@ -175,40 +208,58 @@
 | 
				
			|||||||
    height: 80px;
 | 
					    height: 80px;
 | 
				
			||||||
    font-size: 15px;
 | 
					    font-size: 15px;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#playerTitle {
 | 
					#playerTitle {
 | 
				
			||||||
    display: block;
 | 
					    display: block;
 | 
				
			||||||
    white-space: nowrap;
 | 
					    white-space: nowrap;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#playerArtist {
 | 
					#playerArtist {
 | 
				
			||||||
    display: block;
 | 
					    display: block;
 | 
				
			||||||
    white-space: nowrap;
 | 
					    white-space: nowrap;
 | 
				
			||||||
    min-height: 20px;
 | 
					    min-height: 20px;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#playerActionBox {
 | 
					#playerActionBox {
 | 
				
			||||||
    margin-top: 5px;
 | 
					    margin-top: 5px;
 | 
				
			||||||
    display: flex;
 | 
					    display: flex;
 | 
				
			||||||
    float: right;
 | 
					    float: right;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#playerBarBox {
 | 
					#playerBarBox {
 | 
				
			||||||
    margin-top: 5px;
 | 
					    margin-top: 5px;
 | 
				
			||||||
    height: 15px;
 | 
					    height: 15px;
 | 
				
			||||||
    width: 400px;
 | 
					    width: 400px;
 | 
				
			||||||
    cursor: pointer;
 | 
					    cursor: pointer;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.scrolling {
 | 
					.scrolling {
 | 
				
			||||||
    animation: scrolling 8s linear infinite;
 | 
					    animation: scrolling 8s linear infinite;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@keyframes scrolling {
 | 
					@keyframes scrolling {
 | 
				
			||||||
    0% {
 | 
					    0% {
 | 
				
			||||||
        transform: translateX(100%);
 | 
					        transform: translateX(100%);
 | 
				
			||||||
        opacity: 1;
 | 
					        opacity: 1;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    95%{
 | 
					
 | 
				
			||||||
 | 
					    95% {
 | 
				
			||||||
        transform: translateX(-90%);
 | 
					        transform: translateX(-90%);
 | 
				
			||||||
        opacity: 1;
 | 
					        opacity: 1;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    100% {
 | 
					    100% {
 | 
				
			||||||
        transform: translateX(-100%);
 | 
					        transform: translateX(-100%);
 | 
				
			||||||
        opacity: 0;
 | 
					        opacity: 0;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Allows us to have H3 with the size of an H5
 | 
				
			||||||
 | 
					h3 {
 | 
				
			||||||
 | 
					    font-size: 1.25rem;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Makes legend match the size of other labels
 | 
				
			||||||
 | 
					legend {
 | 
				
			||||||
 | 
					    font-size: 1rem;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,5 +1,7 @@
 | 
				
			|||||||
<!DOCTYPE html>
 | 
					<!DOCTYPE html>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<html lang="en">
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<head>
 | 
					<head>
 | 
				
			||||||
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
 | 
					    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
 | 
				
			||||||
    <meta charset="UTF-8">
 | 
					    <meta charset="UTF-8">
 | 
				
			||||||
@@ -12,28 +14,31 @@
 | 
				
			|||||||
</head>
 | 
					</head>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<body>
 | 
					<body>
 | 
				
			||||||
    <div class="container page-header mb-5" id="banner">
 | 
					    <header class="container page-header mb-5" id="banner">
 | 
				
			||||||
        <div class="row">
 | 
					        <div class="row">
 | 
				
			||||||
            <div class="col-auto">
 | 
					            <div class="col-auto">
 | 
				
			||||||
                <img src="static/image/logo.png" height="200px">
 | 
					                <img src="static/image/logo.png" height="200px"
 | 
				
			||||||
 | 
					                    alt="Botamusique Logo: a person with two headphones, enjoying the music">
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
            <div class="col my-auto">
 | 
					            <div class="col my-auto">
 | 
				
			||||||
                <h1>botamusique Web Interface</h1>
 | 
					                <h1>botamusique Web Interface</h1>
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
    </div>
 | 
					    </header>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <div id="playlist" class="container mb-5">
 | 
					    <main id="playlist" class="container mb-5">
 | 
				
			||||||
        <div class="btn-toolbar mb-2" role="toolbar" aria-label="Playlist controls">
 | 
					        <div class="btn-toolbar mb-2" role="toolbar" aria-label="Playlist controls">
 | 
				
			||||||
            <button type="button" id="play-pause-btn" class="btn btn-info mb-2 btn-space">
 | 
					            <button type="button" id="play-pause-btn" class="btn btn-info mb-2 btn-space" aria-label="Play">
 | 
				
			||||||
                <i class="fas fa-play"></i>
 | 
					                <i class="fas fa-play"></i>
 | 
				
			||||||
            </button>
 | 
					            </button>
 | 
				
			||||||
            <button type="button" id="fast-forward-btn" class="btn btn-info mb-2">
 | 
					            <button type="button" id="fast-forward-btn" class="btn btn-info mb-2" aria-label="Skip Track">
 | 
				
			||||||
                <i class="fas fa-fast-forward"></i>
 | 
					                <i class="fas fa-fast-forward"></i>
 | 
				
			||||||
            </button>
 | 
					            </button>
 | 
				
			||||||
            <div class="ml-auto">
 | 
					            <div class="ml-auto">
 | 
				
			||||||
                <div class="dropdown mr-2">
 | 
					                <div class="dropdown mr-2">
 | 
				
			||||||
                    <button class="btn btn-secondary dropdown-toggle" type="button" id="play-mode" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
 | 
					                    <button class="btn btn-secondary dropdown-toggle" type="button" id="play-mode"
 | 
				
			||||||
 | 
					                        data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"
 | 
				
			||||||
 | 
					                        aria-label="Change Playback Mode">
 | 
				
			||||||
                        <i class="fas fa-tasks mr-2" aria-hidden="true" id="modeIndicator"></i>
 | 
					                        <i class="fas fa-tasks mr-2" aria-hidden="true" id="modeIndicator"></i>
 | 
				
			||||||
                    </button>
 | 
					                    </button>
 | 
				
			||||||
                    <div class="dropdown-menu" aria-labelledby="play-mode">
 | 
					                    <div class="dropdown-menu" aria-labelledby="play-mode">
 | 
				
			||||||
@@ -51,7 +56,8 @@
 | 
				
			|||||||
                        </a>
 | 
					                        </a>
 | 
				
			||||||
                    </div>
 | 
					                    </div>
 | 
				
			||||||
                </div>
 | 
					                </div>
 | 
				
			||||||
                <button type="button" id="volume-popover-btn" class="btn btn-warning ml-1">
 | 
					                <button type="button" id="volume-popover-btn" class="btn btn-warning ml-1"
 | 
				
			||||||
 | 
					                    aria-label="Open Volume Controls">
 | 
				
			||||||
                    <i class="fa fa-volume-up" aria-hidden="true"></i>
 | 
					                    <i class="fa fa-volume-up" aria-hidden="true"></i>
 | 
				
			||||||
                </button>
 | 
					                </button>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -60,7 +66,8 @@
 | 
				
			|||||||
                        <i class="fa fa-volume-down" aria-hidden="true"></i>
 | 
					                        <i class="fa fa-volume-down" aria-hidden="true"></i>
 | 
				
			||||||
                    </a>
 | 
					                    </a>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    <input type="range" class="custom-range ml-1" id="volume-slider" min="0" max="1" step="0.01" value="0.5" />
 | 
					                    <input type="range" class="custom-range ml-1" id="volume-slider" min="0" max="1" step="0.01"
 | 
				
			||||||
 | 
					                        value="0.5" aria-label="Volume Slider" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    <a id="volume-up-btn">
 | 
					                    <a id="volume-up-btn">
 | 
				
			||||||
                        <i class="fa fa-volume-up" aria-hidden="true"></i>
 | 
					                        <i class="fa fa-volume-up" aria-hidden="true"></i>
 | 
				
			||||||
@@ -83,25 +90,29 @@
 | 
				
			|||||||
                <tbody id="playlist-table" class="playlist-table">
 | 
					                <tbody id="playlist-table" class="playlist-table">
 | 
				
			||||||
                    <tr id="playlist-loading">
 | 
					                    <tr id="playlist-loading">
 | 
				
			||||||
                        <td colspan="4" class="text-center">
 | 
					                        <td colspan="4" class="text-center">
 | 
				
			||||||
                            <img style="margin: auto; width: 35px;" src="static/image/loading.svg" />
 | 
					                            <img style="margin: auto; width: 35px;" src="static/image/loading.svg"
 | 
				
			||||||
 | 
					                                alt="A loading spinner" />
 | 
				
			||||||
                        </td>
 | 
					                        </td>
 | 
				
			||||||
                    </tr>
 | 
					                    </tr>
 | 
				
			||||||
                    <tr id="playlist-empty" class="d-none">
 | 
					                    <tr id="playlist-empty" class="d-none">
 | 
				
			||||||
                        <td colspan="4" class="text-center">
 | 
					                        <td colspan="4" class="text-center">
 | 
				
			||||||
                            <img style="margin: auto; width: 35px;" src="static/image/empty_box.svg" />
 | 
					                            <img style="margin: auto; width: 35px;" src="static/image/empty_box.svg"
 | 
				
			||||||
 | 
					                                alt="A drawing of an empty box." />
 | 
				
			||||||
                        </td>
 | 
					                        </td>
 | 
				
			||||||
                    </tr>
 | 
					                    </tr>
 | 
				
			||||||
                    <tr class="playlist-expand table-dark d-none">
 | 
					                    <tr class="playlist-expand table-dark d-none">
 | 
				
			||||||
                        <td colspan="4" class="text-center">
 | 
					                        <td colspan="4" class="text-center">
 | 
				
			||||||
                            <a class="text-muted" href="javascript:">See item <span class="playlist-expand-item-range"></span> on the playlist.</a>
 | 
					                            <a class="text-muted" href="javascript:">See item <span
 | 
				
			||||||
 | 
					                                    class="playlist-expand-item-range"></span> on the playlist.</a>
 | 
				
			||||||
                        </td>
 | 
					                        </td>
 | 
				
			||||||
                    </tr>
 | 
					                    </tr>
 | 
				
			||||||
                    <tr class="playlist-item-template d-none">
 | 
					                    <tr class="playlist-item-template d-none" aria-hidden="true">
 | 
				
			||||||
                        <th scope="row" class="playlist-item-index d-none d-md-table-cell"></th>
 | 
					                        <th scope="row" class="playlist-item-index d-none d-md-table-cell">1</th>
 | 
				
			||||||
                        <td>
 | 
					                        <td>
 | 
				
			||||||
                            <input hidden type="text" class="playlist-item-id" value="" />
 | 
					                            <input hidden type="hidden" class="playlist-item-id" value="" />
 | 
				
			||||||
                            <div class="float-left">
 | 
					                            <div class="float-left">
 | 
				
			||||||
                                <img width="80" class="playlist-item-thumbnail" src="static/image/unknown-album.png" />
 | 
					                                <img width="80" class="playlist-item-thumbnail" src="static/image/unknown-album.png"
 | 
				
			||||||
 | 
					                                    alt="A black square with two eight notes beamed together." />
 | 
				
			||||||
                            </div>
 | 
					                            </div>
 | 
				
			||||||
                            <div class="playlist-artwork">
 | 
					                            <div class="playlist-artwork">
 | 
				
			||||||
                                <b class="playlist-item-title"></b>
 | 
					                                <b class="playlist-item-title"></b>
 | 
				
			||||||
@@ -122,10 +133,12 @@
 | 
				
			|||||||
                        </td>
 | 
					                        </td>
 | 
				
			||||||
                        <td>
 | 
					                        <td>
 | 
				
			||||||
                            <div class="btn-group">
 | 
					                            <div class="btn-group">
 | 
				
			||||||
                                <button type="button" class="playlist-item-play btn btn-info btn-sm">
 | 
					                                <button type="button" class="playlist-item-play btn btn-info btn-sm"
 | 
				
			||||||
 | 
					                                    aria-label="Skip current song and play this song right now">
 | 
				
			||||||
                                    <i class="fas fa-play" aria-hidden="true"></i>
 | 
					                                    <i class="fas fa-play" aria-hidden="true"></i>
 | 
				
			||||||
                                </button>
 | 
					                                </button>
 | 
				
			||||||
                                <button type="button" class="playlist-item-trash btn btn-danger btn-sm ml-1">
 | 
					                                <button type="button" class="playlist-item-trash btn btn-danger btn-sm ml-1"
 | 
				
			||||||
 | 
					                                    aria-label="Remove this song from the current playlist">
 | 
				
			||||||
                                    <i class="fas fa-trash-alt" aria-hidden="true"></i>
 | 
					                                    <i class="fas fa-trash-alt" aria-hidden="true"></i>
 | 
				
			||||||
                                </button>
 | 
					                                </button>
 | 
				
			||||||
                            </div>
 | 
					                            </div>
 | 
				
			||||||
@@ -140,19 +153,19 @@
 | 
				
			|||||||
                <i class="fas fa-trash-alt" aria-hidden="true"></i> Clear Playlist
 | 
					                <i class="fas fa-trash-alt" aria-hidden="true"></i> Clear Playlist
 | 
				
			||||||
            </button>
 | 
					            </button>
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
    </div>
 | 
					    </main>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <div class="container mb-3">
 | 
					    <div class="container mb-3">
 | 
				
			||||||
        <h2 id="forms">Music Library</h2>
 | 
					        <h2 id="forms">Music Library</h2>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        <div class="card mb-3">
 | 
					        <div class="card mb-3">
 | 
				
			||||||
            <div class="card-header">
 | 
					            <div class="card-header">
 | 
				
			||||||
                <h5 class="card-title">Filters</h5>
 | 
					                <h3 class="card-title">Filters</h3>
 | 
				
			||||||
                <hr>
 | 
					                <hr>
 | 
				
			||||||
                <div class="row">
 | 
					                <div class="row">
 | 
				
			||||||
                    <div class="col">
 | 
					                    <div class="col">
 | 
				
			||||||
                        <label>Type</label>
 | 
					                        <fieldset id="filter-type" class="mb-2">
 | 
				
			||||||
                        <div id="filter-type" class="input-group mb-2">
 | 
					                            <legend>Type</legend>
 | 
				
			||||||
                            <div class="btn-group btn-group-sm btn-group-toggle">
 | 
					                            <div class="btn-group btn-group-sm btn-group-toggle">
 | 
				
			||||||
                                <label id="filter-type-file" class="btn btn-secondary">
 | 
					                                <label id="filter-type-file" class="btn btn-secondary">
 | 
				
			||||||
                                    <input type="checkbox" name="options"> File
 | 
					                                    <input type="checkbox" name="options"> File
 | 
				
			||||||
@@ -164,7 +177,7 @@
 | 
				
			|||||||
                                    <input type="checkbox" name="options"> Radio
 | 
					                                    <input type="checkbox" name="options"> Radio
 | 
				
			||||||
                                </label>
 | 
					                                </label>
 | 
				
			||||||
                            </div>
 | 
					                            </div>
 | 
				
			||||||
                        </div>
 | 
					                        </fieldset>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                        <label for="filter-dir">Directory</label>
 | 
					                        <label for="filter-dir">Directory</label>
 | 
				
			||||||
                        <div id="filter-path" class="input-group mb-2">
 | 
					                        <div id="filter-path" class="input-group mb-2">
 | 
				
			||||||
@@ -178,34 +191,39 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
                        <label for="filter-keywords">Keywords</label>
 | 
					                        <label for="filter-keywords">Keywords</label>
 | 
				
			||||||
                        <div id="filter-path" class="input-group mb-2">
 | 
					                        <div id="filter-path" class="input-group mb-2">
 | 
				
			||||||
                            <input class="form-control form-control-sm" id="filter-keywords" name="keywords" placeholder="Keywords..." style="margin-top:5px;" />
 | 
					                            <input class="form-control form-control-sm" id="filter-keywords" name="keywords"
 | 
				
			||||||
 | 
					                                placeholder="Keywords..." style="margin-top:5px;" />
 | 
				
			||||||
                        </div>
 | 
					                        </div>
 | 
				
			||||||
                    </div>
 | 
					                    </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    <div class="col">
 | 
					                    <div class="col">
 | 
				
			||||||
                        <label for="filter-tag">Tags</label>
 | 
					                        <fieldset id="filter-type mb-2">
 | 
				
			||||||
                        <div id="filter-type mb-2">
 | 
					                            <legend>Tags</legend>
 | 
				
			||||||
                            {% for tag in tags_color_lookup.keys() %}
 | 
					                            {% for tag in tags_color_lookup.keys() %}
 | 
				
			||||||
                            <span id="filter-tag" class="filter-tag tag-unclicked tag-click badge badge-{{ tags_color_lookup[tag] }}">{{ tag }}</span>
 | 
					                            <span id="filter-tag"
 | 
				
			||||||
 | 
					                                class="filter-tag tag-unclicked tag-click badge badge-{{ tags_color_lookup[tag] }}">{{ tag }}</span>
 | 
				
			||||||
                            {% endfor %}
 | 
					                            {% endfor %}
 | 
				
			||||||
                        </div>
 | 
					                        </fieldset>
 | 
				
			||||||
                    </div>
 | 
					                    </div>
 | 
				
			||||||
                </div>
 | 
					                </div>
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
            <div class="card-body">
 | 
					            <div class="card-body">
 | 
				
			||||||
                <div id="library-group" class="list-group library-group" style="overflow: auto;">
 | 
					                <div id="library-group" class="list-group library-group" style="overflow: auto;">
 | 
				
			||||||
                    <div id="library-item-loading" class="list-group-item library-item">
 | 
					                    <div id="library-item-loading" class="list-group-item library-item">
 | 
				
			||||||
                        <img style="margin: auto; width: 35px;" src="static/image/loading.svg" />
 | 
					                        <img style="margin: auto; width: 35px;" src="static/image/loading.svg"
 | 
				
			||||||
 | 
					                            alt="A loading spinner" />
 | 
				
			||||||
                    </div>
 | 
					                    </div>
 | 
				
			||||||
                    <div id="library-item-empty" style="display: none" class="list-group-item library-item">
 | 
					                    <div id="library-item-empty" style="display: none" class="list-group-item library-item">
 | 
				
			||||||
                        <img style="margin: auto; width: 35px;" src="static/image/empty_box.svg" />
 | 
					                        <img style="margin: auto; width: 35px;" src="static/image/empty_box.svg"
 | 
				
			||||||
 | 
					                            alt="A drawing of an empty box." />
 | 
				
			||||||
                    </div>
 | 
					                    </div>
 | 
				
			||||||
                    <div id="library-item" style="display: none;" class="list-group-item library-item">
 | 
					                    <div id="library-item" style="display: none;" class="list-group-item library-item">
 | 
				
			||||||
                        <input hidden type="text" class="library-item-id" value="" />
 | 
					                        <input hidden type="hidden" class="library-item-id" value="" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                        <div class="library-thumb-col">
 | 
					                        <div class="library-thumb-col">
 | 
				
			||||||
                            <div class="library-thumb-img">
 | 
					                            <div class="library-thumb-img">
 | 
				
			||||||
                                <img class="library-item-thumb library-thumb-img" src="static/image/unknown-album.png" />
 | 
					                                <img class="library-item-thumb library-thumb-img" src="static/image/unknown-album.png"
 | 
				
			||||||
 | 
					                                    alt="A black square with two eight notes beamed together." />
 | 
				
			||||||
                            </div>
 | 
					                            </div>
 | 
				
			||||||
                            <div class="btn-group-vertical library-thumb-grp">
 | 
					                            <div class="btn-group-vertical library-thumb-grp">
 | 
				
			||||||
                                <div class="library-item-play btn btn-secondary library-thumb-btn-up" title="Play">
 | 
					                                <div class="library-item-play btn btn-secondary library-thumb-btn-up" title="Play">
 | 
				
			||||||
@@ -225,28 +243,40 @@
 | 
				
			|||||||
                        <div class="library-info-col col-4 d-none d-md-flex" style="padding: 3px;">
 | 
					                        <div class="library-info-col col-4 d-none d-md-flex" style="padding: 3px;">
 | 
				
			||||||
                            <span class="library-item-path text-muted path">Path/to/the/file</span>
 | 
					                            <span class="library-item-path text-muted path">Path/to/the/file</span>
 | 
				
			||||||
                            <div class="library-item-tags">
 | 
					                            <div class="library-item-tags">
 | 
				
			||||||
                                <a class="tag-space tag-click library-item-edit"><i class="fas fa-edit" style="color: #AAAAAA"></i></a>
 | 
					                                <a class="tag-space tag-click library-item-edit"><i class="fas fa-edit"
 | 
				
			||||||
 | 
					                                        style="color: #AAAAAA"></i></a>
 | 
				
			||||||
                                <span class="library-item-notag badge badge-light text-muted font-italic">No tag</span>
 | 
					                                <span class="library-item-notag badge badge-light text-muted font-italic">No tag</span>
 | 
				
			||||||
                                <span class="library-item-tag tag-space badge">Tag</span>
 | 
					                                <span class="library-item-tag tag-space badge">Tag</span>
 | 
				
			||||||
                            </div>
 | 
					                            </div>
 | 
				
			||||||
                        </div>
 | 
					                        </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                        <div class="btn-group library-action">
 | 
					                        <div class="btn-group library-action">
 | 
				
			||||||
                            <button class="library-item-add-next btn btn-info btn-sm btn-space" type="button" title="Next to play">
 | 
					                            <button class="library-item-add-next btn btn-info btn-sm btn-space" type="button"
 | 
				
			||||||
                                <svg class="library-btn-svg" style="width: 1rem; fill: currentColor;" viewBox="5 5 17 17">
 | 
					                                title="Next to play" aria-label="Add to playlist right after current song">
 | 
				
			||||||
                                    <path d="m5.700245,3.92964l0,14.150376l11.451127,-7.075188l-11.451127,-7.075188z"></path>
 | 
					                                <svg class="library-btn-svg" style="width: 1rem; fill: currentColor;"
 | 
				
			||||||
                                    <path d="m20.942859,18.221072l-3.323292,0l0,3.323292l-1.107764,0l0,-3.323292l-3.323292,0l0,-1.107764l3.323292,0l0,-3.323292l1.107764,0l0,3.323292l3.323292,0l0,1.107764z"></path>
 | 
					                                    viewBox="5 5 17 17">
 | 
				
			||||||
 | 
					                                    <path d="m5.700245,3.92964l0,14.150376l11.451127,-7.075188l-11.451127,-7.075188z">
 | 
				
			||||||
 | 
					                                    </path>
 | 
				
			||||||
 | 
					                                    <path
 | 
				
			||||||
 | 
					                                        d="m20.942859,18.221072l-3.323292,0l0,3.323292l-1.107764,0l0,-3.323292l-3.323292,0l0,-1.107764l3.323292,0l0,-3.323292l1.107764,0l0,3.323292l3.323292,0l0,1.107764z">
 | 
				
			||||||
 | 
					                                    </path>
 | 
				
			||||||
                                </svg>
 | 
					                                </svg>
 | 
				
			||||||
                            </button>
 | 
					                            </button>
 | 
				
			||||||
                            <button class="library-item-add-bottom library-btn btn btn-info btn-sm btn-space" type="button" title="Add to bottom">
 | 
					                            <button class="library-item-add-bottom library-btn btn btn-info btn-sm btn-space"
 | 
				
			||||||
                                <svg class="library-btn-svg" style="width: 1rem; fill: currentColor;" viewBox="2 2 20 20">
 | 
					                                type="button" title="Add to bottom" aria-label="Add to bottom of current playlist">
 | 
				
			||||||
                                    <path d="M2,16H10V14H2M18,14V10H16V14H12V16H16V20H18V16H22V14M14,6H2V8H14M14,10H2V12H14V10Z"></path>
 | 
					                                <svg class="library-btn-svg" style="width: 1rem; fill: currentColor;"
 | 
				
			||||||
 | 
					                                    viewBox="2 2 20 20">
 | 
				
			||||||
 | 
					                                    <path
 | 
				
			||||||
 | 
					                                        d="M2,16H10V14H2M18,14V10H16V14H12V16H16V20H18V16H22V14M14,6H2V8H14M14,10H2V12H14V10Z">
 | 
				
			||||||
 | 
					                                    </path>
 | 
				
			||||||
                                </svg>
 | 
					                                </svg>
 | 
				
			||||||
                            </button>
 | 
					                            </button>
 | 
				
			||||||
                            <button class="library-item-download btn btn-primary btn-sm btn-space" type="button">
 | 
					                            <button class="library-item-download btn btn-primary btn-sm btn-space" type="button"
 | 
				
			||||||
 | 
					                                aria-label="Download song from library">
 | 
				
			||||||
                                <i class="fas fa-download" aria-hidden="true"></i>
 | 
					                                <i class="fas fa-download" aria-hidden="true"></i>
 | 
				
			||||||
                            </button>
 | 
					                            </button>
 | 
				
			||||||
                            <button class="library-item-trash btn btn-danger btn-sm btn-space" type="button">
 | 
					                            <button class="library-item-trash btn btn-danger btn-sm btn-space" type="button"
 | 
				
			||||||
 | 
					                                aria-label="Remove song from library">
 | 
				
			||||||
                                <i class="fas fa-trash-alt"></i>
 | 
					                                <i class="fas fa-trash-alt"></i>
 | 
				
			||||||
                            </button>
 | 
					                            </button>
 | 
				
			||||||
                        </div>
 | 
					                        </div>
 | 
				
			||||||
@@ -255,56 +285,59 @@
 | 
				
			|||||||
                </div>
 | 
					                </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                <div class="list-group">
 | 
					                <div class="list-group">
 | 
				
			||||||
                  <div id="library-pagination" style="margin-left: auto; margin-top: 10px;">
 | 
					                    <div id="library-pagination" style="margin-left: auto; margin-top: 10px;">
 | 
				
			||||||
                    <ul id="library-page-ul" class="pagination pagination">
 | 
					                        <ul id="library-page-ul" class="pagination pagination">
 | 
				
			||||||
                      <li class="library-page-li page-item active">
 | 
					                            <li class="library-page-li page-item active">
 | 
				
			||||||
                        <a class="library-page-no page-link">1</a>
 | 
					                                <a class="library-page-no page-link">1</a>
 | 
				
			||||||
                      </li>
 | 
					                            </li>
 | 
				
			||||||
                    </ul>
 | 
					                        </ul>
 | 
				
			||||||
                  </div>
 | 
					                    </div>
 | 
				
			||||||
                </div>
 | 
					                </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                <div class="btn-group mb-2" role="group">
 | 
					                <div class="btn-group mb-2" role="group">
 | 
				
			||||||
                  <button id="add-to-playlist-btn" type="button" class="btn btn-secondary mr-1">
 | 
					                    <button id="add-to-playlist-btn" type="button" class="btn btn-secondary mr-1">
 | 
				
			||||||
                    <i class="fas fa-plus" aria-hidden="true"></i> Add All
 | 
					                        <i class="fas fa-plus" aria-hidden="true"></i> Add All
 | 
				
			||||||
                  </button>
 | 
					                    </button>
 | 
				
			||||||
                  <button id="library-rescan-btn" type="button" class="btn btn-secondary mr-1">
 | 
					                    <button id="library-rescan-btn" type="button" class="btn btn-secondary mr-1">
 | 
				
			||||||
                    <i class="fas fa-sync-alt" aria-hidden="true"></i> Rescan Files
 | 
					                        <i class="fas fa-sync-alt" aria-hidden="true"></i> Rescan Files
 | 
				
			||||||
                  </button>
 | 
					                    </button>
 | 
				
			||||||
                  <button id="library-download-btn" type="button" class="btn btn-secondary mr-1">
 | 
					                    <button id="library-download-btn" type="button" class="btn btn-secondary mr-1">
 | 
				
			||||||
                    <i class="fas fa-download" aria-hidden="true"></i> Download All
 | 
					                        <i class="fas fa-download" aria-hidden="true"></i> Download All
 | 
				
			||||||
                  </button>
 | 
					                    </button>
 | 
				
			||||||
                  <button type="button" class="btn btn-danger mr-1" data-toggle="modal" data-target="#deleteWarningModal">
 | 
					                    <button type="button" class="btn btn-danger mr-1" data-toggle="modal"
 | 
				
			||||||
                    <i class="fas fa-trash-alt" aria-hidden="true"></i> Delete All
 | 
					                        data-target="#deleteWarningModal">
 | 
				
			||||||
                  </button>
 | 
					                        <i class="fas fa-trash-alt" aria-hidden="true"></i> Delete All
 | 
				
			||||||
 | 
					                    </button>
 | 
				
			||||||
                </div>
 | 
					                </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                <div class="modal fade" id="deleteWarningModal" tabindex="-1" role="dialog" aria-labelledby="Warning-Delete-File" aria-hidden="true">
 | 
					                <!-- QUESTION: should this div have aria-hidden as true?? -->
 | 
				
			||||||
                  <div class="modal-dialog" role="document">
 | 
					                <div class="modal fade" id="deleteWarningModal" tabindex="-1" role="dialog"
 | 
				
			||||||
                    <div class="modal-content">
 | 
					                    aria-label="Modal Window for warning about deletion of files." aria-hidden="true">
 | 
				
			||||||
                      <div class="modal-header">
 | 
					                    <div class="modal-dialog" role="document">
 | 
				
			||||||
                        <h5 class="modal-title" id="deleteWarningModalLabel">Are you really sure?</h5>
 | 
					                        <div class="modal-content">
 | 
				
			||||||
                        <button type="button" class="close" data-dismiss="modal" aria-label="Close">
 | 
					                            <div class="modal-header">
 | 
				
			||||||
                          <span aria-hidden="true">×</span>
 | 
					                                <h3 class="modal-title" id="deleteWarningModalLabel">Are you really sure?</h3>
 | 
				
			||||||
                        </button>
 | 
					                                <button type="button" class="close" data-dismiss="modal" aria-label="Close">
 | 
				
			||||||
                      </div>
 | 
					                                    <span aria-hidden="true">×</span>
 | 
				
			||||||
                      <div class="modal-body">
 | 
					                                </button>
 | 
				
			||||||
                        All files listed here, include files on other pages, will be deleted from your
 | 
					                            </div>
 | 
				
			||||||
                        hard-drive.
 | 
					                            <div class="modal-body">
 | 
				
			||||||
                        Is that what you want?
 | 
					                                All files listed here, include files on other pages, will be deleted from your
 | 
				
			||||||
                      </div>
 | 
					                                hard-drive.
 | 
				
			||||||
                      <div class="modal-footer">
 | 
					                                Is that what you want?
 | 
				
			||||||
                        <button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
 | 
					                            </div>
 | 
				
			||||||
                        <button id="library-delete-btn" type="button" class="btn btn-danger" data-dismiss="modal">Delete All Listed Files</button>
 | 
					                            <div class="modal-footer">
 | 
				
			||||||
                      </div>
 | 
					                                <button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
 | 
				
			||||||
 | 
					                                <button id="library-delete-btn" type="button" class="btn btn-danger"
 | 
				
			||||||
 | 
					                                    data-dismiss="modal">Delete All Listed Files</button>
 | 
				
			||||||
 | 
					                            </div>
 | 
				
			||||||
 | 
					                        </div>
 | 
				
			||||||
                    </div>
 | 
					                    </div>
 | 
				
			||||||
                  </div>
 | 
					 | 
				
			||||||
                </div>
 | 
					                </div>
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <!-- beautify ignore:start -->
 | 
					 | 
				
			||||||
    {% if upload_enabled %}
 | 
					    {% if upload_enabled %}
 | 
				
			||||||
    <div id="upload" class="container mb-3">
 | 
					    <div id="upload" class="container mb-3">
 | 
				
			||||||
    {% else %}
 | 
					    {% else %}
 | 
				
			||||||
@@ -313,7 +346,7 @@
 | 
				
			|||||||
    {% endif %}
 | 
					    {% endif %}
 | 
				
			||||||
        <div class="card">
 | 
					        <div class="card">
 | 
				
			||||||
            <div class="card-header">
 | 
					            <div class="card-header">
 | 
				
			||||||
                <h5 class="card-title">Upload File</h5>
 | 
					                <h3 class="card-title">Upload File</h3>
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
            <div class="card-body">
 | 
					            <div class="card-body">
 | 
				
			||||||
                <form action="./upload" method="post" enctype="multipart/form-data">
 | 
					                <form action="./upload" method="post" enctype="multipart/form-data">
 | 
				
			||||||
@@ -333,7 +366,7 @@
 | 
				
			|||||||
                                <div class="col">
 | 
					                                <div class="col">
 | 
				
			||||||
                                    <div class="input-group mb-3">
 | 
					                                    <div class="input-group mb-3">
 | 
				
			||||||
                                        <div class="input-group-prepend">
 | 
					                                        <div class="input-group-prepend">
 | 
				
			||||||
                                            <span class="input-group-text">Upload To</span>
 | 
					                                            <label for="uploadTargetDir" class="input-group-text">Upload To</label>
 | 
				
			||||||
                                        </div>
 | 
					                                        </div>
 | 
				
			||||||
                                        <input class="form-control" list="targetdirs" id="uploadTargetDir" name="targetdir" placeholder="uploads" />
 | 
					                                        <input class="form-control" list="targetdirs" id="uploadTargetDir" name="targetdir" placeholder="uploads" />
 | 
				
			||||||
                                        <datalist id="targetdirs">
 | 
					                                        <datalist id="targetdirs">
 | 
				
			||||||
@@ -353,13 +386,12 @@
 | 
				
			|||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
    <!-- beautify ignore:end -->
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <div class="container mb-5">
 | 
					    <div class="container mb-5">
 | 
				
			||||||
        <div class="card-deck">
 | 
					        <div class="card-deck">
 | 
				
			||||||
            <div id="add-music-url" class="card">
 | 
					            <div id="add-music-url" class="card">
 | 
				
			||||||
                <div class="card-header">
 | 
					                <div class="card-header">
 | 
				
			||||||
                    <h5 class="card-title">Add URL</h5>
 | 
					                    <h3 class="card-title">Add URL</h3>
 | 
				
			||||||
                </div>
 | 
					                </div>
 | 
				
			||||||
                <div class="card-body">
 | 
					                <div class="card-body">
 | 
				
			||||||
                    <label for="music-url-input">Add Youtube or Soundcloud URL</label>
 | 
					                    <label for="music-url-input">Add Youtube or Soundcloud URL</label>
 | 
				
			||||||
@@ -367,13 +399,13 @@
 | 
				
			|||||||
                        <input class="form-control" type="text" id="music-url-input" placeholder="URL...">
 | 
					                        <input class="form-control" type="text" id="music-url-input" placeholder="URL...">
 | 
				
			||||||
                    </div>
 | 
					                    </div>
 | 
				
			||||||
                    <button type="submit" class="btn btn-primary">
 | 
					                    <button type="submit" class="btn btn-primary">
 | 
				
			||||||
                      Add URL
 | 
					                        Add URL
 | 
				
			||||||
                    </button>
 | 
					                    </button>
 | 
				
			||||||
                </div>
 | 
					                </div>
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
            <div id="add-radio-url" class="card">
 | 
					            <div id="add-radio-url" class="card">
 | 
				
			||||||
                <div class="card-header">
 | 
					                <div class="card-header">
 | 
				
			||||||
                    <h5 class="card-title">Add Radio</h5>
 | 
					                    <h3 class="card-title">Add Radio</h3>
 | 
				
			||||||
                </div>
 | 
					                </div>
 | 
				
			||||||
                <div class="card-body">
 | 
					                <div class="card-body">
 | 
				
			||||||
                    <label for="radio-url-input">Add Radio URL</label>
 | 
					                    <label for="radio-url-input">Add Radio URL</label>
 | 
				
			||||||
@@ -381,7 +413,7 @@
 | 
				
			|||||||
                        <input id="radio-url-input" class="form-control" type="text" placeholder="Radio Address...">
 | 
					                        <input id="radio-url-input" class="form-control" type="text" placeholder="Radio Address...">
 | 
				
			||||||
                    </div>
 | 
					                    </div>
 | 
				
			||||||
                    <button type="submit" class="btn btn-primary">
 | 
					                    <button type="submit" class="btn btn-primary">
 | 
				
			||||||
                      Add Radio
 | 
					                        Add Radio
 | 
				
			||||||
                    </button>
 | 
					                    </button>
 | 
				
			||||||
                </div>
 | 
					                </div>
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
@@ -405,17 +437,20 @@
 | 
				
			|||||||
            </button>
 | 
					            </button>
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
        <div class="toast-body" id="playerContainer">
 | 
					        <div class="toast-body" id="playerContainer">
 | 
				
			||||||
            <img id="playerArtworkIdle" src="static/image/empty_box.svg" />
 | 
					            <img id="playerArtworkIdle" src="static/image/empty_box.svg" alt="A drawing of an empty box." />
 | 
				
			||||||
            <img id="playerArtwork" src="static/image/unknown-album.png" style="display: none;" />
 | 
					            <img id="playerArtwork" src="static/image/unknown-album.png" style="display: none;"
 | 
				
			||||||
 | 
					                alt="A black square with two eight notes beamed together." />
 | 
				
			||||||
            <div id="playerInfo">
 | 
					            <div id="playerInfo">
 | 
				
			||||||
                <div id="playerActionBox">
 | 
					                <div id="playerActionBox">
 | 
				
			||||||
                    <button id="playerPlayBtn" class="btn btn-primary btn-sm btn-space" style="display: none">
 | 
					                    <button id="playerPlayBtn" class="btn btn-primary btn-sm btn-space" style="display: none"
 | 
				
			||||||
 | 
					                        aria-label="Play">
 | 
				
			||||||
                        <i class="fas fa-play"></i>
 | 
					                        <i class="fas fa-play"></i>
 | 
				
			||||||
                    </button>
 | 
					                    </button>
 | 
				
			||||||
                    <button id="playerPauseBtn" class="btn btn-primary btn-sm btn-space" style="display: none">
 | 
					                    <button id="playerPauseBtn" class="btn btn-primary btn-sm btn-space" style="display: none"
 | 
				
			||||||
 | 
					                        aria-label="Pause">
 | 
				
			||||||
                        <i class="fas fa-pause"></i>
 | 
					                        <i class="fas fa-pause"></i>
 | 
				
			||||||
                    </button>
 | 
					                    </button>
 | 
				
			||||||
                    <button id="playerSkipBtn" class="btn btn-primary btn-sm">
 | 
					                    <button id="playerSkipBtn" class="btn btn-primary btn-sm" aria-label="Skip to next track">
 | 
				
			||||||
                        <i class="fas fa-fast-forward"></i>
 | 
					                        <i class="fas fa-fast-forward"></i>
 | 
				
			||||||
                    </button>
 | 
					                    </button>
 | 
				
			||||||
                </div>
 | 
					                </div>
 | 
				
			||||||
@@ -425,7 +460,9 @@
 | 
				
			|||||||
                </div>
 | 
					                </div>
 | 
				
			||||||
                <span id="playerArtist">Artist</span>
 | 
					                <span id="playerArtist">Artist</span>
 | 
				
			||||||
                <div id="playerBarBox" class="progress">
 | 
					                <div id="playerBarBox" class="progress">
 | 
				
			||||||
                    <div id="playerBar" class="progress-bar pr-2" role="progressbar" aria-valuenow="50" aria-valuemin="0" aria-valuemax="100" style="width: 100%; text-align: right; transform: translateX(-100%);"></div>
 | 
					                    <div id="playerBar" class="progress-bar pr-2" role="progressbar" aria-valuenow="50"
 | 
				
			||||||
 | 
					                        aria-valuemin="0" aria-valuemax="100"
 | 
				
			||||||
 | 
					                        style="width: 100%; text-align: right; transform: translateX(-100%);"></div>
 | 
				
			||||||
                </div>
 | 
					                </div>
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
@@ -434,33 +471,35 @@
 | 
				
			|||||||
    <div id="footer" style="height:50px; width: 100%; margin-top: 100px;"></div>
 | 
					    <div id="footer" style="height:50px; width: 100%; margin-top: 100px;"></div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <form id="download-form" action="download" method="GET" target="_blank">
 | 
					    <form id="download-form" action="download" method="GET" target="_blank">
 | 
				
			||||||
        <input hidden type="text" name="id" value="">
 | 
					        <input hidden type="hidden" name="id" value="">
 | 
				
			||||||
        <input hidden type="text" name="type" value="">
 | 
					        <input hidden type="hidden" name="type" value="">
 | 
				
			||||||
        <input hidden type="text" name="dir" value="">
 | 
					        <input hidden type="hidden" name="dir" value="">
 | 
				
			||||||
        <input hidden type="text" name="tags" value="">
 | 
					        <input hidden type="hidden" name="tags" value="">
 | 
				
			||||||
        <input hidden type="text" name="keywords" value="">
 | 
					        <input hidden type="hidden" name="keywords" value="">
 | 
				
			||||||
    </form>
 | 
					    </form>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <!-- Add tags modal -->
 | 
					    <!-- Add tags modal -->
 | 
				
			||||||
    <div class="modal fade" id="addTagModal" tabindex="-1" role="dialog" aria-hidden="true">
 | 
					    <div class="modal fade" id="addTagModal" tabindex="-1" role="dialog">
 | 
				
			||||||
        <div class="modal-dialog" role="document">
 | 
					        <div class="modal-dialog" role="document">
 | 
				
			||||||
            <div class="modal-content">
 | 
					            <div class="modal-content">
 | 
				
			||||||
                <div class="modal-header">
 | 
					                <div class="modal-header">
 | 
				
			||||||
                    <h5 class="modal-title" id="addTagModalTitle">Edit tags for ?</h5>
 | 
					                    <h3 class="modal-title" id="addTagModalTitle">Edit tags for ?</h3>
 | 
				
			||||||
                    <button type="button" class="close" data-dismiss="modal" aria-label="Close">
 | 
					                    <button type="button" class="close" data-dismiss="modal" aria-label="Close">
 | 
				
			||||||
                        <span aria-hidden="true">×</span>
 | 
					                        <span aria-hidden="true">×</span>
 | 
				
			||||||
                    </button>
 | 
					                    </button>
 | 
				
			||||||
                </div>
 | 
					                </div>
 | 
				
			||||||
                <div id="addTagModalBody" class="modal-body">
 | 
					                <div id="addTagModalBody" class="modal-body">
 | 
				
			||||||
                    <input hidden type="text" id="addTagModalItemId" name="id" value="">
 | 
					                    <input hidden type="hidden" id="addTagModalItemId" name="id" value="">
 | 
				
			||||||
                    <div class="modal-tag" style="display: none; width: 100%;">
 | 
					                    <div class="modal-tag" style="display: none; width: 100%;">
 | 
				
			||||||
                        <span class="modal-tag-text tag-space badge badge-pill badge-dark">Tag</span>
 | 
					                        <span class="modal-tag-text tag-space badge badge-pill badge-dark">Tag</span>
 | 
				
			||||||
                        <a class="modal-tag-remove tag-click small"><i class="fas fa-times-circle btn-outline-danger"></i></a>
 | 
					                        <a class="modal-tag-remove tag-click small"><i
 | 
				
			||||||
 | 
					                                class="fas fa-times-circle btn-outline-danger"></i></a>
 | 
				
			||||||
                    </div>
 | 
					                    </div>
 | 
				
			||||||
                    <div id="addTagModalTags" style="margin-left: 5px; margin-bottom: 10px;">
 | 
					                    <div id="addTagModalTags" style="margin-left: 5px; margin-bottom: 10px;">
 | 
				
			||||||
                    </div>
 | 
					                    </div>
 | 
				
			||||||
                    <div class="input-group">
 | 
					                    <div class="input-group">
 | 
				
			||||||
                        <input class="form-control form-control-sm btn-space" type="text" id="addTagModalInput" placeholder="tag1,tag2,...">
 | 
					                        <input class="form-control form-control-sm btn-space" type="text" id="addTagModalInput"
 | 
				
			||||||
 | 
					                            placeholder="tag1,tag2,..." aria-label="Tags to add">
 | 
				
			||||||
                        <button id="addTagModalAddBtn" type="button" class="btn btn-primary btn-sm">
 | 
					                        <button id="addTagModalAddBtn" type="button" class="btn btn-primary btn-sm">
 | 
				
			||||||
                            <i class="fas fa-plus" aria-hidden="true"></i>
 | 
					                            <i class="fas fa-plus" aria-hidden="true"></i>
 | 
				
			||||||
                            Add
 | 
					                            Add
 | 
				
			||||||
@@ -469,18 +508,19 @@
 | 
				
			|||||||
                </div>
 | 
					                </div>
 | 
				
			||||||
                <div class="modal-footer">
 | 
					                <div class="modal-footer">
 | 
				
			||||||
                    <button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
 | 
					                    <button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
 | 
				
			||||||
                    <button id="addTagModalSubmit" type="button" class="btn btn-success" data-dismiss="modal">Edit!</button>
 | 
					                    <button id="addTagModalSubmit" type="button" class="btn btn-success"
 | 
				
			||||||
 | 
					                        data-dismiss="modal">Edit!</button>
 | 
				
			||||||
                </div>
 | 
					                </div>
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <!-- Upload files modal -->
 | 
					    <!-- Upload files modal -->
 | 
				
			||||||
    <div class="modal fade" id="uploadModal" tabindex="-1" role="dialog" aria-hidden="true">
 | 
					    <div class="modal fade" id="uploadModal" tabindex="-1" role="dialog">
 | 
				
			||||||
        <div class="modal-dialog" role="document">
 | 
					        <div class="modal-dialog" role="document">
 | 
				
			||||||
            <div class="modal-content">
 | 
					            <div class="modal-content">
 | 
				
			||||||
                <div class="modal-header">
 | 
					                <div class="modal-header">
 | 
				
			||||||
                    <h5 class="modal-title" id="uploadTitle"><i class="fas fa-upload mr-1"></i>Uploading files...</h5>
 | 
					                    <h3 class="modal-title" id="uploadTitle"><i class="fas fa-upload mr-1"></i>Uploading files...</h3>
 | 
				
			||||||
                </div>
 | 
					                </div>
 | 
				
			||||||
                <div id="uploadModalBody" class="modal-body">
 | 
					                <div id="uploadModalBody" class="modal-body">
 | 
				
			||||||
                    <div id="uploadSuccessAlert" class="alert alert-success" role="alert" style="display: none">
 | 
					                    <div id="uploadSuccessAlert" class="alert alert-success" role="alert" style="display: none">
 | 
				
			||||||
@@ -493,7 +533,9 @@
 | 
				
			|||||||
                            <span class="uploadItemTitle mr-3"></span>
 | 
					                            <span class="uploadItemTitle mr-3"></span>
 | 
				
			||||||
                            <span class="uploadItemError text-danger"></span>
 | 
					                            <span class="uploadItemError text-danger"></span>
 | 
				
			||||||
                            <div class="progress" style="margin-top: 5px; height: 10px;">
 | 
					                            <div class="progress" style="margin-top: 5px; height: 10px;">
 | 
				
			||||||
                                <div class="uploadProgress progress-bar pr-2" role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100" style="width: 100%; text-align: right; transform: translateX(-100%);"></div>
 | 
					                                <div class="uploadProgress progress-bar pr-2" role="progressbar" aria-valuenow="0"
 | 
				
			||||||
 | 
					                                    aria-valuemin="0" aria-valuemax="100"
 | 
				
			||||||
 | 
					                                    style="width: 100%; text-align: right; transform: translateX(-100%);"></div>
 | 
				
			||||||
                            </div>
 | 
					                            </div>
 | 
				
			||||||
                        </div>
 | 
					                        </div>
 | 
				
			||||||
                    </div>
 | 
					                    </div>
 | 
				
			||||||
@@ -501,7 +543,9 @@
 | 
				
			|||||||
                <div class="modal-footer">
 | 
					                <div class="modal-footer">
 | 
				
			||||||
                    <button type="button" id="uploadClose" class="btn btn-success" data-dismiss="modal">
 | 
					                    <button type="button" id="uploadClose" class="btn btn-success" data-dismiss="modal">
 | 
				
			||||||
                        <i class="fas fa-times mr-1"></i> Close</button>
 | 
					                        <i class="fas fa-times mr-1"></i> Close</button>
 | 
				
			||||||
                    <button type="button" id="uploadCancel" class="btn btn-danger" data-toggle="tooltip" data-html="true" title="<strong>Are you really sure?</strong> <br /> Click again to abort uploading.">
 | 
					                    <button type="button" id="uploadCancel" class="btn btn-danger" data-toggle="tooltip"
 | 
				
			||||||
 | 
					                        data-html="true"
 | 
				
			||||||
 | 
					                        title="<strong>Are you really sure?</strong> <br /> Click again to abort uploading.">
 | 
				
			||||||
                        <i class="fas fa-trash-alt mr-1" aria-hidden="true"></i> Cancel</button>
 | 
					                        <i class="fas fa-trash-alt mr-1" aria-hidden="true"></i> Cancel</button>
 | 
				
			||||||
                </div>
 | 
					                </div>
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user