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.serverType = data.server_type || null;
_tagPreviewServerType = data.server_type || null;
_rebuildAlbumMap();
renderEnhancedView();
} catch (error) {
@ -36538,11 +36539,23 @@ function toggleAlbumExpand(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;
if (!data || !data.albums) return null;
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) {
const header = document.createElement('div');
header.className = 'enhanced-expanded-header';
@ -36781,6 +36794,323 @@ function renderAlbumMetaRow(album) {
return row;
}
function _buildTrackRow(track, album, admin) {
const tr = document.createElement('tr');
tr.dataset.trackId = track.id;
tr.dataset.albumId = album.id;
if (artistDetailPageState.selectedTracks.has(String(track.id))) tr.classList.add('selected');
// Checkbox (admin only)
if (admin) {
const cbTd = document.createElement('td');
const cb = document.createElement('input');
cb.type = 'checkbox';
cb.className = 'enhanced-track-checkbox';
cb.checked = artistDetailPageState.selectedTracks.has(String(track.id));
cbTd.appendChild(cb);
tr.appendChild(cbTd);
}
// Play button
const playTd = document.createElement('td');
playTd.className = 'col-play';
const playBtn = document.createElement('button');
playBtn.className = 'enhanced-play-btn';
playBtn.innerHTML = '▶';
playBtn.title = track.file_path ? 'Play track' : 'No file available';
if (!track.file_path) playBtn.disabled = true;
playTd.appendChild(playBtn);
tr.appendChild(playTd);
// Track number
const numTd = document.createElement('td');
numTd.className = 'col-num' + (admin ? ' editable' : '');
numTd.textContent = track.track_number || '-';
tr.appendChild(numTd);
// Disc number
const discTd = document.createElement('td');
discTd.className = 'col-disc';
discTd.textContent = track.disc_number || '-';
tr.appendChild(discTd);
// Title
const titleTd = document.createElement('td');
titleTd.className = 'col-title' + (admin ? ' editable' : '');
titleTd.textContent = track.title || 'Unknown';
tr.appendChild(titleTd);
// Duration
const durTd = document.createElement('td');
durTd.className = 'col-duration';
durTd.textContent = formatDurationMs(track.duration);
tr.appendChild(durTd);
// Format
const fmtTd = document.createElement('td');
fmtTd.className = 'col-format';
const format = extractFormat(track.file_path);
const fmtSpan = document.createElement('span');
const fmtClass = format === 'FLAC' ? 'flac' : (format === 'MP3' ? 'mp3' : 'other');
fmtSpan.className = `enhanced-format-badge ${fmtClass}`;
fmtSpan.textContent = format;
fmtTd.appendChild(fmtSpan);
tr.appendChild(fmtTd);
// Bitrate
const brTd = document.createElement('td');
brTd.className = 'col-bitrate';
const brSpan = document.createElement('span');
const brClass = (track.bitrate || 0) >= 320 ? 'high' : ((track.bitrate || 0) >= 192 ? 'medium' : 'low');
brSpan.className = `enhanced-bitrate ${brClass}`;
brSpan.textContent = track.bitrate ? track.bitrate + ' kbps' : '-';
brTd.appendChild(brSpan);
tr.appendChild(brTd);
// BPM
const bpmTd = document.createElement('td');
bpmTd.className = 'col-bpm' + (admin ? ' editable' : '');
bpmTd.textContent = track.bpm || '-';
tr.appendChild(bpmTd);
// File path
const pathTd = document.createElement('td');
pathTd.className = 'col-path';
const filePath = track.file_path || '-';
const fileName = filePath !== '-' ? filePath.split(/[\\/]/).pop() : '-';
pathTd.textContent = fileName;
pathTd.title = filePath;
tr.appendChild(pathTd);
// Match status chips
const matchTd = document.createElement('td');
matchTd.className = 'col-match';
const matchCell = document.createElement('div');
matchCell.className = 'enhanced-track-match-cell';
const trackServices = [
{ svc: 'spotify', col: 'spotify_track_id', label: 'SP' },
{ svc: 'musicbrainz', col: 'musicbrainz_recording_id', label: 'MB' },
{ svc: 'deezer', col: 'deezer_id', label: 'Dz' },
{ svc: 'audiodb', col: 'audiodb_id', label: 'ADB' },
{ svc: 'itunes', col: 'itunes_track_id', label: 'iT' },
{ svc: 'lastfm', col: 'lastfm_url', label: 'LFM' },
{ svc: 'genius', col: 'genius_id', label: 'Gen' },
];
trackServices.forEach(s => {
const hasId = !!track[s.col];
const chip = document.createElement('span');
chip.className = 'enhanced-track-match-chip' + (hasId ? ' matched' : ' not-found');
chip.textContent = s.label;
chip.title = hasId ? `${s.svc}: ${track[s.col]}` : `${s.svc}: no match`;
chip.dataset.service = s.svc;
matchCell.appendChild(chip);
});
matchTd.appendChild(matchCell);
tr.appendChild(matchTd);
// Add to Queue button
const queueTd = document.createElement('td');
queueTd.className = 'col-queue';
if (track.file_path) {
const queueBtn = document.createElement('button');
queueBtn.className = 'enhanced-queue-btn';
queueBtn.innerHTML = '+';
queueBtn.title = 'Add to queue';
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 = '✎';
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 = '✕';
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 = '⚑';
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();
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 : '';
let albumArt = album.thumb_url || null;
if (!albumArt && artistDetailPageState.enhancedData) {
albumArt = artistDetailPageState.enhancedData.artist?.thumb_url;
}
addToQueue({
title: track.title || 'Unknown Track',
artist: artistName || 'Unknown Artist',
album: album.title || 'Unknown Album',
file_path: track.file_path,
filename: track.file_path,
is_library: true,
image_url: albumArt,
id: track.id,
artist_id: artistDetailPageState.enhancedData ? artistDetailPageState.enhancedData.artist.id : null,
album_id: album.id,
bitrate: track.bitrate,
sample_rate: track.sample_rate
});
}
return;
}
// Write tags button (admin)
if (target.closest('.enhanced-write-tag-btn')) {
e.stopPropagation();
showTagPreview(track.id);
return;
}
// Delete button (admin)
if (target.closest('.enhanced-delete-btn')) {
e.stopPropagation();
deleteLibraryTrack(track.id, album.id);
return;
}
// Report button (non-admin)
if (target.closest('.enhanced-track-report-btn')) {
e.stopPropagation();
const artistName = artistDetailPageState.enhancedData ? artistDetailPageState.enhancedData.artist.name : '';
showReportIssueModal('track', track.id, track.title || 'Unknown', artistName, album.title || '');
return;
}
});
}
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 || [];
@ -36798,6 +37128,7 @@ function renderTrackTable(album) {
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
@ -36813,7 +37144,6 @@ function renderTrackTable(album) {
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);
}
@ -36837,34 +37167,19 @@ function renderTrackTable(album) {
{ label: '', cls: 'col-report' },
]),
];
const currentSort = artistDetailPageState.enhancedTrackSort[album.id];
columns.forEach(col => {
const th = document.createElement('th');
th.className = col.cls;
const sortField = col.sortField;
const currentSort = artistDetailPageState.enhancedTrackSort[album.id];
if (sortField) {
if (col.sortField) {
let headerText = col.label;
if (currentSort && currentSort.field === sortField) {
if (currentSort && currentSort.field === col.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);
}
}
};
th.dataset.sortField = col.sortField;
th.dataset.label = col.label;
} else {
th.textContent = col.label;
}
@ -36876,223 +37191,14 @@ function renderTrackTable(album) {
// Body
const tbody = document.createElement('tbody');
tracks.forEach(track => {
const tr = document.createElement('tr');
tr.dataset.trackId = track.id;
if (artistDetailPageState.selectedTracks.has(String(track.id))) tr.classList.add('selected');
// Checkbox (admin only)
if (admin) {
const cbTd = document.createElement('td');
const cb = document.createElement('input');
cb.type = 'checkbox';
cb.className = 'enhanced-track-checkbox';
cb.checked = artistDetailPageState.selectedTracks.has(String(track.id));
cb.onchange = () => toggleTrackSelection(String(track.id));
cbTd.appendChild(cb);
tr.appendChild(cbTd);
}
// Play button
const playTd = document.createElement('td');
playTd.className = 'col-play';
const playBtn = document.createElement('button');
playBtn.className = 'enhanced-play-btn';
playBtn.innerHTML = '▶';
playBtn.title = 'Play track';
if (track.file_path) {
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);
tr.appendChild(playTd);
// Track number (editable for admin)
const numTd = document.createElement('td');
numTd.className = 'col-num' + (admin ? ' editable' : '');
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);
// Disc number
const discTd = document.createElement('td');
discTd.className = 'col-disc';
discTd.textContent = track.disc_number || '-';
tr.appendChild(discTd);
// Title (editable for admin)
const titleTd = document.createElement('td');
titleTd.className = 'col-title' + (admin ? ' editable' : '');
titleTd.textContent = track.title || 'Unknown';
if (admin) {
titleTd.onclick = (e) => { e.stopPropagation(); startInlineEdit(titleTd, 'track', track.id, 'title', track.title || ''); };
}
tr.appendChild(titleTd);
// Duration
const durTd = document.createElement('td');
durTd.className = 'col-duration';
durTd.textContent = formatDurationMs(track.duration);
tr.appendChild(durTd);
// Format
const fmtTd = document.createElement('td');
fmtTd.className = 'col-format';
const format = extractFormat(track.file_path);
const fmtSpan = document.createElement('span');
const fmtClass = format === 'FLAC' ? 'flac' : (format === 'MP3' ? 'mp3' : 'other');
fmtSpan.className = `enhanced-format-badge ${fmtClass}`;
fmtSpan.textContent = format;
fmtTd.appendChild(fmtSpan);
tr.appendChild(fmtTd);
// Bitrate
const brTd = document.createElement('td');
brTd.className = 'col-bitrate';
const brSpan = document.createElement('span');
const brClass = (track.bitrate || 0) >= 320 ? 'high' : ((track.bitrate || 0) >= 192 ? 'medium' : 'low');
brSpan.className = `enhanced-bitrate ${brClass}`;
brSpan.textContent = track.bitrate ? track.bitrate + ' kbps' : '-';
brTd.appendChild(brSpan);
tr.appendChild(brTd);
// BPM (editable for admin)
const bpmTd = document.createElement('td');
bpmTd.className = 'col-bpm' + (admin ? ' editable' : '');
bpmTd.textContent = track.bpm || '-';
if (admin) {
bpmTd.onclick = (e) => { e.stopPropagation(); startInlineEdit(bpmTd, 'track', track.id, 'bpm', track.bpm || ''); };
}
tr.appendChild(bpmTd);
// File path (last column)
const pathTd = document.createElement('td');
pathTd.className = 'col-path';
const filePath = track.file_path || '-';
const fileName = filePath !== '-' ? filePath.split(/[\\/]/).pop() : '-';
pathTd.textContent = fileName;
pathTd.title = filePath;
tr.appendChild(pathTd);
// Match status chips
const matchTd = document.createElement('td');
matchTd.className = 'col-match';
const matchCell = document.createElement('div');
matchCell.className = 'enhanced-track-match-cell';
const trackServices = [
{ svc: 'spotify', col: 'spotify_track_id', label: 'SP' },
{ svc: 'musicbrainz', col: 'musicbrainz_recording_id', label: 'MB' },
{ svc: 'deezer', col: 'deezer_id', label: 'Dz' },
{ svc: 'audiodb', col: 'audiodb_id', label: 'ADB' },
{ svc: 'itunes', col: 'itunes_track_id', label: 'iT' },
{ svc: 'lastfm', col: 'lastfm_url', label: 'LFM' },
{ svc: 'genius', col: 'genius_id', label: 'Gen' },
];
const aId = artistDetailPageState.enhancedData ? artistDetailPageState.enhancedData.artist.id : null;
trackServices.forEach(s => {
const hasId = !!track[s.col];
const chip = document.createElement('span');
chip.className = 'enhanced-track-match-chip' + (hasId ? ' matched' : ' not-found');
chip.textContent = s.label;
chip.title = hasId ? `${s.svc}: ${track[s.col]}` : `${s.svc}: no match`;
if (admin) {
chip.onclick = (e) => {
e.stopPropagation();
openManualMatchModal('track', track.id, s.svc, track.title || '', aId);
};
}
matchCell.appendChild(chip);
});
matchTd.appendChild(matchCell);
tr.appendChild(matchTd);
// Add to Queue button
const queueTd = document.createElement('td');
queueTd.className = 'col-queue';
if (track.file_path) {
const queueBtn = document.createElement('button');
queueBtn.className = 'enhanced-queue-btn';
queueBtn.innerHTML = '+';
queueBtn.title = 'Add to queue';
queueBtn.onclick = (e) => {
e.stopPropagation();
const artistName = artistDetailPageState.enhancedData ? artistDetailPageState.enhancedData.artist.name : '';
let albumArt = album.thumb_url || null;
if (!albumArt && artistDetailPageState.enhancedData) {
albumArt = artistDetailPageState.enhancedData.artist?.thumb_url;
}
addToQueue({
title: track.title || 'Unknown Track',
artist: artistName || 'Unknown Artist',
album: album.title || 'Unknown Album',
file_path: track.file_path,
filename: track.file_path,
is_library: true,
image_url: albumArt,
id: track.id,
artist_id: artistDetailPageState.enhancedData ? artistDetailPageState.enhancedData.artist.id : null,
album_id: album.id,
bitrate: track.bitrate,
sample_rate: track.sample_rate
});
};
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 = '✎';
tagBtn.title = 'Write tags to file';
tagBtn.onclick = (e) => { e.stopPropagation(); showTagPreview(track.id); };
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 = '✕';
delBtn.title = 'Delete track from library';
delBtn.onclick = (e) => { e.stopPropagation(); deleteLibraryTrack(track.id, album.id); };
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 = '⚑';
reportBtn.title = 'Report issue with this track';
reportBtn.onclick = (e) => {
e.stopPropagation();
const artistName = artistDetailPageState.enhancedData ? artistDetailPageState.enhancedData.artist.name : '';
showReportIssueModal('track', track.id, track.title || 'Unknown', artistName, album.title || '');
};
reportTd.appendChild(reportBtn);
tr.appendChild(reportTd);
}
tbody.appendChild(tr);
tbody.appendChild(_buildTrackRow(track, album, admin));
});
table.appendChild(tbody);
wrapper.appendChild(table);
// Single delegated event listener for the whole table
_attachTableDelegation(table, album);
wrapper.appendChild(table);
return wrapper;
}
@ -37153,6 +37259,7 @@ async function deleteLibraryAlbum(albumId) {
album.tracks.forEach(t => artistDetailPageState.selectedTracks.delete(String(t.id)));
}
artistDetailPageState.enhancedData.albums = (artistDetailPageState.enhancedData.albums || []).filter(a => a.id !== albumId);
_rebuildAlbumMap();
}
artistDetailPageState.expandedAlbums.delete(albumId);
delete artistDetailPageState.enhancedTrackSort[albumId];
@ -37365,32 +37472,37 @@ function toggleSelectAllTracks(albumId, checked) {
const album = findEnhancedAlbum(albumId);
if (!album || !album.tracks) return;
// Batch update state
album.tracks.forEach(track => {
const tid = String(track.id);
if (checked) artistDetailPageState.selectedTracks.add(tid);
else artistDetailPageState.selectedTracks.delete(tid);
});
const row = document.querySelector(`tr[data-track-id="${tid}"]`);
if (row) {
// Scoped DOM query — only search within this album's panel, not entire document
const panel = document.getElementById(`enhanced-tracks-panel-${albumId}`);
if (panel) {
panel.querySelectorAll('tr[data-track-id]').forEach(row => {
row.classList.toggle('selected', checked);
const cb = row.querySelector('.enhanced-track-checkbox');
if (cb) cb.checked = checked;
}
});
});
}
updateBulkBar();
}
function clearTrackSelection() {
artistDetailPageState.selectedTracks.forEach(tid => {
const row = document.querySelector(`tr[data-track-id="${tid}"]`);
if (row) {
// Scoped batch clear — query the container once instead of per-track
const container = document.getElementById('enhanced-view-container');
if (container) {
container.querySelectorAll('tr[data-track-id].selected').forEach(row => {
row.classList.remove('selected');
const cb = row.querySelector('.enhanced-track-checkbox');
if (cb) cb.checked = false;
}
});
});
container.querySelectorAll('.enhanced-track-table thead .enhanced-track-checkbox').forEach(cb => cb.checked = false);
}
artistDetailPageState.selectedTracks.clear();
document.querySelectorAll('.enhanced-track-table thead .enhanced-track-checkbox').forEach(cb => cb.checked = false);
updateBulkBar();
}
@ -37821,6 +37933,7 @@ async function applyManualMatch(entityType, entityId, service, serviceId, artist
// Update view with fresh data
if (result.updated_data && result.updated_data.success) {
artistDetailPageState.enhancedData = result.updated_data;
_rebuildAlbumMap();
renderEnhancedView();
} else if (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)
if (result.updated_data && result.updated_data.success) {
artistDetailPageState.enhancedData = result.updated_data;
_rebuildAlbumMap();
renderEnhancedView();
} else if (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) => {
if (!artistDetailPageState.enhancedView) return;
if (!e.target.closest('.enhanced-enrich-wrap')) {
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 */
.enhanced-tracks-panel {
max-height: 0;
overflow: hidden;
transition: max-height 0.35s cubic-bezier(0.4, 0, 0.2, 1);
display: grid;
grid-template-rows: 0fr;
transition: grid-template-rows 0.3s ease-out;
background: rgba(0,0,0,0.2);
border: 1px solid rgba(var(--accent-rgb), 0.12);
border-top: none;
@ -34004,10 +34004,14 @@ textarea.enhanced-meta-field-input {
margin-bottom: 4px;
}
.enhanced-tracks-panel.visible {
max-height: 2000px;
transition: max-height 0.5s cubic-bezier(0.4, 0, 0.2, 1);
grid-template-rows: 1fr;
}
.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;
}
@ -34073,6 +34077,7 @@ textarea.enhanced-meta-field-input {
width: 100%;
border-collapse: collapse;
table-layout: fixed;
contain: layout style;
}
.enhanced-track-table th {
background: rgba(255,255,255,0.06);