fix sorting on monochrome's playlists
This commit is contained in:
parent
cf499baeb4
commit
26acd4b225
3 changed files with 93 additions and 101 deletions
|
|
@ -53,7 +53,7 @@
|
|||
|
||||
<div id="sort-menu" style="display: none">
|
||||
<ul>
|
||||
<li data-sort="custom">Playlist Order</li>
|
||||
<li data-sort="custom" class="requires-custom-order">Playlist Order</li>
|
||||
<li data-sort="added-newest" class="requires-added-date">Date Added (Newest)</li>
|
||||
<li data-sort="added-oldest" class="requires-added-date">Date Added (Oldest)</li>
|
||||
<li data-sort="title">Title (A-Z)</li>
|
||||
|
|
|
|||
188
js/ui.js
188
js/ui.js
|
|
@ -43,6 +43,37 @@ import {
|
|||
createTrackFromSong,
|
||||
} from './tracker.js';
|
||||
|
||||
function sortTracks(tracks, sortType) {
|
||||
if (sortType === 'custom') return [...tracks];
|
||||
const sorted = [...tracks];
|
||||
switch (sortType) {
|
||||
case 'added-newest':
|
||||
return sorted.sort((a, b) => (b.addedAt || 0) - (a.addedAt || 0));
|
||||
case 'added-oldest':
|
||||
return sorted.sort((a, b) => (a.addedAt || 0) - (b.addedAt || 0));
|
||||
case 'title':
|
||||
return sorted.sort((a, b) => (a.title || '').localeCompare(b.title || ''));
|
||||
case 'artist':
|
||||
return sorted.sort((a, b) => {
|
||||
const artistA = a.artist?.name || a.artists?.[0]?.name || '';
|
||||
const artistB = b.artist?.name || b.artists?.[0]?.name || '';
|
||||
return artistA.localeCompare(artistB);
|
||||
});
|
||||
case 'album':
|
||||
return sorted.sort((a, b) => {
|
||||
const albumA = a.album?.title || '';
|
||||
const albumB = b.album?.title || '';
|
||||
const albumCompare = albumA.localeCompare(albumB);
|
||||
if (albumCompare !== 0) return albumCompare;
|
||||
const trackNumA = a.trackNumber || a.position || 0;
|
||||
const trackNumB = b.trackNumber || b.position || 0;
|
||||
return trackNumA - trackNumB;
|
||||
});
|
||||
default:
|
||||
return sorted;
|
||||
}
|
||||
}
|
||||
|
||||
export class UIRenderer {
|
||||
constructor(api, player) {
|
||||
this.api = api;
|
||||
|
|
@ -2131,9 +2162,12 @@ export class UIRenderer {
|
|||
|
||||
const originalTracks = [...tracks];
|
||||
let currentTracks = [...tracks];
|
||||
currentSort = 'custom';
|
||||
|
||||
const renderTracks = () => {
|
||||
tracklistContainer.innerHTML = `
|
||||
// Re-fetch container each time because enableTrackReordering clones it
|
||||
const container = document.getElementById('playlist-detail-tracklist');
|
||||
container.innerHTML = `
|
||||
<div class="track-list-header">
|
||||
<span style="width: 40px; text-align: center;">#</span>
|
||||
<span>Title</span>
|
||||
|
|
@ -2141,11 +2175,11 @@ export class UIRenderer {
|
|||
<span style="display: flex; justify-content: flex-end; opacity: 0.8;">Menu</span>
|
||||
</div>
|
||||
`;
|
||||
this.renderListWithTracks(tracklistContainer, currentTracks, true, true);
|
||||
this.renderListWithTracks(container, currentTracks, true, true);
|
||||
|
||||
// Add remove buttons and enable reordering ONLY IF OWNED
|
||||
if (ownedPlaylist) {
|
||||
const trackItems = tracklistContainer.querySelectorAll('.track-item');
|
||||
const trackItems = container.querySelectorAll('.track-item');
|
||||
trackItems.forEach((item, index) => {
|
||||
const actionsDiv = item.querySelector('.track-item-actions');
|
||||
const removeBtn = document.createElement('button');
|
||||
|
|
@ -2160,46 +2194,19 @@ export class UIRenderer {
|
|||
});
|
||||
|
||||
if (currentSort === 'custom') {
|
||||
tracklistContainer.classList.add('is-editable');
|
||||
this.enableTrackReordering(tracklistContainer, currentTracks, playlistId, syncManager);
|
||||
container.classList.add('is-editable');
|
||||
this.enableTrackReordering(container, currentTracks, playlistId, syncManager);
|
||||
} else {
|
||||
tracklistContainer.classList.remove('is-editable');
|
||||
container.classList.remove('is-editable');
|
||||
}
|
||||
} else {
|
||||
tracklistContainer.classList.remove('is-editable');
|
||||
container.classList.remove('is-editable');
|
||||
}
|
||||
};
|
||||
|
||||
const applySort = (sortType) => {
|
||||
currentSort = sortType;
|
||||
if (sortType === 'custom') {
|
||||
currentTracks = [...originalTracks];
|
||||
} else if (sortType === 'added-newest') {
|
||||
currentTracks = [...originalTracks].sort((a, b) => (b.addedAt || 0) - (a.addedAt || 0));
|
||||
} else if (sortType === 'added-oldest') {
|
||||
currentTracks = [...originalTracks].sort((a, b) => (a.addedAt || 0) - (b.addedAt || 0));
|
||||
} else if (sortType === 'title') {
|
||||
currentTracks = [...originalTracks].sort((a, b) =>
|
||||
(a.title || '').localeCompare(b.title || '')
|
||||
);
|
||||
} else if (sortType === 'artist') {
|
||||
currentTracks = [...originalTracks].sort((a, b) => {
|
||||
const artistA = a.artist?.name || a.artists?.[0]?.name || '';
|
||||
const artistB = b.artist?.name || b.artists?.[0]?.name || '';
|
||||
return artistA.localeCompare(artistB);
|
||||
});
|
||||
} else if (sortType === 'album') {
|
||||
currentTracks = [...originalTracks].sort((a, b) => {
|
||||
const albumA = a.album?.title || '';
|
||||
const albumB = b.album?.title || '';
|
||||
const albumCompare = albumA.localeCompare(albumB);
|
||||
if (albumCompare !== 0) return albumCompare;
|
||||
// If same album, sort by track number
|
||||
const trackNumA = a.trackNumber || a.position || 0;
|
||||
const trackNumB = b.trackNumber || b.position || 0;
|
||||
return trackNumA - trackNumB;
|
||||
});
|
||||
}
|
||||
currentTracks = sortTracks(originalTracks, sortType);
|
||||
renderTracks();
|
||||
};
|
||||
|
||||
|
|
@ -2216,13 +2223,14 @@ export class UIRenderer {
|
|||
this.loadRecommendedSongsForPlaylist(tracks);
|
||||
}
|
||||
|
||||
// Render Actions (Shuffle, Edit, Delete, Share, Sort)
|
||||
// Render Actions (Sort, Shuffle, Edit, Delete, Share)
|
||||
this.updatePlaylistHeaderActions(
|
||||
playlistData,
|
||||
!!ownedPlaylist,
|
||||
tracks,
|
||||
currentTracks,
|
||||
false,
|
||||
ownedPlaylist ? applySort : null
|
||||
applySort,
|
||||
() => currentSort
|
||||
);
|
||||
|
||||
playBtn.onclick = () => {
|
||||
|
|
@ -2305,34 +2313,7 @@ export class UIRenderer {
|
|||
|
||||
const applySort = (sortType) => {
|
||||
currentSort = sortType;
|
||||
if (sortType === 'custom') {
|
||||
currentTracks = [...originalTracks];
|
||||
} else if (sortType === 'added-newest') {
|
||||
currentTracks = [...originalTracks].sort((a, b) => (b.addedAt || 0) - (a.addedAt || 0));
|
||||
} else if (sortType === 'added-oldest') {
|
||||
currentTracks = [...originalTracks].sort((a, b) => (a.addedAt || 0) - (b.addedAt || 0));
|
||||
} else if (sortType === 'title') {
|
||||
currentTracks = [...originalTracks].sort((a, b) =>
|
||||
(a.title || '').localeCompare(b.title || '')
|
||||
);
|
||||
} else if (sortType === 'artist') {
|
||||
currentTracks = [...originalTracks].sort((a, b) => {
|
||||
const artistA = a.artist?.name || a.artists?.[0]?.name || '';
|
||||
const artistB = b.artist?.name || b.artists?.[0]?.name || '';
|
||||
return artistA.localeCompare(artistB);
|
||||
});
|
||||
} else if (sortType === 'album') {
|
||||
currentTracks = [...originalTracks].sort((a, b) => {
|
||||
const albumA = a.album?.title || '';
|
||||
const albumB = b.album?.title || '';
|
||||
const albumCompare = albumA.localeCompare(albumB);
|
||||
if (albumCompare !== 0) return albumCompare;
|
||||
// If same album, sort by track number
|
||||
const trackNumA = a.trackNumber || a.position || 0;
|
||||
const trackNumB = b.trackNumber || b.position || 0;
|
||||
return trackNumA - trackNumB;
|
||||
});
|
||||
}
|
||||
currentTracks = sortTracks(originalTracks, sortType);
|
||||
renderTracks();
|
||||
};
|
||||
|
||||
|
|
@ -2365,7 +2346,7 @@ export class UIRenderer {
|
|||
}
|
||||
|
||||
// Render Actions (Shuffle + Sort + Share)
|
||||
this.updatePlaylistHeaderActions(playlist, false, currentTracks, false, applySort);
|
||||
this.updatePlaylistHeaderActions(playlist, false, currentTracks, false, applySort, () => currentSort);
|
||||
|
||||
recentActivityManager.addPlaylist(playlist);
|
||||
document.title = playlist.title || 'Artist Mix';
|
||||
|
|
@ -2873,7 +2854,7 @@ export class UIRenderer {
|
|||
await renderTrackerTrackContent(trackId, container, this);
|
||||
}
|
||||
|
||||
updatePlaylistHeaderActions(playlist, isOwned, tracks, showShare = false, onSort = null) {
|
||||
updatePlaylistHeaderActions(playlist, isOwned, tracks, showShare = false, onSort = null, getCurrentSort = null) {
|
||||
const actionsDiv = document.getElementById('page-playlist').querySelector('.detail-header-actions');
|
||||
|
||||
// Cleanup existing dynamic buttons
|
||||
|
|
@ -2901,11 +2882,11 @@ export class UIRenderer {
|
|||
this.player.setQueue(shuffledTracks, 0);
|
||||
this.player.playTrackFromQueue();
|
||||
};
|
||||
fragment.appendChild(shuffleBtn);
|
||||
|
||||
// Sort button (always available if onSort is provided)
|
||||
let sortBtn = null;
|
||||
if (onSort) {
|
||||
const sortBtn = document.createElement('button');
|
||||
sortBtn = document.createElement('button');
|
||||
sortBtn.id = 'sort-playlist-btn';
|
||||
sortBtn.className = 'btn-secondary';
|
||||
sortBtn.innerHTML =
|
||||
|
|
@ -2915,12 +2896,20 @@ export class UIRenderer {
|
|||
e.stopPropagation();
|
||||
const menu = document.getElementById('sort-menu');
|
||||
|
||||
// Check if any track has addedAt data
|
||||
// Show "Date Added" if tracks have addedAt, otherwise show "Playlist Order"
|
||||
const hasAddedDate = tracks.some((t) => t.addedAt);
|
||||
const dateOptions = menu.querySelectorAll('.requires-added-date');
|
||||
dateOptions.forEach((opt) => {
|
||||
menu.querySelectorAll('.requires-added-date').forEach((opt) => {
|
||||
opt.style.display = hasAddedDate ? '' : 'none';
|
||||
});
|
||||
menu.querySelectorAll('.requires-custom-order').forEach((opt) => {
|
||||
opt.style.display = hasAddedDate ? 'none' : '';
|
||||
});
|
||||
|
||||
// Highlight current sort option
|
||||
const currentSortType = getCurrentSort ? getCurrentSort() : 'custom';
|
||||
menu.querySelectorAll('li').forEach((opt) => {
|
||||
opt.classList.toggle('sort-active', opt.dataset.sort === currentSortType);
|
||||
});
|
||||
|
||||
const rect = sortBtn.getBoundingClientRect();
|
||||
menu.style.top = `${rect.bottom + 5}px`;
|
||||
|
|
@ -2944,7 +2933,6 @@ export class UIRenderer {
|
|||
|
||||
setTimeout(() => document.addEventListener('click', closeMenu), 0);
|
||||
};
|
||||
fragment.appendChild(sortBtn);
|
||||
}
|
||||
|
||||
// Edit/Delete (Owned Only)
|
||||
|
|
@ -2979,39 +2967,39 @@ export class UIRenderer {
|
|||
fragment.appendChild(shareBtn);
|
||||
}
|
||||
|
||||
// Insert before Download button if possible, else append
|
||||
// Insert buttons in the correct order: Play, Shuffle, Download, Sort, Like, Edit/Delete/Share
|
||||
const dlBtn = actionsDiv.querySelector('#download-playlist-btn');
|
||||
const likeBtn = actionsDiv.querySelector('#like-playlist-btn');
|
||||
|
||||
if (dlBtn) {
|
||||
// We want Shuffle first, then Edit/Delete/Share.
|
||||
// But Download is usually first or second.
|
||||
// In renderPlaylistPage: Play, Download, Like.
|
||||
// We want Shuffle after Play? Or after Download?
|
||||
// Previous code: actionsDiv.insertBefore(shuffleBtn, dlBtn); => Shuffle before Download.
|
||||
// Then appended others.
|
||||
|
||||
// Let's just append everything for now to keep it simple, or insert Shuffle specifically.
|
||||
// The Play button is static. Download is static.
|
||||
|
||||
// If we want Shuffle before Download:
|
||||
// fragment has Shuffle, Edit, Delete, Share.
|
||||
// If we insert fragment before Download, all go before Download.
|
||||
// That might change the order.
|
||||
// Previous order: Shuffle (before Download), then Edit/Delete/Share (appended = after Like).
|
||||
|
||||
// Let's split fragment?
|
||||
// Or just use append for all.
|
||||
// The user didn't complain about order, but consistency is good.
|
||||
// "Fix popup buttons" was the request.
|
||||
|
||||
// Let's stick to appending for now to minimize visual layout shifts from previous (where Edit/Delete were appended).
|
||||
// Shuffle was inserted before Download.
|
||||
// Insert Shuffle before Download
|
||||
actionsDiv.insertBefore(shuffleBtn, dlBtn);
|
||||
// Append the rest
|
||||
|
||||
// Insert Sort after Download, before Like
|
||||
if (sortBtn) {
|
||||
if (likeBtn) {
|
||||
actionsDiv.insertBefore(sortBtn, likeBtn);
|
||||
} else {
|
||||
// If no Like button, insert Sort after Download
|
||||
if (dlBtn.nextSibling) {
|
||||
actionsDiv.insertBefore(sortBtn, dlBtn.nextSibling);
|
||||
} else {
|
||||
actionsDiv.appendChild(sortBtn);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Append Edit/Delete/Share buttons after Like
|
||||
while (fragment.firstChild) {
|
||||
actionsDiv.appendChild(fragment.firstChild);
|
||||
}
|
||||
} else {
|
||||
actionsDiv.appendChild(fragment);
|
||||
// If no Download button, just append everything
|
||||
actionsDiv.appendChild(shuffleBtn);
|
||||
if (sortBtn) actionsDiv.appendChild(sortBtn);
|
||||
while (fragment.firstChild) {
|
||||
actionsDiv.appendChild(fragment.firstChild);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2214,6 +2214,10 @@ input:checked + .slider::before {
|
|||
align-items: center;
|
||||
}
|
||||
|
||||
#sort-menu li.sort-active {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#context-menu li:hover,
|
||||
#sort-menu li:hover {
|
||||
background-color: var(--secondary);
|
||||
|
|
|
|||
Loading…
Reference in a new issue