Optimize enhanced view performance with event delegation and scoped DOM queries

This commit is contained in:
Broque Thomas 2026-03-14 18:00:20 -07:00
parent 7e19e66ef3
commit 7db1545208
2 changed files with 371 additions and 251 deletions

View file

@ -36047,6 +36047,7 @@ async function loadEnhancedViewData(artistId) {
artistDetailPageState.enhancedTrackSort = {}; artistDetailPageState.enhancedTrackSort = {};
artistDetailPageState.serverType = data.server_type || null; artistDetailPageState.serverType = data.server_type || null;
_tagPreviewServerType = data.server_type || null; _tagPreviewServerType = data.server_type || null;
_rebuildAlbumMap();
renderEnhancedView(); renderEnhancedView();
} catch (error) { } catch (error) {
@ -36538,11 +36539,23 @@ function toggleAlbumExpand(albumId) {
} }
function findEnhancedAlbum(albumId) { function findEnhancedAlbum(albumId) {
// Use cached map for O(1) lookups instead of O(n) array scan
if (artistDetailPageState._albumMap) {
return artistDetailPageState._albumMap.get(String(albumId)) || null;
}
const data = artistDetailPageState.enhancedData; const data = artistDetailPageState.enhancedData;
if (!data || !data.albums) return null; if (!data || !data.albums) return null;
return data.albums.find(a => String(a.id) === String(albumId)); return data.albums.find(a => String(a.id) === String(albumId));
} }
function _rebuildAlbumMap() {
const data = artistDetailPageState.enhancedData;
if (!data || !data.albums) { artistDetailPageState._albumMap = null; return; }
const map = new Map();
data.albums.forEach(a => map.set(String(a.id), a));
artistDetailPageState._albumMap = map;
}
function renderExpandedAlbumHeader(album) { function renderExpandedAlbumHeader(album) {
const header = document.createElement('div'); const header = document.createElement('div');
header.className = 'enhanced-expanded-header'; header.className = 'enhanced-expanded-header';
@ -36781,103 +36794,10 @@ function renderAlbumMetaRow(album) {
return row; return row;
} }
function renderTrackTable(album) { function _buildTrackRow(track, album, admin) {
const wrapper = document.createElement('div');
const tracks = album.tracks || [];
// Re-apply stored sort order if any
const activeSort = artistDetailPageState.enhancedTrackSort[album.id];
if (activeSort) {
sortEnhancedTracks(album, activeSort.field, activeSort.ascending);
}
if (tracks.length === 0) {
wrapper.innerHTML = '<div class="enhanced-no-tracks">No tracks in database</div>';
return wrapper;
}
const table = document.createElement('table');
table.className = 'enhanced-track-table';
const admin = isEnhancedAdmin();
// Clear stale selections for non-admin to prevent ghost state
if (!admin) {
artistDetailPageState.selectedTracks.clear();
}
// Header
const thead = document.createElement('thead');
const headRow = document.createElement('tr');
if (admin) {
const selectAllTh = document.createElement('th');
const selectAllCb = document.createElement('input');
selectAllCb.type = 'checkbox';
selectAllCb.className = 'enhanced-track-checkbox';
selectAllCb.onchange = function() { toggleSelectAllTracks(album.id, this.checked); };
selectAllTh.appendChild(selectAllCb);
headRow.appendChild(selectAllTh);
}
const columns = [
{ label: '', cls: 'col-play' },
{ label: '#', cls: 'col-num', sortField: 'track_number' },
{ label: 'Disc', cls: 'col-disc', sortField: 'disc_number' },
{ label: 'Title', cls: 'col-title', sortField: 'title' },
{ label: 'Duration', cls: 'col-duration', sortField: 'duration' },
{ label: 'Format', cls: 'col-format', sortField: 'format' },
{ label: 'Bitrate', cls: 'col-bitrate', sortField: 'bitrate' },
{ label: 'BPM', cls: 'col-bpm', sortField: 'bpm' },
{ label: 'File', cls: 'col-path' },
{ label: 'Match', cls: 'col-match' },
{ label: '', cls: 'col-queue' },
...(admin ? [
{ label: '', cls: 'col-writetag' },
{ label: '', cls: 'col-delete' },
] : [
{ label: '', cls: 'col-report' },
]),
];
columns.forEach(col => {
const th = document.createElement('th');
th.className = col.cls;
const sortField = col.sortField;
const currentSort = artistDetailPageState.enhancedTrackSort[album.id];
if (sortField) {
let headerText = col.label;
if (currentSort && currentSort.field === sortField) {
headerText += currentSort.ascending ? ' \u25B2' : ' \u25BC';
}
th.textContent = headerText;
th.style.cursor = 'pointer';
th.onclick = () => {
cancelInlineEdit();
const current = artistDetailPageState.enhancedTrackSort[album.id];
const ascending = current && current.field === sortField ? !current.ascending : true;
artistDetailPageState.enhancedTrackSort[album.id] = { field: sortField, ascending };
sortEnhancedTracks(album, sortField, ascending);
const panelWrapper = th.closest('.enhanced-tracks-panel');
if (panelWrapper) {
const tableContainer = panelWrapper.querySelector('.enhanced-track-table')?.parentElement;
if (tableContainer) {
const parent = tableContainer.parentElement;
const newTable = renderTrackTable(album);
parent.replaceChild(newTable, tableContainer);
}
}
};
} else {
th.textContent = col.label;
}
headRow.appendChild(th);
});
thead.appendChild(headRow);
table.appendChild(thead);
// Body
const tbody = document.createElement('tbody');
tracks.forEach(track => {
const tr = document.createElement('tr'); const tr = document.createElement('tr');
tr.dataset.trackId = track.id; tr.dataset.trackId = track.id;
tr.dataset.albumId = album.id;
if (artistDetailPageState.selectedTracks.has(String(track.id))) tr.classList.add('selected'); if (artistDetailPageState.selectedTracks.has(String(track.id))) tr.classList.add('selected');
// Checkbox (admin only) // Checkbox (admin only)
@ -36887,7 +36807,6 @@ function renderTrackTable(album) {
cb.type = 'checkbox'; cb.type = 'checkbox';
cb.className = 'enhanced-track-checkbox'; cb.className = 'enhanced-track-checkbox';
cb.checked = artistDetailPageState.selectedTracks.has(String(track.id)); cb.checked = artistDetailPageState.selectedTracks.has(String(track.id));
cb.onchange = () => toggleTrackSelection(String(track.id));
cbTd.appendChild(cb); cbTd.appendChild(cb);
tr.appendChild(cbTd); tr.appendChild(cbTd);
} }
@ -36898,27 +36817,15 @@ function renderTrackTable(album) {
const playBtn = document.createElement('button'); const playBtn = document.createElement('button');
playBtn.className = 'enhanced-play-btn'; playBtn.className = 'enhanced-play-btn';
playBtn.innerHTML = '&#9654;'; playBtn.innerHTML = '&#9654;';
playBtn.title = 'Play track'; playBtn.title = track.file_path ? 'Play track' : 'No file available';
if (track.file_path) { if (!track.file_path) playBtn.disabled = true;
const artistName = artistDetailPageState.enhancedData ? artistDetailPageState.enhancedData.artist.name : '';
playBtn.onclick = (e) => {
e.stopPropagation();
playLibraryTrack(track, album.title || '', artistName);
};
} else {
playBtn.disabled = true;
playBtn.title = 'No file available';
}
playTd.appendChild(playBtn); playTd.appendChild(playBtn);
tr.appendChild(playTd); tr.appendChild(playTd);
// Track number (editable for admin) // Track number
const numTd = document.createElement('td'); const numTd = document.createElement('td');
numTd.className = 'col-num' + (admin ? ' editable' : ''); numTd.className = 'col-num' + (admin ? ' editable' : '');
numTd.textContent = track.track_number || '-'; numTd.textContent = track.track_number || '-';
if (admin) {
numTd.onclick = (e) => { e.stopPropagation(); startInlineEdit(numTd, 'track', track.id, 'track_number', track.track_number || ''); };
}
tr.appendChild(numTd); tr.appendChild(numTd);
// Disc number // Disc number
@ -36927,13 +36834,10 @@ function renderTrackTable(album) {
discTd.textContent = track.disc_number || '-'; discTd.textContent = track.disc_number || '-';
tr.appendChild(discTd); tr.appendChild(discTd);
// Title (editable for admin) // Title
const titleTd = document.createElement('td'); const titleTd = document.createElement('td');
titleTd.className = 'col-title' + (admin ? ' editable' : ''); titleTd.className = 'col-title' + (admin ? ' editable' : '');
titleTd.textContent = track.title || 'Unknown'; titleTd.textContent = track.title || 'Unknown';
if (admin) {
titleTd.onclick = (e) => { e.stopPropagation(); startInlineEdit(titleTd, 'track', track.id, 'title', track.title || ''); };
}
tr.appendChild(titleTd); tr.appendChild(titleTd);
// Duration // Duration
@ -36963,16 +36867,13 @@ function renderTrackTable(album) {
brTd.appendChild(brSpan); brTd.appendChild(brSpan);
tr.appendChild(brTd); tr.appendChild(brTd);
// BPM (editable for admin) // BPM
const bpmTd = document.createElement('td'); const bpmTd = document.createElement('td');
bpmTd.className = 'col-bpm' + (admin ? ' editable' : ''); bpmTd.className = 'col-bpm' + (admin ? ' editable' : '');
bpmTd.textContent = track.bpm || '-'; bpmTd.textContent = track.bpm || '-';
if (admin) {
bpmTd.onclick = (e) => { e.stopPropagation(); startInlineEdit(bpmTd, 'track', track.id, 'bpm', track.bpm || ''); };
}
tr.appendChild(bpmTd); tr.appendChild(bpmTd);
// File path (last column) // File path
const pathTd = document.createElement('td'); const pathTd = document.createElement('td');
pathTd.className = 'col-path'; pathTd.className = 'col-path';
const filePath = track.file_path || '-'; const filePath = track.file_path || '-';
@ -36995,19 +36896,13 @@ function renderTrackTable(album) {
{ svc: 'lastfm', col: 'lastfm_url', label: 'LFM' }, { svc: 'lastfm', col: 'lastfm_url', label: 'LFM' },
{ svc: 'genius', col: 'genius_id', label: 'Gen' }, { svc: 'genius', col: 'genius_id', label: 'Gen' },
]; ];
const aId = artistDetailPageState.enhancedData ? artistDetailPageState.enhancedData.artist.id : null;
trackServices.forEach(s => { trackServices.forEach(s => {
const hasId = !!track[s.col]; const hasId = !!track[s.col];
const chip = document.createElement('span'); const chip = document.createElement('span');
chip.className = 'enhanced-track-match-chip' + (hasId ? ' matched' : ' not-found'); chip.className = 'enhanced-track-match-chip' + (hasId ? ' matched' : ' not-found');
chip.textContent = s.label; chip.textContent = s.label;
chip.title = hasId ? `${s.svc}: ${track[s.col]}` : `${s.svc}: no match`; chip.title = hasId ? `${s.svc}: ${track[s.col]}` : `${s.svc}: no match`;
if (admin) { chip.dataset.service = s.svc;
chip.onclick = (e) => {
e.stopPropagation();
openManualMatchModal('track', track.id, s.svc, track.title || '', aId);
};
}
matchCell.appendChild(chip); matchCell.appendChild(chip);
}); });
matchTd.appendChild(matchCell); matchTd.appendChild(matchCell);
@ -37021,8 +36916,142 @@ function renderTrackTable(album) {
queueBtn.className = 'enhanced-queue-btn'; queueBtn.className = 'enhanced-queue-btn';
queueBtn.innerHTML = '&#43;'; queueBtn.innerHTML = '&#43;';
queueBtn.title = 'Add to queue'; queueBtn.title = 'Add to queue';
queueBtn.onclick = (e) => { queueTd.appendChild(queueBtn);
}
tr.appendChild(queueTd);
if (admin) {
// Write Tags button (admin only)
const tagTd = document.createElement('td');
tagTd.className = 'col-writetag';
if (track.file_path) {
const tagBtn = document.createElement('button');
tagBtn.className = 'enhanced-write-tag-btn';
tagBtn.innerHTML = '&#9998;';
tagBtn.title = 'Write tags to file';
tagTd.appendChild(tagBtn);
}
tr.appendChild(tagTd);
// Delete button (admin only)
const delTd = document.createElement('td');
delTd.className = 'col-delete';
const delBtn = document.createElement('button');
delBtn.className = 'enhanced-delete-btn';
delBtn.innerHTML = '&#10005;';
delBtn.title = 'Delete track from library';
delTd.appendChild(delBtn);
tr.appendChild(delTd);
} else {
// Report Issue button per track (non-admin)
const reportTd = document.createElement('td');
reportTd.className = 'col-report';
const reportBtn = document.createElement('button');
reportBtn.className = 'enhanced-track-report-btn';
reportBtn.innerHTML = '&#9873;';
reportBtn.title = 'Report issue with this track';
reportTd.appendChild(reportBtn);
tr.appendChild(reportTd);
}
return tr;
}
function _getTrackDataFromRow(tr) {
const trackId = tr.dataset.trackId;
const albumId = tr.dataset.albumId;
const album = findEnhancedAlbum(albumId);
if (!album) return null;
const track = (album.tracks || []).find(t => String(t.id) === String(trackId));
return track ? { track, album, trackId, albumId } : null;
}
function _attachTableDelegation(table, album) {
// Single click handler for the entire table — replaces 12-16 per-row handlers
const admin = isEnhancedAdmin();
table.addEventListener('click', (e) => {
const target = e.target;
const tr = target.closest('tr[data-track-id]');
// Header checkbox (select all)
if (target.closest('thead') && target.classList.contains('enhanced-track-checkbox')) {
toggleSelectAllTracks(album.id, target.checked);
return;
}
// Sort header click
const th = target.closest('th[data-sort-field]');
if (th) {
cancelInlineEdit();
const sortField = th.dataset.sortField;
const current = artistDetailPageState.enhancedTrackSort[album.id];
const ascending = current && current.field === sortField ? !current.ascending : true;
artistDetailPageState.enhancedTrackSort[album.id] = { field: sortField, ascending };
sortEnhancedTracks(album, sortField, ascending);
_rebuildTbody(table, album);
// Update header sort indicators
table.querySelectorAll('th[data-sort-field]').forEach(h => {
const sf = h.dataset.sortField;
const baseLabel = h.dataset.label || '';
const sort = artistDetailPageState.enhancedTrackSort[album.id];
h.textContent = sort && sort.field === sf ? baseLabel + (sort.ascending ? ' \u25B2' : ' \u25BC') : baseLabel;
});
return;
}
if (!tr) return;
const info = _getTrackDataFromRow(tr);
if (!info) return;
const { track, trackId } = info;
// Checkbox
if (target.classList.contains('enhanced-track-checkbox')) {
toggleTrackSelection(String(trackId));
return;
}
// Play button
if (target.closest('.enhanced-play-btn')) {
e.stopPropagation(); e.stopPropagation();
if (track.file_path) {
const artistName = artistDetailPageState.enhancedData ? artistDetailPageState.enhancedData.artist.name : '';
playLibraryTrack(track, album.title || '', artistName);
}
return;
}
// Inline editable cells (admin)
if (admin) {
const cell = target.closest('td.editable');
if (cell) {
e.stopPropagation();
if (cell.classList.contains('col-num')) {
startInlineEdit(cell, 'track', track.id, 'track_number', track.track_number || '');
} else if (cell.classList.contains('col-title')) {
startInlineEdit(cell, 'track', track.id, 'title', track.title || '');
} else if (cell.classList.contains('col-bpm')) {
startInlineEdit(cell, 'track', track.id, 'bpm', track.bpm || '');
}
return;
}
}
// Match chip click (admin — open manual match modal)
if (admin) {
const chip = target.closest('.enhanced-track-match-chip');
if (chip) {
e.stopPropagation();
const svc = chip.dataset.service;
const aId = artistDetailPageState.enhancedData ? artistDetailPageState.enhancedData.artist.id : null;
openManualMatchModal('track', track.id, svc, track.title || '', aId);
return;
}
}
// Queue button
if (target.closest('.enhanced-queue-btn')) {
e.stopPropagation();
if (track.file_path) {
const artistName = artistDetailPageState.enhancedData ? artistDetailPageState.enhancedData.artist.name : ''; const artistName = artistDetailPageState.enhancedData ? artistDetailPageState.enhancedData.artist.name : '';
let albumArt = album.thumb_url || null; let albumArt = album.thumb_url || null;
if (!albumArt && artistDetailPageState.enhancedData) { if (!albumArt && artistDetailPageState.enhancedData) {
@ -37042,57 +37071,134 @@ function renderTrackTable(album) {
bitrate: track.bitrate, bitrate: track.bitrate,
sample_rate: track.sample_rate sample_rate: track.sample_rate
}); });
};
queueTd.appendChild(queueBtn);
} }
tr.appendChild(queueTd); return;
if (admin) {
// Write Tags button (admin only)
const tagTd = document.createElement('td');
tagTd.className = 'col-writetag';
if (track.file_path) {
const tagBtn = document.createElement('button');
tagBtn.className = 'enhanced-write-tag-btn';
tagBtn.innerHTML = '&#9998;';
tagBtn.title = 'Write tags to file';
tagBtn.onclick = (e) => { e.stopPropagation(); showTagPreview(track.id); };
tagTd.appendChild(tagBtn);
} }
tr.appendChild(tagTd);
// Delete button (admin only) // Write tags button (admin)
const delTd = document.createElement('td'); if (target.closest('.enhanced-write-tag-btn')) {
delTd.className = 'col-delete'; e.stopPropagation();
const delBtn = document.createElement('button'); showTagPreview(track.id);
delBtn.className = 'enhanced-delete-btn'; return;
delBtn.innerHTML = '&#10005;'; }
delBtn.title = 'Delete track from library';
delBtn.onclick = (e) => { e.stopPropagation(); deleteLibraryTrack(track.id, album.id); }; // Delete button (admin)
delTd.appendChild(delBtn); if (target.closest('.enhanced-delete-btn')) {
tr.appendChild(delTd); e.stopPropagation();
} else { deleteLibraryTrack(track.id, album.id);
// Report Issue button per track (non-admin) return;
const reportTd = document.createElement('td'); }
reportTd.className = 'col-report';
const reportBtn = document.createElement('button'); // Report button (non-admin)
reportBtn.className = 'enhanced-track-report-btn'; if (target.closest('.enhanced-track-report-btn')) {
reportBtn.innerHTML = '&#9873;';
reportBtn.title = 'Report issue with this track';
reportBtn.onclick = (e) => {
e.stopPropagation(); e.stopPropagation();
const artistName = artistDetailPageState.enhancedData ? artistDetailPageState.enhancedData.artist.name : ''; const artistName = artistDetailPageState.enhancedData ? artistDetailPageState.enhancedData.artist.name : '';
showReportIssueModal('track', track.id, track.title || 'Unknown', artistName, album.title || ''); showReportIssueModal('track', track.id, track.title || 'Unknown', artistName, album.title || '');
}; return;
reportTd.appendChild(reportBtn); }
tr.appendChild(reportTd); });
} }
tbody.appendChild(tr); function _rebuildTbody(table, album) {
// Replace only the tbody — keeps thead and event delegation intact
const admin = isEnhancedAdmin();
const oldTbody = table.querySelector('tbody');
const newTbody = document.createElement('tbody');
(album.tracks || []).forEach(track => {
newTbody.appendChild(_buildTrackRow(track, album, admin));
});
if (oldTbody) table.replaceChild(newTbody, oldTbody);
else table.appendChild(newTbody);
}
function renderTrackTable(album) {
const wrapper = document.createElement('div');
const tracks = album.tracks || [];
// Re-apply stored sort order if any
const activeSort = artistDetailPageState.enhancedTrackSort[album.id];
if (activeSort) {
sortEnhancedTracks(album, activeSort.field, activeSort.ascending);
}
if (tracks.length === 0) {
wrapper.innerHTML = '<div class="enhanced-no-tracks">No tracks in database</div>';
return wrapper;
}
const table = document.createElement('table');
table.className = 'enhanced-track-table';
table.dataset.albumId = album.id;
const admin = isEnhancedAdmin();
// Clear stale selections for non-admin to prevent ghost state
if (!admin) {
artistDetailPageState.selectedTracks.clear();
}
// Header
const thead = document.createElement('thead');
const headRow = document.createElement('tr');
if (admin) {
const selectAllTh = document.createElement('th');
const selectAllCb = document.createElement('input');
selectAllCb.type = 'checkbox';
selectAllCb.className = 'enhanced-track-checkbox';
selectAllTh.appendChild(selectAllCb);
headRow.appendChild(selectAllTh);
}
const columns = [
{ label: '', cls: 'col-play' },
{ label: '#', cls: 'col-num', sortField: 'track_number' },
{ label: 'Disc', cls: 'col-disc', sortField: 'disc_number' },
{ label: 'Title', cls: 'col-title', sortField: 'title' },
{ label: 'Duration', cls: 'col-duration', sortField: 'duration' },
{ label: 'Format', cls: 'col-format', sortField: 'format' },
{ label: 'Bitrate', cls: 'col-bitrate', sortField: 'bitrate' },
{ label: 'BPM', cls: 'col-bpm', sortField: 'bpm' },
{ label: 'File', cls: 'col-path' },
{ label: 'Match', cls: 'col-match' },
{ label: '', cls: 'col-queue' },
...(admin ? [
{ label: '', cls: 'col-writetag' },
{ label: '', cls: 'col-delete' },
] : [
{ label: '', cls: 'col-report' },
]),
];
const currentSort = artistDetailPageState.enhancedTrackSort[album.id];
columns.forEach(col => {
const th = document.createElement('th');
th.className = col.cls;
if (col.sortField) {
let headerText = col.label;
if (currentSort && currentSort.field === col.sortField) {
headerText += currentSort.ascending ? ' \u25B2' : ' \u25BC';
}
th.textContent = headerText;
th.style.cursor = 'pointer';
th.dataset.sortField = col.sortField;
th.dataset.label = col.label;
} else {
th.textContent = col.label;
}
headRow.appendChild(th);
});
thead.appendChild(headRow);
table.appendChild(thead);
// Body
const tbody = document.createElement('tbody');
tracks.forEach(track => {
tbody.appendChild(_buildTrackRow(track, album, admin));
}); });
table.appendChild(tbody); table.appendChild(tbody);
wrapper.appendChild(table);
// Single delegated event listener for the whole table
_attachTableDelegation(table, album);
wrapper.appendChild(table);
return wrapper; return wrapper;
} }
@ -37153,6 +37259,7 @@ async function deleteLibraryAlbum(albumId) {
album.tracks.forEach(t => artistDetailPageState.selectedTracks.delete(String(t.id))); album.tracks.forEach(t => artistDetailPageState.selectedTracks.delete(String(t.id)));
} }
artistDetailPageState.enhancedData.albums = (artistDetailPageState.enhancedData.albums || []).filter(a => a.id !== albumId); artistDetailPageState.enhancedData.albums = (artistDetailPageState.enhancedData.albums || []).filter(a => a.id !== albumId);
_rebuildAlbumMap();
} }
artistDetailPageState.expandedAlbums.delete(albumId); artistDetailPageState.expandedAlbums.delete(albumId);
delete artistDetailPageState.enhancedTrackSort[albumId]; delete artistDetailPageState.enhancedTrackSort[albumId];
@ -37365,32 +37472,37 @@ function toggleSelectAllTracks(albumId, checked) {
const album = findEnhancedAlbum(albumId); const album = findEnhancedAlbum(albumId);
if (!album || !album.tracks) return; if (!album || !album.tracks) return;
// Batch update state
album.tracks.forEach(track => { album.tracks.forEach(track => {
const tid = String(track.id); const tid = String(track.id);
if (checked) artistDetailPageState.selectedTracks.add(tid); if (checked) artistDetailPageState.selectedTracks.add(tid);
else artistDetailPageState.selectedTracks.delete(tid); else artistDetailPageState.selectedTracks.delete(tid);
});
const row = document.querySelector(`tr[data-track-id="${tid}"]`); // Scoped DOM query — only search within this album's panel, not entire document
if (row) { const panel = document.getElementById(`enhanced-tracks-panel-${albumId}`);
if (panel) {
panel.querySelectorAll('tr[data-track-id]').forEach(row => {
row.classList.toggle('selected', checked); row.classList.toggle('selected', checked);
const cb = row.querySelector('.enhanced-track-checkbox'); const cb = row.querySelector('.enhanced-track-checkbox');
if (cb) cb.checked = checked; if (cb) cb.checked = checked;
}
}); });
}
updateBulkBar(); updateBulkBar();
} }
function clearTrackSelection() { function clearTrackSelection() {
artistDetailPageState.selectedTracks.forEach(tid => { // Scoped batch clear — query the container once instead of per-track
const row = document.querySelector(`tr[data-track-id="${tid}"]`); const container = document.getElementById('enhanced-view-container');
if (row) { if (container) {
container.querySelectorAll('tr[data-track-id].selected').forEach(row => {
row.classList.remove('selected'); row.classList.remove('selected');
const cb = row.querySelector('.enhanced-track-checkbox'); const cb = row.querySelector('.enhanced-track-checkbox');
if (cb) cb.checked = false; if (cb) cb.checked = false;
}
}); });
container.querySelectorAll('.enhanced-track-table thead .enhanced-track-checkbox').forEach(cb => cb.checked = false);
}
artistDetailPageState.selectedTracks.clear(); artistDetailPageState.selectedTracks.clear();
document.querySelectorAll('.enhanced-track-table thead .enhanced-track-checkbox').forEach(cb => cb.checked = false);
updateBulkBar(); updateBulkBar();
} }
@ -37821,6 +37933,7 @@ async function applyManualMatch(entityType, entityId, service, serviceId, artist
// Update view with fresh data // Update view with fresh data
if (result.updated_data && result.updated_data.success) { if (result.updated_data && result.updated_data.success) {
artistDetailPageState.enhancedData = result.updated_data; artistDetailPageState.enhancedData = result.updated_data;
_rebuildAlbumMap();
renderEnhancedView(); renderEnhancedView();
} else if (artistDetailPageState.currentArtistId) { } else if (artistDetailPageState.currentArtistId) {
await loadEnhancedViewData(artistDetailPageState.currentArtistId); await loadEnhancedViewData(artistDetailPageState.currentArtistId);
@ -37903,6 +38016,7 @@ async function runEnrichment(entityType, entityId, service, name, artistName, ar
// Update local data with fresh response and re-render (preserves expanded state) // Update local data with fresh response and re-render (preserves expanded state)
if (result.updated_data && result.updated_data.success) { if (result.updated_data && result.updated_data.success) {
artistDetailPageState.enhancedData = result.updated_data; artistDetailPageState.enhancedData = result.updated_data;
_rebuildAlbumMap();
renderEnhancedView(); renderEnhancedView();
} else if (artistDetailPageState.currentArtistId) { } else if (artistDetailPageState.currentArtistId) {
await loadEnhancedViewData(artistDetailPageState.currentArtistId); await loadEnhancedViewData(artistDetailPageState.currentArtistId);
@ -37917,8 +38031,9 @@ async function runEnrichment(entityType, entityId, service, name, artistName, ar
} }
} }
// Close enrich dropdowns when clicking outside // Close enrich dropdowns when clicking outside (early bail when enhanced view isn't active)
document.addEventListener('click', (e) => { document.addEventListener('click', (e) => {
if (!artistDetailPageState.enhancedView) return;
if (!e.target.closest('.enhanced-enrich-wrap')) { if (!e.target.closest('.enhanced-enrich-wrap')) {
document.querySelectorAll('.enhanced-enrich-menu.visible').forEach(m => m.classList.remove('visible')); document.querySelectorAll('.enhanced-enrich-menu.visible').forEach(m => m.classList.remove('visible'));
} }

View file

@ -33994,9 +33994,9 @@ textarea.enhanced-meta-field-input {
/* Expandable Tracks Panel */ /* Expandable Tracks Panel */
.enhanced-tracks-panel { .enhanced-tracks-panel {
max-height: 0; display: grid;
overflow: hidden; grid-template-rows: 0fr;
transition: max-height 0.35s cubic-bezier(0.4, 0, 0.2, 1); transition: grid-template-rows 0.3s ease-out;
background: rgba(0,0,0,0.2); background: rgba(0,0,0,0.2);
border: 1px solid rgba(var(--accent-rgb), 0.12); border: 1px solid rgba(var(--accent-rgb), 0.12);
border-top: none; border-top: none;
@ -34004,10 +34004,14 @@ textarea.enhanced-meta-field-input {
margin-bottom: 4px; margin-bottom: 4px;
} }
.enhanced-tracks-panel.visible { .enhanced-tracks-panel.visible {
max-height: 2000px; grid-template-rows: 1fr;
transition: max-height 0.5s cubic-bezier(0.4, 0, 0.2, 1);
} }
.enhanced-tracks-panel-inner { .enhanced-tracks-panel-inner {
overflow: hidden;
padding: 0 16px;
transition: padding 0.3s ease-out;
}
.enhanced-tracks-panel.visible > .enhanced-tracks-panel-inner {
padding: 16px; padding: 16px;
} }
@ -34073,6 +34077,7 @@ textarea.enhanced-meta-field-input {
width: 100%; width: 100%;
border-collapse: collapse; border-collapse: collapse;
table-layout: fixed; table-layout: fixed;
contain: layout style;
} }
.enhanced-track-table th { .enhanced-track-table th {
background: rgba(255,255,255,0.06); background: rgba(255,255,255,0.06);