improved artist ui

This commit is contained in:
cool.gitter.choco
2025-02-05 21:25:45 -06:00
parent 081bb5096c
commit 0ae1ca5a8d
4 changed files with 251 additions and 212 deletions

View File

@@ -74,52 +74,6 @@ body {
margin-bottom: 0.5rem;
}
/* Playlist Header */
#playlist-header {
display: flex;
gap: 20px;
margin-bottom: 2rem;
align-items: center;
padding-bottom: 1.5rem;
border-bottom: 1px solid #2a2a2a;
flex-wrap: wrap;
transition: all 0.3s ease;
}
#playlist-image {
width: 200px;
height: 200px;
object-fit: cover;
border-radius: 8px;
flex-shrink: 0;
box-shadow: 0 4px 8px rgba(0,0,0,0.3);
transition: transform 0.3s ease;
}
#playlist-image:hover {
transform: scale(1.02);
}
#playlist-info {
flex: 1;
min-width: 0;
}
#playlist-name {
font-size: 2.5rem;
margin-bottom: 0.5rem;
background: linear-gradient(90deg, #1db954, #17a44b);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
#playlist-owner,
#playlist-stats,
#playlist-description {
font-size: 1.1rem;
color: #b3b3b3;
margin-bottom: 0.5rem;
}
/* Tracks Container */
#tracks-container {
margin-top: 2rem;
@@ -182,17 +136,6 @@ body {
color: #b3b3b3;
}
.track-album {
max-width: 200px;
font-size: 0.9rem;
color: #b3b3b3;
margin-left: 1rem;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
text-align: right;
}
.track-duration {
width: 60px;
text-align: right;
@@ -244,27 +187,37 @@ body {
transform: scale(0.98);
}
/* Circular Variant for Compact Areas (e.g., in a queue list) */
/* Circular Variant for Compact Areas */
.download-btn--circle {
width: 32px;
height: 32px;
padding: 0;
border-radius: 50%;
font-size: 0;
}
.download-btn--circle::before {
content: "↓";
font-size: 16px;
color: #fff;
display: inline-block;
}
/* Icon next to text */
.download-btn .btn-icon {
margin-right: 0.5rem;
font-size: 0; /* Hide any text */
background-color: #1db954;
border: none;
display: inline-flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: background-color 0.2s ease, transform 0.2s ease;
margin: 0.5rem;
}
.download-btn--circle img {
width: 20px;
height: 20px;
filter: brightness(0) invert(1);
display: block;
}
.download-btn--circle:hover {
background-color: #17a44b;
transform: scale(1.05);
}
.download-btn--circle:active {
transform: scale(0.98);
}
/* Home Button Styling */
@@ -279,7 +232,7 @@ body {
.home-btn img {
width: 32px;
height: 32px;
filter: invert(1); /* Makes the SVG icon appear white */
filter: invert(1);
transition: transform 0.2s ease;
}
@@ -291,11 +244,8 @@ body {
transform: scale(0.98);
}
/* Download Queue Toggle Button */
/* Queue Toggle Button */
.queue-toggle {
position: fixed;
bottom: 20px;
right: 20px;
background: #1db954;
color: #fff;
border: none;
@@ -308,125 +258,130 @@ body {
box-shadow: 0 2px 8px rgba(0,0,0,0.3);
transition: background-color 0.3s ease, transform 0.2s ease;
z-index: 1002;
/* Remove any fixed positioning by default for mobile; fixed positioning remains for larger screens */
}
.queue-toggle:hover {
background: #1ed760;
transform: scale(1.05);
}
.queue-toggle:active {
transform: scale(1);
/* Actions Container for Small Screens */
#album-actions {
display: flex;
width: 100%;
justify-content: space-between;
align-items: center;
margin-top: 1rem;
}
/* Responsive Styles */
/* Medium Devices (Tablets) */
@media (max-width: 768px) {
#album-header, #playlist-header {
flex-direction: column;
align-items: center;
text-align: center;
#album-header {
flex-direction: column;
align-items: center;
text-align: center;
}
#album-image, #playlist-image {
width: 180px;
height: 180px;
margin-bottom: 1rem;
#album-image {
width: 180px;
height: 180px;
margin-bottom: 1rem;
}
#album-name, #playlist-name {
font-size: 2rem;
#album-name {
font-size: 2rem;
}
#album-artist,
#album-stats,
#playlist-owner,
#playlist-stats,
#playlist-description {
font-size: 1rem;
#album-stats {
font-size: 1rem;
}
.track {
flex-direction: column;
align-items: flex-start;
flex-direction: column;
align-items: flex-start;
}
.track-album,
.track-duration {
margin-left: 0;
margin-top: 0.5rem;
width: 100%;
text-align: left;
margin-left: 0;
margin-top: 0.5rem;
width: 100%;
text-align: left;
}
}
/* Small Devices (Mobile Phones) */
@media (max-width: 480px) {
#app {
padding: 10px;
padding: 10px;
}
#album-name, #playlist-name {
font-size: 1.75rem;
#album-header {
flex-direction: column;
align-items: center;
text-align: center;
}
/* Center the album cover */
#album-image {
margin: 0 auto;
}
#album-name {
font-size: 1.75rem;
}
#album-artist,
#album-stats,
#album-copyright,
#playlist-owner,
#playlist-stats,
#playlist-description {
font-size: 0.9rem;
#album-copyright {
font-size: 0.9rem;
}
.track {
padding: 0.8rem;
flex-direction: column;
align-items: center;
text-align: center;
padding: 0.8rem;
flex-direction: column;
align-items: center;
text-align: center;
}
.track-number {
font-size: 0.9rem;
margin-right: 0;
margin-bottom: 0.5rem;
font-size: 0.9rem;
margin-right: 0;
margin-bottom: 0.5rem;
}
.track-info {
align-items: center;
}
.track-album,
.track-duration {
margin-left: 0;
margin-top: 0.5rem;
width: 100%;
text-align: center;
margin-left: 0;
margin-top: 0.5rem;
width: 100%;
text-align: center;
}
/* Ensure the actions container lays out buttons properly */
#album-actions {
flex-direction: row;
justify-content: space-between;
}
/* Remove extra margins from the queue toggle */
.queue-toggle {
position: static;
margin: 0;
}
}
/* Prevent anchor links from appearing all blue */
a {
color: inherit; /* Inherit color from the parent */
text-decoration: none; /* Remove default underline */
color: inherit;
text-decoration: none;
transition: color 0.2s ease;
}
a:hover,
a:focus {
color: #1db954; /* Change to a themed green on hover/focus */
color: #1db954;
text-decoration: underline;
}
/* Override the default pseudo-element for the circular download button */
/* (Optional) Override for circular download button pseudo-element */
.download-btn--circle::before {
content: none;
}
/* Style the icon inside the circular download button */
.download-btn--circle img {
width: 20px;
height: 20px;
filter: brightness(0) invert(1); /* ensures the icon appears white */
display: block;
}

View File

@@ -98,7 +98,49 @@ body {
gap: 1rem;
}
/* Track Card (for Albums) */
/* Unified Album Card (Desktop & Mobile) */
.album-card {
background: #181818;
border-radius: 8px;
overflow: hidden;
box-shadow: 0 2px 4px rgba(0,0,0,0.2);
transition: transform 0.2s ease, box-shadow 0.2s ease;
display: flex;
align-items: center;
padding: 0.5rem;
}
.album-card:hover {
transform: translateY(-5px);
box-shadow: 0 4px 8px rgba(0,0,0,0.3);
}
/* Album Cover Image */
.album-cover {
width: 80px;
height: 80px;
object-fit: cover;
border-radius: 4px;
margin-right: 1rem;
}
/* Album Info */
.album-info {
flex: 1;
}
.album-title {
font-size: 1rem;
font-weight: bold;
margin-bottom: 0.25rem;
}
.album-artist {
font-size: 0.9rem;
color: #b3b3b3;
}
/* Track Card (for Albums or Songs) */
.track {
display: flex;
align-items: center;
@@ -221,7 +263,6 @@ body {
margin: 0.5rem;
}
/* Image inside circular download button */
.download-btn--circle img {
width: 20px;
height: 20px;
@@ -302,7 +343,6 @@ body {
font-size: 1.75rem;
}
/* Adjust album card layout */
.track {
padding: 0.8rem;
flex-direction: column;
@@ -327,6 +367,38 @@ body {
width: 100%;
text-align: center;
}
/* Mobile Album Grid Styles Inspired by Spotify */
.albums-list {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 1rem;
}
/* Adjust album card for mobile grid */
.album-card {
flex-direction: column;
padding: 0;
}
.album-cover {
width: 100%;
aspect-ratio: 1 / 1;
margin: 0;
}
.album-info {
padding: 0.5rem;
text-align: center;
}
.album-title {
font-size: 1rem;
}
.album-artist {
font-size: 0.9rem;
}
}
/* Prevent anchor links from appearing blue */

View File

@@ -29,18 +29,16 @@ document.addEventListener('DOMContentLoaded', () => {
});
function renderAlbum(album) {
// Hide loading and error messages.
document.getElementById('loading').classList.add('hidden');
document.getElementById('error').classList.add('hidden');
const baseUrl = window.location.origin;
// Album header info with embedded links
// Album name becomes a link to the album page.
// Set album header info.
document.getElementById('album-name').innerHTML =
`<a href="${baseUrl}/album/${album.id}">${album.name}</a>`;
// Album artists become links to their artist pages.
document.getElementById('album-artist').innerHTML =
`By ${album.artists.map(artist => `<a href="${baseUrl}/artist/${artist.id}">${artist.name}</a>`).join(', ')}`;
@@ -54,19 +52,19 @@ function renderAlbum(album) {
const image = album.images[0]?.url || 'placeholder.jpg';
document.getElementById('album-image').src = image;
// Home Button using SVG icon
// Create (if needed) the Home Button.
let homeButton = document.getElementById('homeButton');
if (!homeButton) {
homeButton = document.createElement('button');
homeButton.id = 'homeButton';
homeButton.className = 'home-btn';
// Create an image element for the home icon.
const homeIcon = document.createElement('img');
homeIcon.src = '/static/images/home.svg';
homeIcon.alt = 'Home';
homeButton.appendChild(homeIcon);
// Insert as first child of album-header.
const headerContainer = document.getElementById('album-header');
headerContainer.insertBefore(homeButton, headerContainer.firstChild);
}
@@ -74,7 +72,7 @@ function renderAlbum(album) {
window.location.href = window.location.origin;
});
// Download Album Button
// Create (if needed) the Download Album Button.
let downloadAlbumBtn = document.getElementById('downloadAlbumBtn');
if (!downloadAlbumBtn) {
downloadAlbumBtn = document.createElement('button');
@@ -85,6 +83,7 @@ function renderAlbum(album) {
}
downloadAlbumBtn.addEventListener('click', () => {
// Remove any other download buttons (keeping the full-album button in place).
document.querySelectorAll('.download-btn').forEach(btn => {
if (btn.id !== 'downloadAlbumBtn') btn.remove();
});
@@ -92,15 +91,17 @@ function renderAlbum(album) {
downloadAlbumBtn.disabled = true;
downloadAlbumBtn.textContent = 'Queueing...';
downloadWholeAlbum(album).then(() => {
downloadAlbumBtn.textContent = 'Queued!';
}).catch(err => {
showError('Failed to queue album download: ' + err.message);
downloadAlbumBtn.disabled = false;
});
downloadWholeAlbum(album)
.then(() => {
downloadAlbumBtn.textContent = 'Queued!';
})
.catch(err => {
showError('Failed to queue album download: ' + err.message);
downloadAlbumBtn.disabled = false;
});
});
// Render tracks
// Render each track.
const tracksList = document.getElementById('tracks-list');
tracksList.innerHTML = '';
@@ -108,36 +109,54 @@ function renderAlbum(album) {
const trackElement = document.createElement('div');
trackElement.className = 'track';
trackElement.innerHTML = `
<div class="track-number">${index + 1}</div>
<div class="track-info">
<div class="track-name">
<a href="${baseUrl}/track/${track.id}">${track.name}</a>
<div class="track-number">${index + 1}</div>
<div class="track-info">
<div class="track-name">
<a href="${baseUrl}/track/${track.id}">${track.name}</a>
</div>
<div class="track-artist">
${track.artists.map(a => `<a href="${baseUrl}/artist/${a.id}">${a.name}</a>`).join(', ')}
</div>
</div>
<div class="track-artist">
${track.artists.map(a => `<a href="${baseUrl}/artist/${a.id}">${a.name}</a>`).join(', ')}
</div>
</div>
<div class="track-duration">${msToTime(track.duration_ms)}</div>
<button class="download-btn download-btn--circle"
data-url="${track.external_urls.spotify}"
data-type="track"
data-name="${track.name}"
title="Download">
<img src="/static/images/download.svg" alt="Download">
</button>
`;
<div class="track-duration">${msToTime(track.duration_ms)}</div>
<button class="download-btn download-btn--circle"
data-url="${track.external_urls.spotify}"
data-type="track"
data-name="${track.name}"
title="Download">
<img src="/static/images/download.svg" alt="Download">
</button>
`;
tracksList.appendChild(trackElement);
});
// Reveal header and track list.
document.getElementById('album-header').classList.remove('hidden');
document.getElementById('tracks-container').classList.remove('hidden');
attachDownloadListeners();
// If on a small screen, re-arrange the action buttons.
if (window.innerWidth <= 480) {
let actionsContainer = document.getElementById('album-actions');
if (!actionsContainer) {
actionsContainer = document.createElement('div');
actionsContainer.id = 'album-actions';
document.getElementById('album-header').appendChild(actionsContainer);
}
// Append in the desired order: Home, Download, then Queue Toggle (if exists).
actionsContainer.innerHTML = ''; // Clear any previous content
actionsContainer.appendChild(document.getElementById('homeButton'));
actionsContainer.appendChild(document.getElementById('downloadAlbumBtn'));
const queueToggle = document.querySelector('.queue-toggle');
if (queueToggle) {
actionsContainer.appendChild(queueToggle);
}
}
}
async function downloadWholeAlbum(album) {
const url = album.external_urls.spotify;
startDownload(url, 'album', { name: album.name });
return startDownload(url, 'album', { name: album.name });
}
function msToTime(duration) {
@@ -160,19 +179,14 @@ function attachDownloadListeners() {
const url = e.currentTarget.dataset.url;
const type = e.currentTarget.dataset.type;
const name = e.currentTarget.dataset.name || extractName(url);
const albumType = e.currentTarget.dataset.albumType;
// Remove the button after click
// Remove the button immediately after click.
e.currentTarget.remove();
// Start the download for this track.
startDownload(url, type, { name }, albumType);
startDownload(url, type, { name });
});
});
}
async function startDownload(url, type, item, albumType) {
// Retrieve configuration (if any) from localStorage
const config = JSON.parse(localStorage.getItem('activeConfig')) || {};
const {
fallback = false,
@@ -186,17 +200,14 @@ async function startDownload(url, type, item, albumType) {
const service = url.includes('open.spotify.com') ? 'spotify' : 'deezer';
let apiUrl = '';
// Build API URL based on the download type.
if (type === 'album') {
apiUrl = `/api/album/download?service=${service}&url=${encodeURIComponent(url)}`;
} else if (type === 'artist') {
apiUrl = `/api/artist/download?service=${service}&artist_url=${encodeURIComponent(url)}&album_type=${encodeURIComponent(albumType || 'album,single,compilation')}`;
} else {
// Default is track download.
apiUrl = `/api/${type}/download?service=${service}&url=${encodeURIComponent(url)}`;
}
// Append account and quality details.
if (fallback && service === 'spotify') {
apiUrl += `&main=${deezer}&fallback=${spotify}`;
apiUrl += `&quality=${deezerQuality}&fall_quality=${spotifyQuality}`;
@@ -212,9 +223,12 @@ async function startDownload(url, type, item, albumType) {
try {
const response = await fetch(apiUrl);
const data = await response.json();
// Add the download to the queue using the working queue implementation.
downloadQueue.addDownload(item, type, data.prg_file);
} catch (error) {
showError('Download failed: ' + error.message);
}
}
function extractName(url) {
return url;
}

View File

@@ -85,12 +85,14 @@ function renderArtist(artistData, artistId) {
downloadArtistBtn.disabled = true;
downloadArtistBtn.textContent = 'Queueing...';
downloadWholeArtist(artistData).then(() => {
downloadArtistBtn.textContent = 'Queued!';
}).catch(err => {
showError('Failed to queue artist download: ' + err.message);
downloadArtistBtn.disabled = false;
});
downloadWholeArtist(artistData)
.then(() => {
downloadArtistBtn.textContent = 'Queued!';
})
.catch(err => {
showError('Failed to queue artist download: ' + err.message);
downloadArtistBtn.disabled = false;
});
});
// Group albums by album type.
@@ -128,24 +130,20 @@ function renderArtist(artistData, artistId) {
// Container for individual albums in this group.
const albumsContainer = document.createElement('div');
albumsContainer.className = 'albums-list';
albums.forEach((album, index) => {
albums.forEach(album => {
const albumElement = document.createElement('div');
albumElement.className = 'track'; // reusing styling from the playlist view
// Use an <a> around the album image and name.
// Build a unified album card markup that works for both desktop and mobile.
albumElement.className = 'album-card';
albumElement.innerHTML = `
<div class="track-number">${index + 1}</div>
<a href="/album/${album.id}" class="album-link">
<img class="track-image" src="${album.images[1]?.url || album.images[0]?.url || 'placeholder.jpg'}"
<img src="${album.images[1]?.url || album.images[0]?.url || 'placeholder.jpg'}"
alt="Album cover"
style="width: 64px; height: 64px; border-radius: 4px; margin-right: 1rem;">
class="album-cover">
</a>
<div class="track-info">
<a href="/album/${album.id}" class="track-name">${album.name}</a>
<div class="track-artist"></div>
<div class="album-info">
<div class="album-title">${album.name}</div>
<div class="album-artist">${album.artists.map(a => a.name).join(', ')}</div>
</div>
<div class="track-album">${album.release_date}</div>
<div class="track-duration">${album.total_tracks} tracks</div>
<button class="download-btn download-btn--circle"
data-url="${album.external_urls.spotify}"
data-type="album"