Bri-Tunes/src/views/public/songs.ejs

296 lines
11 KiB
Plaintext

<h1>Songs</h1>
<% if (recentlyLiked && recentlyLiked.length > 0) { %>
<section class="liked-strip">
<h2 class="liked-strip-heading">Trending</h2>
<div class="liked-strip-scroll" id="trending-scroll">
<% recentlyLiked.forEach((s) => { %>
<a href="/songs/<%= s.slug %>" class="liked-card">
<% if (s.coverPath) { %>
<img src="/media/<%= s.coverPath %>" alt="" class="liked-card-cover lb-trigger" data-lightbox>
<% } else { %>
<div class="liked-card-cover liked-card-cover--fallback">♪</div>
<% } %>
<span class="liked-card-info">
<span class="liked-card-title"><%= s.title %></span>
<span class="liked-card-artist"><%= s.artist %></span>
</span>
</a>
<% }) %>
</div>
</section>
<script>
(function () {
var el = document.getElementById('trending-scroll');
if (!el) return;
var dragging = false, startX, scrollLeft;
el.addEventListener('mousedown', function (e) {
dragging = true;
startX = e.pageX - el.offsetLeft;
scrollLeft = el.scrollLeft;
el.style.cursor = 'grabbing';
el.style.userSelect = 'none';
});
document.addEventListener('mouseup', function () {
dragging = false;
el.style.cursor = '';
el.style.userSelect = '';
});
document.addEventListener('mousemove', function (e) {
if (!dragging) return;
e.preventDefault();
var x = e.pageX - el.offsetLeft;
el.scrollLeft = scrollLeft - (x - startX);
});
})();
</script>
<% } %>
<% if (mostPopular && mostPopular.length > 0) { %>
<section class="liked-strip">
<h2 class="liked-strip-heading">Most Popular</h2>
<div class="liked-strip-scroll" id="popular-scroll">
<% mostPopular.forEach((s) => { %>
<a href="/songs/<%= s.slug %>" class="liked-card">
<% if (s.coverPath) { %>
<img src="/media/<%= s.coverPath %>" alt="" class="liked-card-cover lb-trigger" data-lightbox>
<% } else { %>
<div class="liked-card-cover liked-card-cover--fallback">♪</div>
<% } %>
<span class="liked-card-info">
<span class="liked-card-title"><%= s.title %></span>
<span class="liked-card-artist"><%= s.artist %></span>
</span>
</a>
<% }) %>
</div>
</section>
<script>
(function () {
var el = document.getElementById('popular-scroll');
if (!el) return;
var dragging = false, startX, scrollLeft;
el.addEventListener('mousedown', function (e) {
dragging = true;
startX = e.pageX - el.offsetLeft;
scrollLeft = el.scrollLeft;
el.style.cursor = 'grabbing';
el.style.userSelect = 'none';
});
document.addEventListener('mouseup', function () {
dragging = false;
el.style.cursor = '';
el.style.userSelect = '';
});
document.addEventListener('mousemove', function (e) {
if (!dragging) return;
e.preventDefault();
var x = e.pageX - el.offsetLeft;
el.scrollLeft = scrollLeft - (x - startX);
});
})();
</script>
<% } %>
<hr class="hr-neon">
<form method="get" action="/songs" class="search-form">
<div class="search-row">
<input type="search" name="q" value="<%= q %>" placeholder="Search title, artist, album…" />
<button type="submit">Search</button>
<% if (q || genre || uploadedBy) { %><a href="/songs" class="muted small">clear</a><% } %>
</div>
<% if (genres.length > 0 || uploaders.length > 0) { %>
<div class="facet-row">
<% if (genres.length > 0) { %>
<select name="genre" onchange="this.form.submit()" class="facet-select">
<option value="">All genres</option>
<% genres.forEach((g) => { %>
<option value="<%= g %>"<%= genre === g ? ' selected' : '' %>><%= g %></option>
<% }) %>
</select>
<% } %>
<% if (uploaders.length > 0) { %>
<select name="uploader" onchange="this.form.submit()" class="facet-select">
<option value="">All profiles</option>
<% uploaders.forEach((u) => { %>
<option value="<%= u.id %>"<%= uploadedBy == u.id ? ' selected' : '' %>><%= u.display_name %></option>
<% }) %>
</select>
<% } %>
</div>
<% } %>
</form>
<p class="muted small"><%= total %> song<%= total === 1 ? '' : 's' %><% if (q) { %> matching "<%= q %>"<% } %></p>
<% if (songs.length === 0) { %>
<p class="muted">No songs found.</p>
<% } else { %>
<div id="song-sel-toolbar" style="display:none">
<span><span id="song-sel-count">0</span> selected</span>
<button type="button" id="song-sel-play" class="btn-primary" data-play-playlist="[]" data-nowplaying-meta="{}">▶ Play Selected</button>
<button type="button" id="song-sel-shuffle" class="btn-primary" data-shuffle-playlist="[]" data-nowplaying-meta="{}">⇄ Shuffle Selected</button>
<% if (user && (user.isVip || user.role === 'admin')) { %>
<div class="bulk-dropdown">
<button type="button" id="song-sel-add-btn" class="btn-secondary">Add to Playlist &#9660;</button>
<ul class="bulk-menu" id="song-sel-add-menu">
<li><a id="song-sel-new-playlist" href="#">+ New Playlist</a></li>
<li class="has-submenu">
<span>Existing Playlist</span>
<ul class="bulk-submenu">
<% userPlaylists.forEach(function(pl) { %>
<li><a class="song-sel-pl-item" data-playlist-id="<%= pl.id %>" href="#"><%= pl.title %></a></li>
<% }) %>
<% if (!userPlaylists.length) { %>
<li class="bulk-submenu-empty">No playlists yet</li>
<% } %>
</ul>
</li>
</ul>
</div>
<% } %>
<button type="button" id="song-sel-clear" class="linklike muted small">Clear</button>
</div>
<%- include('../partials/song_list', { songs, selectable: true }) %>
<script>
(function () {
var toolbar = document.getElementById('song-sel-toolbar');
var countEl = document.getElementById('song-sel-count');
var playBtn = document.getElementById('song-sel-play');
var shuffleBtn = document.getElementById('song-sel-shuffle');
var clearBtn = document.getElementById('song-sel-clear');
var selectAll = document.getElementById('song-select-all');
var addBtn = document.getElementById('song-sel-add-btn');
var addMenu = document.getElementById('song-sel-add-menu');
var newPlBtn = document.getElementById('song-sel-new-playlist');
var csrfToken = (document.querySelector('meta[name="csrf-token"]') || {}).content || '';
function allBoxes() { return document.querySelectorAll('.song-select'); }
function checkedBoxes() { return document.querySelectorAll('.song-select:checked'); }
function getSongData(cb) {
var row = cb.closest('.song-row');
var btn = row && row.querySelector('[data-play-song]');
if (!btn) return null;
try { return JSON.parse(btn.getAttribute('data-play-song')); } catch (e) { return null; }
}
function getIds() { return Array.from(checkedBoxes()).map(function (cb) { return cb.value; }); }
function updateToolbar() {
var checked = Array.from(checkedBoxes());
var total = allBoxes().length;
toolbar.style.display = checked.length ? '' : 'none';
countEl.textContent = checked.length;
if (selectAll) {
selectAll.indeterminate = checked.length > 0 && checked.length < total;
selectAll.checked = checked.length > 0 && checked.length === total;
}
if (checked.length) {
var list = checked.map(getSongData).filter(Boolean);
var json = JSON.stringify(list);
var npMeta = JSON.stringify({
title: checked.length + ' Selected Song' + (checked.length === 1 ? '' : 's'),
description: null,
cover: list.length === 1 ? (list[0].cover || null) : null,
trackCount: list.length,
creator: null,
});
playBtn.setAttribute('data-play-playlist', json);
playBtn.setAttribute('data-nowplaying-meta', npMeta);
shuffleBtn.setAttribute('data-shuffle-playlist', json);
shuffleBtn.setAttribute('data-nowplaying-meta', npMeta);
}
}
document.addEventListener('change', function (e) {
if (e.target.matches('.song-select')) updateToolbar();
});
if (selectAll) {
selectAll.addEventListener('change', function () {
allBoxes().forEach(function (cb) { cb.checked = selectAll.checked; });
updateToolbar();
});
}
if (clearBtn) {
clearBtn.addEventListener('click', function () {
allBoxes().forEach(function (cb) { cb.checked = false; });
if (selectAll) { selectAll.checked = false; selectAll.indeterminate = false; }
updateToolbar();
});
}
if (addBtn && addMenu) {
addBtn.addEventListener('click', function (e) {
e.stopPropagation();
addMenu.classList.toggle('open');
});
document.addEventListener('click', function () { addMenu.classList.remove('open'); });
}
if (newPlBtn) {
newPlBtn.addEventListener('click', function (e) {
e.preventDefault();
var ids = getIds();
if (!ids.length) return;
window.location.href = '/mymusic/playlists/new?songs=' + ids.join(',');
});
}
document.addEventListener('click', function (e) {
var item = e.target.closest('.song-sel-pl-item');
if (!item) return;
e.preventDefault();
var playlistId = item.dataset.playlistId;
var songIds = getIds();
if (!songIds.length || !playlistId) return;
var body = new URLSearchParams({ playlistId: playlistId, _csrf: csrfToken });
songIds.forEach(function (id) { body.append('songIds[]', id); });
fetch('/mymusic/songs/bulk-add', { method: 'POST', body: body })
.then(function (r) { return r.json(); })
.then(function (data) {
if (data.ok) {
allBoxes().forEach(function (cb) { cb.checked = false; });
updateToolbar();
showToast('Added ' + data.added + ' song' + (data.added === 1 ? '' : 's') + ' to playlist');
}
})
.catch(function (err) { console.error(err); });
addMenu.classList.remove('open');
});
function showToast(msg) {
var t = document.createElement('div');
t.className = 'bulk-toast';
t.textContent = msg;
document.body.appendChild(t);
setTimeout(function () { t.classList.add('visible'); }, 10);
setTimeout(function () { t.classList.remove('visible'); setTimeout(function () { t.remove(); }, 300); }, 2800);
}
})();
</script>
<% } %>
<% if (totalPages > 1) { %>
<%
const qs = [
q ? 'q=' + encodeURIComponent(q) : '',
genre ? 'genre=' + encodeURIComponent(genre) : '',
uploadedBy ? 'uploader=' + encodeURIComponent(uploadedBy) : '',
].filter(Boolean).join('&');
const pref = qs ? qs + '&' : '';
%>
<nav class="pagination">
<% if (page > 1) { %><a href="?<%= pref %>page=<%= page - 1 %>">← Prev</a><% } %>
<span class="muted">Page <%= page %> / <%= totalPages %></span>
<% if (page < totalPages) { %><a href="?<%= pref %>page=<%= page + 1 %>">Next →</a><% } %>
</nav>
<% } %>