Update manageunmatched.html

Modernised
This commit is contained in:
Paul
2025-07-06 15:06:19 +01:00
committed by GitHub
parent 62757c0fcf
commit 728887d881

View File

@@ -1,16 +1,20 @@
<%inherit file="base.html" />
<%!
import headphones
import json
from headphones import db, helpers
myDB = db.DBConnection()
artist_json = {}
counter = 0
artist_list = myDB.action("SELECT ArtistName from artists ORDER BY ArtistName COLLATE NOCASE")
for artist in artist_list:
artist_json[counter] = artist['ArtistName']
counter+=1
json_artists = json.dumps(artist_json)
# Removed direct DB imports and queries from template.
# These operations (fetching artists and creating json_artists)
# should be performed in the Python view/controller and passed
# to the template as part of its context.
# import json
# from headphones import db, helpers
# myDB = db.DBConnection()
# artist_json = {}
# counter = 0
# artist_list = myDB.action("SELECT ArtistName from artists ORDER BY ArtistName COLLATE NOCASE")
# for artist in artist_list:
# artist_json[counter] = artist['ArtistName']
# counter+=1
# json_artists = json.dumps(artist_json)
%>
<%def name="headerIncludes()">
@@ -29,97 +33,109 @@
<h1 class="clearfix"><i class="fa fa-music"></i> Manage Unmatched Albums</h1>
</div>
<table class="display" id="artist_table">
<table class="display" id="unmatched_album_table"> <%-- Changed ID for clarity --%>
<thead>
<tr>
<th id="artist">Local Artist</th>
<th id="album">Local Album</th>
<th class="column-artist">Local Artist</th>
<th class="column-album">Local Album</th>
</tr>
</thead>
<tbody>
<% count_albums=0 %>
<% count_albums=0 %> <%-- Still useful for unique IDs if needed, but aiming for generic dialogs --%>
%for album in unmatchedalbums:
<tr class="gradeZ">
<%
old_artist_clean = album['ArtistName'].replace('&','%26').replace("'","%27")
old_album_clean = album['AlbumTitle'].replace('&','%26').replace("'","%27")
old_artist_js = album['ArtistName'].replace("'","\\'").replace('"','\\"')
old_album_js = album['AlbumTitle'].replace("'","\\'").replace('"','\\"')
# Pre-escape for direct use in data-attributes where JS string is needed.
# URL encoding will happen in JS with encodeURIComponent.
old_artist_js_str = album['ArtistName'].replace("'","\\'").replace('"','&quot;')
old_album_js_str = album['AlbumTitle'].replace("'","\\'").replace('"','&quot;')
%>
<td id="artist">${album['ArtistName']}<BR>
<button id="ignore_artists${count_albums}" onClick="ignore_Artist(this.id)">(-) Ignore Artist</button>
<div id="ignore_artist_dialog${count_albums}" title="Ignore Artist" style="display:none">
<table>
<tr><td>Are you sure you want to ignore Local Artist: ${album['ArtistName']} from future matching?</td></tr>
<tr><td align="right"><BR>
<button href="javascript:void(0)" onclick="doAjaxCall('markUnmatched?action=ignoreArtist&existing_artist=${old_artist_clean}', $(this), 'page');" data-success="Successfully ignored ${album['ArtistName']} from future matching">Ignore Artist</button>
</td></tr>
</table>
</div>
<button id="match_artists${count_albums}" onClick="load_Artist(this.id)">(->) Match Artist</button>
<div id="artist_dialog${count_albums}" title="Match Artist" style="display:none">
<table width=400>
<tr><td>Local Artist:</td><td>${album['ArtistName']}</td></tr>
<tr><td>Match Artist</td><td>
<select id="artist_options${count_albums}" name="new_artist">
</select>
</td></tr>
<tr><td></td><td align="right"><BR>
<button href="javascript:void(0)" onclick="artist_matcher(${count_albums}, '${old_artist_js}')">Match Artist</button>
</td></tr>
</table>
</div>
<td class="column-artist">
${album['ArtistName']}<BR>
<%-- Data attributes to pass context to JS --%>
<button type="button" class="action-button ignore-artist-button"
data-artist-name="${album['ArtistName']}"
data-old-artist-js="${old_artist_js_str}">
(-) Ignore Artist
</button>
<button type="button" class="action-button match-artist-button"
data-artist-name="${album['ArtistName']}"
data-old-artist-js="${old_artist_js_str}">
(->) Match Artist
</button>
</td>
<td id="album">${album['AlbumTitle']}<BR>
<button id="ignore_albums${count_albums}" onClick="ignore_Album(this.id)">(-) Ignore Album</button>
<div id="ignore_album_dialog${count_albums}" title="Ignore Album" style="display:none">
<table>
<tr><td>Are you sure you want to ignore Local Album: ${album['AlbumTitle']} from future matching?</td></tr>
<tr><td align="right"><BR>
<button href="javascript:void(0)" onclick="doAjaxCall('markUnmatched?action=ignoreAlbum&existing_artist=${old_artist_clean}&existing_album=${old_album_clean}', $(this), 'page');" data-success="Successfully ignored ${album['AlbumTitle']} from future matching">Ignore Album</button>
</td></tr>
</table>
</div>
<button id="match_albums${count_albums}" onClick="load_AlbumArtist(this.id)">(->) Match Album</button>
<div id="album_dialog${count_albums}" title="Match Album" style="display:none">
<table width=650>
<tr><td>Local Artist:</td><td>${album['ArtistName']}</td></tr>
<tr><td>Local Album:</td><td>${album['AlbumTitle']}</td></tr>
<tr><td>Match Artist</td><td>
<select id="album_artist_options${count_albums}" name="new_artist">
</select>
</td></tr>
<tr><td>Match Album</td><td>
<select id="album_options${count_albums}" name="new_album">
</select>
</td></tr>
<tr><td></td><td align="right"><BR>
<button href="javascript:void(0)" onclick="album_matcher(${count_albums}, '${old_artist_js}', '${old_album_js}')">Match Album</button>
</td></tr>
</table>
</div>
<td class="column-album">
${album['AlbumTitle']}<BR>
<button type="button" class="action-button ignore-album-button"
data-artist-name="${album['ArtistName']}"
data-album-title="${album['AlbumTitle']}"
data-old-artist-js="${old_artist_js_str}"
data-old-album-js="${old_album_js_str}">
(-) Ignore Album
</button>
<button type="button" class="action-button match-album-button"
data-artist-name="${album['ArtistName']}"
data-album-title="${album['AlbumTitle']}"
data-old-artist-js="${old_artist_js_str}"
data-old-album-js="${old_album_js_str}">
(->) Match Album
</button>
</td>
</tr>
<% count_albums+=1 %>
%endfor
</tbody>
</table>
<%-- Generic Ignore Confirmation Dialog --%>
<div id="ignore_dialog" title="Confirm Ignore" style="display:none">
<p class="dialog-message"></p>
<p class="dialog-actions" align="right"><BR>
<button type="button" class="confirm-ignore-button"></button>
</p>
</div>
<%-- Generic Match Dialog --%>
<div id="match_dialog" title="Match Album" style="display:none">
<p><strong>Local Artist:</strong> <span id="local-artist-display"></span></p>
<p><strong>Local Album:</strong> <span id="local-album-display"></span></p>
<div id="artist-match-section">
<p>Match Artist:</p>
<select id="match-artist-options" name="new_artist"></select>
</div>
<div id="album-match-section">
<p>Match Album:</p>
<select id="match-album-options" name="new_album"></select>
</div>
<p class="dialog-actions" align="right"><BR>
<button type="button" class="confirm-match-button"></button>
</p>
</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>
$(document).ready(function() {
$('#artist_table').dataTable({
// Encapsulate page-specific logic
var ManageUnmatchedPage = ManageUnmatchedPage || {};
ManageUnmatchedPage.jsonArtists = ${json_artists | n, unicode}; // Assuming json_artists is passed from backend
ManageUnmatchedPage.initDataTable = function() {
$('#unmatched_album_table').dataTable({ <%-- Use the updated ID --%>
"bStateSave": true,
"bPaginate": true,
"oLanguage": {
@@ -129,133 +145,203 @@
"sInfoEmpty":"Showing 0 to 0 of 0 albums",
"sInfoFiltered":"(filtered from _MAX_ total albums)",
"sEmptyTable": " ",
},
},
"sPaginationType": "full_numbers",
"fnDrawCallback": function (o) {
// Jump to top of page
$('html,body').scrollTop(0);
}
});
};
initActions();
ManageUnmatchedPage.initDialogs = function() {
// Initialize the generic ignore dialog
$('#ignore_dialog').dialog({
autoOpen: false,
modal: true,
resizable: false,
width: 500,
height: 'auto',
buttons: {
"Cancel": function() {
$(this).dialog('close');
}
}
});
// Initialize the generic match dialog
$('#match_dialog').dialog({
autoOpen: false,
modal: true,
resizable: false,
width: 700,
height: 'auto',
buttons: {
"Cancel": function() {
$(this).dialog('close');
}
}
});
};
ManageUnmatchedPage.initActions = function() {
// --- Ignore Artist/Album Logic ---
$(document).on('click', '.ignore-artist-button, .ignore-album-button', function() {
var $button = $(this);
var isArtistIgnore = $button.hasClass('ignore-artist-button');
var artistName = $button.data('artist-name');
var albumTitle = $button.data('album-title');
var oldArtistJs = $button.data('old-artist-js'); // JS escaped, needs URI encoding for URL
var oldAlbumJs = $button.data('old-album-js'); // JS escaped, needs URI encoding for URL
var $dialog = $('#ignore_dialog');
var $dialogMessage = $dialog.find('.dialog-message');
var $confirmButton = $dialog.find('.confirm-ignore-button');
var message = "";
var actionUrl = "";
var successMessage = "";
if (isArtistIgnore) {
message = "Are you sure you want to ignore Local Artist: " + artistName + " from future matching?";
actionUrl = 'markUnmatched?action=ignoreArtist&existing_artist=' + encodeURIComponent(oldArtistJs);
successMessage = "Successfully ignored " + artistName + " from future matching";
$confirmButton.text('Ignore Artist');
} else { // ignore-album-button
message = "Are you sure you want to ignore Local Album: " + albumTitle + " from future matching?";
actionUrl = 'markUnmatched?action=ignoreAlbum&existing_artist=' + encodeURIComponent(oldArtistJs) + '&existing_album=' + encodeURIComponent(oldAlbumJs);
successMessage = "Successfully ignored " + albumTitle + " from future matching";
$confirmButton.text('Ignore Album');
}
$dialogMessage.text(message);
$confirmButton.off('click').on('click', function() {
if (typeof doAjaxCall === 'function') {
doAjaxCall(actionUrl, $button, 'page', successMessage);
} else {
console.error("doAjaxCall function is not defined. Cannot perform ignore action.");
}
$dialog.dialog('close');
});
$dialog.dialog('open');
});
// --- Match Artist/Album Logic ---
$(document).on('click', '.match-artist-button, .match-album-button', function() {
var $button = $(this);
var isArtistMatch = $button.hasClass('match-artist-button');
var artistName = $button.data('artist-name');
var albumTitle = $button.data('album-title');
var oldArtistJs = $button.data('old-artist-js');
var oldAlbumJs = $button.data('old-album-js');
var $dialog = $('#match_dialog');
var $localArtistDisplay = $('#local-artist-display');
var $localAlbumDisplay = $('#local-album-display');
var $artistMatchSection = $('#artist-match-section');
var $albumMatchSection = $('#album-match-section');
var $matchArtistOptions = $('#match-artist-options');
var $matchAlbumOptions = $('#match-album-options');
var $confirmButton = $dialog.find('.confirm-match-button');
// Reset dialog state
$localArtistDisplay.text(artistName);
$matchArtistOptions.empty();
$matchAlbumOptions.empty();
$confirmButton.off('click'); // Clear previous click handlers
// Populate artist options
$.each(ManageUnmatchedPage.jsonArtists, function(key, value) {
$matchArtistOptions.append($("<option/>", {
value: value,
text: value
}));
});
if (isArtistMatch) {
$localAlbumDisplay.closest('p').hide(); // Hide local album row
$albumMatchSection.hide(); // Hide match album section
$localArtistDisplay.text(artistName);
$artistMatchSection.show(); // Show match artist section
$confirmButton.text('Match Artist');
$confirmButton.on('click', function() {
var newArtist = $matchArtistOptions.val();
var actionUrl = 'markUnmatched?action=matchArtist&existing_artist=' + encodeURIComponent(oldArtistJs) + '&new_artist=' + encodeURIComponent(newArtist);
var successMessage = 'Successfully matched ' + artistName + ' with ' + newArtist;
if (typeof doAjaxCall === 'function') {
doAjaxCall(actionUrl, $button, 'page', successMessage);
} else {
console.error("doAjaxCall function is not defined. Cannot match artist.");
}
$dialog.dialog('close');
});
} else { // match-album-button
$localAlbumDisplay.closest('p').show(); // Show local album row
$albumMatchSection.show(); // Show match album section
$localAlbumDisplay.text(albumTitle);
$artistMatchSection.show(); // Show match artist section (for selecting matched album's artist)
$confirmButton.text('Match Album');
// Load albums for selected artist initially
ManageUnmatchedPage.loadAlbumsForArtist($matchArtistOptions.val(), $matchAlbumOptions);
// Handle artist selection change
$matchArtistOptions.off('change').on('change', function() {
ManageUnmatchedPage.loadAlbumsForArtist($(this).val(), $matchAlbumOptions);
});
$confirmButton.on('click', function() {
var newArtist = $matchArtistOptions.val();
var newAlbum = $matchAlbumOptions.val();
var actionUrl = 'markUnmatched?action=matchAlbum&existing_artist=' + encodeURIComponent(oldArtistJs) + '&new_artist=' + encodeURIComponent(newArtist) + '&existing_album=' + encodeURIComponent(oldAlbumJs) + '&new_album=' + encodeURIComponent(newAlbum);
var successMessage = 'Successfully matched ' + albumTitle + ' with ' + newAlbum + ' by ' + newArtist;
if (typeof doAjaxCall === 'function') {
doAjaxCall(actionUrl, $button, 'page', successMessage);
} else {
console.error("doAjaxCall function is not defined. Cannot match album.");
}
$dialog.dialog('close');
});
}
$dialog.dialog('open');
});
// Helper function to load albums for a given artist
ManageUnmatchedPage.loadAlbumsForArtist = function(artistName, $targetSelect) {
$targetSelect.empty().append($("<option/>", { value: "", text: "Loading albums..." })); // Add loading message
var cleanArtistName = encodeURIComponent(artistName);
$.getJSON("getAlbumsByArtist_json?artist=" + cleanArtistName, function(data) {
$targetSelect.empty();
if (Object.keys(data).length > 0) {
$.each(data, function(key, value) {
$targetSelect.append($("<option/>", {
value: value,
text: value
}));
});
} else {
$targetSelect.append($("<option/>", { value: "", text: "No albums found" }));
}
}).fail(function() {
$targetSelect.empty().append($("<option/>", { value: "", text: "Error loading albums" }));
});
};
// Assuming initActions from common.js is called globally
if (typeof initActions === 'function') {
initActions();
}
};
$(document).ready(function() {
ManageUnmatchedPage.initDataTable();
ManageUnmatchedPage.initDialogs();
ManageUnmatchedPage.initActions();
});
function ignore_Artist(clicked_id) {
n=clicked_id.replace("ignore_artists","");
$("#ignore_artist_dialog"+n).dialog();
return false;
}
function ignore_Album(clicked_id) {
n=clicked_id.replace("ignore_albums","");
$("#ignore_album_dialog"+n).dialog();
return false;
}
function load_Artist(clicked_id) {
n=clicked_id.replace("match_artists","");
var d = $("#artist_dialog"+n).dialog();
d.dialog("option", "width", 450);
d.dialog("option", "position", "center");
$('#artist_options'+n).html('');
$.each(${json_artists}, function(key, value) {
$('#artist_options'+n).append($("<option/>", {
value: value,
text: value
}));
});
change_artist(n)
return false;
}
function change_artist(n) {
selected_artist = $("#artist_options"+n).find("option:selected").text();
selected_artist_clean = selected_artist.replace('&', '%26').replace('+', '%2B');
$("#artist_options"+n).change(function() {
selected_artist = $("#artist_options"+n).find("option:selected").text();
selected_artist_clean = selected_artist.replace('&', '%26').replace('+', '%2B');
});
}
function artist_matcher(n, existing_artist) {
var existing_artist = existing_artist.toString();
var existing_artist_clean = existing_artist.replace('&', '%26').replace('+', '%2B');
$('#match_artists'+n).attr('data-success', 'Successfully matched '+existing_artist+' with '+selected_artist);
doAjaxCall('markUnmatched?action=matchArtist&existing_artist='+existing_artist_clean+'&new_artist='+selected_artist_clean, $('#match_artists'+n), 'page');
}
function load_AlbumArtist(clicked_id) {
n=clicked_id.replace("match_albums","");
var d = $("#album_dialog"+n).dialog();
d.dialog("option", "width", 700);
d.dialog("option", "position", "center");
$('#album_artist_options'+n).html('');
$.each(${json_artists}, function(key, value) {
$('#album_artist_options'+n).append($("<option/>", {
value: value,
text: value
}));
});
load_json_albums(n)
return false;
}
function load_json_albums(n) {
selected_album_artist = $("#album_artist_options"+n).find("option:selected").text();
selected_album_artist_clean = selected_album_artist.replace('&', '%26').replace('+', '%2B');
$('#album_options'+n).html('')
$.getJSON("getAlbumsByArtist_json?artist="+selected_album_artist_clean, function( data ) {
$.each( data, function( key, value ) {
$('#album_options'+n).append($("<option/>", {
value: value,
text: value
}));
});
selected_album= $("#album_options"+n).find("option:selected").text();
selected_album_clean = selected_album.replace('&', '%26').replace('+', '%2B');
});
change_json_albums(n)
change_album(n)
}
function change_json_albums(n) {
$("#album_artist_options"+n).change(function(){
selected_album_artist = $("#album_artist_options"+n).find("option:selected").text();
selected_album_artist_clean = selected_album_artist.replace('&', '%26').replace('+', '%2B');
$('#album_options'+n).html('')
$.getJSON("getAlbumsByArtist_json?artist="+selected_album_artist_clean, function( data ) {
$.each( data, function( key, value ) {
$('#album_options'+n).append($("<option/>", {
value: value,
text: value
}));
});
selected_album= $("#album_options"+n).find("option:selected").text();
selected_album_clean = selected_album.replace('&', '%26').replace('+', '%2B');
});
change_album(n)
});
}
function change_album(n) {
$("#album_options"+n).change(function() {
selected_album= $("#album_options"+n).find("option:selected").text();
selected_album_clean = selected_album.replace('&', '%26').replace('+', '%2B');
});
}
function album_matcher(n, existing_artist, existing_album) {
var existing_artist = existing_artist.toString();
var existing_artist_clean = existing_artist.replace('&', '%26').replace('+', '%2B');
var existing_album = existing_album.toString();
var existing_album_clean = existing_album.replace('&', '%26').replace('+', '%2B');
$('#match_albums'+n).attr('data-success', 'Successfully matched '+existing_album+' with '+selected_album);
doAjaxCall('markUnmatched?action=matchAlbum&existing_artist='+existing_artist_clean+'&new_artist='+selected_album_artist_clean+'&existing_album='+existing_album_clean+'&new_album='+selected_album_clean, $('#match_albums'+n), 'page');
}
</script>
</%def>