Files
headphones/data/interfaces/default/manageunmatched.html
2025-07-06 15:06:19 +01:00

348 lines
14 KiB
HTML

<%inherit file="base.html" />
<%!
import headphones
# 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()">
<div id="subhead_container">
<div id="subhead_menu">
<a class="menu_link_edit" href="manageManual"><i class="fa fa-pencil"></i> Manage Manually Matched & Ignored</a>
</div>
</div>
<a href="manage" class="back">&laquo; Back to manage overview</a>
</%def>
<%def name="body()">
<div class="table_wrapper">
<div id="manageheader" class="title">
<h1 class="clearfix"><i class="fa fa-music"></i> Manage Unmatched Albums</h1>
</div>
<table class="display" id="unmatched_album_table"> <%-- Changed ID for clarity --%>
<thead>
<tr>
<th class="column-artist">Local Artist</th>
<th class="column-album">Local Album</th>
</tr>
</thead>
<tbody>
<% count_albums=0 %> <%-- Still useful for unique IDs if needed, but aiming for generic dialogs --%>
%for album in unmatchedalbums:
<tr class="gradeZ">
<%
# 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 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 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>
// 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": {
"sSearch": "",
"sLengthMenu":"Show _MENU_ albums per page",
"sInfo":"Showing _START_ to _END_ of _TOTAL_ albums",
"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);
}
});
};
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();
});
</script>
</%def>