Files
headphones/data/interfaces/default/album.html
Paul ab70020574 Update album.html
More modernisations.
2025-07-06 11:43:51 +01:00

481 lines
20 KiB
HTML

<%inherit file="base.html" />
<%!
from headphones import db, helpers
myDB = db.DBConnection()
%>
<%def name="headerIncludes()">
<div id="subhead_container">
<div id="back_to_previous_link">
<a href="artistPage?ArtistID=${album['ArtistID']}" class="back">&laquo; Back to ${album['ArtistName']}</a>
</div>
<div id="subhead_menu">
<a id="menu_link_delete" href="deleteAlbum?AlbumID=${album['AlbumID']}&ArtistID=${album['ArtistID']}"><i class="fa fa-trash-o"></i> Delete Album</a>
%if album['Status'] == 'Skipped' or album['Status'] == 'Ignored':
<a id="menu_link_wanted" href="#" class="album-action" data-action="queueAlbum" data-album-id="${album['AlbumID']}" data-artist-id="${album['ArtistID']}" data-new="False" data-success-msg="'${album['AlbumTitle']}' added to queue"><i class="fa fa-heart"></i> Mark Album as Wanted</a>
%elif album['Status'] == 'Wanted':
<a id="menu_link_check" href="#" class="album-action" data-action="queueAlbum" data-album-id="${album['AlbumID']}" data-artist-id="${album['ArtistID']}" data-new="True" data-success-msg="Forced checking successful"><i class="fa fa-search"></i> Force Check</a>
<a id="menu_link_skipped" href="#" class="album-action" data-action="unqueueAlbum" data-album-id="${album['AlbumID']}" data-artist-id="${album['ArtistID']}" data-success-msg="'${album['AlbumTitle']}' marked as Skipped"><i class="fa fa-step-forward"></i> Mark Album as Skipped</a>
%else:
<a id="menu_link_retry" href="#" class="album-action" data-action="queueAlbum" data-album-id="${album['AlbumID']}" data-artist-id="${album['ArtistID']}" data-new="False" data-success-msg="Retrying the same version of '${album['AlbumTitle']}'"><i class="fa fa-refresh"></i> Retry Download</a>
<a id="menu_link_new" href="#" class="album-action" data-action="queueAlbum" data-album-id="${album['AlbumID']}" data-artist-id="${album['ArtistID']}" data-new="True" data-success-msg="Looking for a new version of '${album['AlbumTitle']}'"><i class="fa fa-download"></i> Try New Version</a>
%endif
<a class="menu_link_edit dialog-trigger" id="album_chooser" href="#" data-dialog-id="dialog"><i class="fa fa-pencil"></i> Choose Alternate Release</a>
<div id="dialog" title="Choose an Alternate Release" style="display:none" class="configtable">
<div class="links">
<%
alternate_albums = myDB.select("SELECT * from allalbums WHERE AlbumID=? ORDER BY ReleaseDate ASC", [album['AlbumID']])
%>
%if not alternate_albums:
<p>No alternate releases found. Try refreshing the artist (if the artist is being refreshed, please wait until it's finished)</p>
<h2><a id="refresh_artist_btn" href="#" class="album-action" data-action="refreshArtist" data-artist-id="${album['ArtistID']}" data-success-msg="'${album['ArtistName']}' is being refreshed">Refresh Artist</a></h2>
%else:
%for alternate_album in alternate_albums:
<%
track_count = len(myDB.select("SELECT * from alltracks WHERE ReleaseID=?", [alternate_album['ReleaseID']]))
mb_link = "http://musicbrainz.org/release/" + alternate_album['ReleaseID']
have_track_count = len(myDB.select("SELECT * from alltracks WHERE ReleaseID=? AND Location IS NOT NULL", [alternate_album['ReleaseID']]))
if alternate_album['AlbumID'] == alternate_album['ReleaseID']:
alternate_album_name = "Headphones Default Release (" + str(alternate_album['ReleaseDate']) + ") [" + str(have_track_count) + "/" + str(track_count) + " tracks]"
else:
alternate_album_name = alternate_album['AlbumTitle'] + " (" + alternate_album['ReleaseCountry'] + ", " + str(alternate_album['ReleaseDate']) + ", " + alternate_album['ReleaseFormat'] + ") [" + str(have_track_count) + "/" + str(track_count) + " tracks]"
%>
<a href="#" class="album-action alternate-release-switch" data-action="switchAlbum" data-album-id="${album['AlbumID']}" data-release-id="${alternate_album['ReleaseID']}" data-success-msg="Switched release to: ${alternate_album_name}">${alternate_album_name}</a><a href="${mb_link}" target="_blank" class="external-link">MB</a><br>
%endfor
%endif
</div>
</div>
<a class="menu_link_edit dialog-trigger" id="edit_search_term" href="#" data-dialog-id="dialog2"><i class="fa fa-pencil"></i> Edit Search Term</a>
<div id="dialog2" title="Enter your own search term for this album" style="display:none" class="configtable">
<form action="editSearchTerm" method="GET" id="editSearchTermForm">
<input type="hidden" name="AlbumID" value="${album['AlbumID']}">
<div class="row">
<%
if not album['SearchTerm']:
search_term = ""
else:
search_term = album['SearchTerm']
%>
<input type="text" value="${search_term}" name="SearchTerm" size="40" />
</div>
<button type="submit" class="album-action-submit" data-action="editSearchTerm" data-success-msg="Search term updated">Save changes</button>
</form>
</div>
<a class="menu_link_edit dialog-trigger" id="choose_specific_download" href="#" data-dialog-id="choose_specific_download_dialog" data-action="getAvailableDownloads"><i class="fa fa-search"></i> Choose Specific Download</a>
<div id="choose_specific_download_dialog" title="Choose a specific download for this album" style="display:none" class="configtable">
<table class="display" id="downloads_table">
<thead>
<tr>
<th id="title">Title</th>
<th id="size">Size</th>
<th id="provider">Provider</th>
<th id="kind">Kind</th>
<th id="matches" title="Matches quality and/or seed settings.">Matches</th>
</tr>
</thead>
<tbody id="downloads_table_body">
</tbody>
</table>
</div>
</div>
</div>
</%def>
<%def name="body()">
<div class="table_wrapper">
<div id="albumheader" class="clearfix">
<div id="albumImg">
<img alt="${album['AlbumTitle']} album art" class="albumArt" src="artwork/album/${album['AlbumID']}">
</div>
<h1 id="albumname">
<a href="http://musicbrainz.org/release-group/${album['AlbumID']}" id="albumnamelink">${album['AlbumTitle']}</a>
</h1>
<h2 id="artistname">
<a href="http://musicbrainz.org/artist/${album['ArtistID']}" id="artistnamelink">${album['ArtistName']}</a>
</h2>
<%
totalduration = myDB.action("SELECT SUM(TrackDuration) FROM tracks WHERE AlbumID=?", [album['AlbumID']]).fetchone()[0]
totaltracks = len(myDB.select("SELECT TrackTitle from tracks WHERE AlbumID=?", [album['AlbumID']]))
try:
albumduration = helpers.convert_milliseconds(totalduration)
except:
albumduration = 'n/a'
%>
<div class="albuminfo">
<div id="albumInfo"></div>
<ul>
<li>Tracks: <span>${totaltracks}</span></li>
<li>Duration: <span>${albumduration}</span></li>
</ul>
</div>
</div>
<div id="track_wrapper">
<table class="display" id="track_table">
<thead>
<tr>
<th id="number">#</th>
<th id="name">Track Title</th>
<th id="duration">Duration</th>
<th id="location">Local File</th>
<th id="bitrate">Bit Rate</th>
<th id="format">Format</th>
</tr>
</thead>
<tbody>
%for track in tracks:
<%
if track['Location']:
grade = 'A'
location = track['Location']
else:
grade = 'X'
location = ''
if track['BitRate']:
bitrate = str(track['BitRate']/1000) + ' kbps'
else:
bitrate = ''
try:
trackduration = helpers.convert_milliseconds(track['TrackDuration'])
except:
trackduration = 'n/a'
if not track['Format']:
format = ''
else:
format = track['Format']
%>
<tr class="grade${grade}">
<td id="number">${track['TrackNumber']}</td>
<td id="name">${track['TrackTitle']}</td>
<td id="duration">${trackduration}</td>
<td id="location">${location}</td>
<td id="bitrate">${bitrate}</td>
<td id="format">${format}</td>
</tr>
%endfor
<%
unmatched = myDB.select('SELECT * from have WHERE ArtistName LIKE ? AND AlbumTitle LIKE ? AND Matched = "Failed" ORDER BY CAST(TrackNumber AS INTEGER)', [album['ArtistName'], album['AlbumTitle']])
%>
%if unmatched:
%for track in unmatched:
<%
duration = helpers.convert_seconds(float(track['TrackLength']))
%>
<tr class="gradeC">
<td id="number">${track['TrackNumber']}</td>
<td id="name">${track['TrackTitle']}</td>
<td id="duration">${duration}</td>
<td id="location">${track['Location']}</td>
<td id="bitrate">${int(track['BitRate'])/1000} kbps</td>
<td id="format">${track['Format']}</td>
</tr>
%endfor
%endif
</tbody>
</table>
</div>
</div>
</%def>
<%def name="headIncludes()">
${parent.headIncludes()} <%-- Ensure parent head includes are kept --%>
<link rel="stylesheet" href="interfaces/default/css/data_table.css">
</%def>
<%def name="javascriptIncludes()">
${parent.javascriptIncludes()} <%-- Ensure parent javascript includes are kept --%>
<script src="js/libs/jquery.dataTables.min.js"></script>
<script>
// Define a global object for page-specific functions to avoid polluting global scope directly
var AlbumPage = AlbumPage || {};
AlbumPage.search_results = []; // Moved to page-specific scope
AlbumPage.getAlbumInfo = function() {
var id = "${album['AlbumID']}";
var elem = $("#albumInfo");
// Assuming getInfo is defined in common.js
if (typeof getInfo === 'function') {
getInfo(elem,id,'album');
} else {
console.warn("getInfo function not found. Album info might not be loaded.");
}
};
AlbumPage.initDialogs = function() {
// General handler for opening dialogs based on data-dialog-id
$(document).on('click', '.dialog-trigger', function(e) {
e.preventDefault();
var dialogId = $(this).data('dialog-id');
var $dialog = $('#' + dialogId);
if ($dialog.length) {
// Specific logic for choose_specific_download_dialog before opening
if (dialogId === 'choose_specific_download_dialog') {
AlbumPage.getAvailableDownloads();
} else {
$dialog.dialog({
width: 500,
maxHeight: 500,
// Add close event to clear dynamic content if any
close: function() {
if (dialogId === 'downloads_table_body') {
$('#downloads_table_body').empty(); // Clear table content on close
// Re-initialize DataTable if needed on next open, or destroy it here
}
}
});
}
}
});
// Specific handler for refreshing artist from alternate releases dialog
$(document).on('click', '#refresh_artist_btn', function(e) {
e.preventDefault();
var $this = $(this);
// Assuming doAjaxCall is defined in common.js
if (typeof doAjaxCall === 'function') {
doAjaxCall($this.data('action') + '?ArtistID=' + $this.data('artist-id'), $this, true, $this.data('success-msg'));
}
$('#dialog').dialog("close"); // Close dialog after action
});
};
AlbumPage.initDataTables = function() {
// Initialize track_table
$('#track_table').DataTable({ // Use .DataTable() for new versions of DataTables
"ordering": false, // aaSorting -> ordering
"searching": false, // bFilter -> searching
"info": false, // bInfo -> info
"paging": false, // bPaginate -> paging
"destroy": true // bDestroy -> destroy
});
};
AlbumPage.getAvailableDownloads = function() {
AlbumPage.ShowSpinner();
var albumId = "${album['AlbumID']}";
$.getJSON("choose_specific_download?AlbumID=" + albumId, function(data) {
AlbumPage.loader.remove(); // Assuming loader is attached to AlbumPage scope now
AlbumPage.feedback.fadeOut(); // Assuming feedback is attached to AlbumPage scope now
AlbumPage.search_results = data; // Store results
// Clear previous content
$('#downloads_table_body').empty();
for( var i = 0, len = data.length; i < len; i++ ) {
$('#downloads_table_body').append(
'<tr>' +
'<td id="title"><a href="#" class="download-specific-release-link" data-index="' + i + '">' + data[i].title + '</a></td>' +
'<td id="size"><span title="'+data[i].size+'"></span>' + (data[i].size / (1024*1024)).toFixed(2) + ' MB</td>' +
'<td id="provider">' + data[i].provider + '</td>' +
'<td id="kind">' + data[i].kind + '</td>' +
'<td id="matches">' + data[i].matches + '</td>' +
'</tr>'
);
}
// Destroy and re-initialize the DataTable for downloads
if ($.fn.DataTable.isDataTable('#downloads_table')) {
$('#downloads_table').DataTable().destroy();
}
$('#downloads_table').DataTable({
"columnDefs": [ // aoColumns -> columnDefs
{ "orderable": false, "targets": [0, 2, 3] }, // Disable ordering for Title, Provider, Kind
{ "type": "title-numeric", "targets": 1 }, // sType -> type
{ "type": "string", "targets": 4 }
],
"order": [[ 4, 'desc']], // aaSorting -> order
"searching": false,
"info": false,
"paging": false,
"destroy": true // Important for re-initialization
});
$("#choose_specific_download_dialog").dialog({
width: "80%",
maxHeight: 500,
modal: true // Added modal to make it more common practice
});
});
};
AlbumPage.downloadSpecificRelease = function(i){
var release = AlbumPage.search_results[i]; // Get from stored results
var url = "download_specific_release?AlbumID=${album['AlbumID']}" +
"&title=" + encodeURIComponent(release.title) +
"&size=" + release.size +
"&url=" + encodeURIComponent(release.url) +
"&provider=" + encodeURIComponent(release.provider) +
"&kind=" + encodeURIComponent(release.kind);
AlbumPage.ShowSpinner();
$.getJSON(url, function(data) {
AlbumPage.loader.remove();
AlbumPage.feedback.fadeOut();
// Assuming refreshSubmenu is defined globally or in common.js
if (typeof refreshSubmenu === 'function') {
refreshSubmenu();
}
$("#choose_specific_download_dialog").dialog("close");
});
};
AlbumPage.ShowSpinner = function() {
AlbumPage.feedback = $("#ajaxMsg"); // Assign to AlbumPage scope
var update = $("#updatebar");
if ( update.is(":visible") ) {
var height = update.height() + 35;
AlbumPage.feedback.css("bottom",height + "px");
} else {
AlbumPage.feedback.removeAttr("style");
}
AlbumPage.loader = $("<i class='fa fa-refresh fa-spin'></i>"); // Assign to AlbumPage scope
AlbumPage.feedback.prepend(AlbumPage.loader);
AlbumPage.feedback.fadeIn();
};
AlbumPage.loadingMessage = false;
AlbumPage.spinner_active = false;
AlbumPage.loadingtext_active = false;
AlbumPage.refreshInterval = null; // Initialize as null
AlbumPage.wasLoading = false;
AlbumPage.x = 0;
AlbumPage.checkAlbumStatus = function() {
$.getJSON("getAlbumjson?AlbumID=${album['AlbumID']}", function(data) {
if (data['Status'] === "Loading"){
AlbumPage.wasLoading = true;
$('#albumnamelink').text(data["AlbumTitle"]);
$('#artistnamelink').text(data["ArtistName"]);
if (AlbumPage.loadingMessage === false){
$("#ajaxMsg").after( "<div id='ajaxMsg2' class='ajaxMsg'></div>" );
// Assuming showArtistMsg is defined globally or in common.js
if (typeof showArtistMsg === 'function') {
showArtistMsg("Getting album information");
}
AlbumPage.loadingMessage = true;
}
if (AlbumPage.spinner_active === false){
$('#albumname').prepend('<i class="fa fa-refresh fa-spin" id="albumnamespinner"></i>');
AlbumPage.spinner_active = true;
}
if (AlbumPage.loadingtext_active === false){
$('#albumname').append('<h3 id="loadingtext"><i>(Album information is currently being loaded)</i></h3>');
AlbumPage.loadingtext_active = true;
}
} else {
AlbumPage.x++;
if (AlbumPage.x === 10 || AlbumPage.wasLoading || $("#artistname").text().trim() === "Loading") { // Combined conditions
if (AlbumPage.refreshInterval) { // Clear only if interval is set
clearInterval(AlbumPage.refreshInterval);
}
location.reload(); // Reload the page to show updated status
}
$('#albumnamespinner').remove();
$('#loadingtext').remove();
$('#ajaxMsg2').remove();
AlbumPage.spinner_active = false;
AlbumPage.loadingtext_active = false;
AlbumPage.loadingMessage = false;
}
});
};
// jQuery DataTables custom sorting
jQuery.extend( jQuery.fn.dataTableExt.oSort, {
"title-numeric-pre": function ( a ) {
// Ensure it handles cases where title attribute might be missing or different
var match = a.match(/title="*(-?[0-9\.]+)/);
return match ? parseFloat( match[1] ) : -Infinity; // Return a safe default
},
"title-numeric-asc": function ( a, b ) {
return ((a < b) ? -1 : ((a > b) ? 1 : 0));
},
"title-numeric-desc": function ( a, b ) {
return ((a < b) ? 1 : ((a > b) ? -1 : 0));
}
});
$(document).ready(function() {
AlbumPage.getAlbumInfo();
AlbumPage.initDialogs(); // Initialize dialog triggers
AlbumPage.initDataTables(); // Initialize DataTables
// Do not use initActions() here unless it's strictly needed and modernized itself.
// setTimeout for fancybox is likely not needed if elements are ready or use delegated events.
// If fancybox is for dynamic content, initialize it after content is loaded.
// initFancybox(); // Re-evaluate if this is still needed or how it's used.
// Event handler for album actions
// Delegated to #subhead_menu as these links are within it
$('#subhead_menu').on('click', '.album-action', function(e) {
e.preventDefault();
var $this = $(this);
var action = $this.data('action');
var albumId = $this.data('album-id');
var artistId = $this.data('artist-id');
var newStatus = $this.data('new'); // Boolean 'true' or 'false'
var releaseId = $this.data('release-id');
var successMsg = $this.data('success-msg');
var url = action + '?AlbumID=' + albumId;
if (artistId) url += '&ArtistID=' + artistId;
if (newStatus !== undefined) url += '&new=' + newStatus;
if (releaseId) url += '&ReleaseID=' + releaseId;
// Assuming doAjaxCall is defined globally or in common.js
if (typeof doAjaxCall === 'function') {
doAjaxCall(url, $this, true, successMsg);
} else {
console.error("doAjaxCall function is not defined!");
}
});
// Event handler for specific download links within the dialog
// Delegated to #downloads_table_body as these links are dynamically added
$('#downloads_table_body').on('click', '.download-specific-release-link', function(e) {
e.preventDefault();
var index = $(this).data('index');
AlbumPage.downloadSpecificRelease(index);
});
// Event handler for form submission within dialogs
// Delegated to the specific form's ID
$('#editSearchTermForm').on('submit', function(e) {
e.preventDefault(); // Prevent default form submission
var $this = $(this);
var action = $this.attr('action'); // Get action from form
var formData = $this.serialize(); // Serialize form data
var successMsg = $this.find('.album-action-submit').data('success-msg');
// Assuming doAjaxCall is defined globally or in common.js
if (typeof doAjaxCall === 'function') {
// Pass form data instead of element, specify 'form' type if common.js handles it
doAjaxCall(action + '?' + formData, $this.find('.album-action-submit'), true, successMsg);
}
$('#dialog2').dialog("close"); // Close dialog after submission
});
// Start checking album status periodically
AlbumPage.checkAlbumStatus();
AlbumPage.refreshInterval = setInterval(function(){
AlbumPage.checkAlbumStatus();
}, 3000);
});
</script>
</%def>