feat: add search filter bar to playlist detail and favorites views
This commit is contained in:
1 parent
d5470a6440
commit
e78df7bdb9
4 files changed
+37
-2
No files matched your search
@@ -232,6 +232,10 @@
|
|||||||
<button class="sort-chip" data-sort="artist" onclick="loadStarred('artist')">Künstler</button>
|
<button class="sort-chip" data-sort="artist" onclick="loadStarred('artist')">Künstler</button>
|
||||||
<button class="sort-chip" data-sort="plays" onclick="loadStarred('plays')">Meistgespielt</button>
|
<button class="sort-chip" data-sort="plays" onclick="loadStarred('plays')">Meistgespielt</button>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="list-search-wrap">
|
||||||
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>
|
||||||
|
<input id="starred-search" class="list-search" type="text" placeholder="In Favoriten suchen…" oninput="filterStarred(this.value)">
|
||||||
|
</div>
|
||||||
<div id="starred-songs" class="song-list"></div>
|
<div id="starred-songs" class="song-list"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -265,6 +269,10 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="list-search-wrap">
|
||||||
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>
|
||||||
|
<input id="playlist-search" class="list-search" type="text" placeholder="In Playlist suchen…" oninput="filterPlaylist(this.value)">
|
||||||
|
</div>
|
||||||
<div class="song-list" id="playlist-songs"></div>
|
<div class="song-list" id="playlist-songs"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -15,7 +15,7 @@ import {
|
|||||||
navigate, _navGoBack, _navLoad, onLocalSearch, onMobileSearch,
|
navigate, _navGoBack, _navLoad, onLocalSearch, onMobileSearch,
|
||||||
onGlobalSearch, s_goArtist, s_goAlbum, playerBarGoArtist, playerBarGoAlbum,
|
onGlobalSearch, s_goArtist, s_goAlbum, playerBarGoArtist, playerBarGoAlbum,
|
||||||
searchSimilarArtist, searchAndPreview, loadItunesAlbum, expandArtistBio,
|
searchSimilarArtist, searchAndPreview, loadItunesAlbum, expandArtistBio,
|
||||||
loadAlbums, loadStarred, loadRandom,
|
loadAlbums, loadStarred, loadRandom, filterStarred, filterPlaylist,
|
||||||
} from './views.js';
|
} from './views.js';
|
||||||
import { loadSpotifyImporter, resolveSpotifyLink, clearSpotifyPreview, spotifyDlAll } from './spotify.js';
|
import { loadSpotifyImporter, resolveSpotifyLink, clearSpotifyPreview, spotifyDlAll } from './spotify.js';
|
||||||
import {
|
import {
|
||||||
@@ -282,7 +282,7 @@ Object.assign(window, {
|
|||||||
openSettings, closeSettings, saveSettings, confirmDisconnect, checkBackend,
|
openSettings, closeSettings, saveSettings, confirmDisconnect, checkBackend,
|
||||||
openSidebar, closeSidebar, switchNavMode, showCtxMenu,
|
openSidebar, closeSidebar, switchNavMode, showCtxMenu,
|
||||||
// Views
|
// Views
|
||||||
loadAlbums, loadStarred, loadRandom, loadSpotifyImporter, resolveSpotifyLink, clearSpotifyPreview, spotifyDlAll,
|
loadAlbums, loadStarred, loadRandom, filterStarred, filterPlaylist, loadSpotifyImporter, resolveSpotifyLink, clearSpotifyPreview, spotifyDlAll,
|
||||||
executeSpotifyDownload,
|
executeSpotifyDownload,
|
||||||
openPlModal, closePlModal, addSongsToPlaylist, createAndAddPlaylist, createPlaylistPrompt,
|
openPlModal, closePlModal, addSongsToPlaylist, createAndAddPlaylist, createPlaylistPrompt,
|
||||||
showSongInfo, closeInfoModal,
|
showSongInfo, closeInfoModal,
|
||||||
|
|||||||
@@ -557,11 +557,22 @@ export async function loadPlaylistDetail(pl) {
|
|||||||
};
|
};
|
||||||
sl.innerHTML = '';
|
sl.innerHTML = '';
|
||||||
sl.classList.add('pl-draggable');
|
sl.classList.add('pl-draggable');
|
||||||
|
const plSearch = document.getElementById('playlist-search');
|
||||||
|
if (plSearch) plSearch.value = '';
|
||||||
songs.forEach((s,i) => sl.appendChild(makeSongItem(s, i, songs, { noArtistLink: true, noAlbumLink: true })));
|
songs.forEach((s,i) => sl.appendChild(makeSongItem(s, i, songs, { noArtistLink: true, noAlbumLink: true })));
|
||||||
_initPlaylistDragDrop(sl, songs, S._currentPlaylist);
|
_initPlaylistDragDrop(sl, songs, S._currentPlaylist);
|
||||||
} catch(e) { sl.innerHTML = `<p style="color:var(--danger)">${e.message}</p>`; }
|
} catch(e) { sl.innerHTML = `<p style="color:var(--danger)">${e.message}</p>`; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function filterPlaylist(val) {
|
||||||
|
const q = val.trim().toLowerCase();
|
||||||
|
document.querySelectorAll('#playlist-songs .song-item').forEach(item => {
|
||||||
|
const title = (item.querySelector('.song-title')?.textContent || '').toLowerCase();
|
||||||
|
const artist = (item.querySelector('.song-artist')?.textContent || '').toLowerCase();
|
||||||
|
item.style.display = (!q || title.includes(q) || artist.includes(q)) ? '' : 'none';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function _initPlaylistDragDrop(container, songs, pl) {
|
function _initPlaylistDragDrop(container, songs, pl) {
|
||||||
let dragSrc = null;
|
let dragSrc = null;
|
||||||
let saveTimer = null;
|
let saveTimer = null;
|
||||||
@@ -645,6 +656,8 @@ export async function loadStarred(sort = _starredSort) {
|
|||||||
c.classList.toggle('active', c.dataset.sort === sort));
|
c.classList.toggle('active', c.dataset.sort === sort));
|
||||||
const el = document.getElementById('starred-songs');
|
const el = document.getElementById('starred-songs');
|
||||||
el.innerHTML = '<div class="spinner"></div>';
|
el.innerHTML = '<div class="spinner"></div>';
|
||||||
|
const searchEl = document.getElementById('starred-search');
|
||||||
|
if (searchEl) searchEl.value = '';
|
||||||
try {
|
try {
|
||||||
const r = await api('getStarred2');
|
const r = await api('getStarred2');
|
||||||
let songs = (r.starred2?.song || []).map(s => ({...s, _starred: true}));
|
let songs = (r.starred2?.song || []).map(s => ({...s, _starred: true}));
|
||||||
@@ -659,6 +672,15 @@ export async function loadStarred(sort = _starredSort) {
|
|||||||
} catch(e) { el.innerHTML = `<p style="color:var(--danger)">${e.message}</p>`; }
|
} catch(e) { el.innerHTML = `<p style="color:var(--danger)">${e.message}</p>`; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function filterStarred(val) {
|
||||||
|
const q = val.trim().toLowerCase();
|
||||||
|
document.querySelectorAll('#starred-songs .song-item').forEach(item => {
|
||||||
|
const title = (item.querySelector('.song-title')?.textContent || '').toLowerCase();
|
||||||
|
const artist = (item.querySelector('.song-artist')?.textContent || '').toLowerCase();
|
||||||
|
item.style.display = (!q || title.includes(q) || artist.includes(q)) ? '' : 'none';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
export async function loadRandom() {
|
export async function loadRandom() {
|
||||||
const el = document.getElementById('random-songs');
|
const el = document.getElementById('random-songs');
|
||||||
el.innerHTML = '<div class="spinner"></div>';
|
el.innerHTML = '<div class="spinner"></div>';
|
||||||
|
|||||||
@@ -718,6 +718,11 @@ nav{flex:1;overflow-y:auto;padding:4px 10px}
|
|||||||
.sort-chip{padding:6px 14px;background:var(--surface);border:1px solid var(--border2);border-radius:20px;font-size:12px;font-weight:600;cursor:pointer;white-space:nowrap;color:var(--text2);font-family:var(--font);transition:border-color .15s,color .15s,background .15s}
|
.sort-chip{padding:6px 14px;background:var(--surface);border:1px solid var(--border2);border-radius:20px;font-size:12px;font-weight:600;cursor:pointer;white-space:nowrap;color:var(--text2);font-family:var(--font);transition:border-color .15s,color .15s,background .15s}
|
||||||
.sort-chip:hover{border-color:var(--accent);color:var(--accent)}
|
.sort-chip:hover{border-color:var(--accent);color:var(--accent)}
|
||||||
.sort-chip.active{background:var(--accent);border-color:var(--accent);color:#000}
|
.sort-chip.active{background:var(--accent);border-color:var(--accent);color:#000}
|
||||||
|
.list-search-wrap{position:relative;margin-bottom:12px}
|
||||||
|
.list-search-wrap svg{position:absolute;left:10px;top:50%;transform:translateY(-50%);color:var(--text3);pointer-events:none}
|
||||||
|
.list-search{width:100%;box-sizing:border-box;padding:8px 12px 8px 32px;background:var(--surface);border:1px solid var(--border2);border-radius:8px;color:var(--text1);font-size:13px;font-family:var(--font);outline:none;transition:border-color .15s}
|
||||||
|
.list-search:focus{border-color:var(--accent)}
|
||||||
|
.list-search::placeholder{color:var(--text3)}
|
||||||
.topsong-item{display:grid;grid-template-columns:28px 44px 1fr auto;align-items:center;gap:10px;padding:7px 10px;border-radius:8px;cursor:pointer;transition:background .15s}
|
.topsong-item{display:grid;grid-template-columns:28px 44px 1fr auto;align-items:center;gap:10px;padding:7px 10px;border-radius:8px;cursor:pointer;transition:background .15s}
|
||||||
.topsong-item:hover{background:var(--surface)}
|
.topsong-item:hover{background:var(--surface)}
|
||||||
.topsong-num{font-size:11px;color:var(--text3);font-family:var(--mono);text-align:center}
|
.topsong-num{font-size:11px;color:var(--text3);font-family:var(--mono);text-align:center}
|
||||||
|
|||||||
Reference in new issue
Block a user