Merge branch 'main' of github.com:monochrome-music/monochrome
This commit is contained in:
commit
b4a7f116f9
7 changed files with 168 additions and 84 deletions
21
.github/workflows/editors-picks.yml
vendored
21
.github/workflows/editors-picks.yml
vendored
|
|
@ -37,10 +37,6 @@ jobs:
|
||||||
with:
|
with:
|
||||||
python-version: '3.x'
|
python-version: '3.x'
|
||||||
|
|
||||||
- name: Install webp
|
|
||||||
if: steps.backoff.outputs.skip == 'false'
|
|
||||||
run: sudo apt-get update && sudo apt-get install -y webp
|
|
||||||
|
|
||||||
- name: Archive current editors picks
|
- name: Archive current editors picks
|
||||||
if: steps.backoff.outputs.skip == 'false'
|
if: steps.backoff.outputs.skip == 'false'
|
||||||
run: |
|
run: |
|
||||||
|
|
@ -68,14 +64,19 @@ jobs:
|
||||||
with open("public/editors-picks.json") as f:
|
with open("public/editors-picks.json") as f:
|
||||||
current = json.load(f)
|
current = json.load(f)
|
||||||
|
|
||||||
# Replace cover URLs with original UUIDs
|
# Replace cover URLs with original UUIDs (handles both legacy and intermediate formats)
|
||||||
url_pattern = re.compile(r'^https://monochrome\.tf/editors-picks-images/([a-f0-9-]+)\.webp$')
|
url_pattern1 = re.compile(r'^https://monochrome\.tf/editors-picks-images/([a-f0-9-]+)\.webp$')
|
||||||
|
url_pattern2 = re.compile(r'^https://wsrv\.nl/\?url=https://resources\.tidal\.com/images/([a-f0-9/]+)/320x320\.jpg&w=250&h=250&output=webp$')
|
||||||
for item in current:
|
for item in current:
|
||||||
for field in ['cover', 'picture', 'image']:
|
for field in ['cover', 'picture', 'image']:
|
||||||
if field in item and item[field]:
|
if field in item and item[field]:
|
||||||
match = url_pattern.match(item[field])
|
m1 = url_pattern1.match(item[field])
|
||||||
if match:
|
if m1:
|
||||||
item[field] = match.group(1)
|
item[field] = m1.group(1)
|
||||||
|
continue
|
||||||
|
m2 = url_pattern2.match(item[field])
|
||||||
|
if m2:
|
||||||
|
item[field] = m2.group(1).replace("/", "-")
|
||||||
|
|
||||||
with open(archive_path, "w") as f:
|
with open(archive_path, "w") as f:
|
||||||
json.dump(current, f, indent=4)
|
json.dump(current, f, indent=4)
|
||||||
|
|
@ -103,7 +104,7 @@ jobs:
|
||||||
run: |
|
run: |
|
||||||
git config user.name "github-actions[bot]"
|
git config user.name "github-actions[bot]"
|
||||||
git config user.email "github-actions[bot]@users.noreply.github.com"
|
git config user.email "github-actions[bot]@users.noreply.github.com"
|
||||||
git add public/editors-picks.json public/editors-picks-old/ public/editors-picks-images/
|
git add public/editors-picks.json public/editors-picks-old/
|
||||||
git diff --staged --quiet && echo "No changes to commit." && exit 0
|
git diff --staged --quiet && echo "No changes to commit." && exit 0
|
||||||
git commit -m "chore: update editors picks"
|
git commit -m "chore: update editors picks"
|
||||||
git push
|
git push
|
||||||
|
|
|
||||||
22
fix-gen.py
Normal file
22
fix-gen.py
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
import re
|
||||||
|
with open("gen-editors-picks.py", "r") as f: content = f.read()
|
||||||
|
|
||||||
|
content = re.sub(r"IMAGES_DIR = \"public/editors-picks-images\"\n+", "", content)
|
||||||
|
|
||||||
|
# Remove clear_images_dir definition
|
||||||
|
content = re.sub(r"def clear_images_dir\(\):[\s\S]*?os\.makedirs[^\n]*\n+", "", content)
|
||||||
|
content = re.sub(r"clear_images_dir\(\)\n+", "", content)
|
||||||
|
|
||||||
|
# Remove the import line for subprocess, shutil if present
|
||||||
|
content = re.sub(r"import subprocess\n", "", content)
|
||||||
|
content = re.sub(r"import shutil\n", "", content)
|
||||||
|
|
||||||
|
# Replace download_and_process_cover
|
||||||
|
new_func = """def download_and_process_cover(cover_uuid):
|
||||||
|
url = f"https://resources.tidal.com/images/{uuid_to_path_segments(cover_uuid)}/320x320.jpg"
|
||||||
|
return f"https://wsrv.nl/?url={url}&w=250&h=250&output=webp"
|
||||||
|
"""
|
||||||
|
content = re.sub(r"def download_and_process_cover\(cover_uuid\):[\s\S]*?(?=def process_cover)", new_func + "\n\n", content)
|
||||||
|
|
||||||
|
with open("gen-editors-picks.py", "w") as f: f.write(content)
|
||||||
|
|
||||||
|
|
@ -8,12 +8,9 @@ import sys
|
||||||
import hashlib
|
import hashlib
|
||||||
import time
|
import time
|
||||||
import os
|
import os
|
||||||
import subprocess
|
|
||||||
import shutil
|
|
||||||
import tempfile
|
import tempfile
|
||||||
|
|
||||||
INPUT_FILE = "editors-picks-input.txt"
|
INPUT_FILE = "editors-picks-input.txt"
|
||||||
IMAGES_DIR = "public/editors-picks-images"
|
|
||||||
COUNTRY = "US"
|
COUNTRY = "US"
|
||||||
|
|
||||||
# Tidal internal token replace when expired
|
# Tidal internal token replace when expired
|
||||||
|
|
@ -90,59 +87,6 @@ def fetch_podcast(feed_id):
|
||||||
|
|
||||||
# ── Image processing ───────────────────────────────────────────────────────────
|
# ── Image processing ───────────────────────────────────────────────────────────
|
||||||
|
|
||||||
def clear_images_dir():
|
|
||||||
if os.path.exists(IMAGES_DIR):
|
|
||||||
shutil.rmtree(IMAGES_DIR)
|
|
||||||
os.makedirs(IMAGES_DIR, exist_ok=True)
|
|
||||||
|
|
||||||
|
|
||||||
def is_uuid_cover(cover_value):
|
|
||||||
if not cover_value:
|
|
||||||
return False
|
|
||||||
return bool(re.match(r'^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$', cover_value))
|
|
||||||
|
|
||||||
|
|
||||||
def uuid_to_path_segments(uuid):
|
|
||||||
return uuid.replace('-', '/')
|
|
||||||
|
|
||||||
|
|
||||||
def download_and_process_cover(cover_uuid):
|
|
||||||
url = f"https://resources.tidal.com/images/{uuid_to_path_segments(cover_uuid)}/320x320.jpg"
|
|
||||||
|
|
||||||
with tempfile.NamedTemporaryFile(suffix='.jpg', delete=False) as tmp:
|
|
||||||
tmp_path = tmp.name
|
|
||||||
|
|
||||||
try:
|
|
||||||
req = urllib.request.Request(url, headers={"User-Agent": "Mozilla/5.0"})
|
|
||||||
with urllib.request.urlopen(req) as resp:
|
|
||||||
with open(tmp_path, 'wb') as f:
|
|
||||||
shutil.copyfileobj(resp, f)
|
|
||||||
|
|
||||||
output_path = os.path.join(IMAGES_DIR, f"{cover_uuid}.webp")
|
|
||||||
|
|
||||||
subprocess.run(
|
|
||||||
['cwebp', '-q', '50', tmp_path, '-o', output_path],
|
|
||||||
check=True,
|
|
||||||
capture_output=True
|
|
||||||
)
|
|
||||||
|
|
||||||
return f"https://monochrome.tf/editors-picks-images/{cover_uuid}.webp"
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Error processing cover {cover_uuid}: {e}", file=sys.stderr)
|
|
||||||
return None
|
|
||||||
finally:
|
|
||||||
if os.path.exists(tmp_path):
|
|
||||||
os.remove(tmp_path)
|
|
||||||
|
|
||||||
|
|
||||||
def process_cover(cover_value):
|
|
||||||
if not cover_value:
|
|
||||||
return cover_value
|
|
||||||
if is_uuid_cover(cover_value):
|
|
||||||
return download_and_process_cover(cover_value)
|
|
||||||
return cover_value
|
|
||||||
|
|
||||||
|
|
||||||
# ── Transformers ──────────────────────────────────────────────────────────────
|
# ── Transformers ──────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
def transform_album(d):
|
def transform_album(d):
|
||||||
|
|
@ -155,7 +99,7 @@ def transform_album(d):
|
||||||
"name": d.get("artist", {}).get("name"),
|
"name": d.get("artist", {}).get("name"),
|
||||||
},
|
},
|
||||||
"releaseDate": d.get("releaseDate"),
|
"releaseDate": d.get("releaseDate"),
|
||||||
"cover": process_cover(d.get("cover")),
|
"cover": d.get("cover"),
|
||||||
"explicit": d.get("explicit"),
|
"explicit": d.get("explicit"),
|
||||||
"audioQuality": d.get("audioQuality"),
|
"audioQuality": d.get("audioQuality"),
|
||||||
"mediaMetadata": d.get("mediaMetadata"),
|
"mediaMetadata": d.get("mediaMetadata"),
|
||||||
|
|
@ -167,7 +111,7 @@ def transform_artist(d):
|
||||||
"type": "artist",
|
"type": "artist",
|
||||||
"id": d.get("id"),
|
"id": d.get("id"),
|
||||||
"name": d.get("name"),
|
"name": d.get("name"),
|
||||||
"picture": process_cover(d.get("picture")),
|
"picture": d.get("picture"),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -184,7 +128,7 @@ def transform_track(d):
|
||||||
"album": {
|
"album": {
|
||||||
"id": album.get("id"),
|
"id": album.get("id"),
|
||||||
"title": album.get("title"),
|
"title": album.get("title"),
|
||||||
"cover": process_cover(album.get("cover")),
|
"cover": album.get("cover"),
|
||||||
},
|
},
|
||||||
"duration": d.get("duration"),
|
"duration": d.get("duration"),
|
||||||
"explicit": d.get("explicit"),
|
"explicit": d.get("explicit"),
|
||||||
|
|
@ -200,7 +144,7 @@ def transform_playlist(d):
|
||||||
"type": "playlist",
|
"type": "playlist",
|
||||||
"id": d.get("uuid"),
|
"id": d.get("uuid"),
|
||||||
"title": d.get("title"),
|
"title": d.get("title"),
|
||||||
"cover": process_cover(cover),
|
"cover": cover,
|
||||||
"numberOfTracks": d.get("numberOfTracks", 0),
|
"numberOfTracks": d.get("numberOfTracks", 0),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -213,7 +157,7 @@ def transform_userplaylist(d):
|
||||||
"type": "user-playlist",
|
"type": "user-playlist",
|
||||||
"id": d.get("uuid"),
|
"id": d.get("uuid"),
|
||||||
"name": d.get("title"),
|
"name": d.get("title"),
|
||||||
"cover": process_cover(cover),
|
"cover": cover,
|
||||||
"numberOfTracks": d.get("numberOfTracks", 0),
|
"numberOfTracks": d.get("numberOfTracks", 0),
|
||||||
"username": creator.get("name"),
|
"username": creator.get("name"),
|
||||||
}
|
}
|
||||||
|
|
@ -258,8 +202,6 @@ def read_items(path):
|
||||||
|
|
||||||
# ── Main ──────────────────────────────────────────────────────────────────────
|
# ── Main ──────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
clear_images_dir()
|
|
||||||
|
|
||||||
FETCHERS = {
|
FETCHERS = {
|
||||||
"album": (fetch_album, transform_album),
|
"album": (fetch_album, transform_album),
|
||||||
"artist": (fetch_artist, transform_artist),
|
"artist": (fetch_artist, transform_artist),
|
||||||
|
|
|
||||||
23
js/api.js
23
js/api.js
|
|
@ -1952,6 +1952,19 @@ export class LosslessAPI {
|
||||||
return `https://resources.tidal.com/images/${formattedId}/${size}x${size}.jpg`;
|
return `https://resources.tidal.com/images/${formattedId}/${size}x${size}.jpg`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getCoverSrcset(id) {
|
||||||
|
if (
|
||||||
|
!id ||
|
||||||
|
(typeof id === 'string' && (id.startsWith('http') || id.startsWith('blob:') || id.startsWith('assets/')))
|
||||||
|
) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
const formattedId = String(id).replace(/-/g, '/');
|
||||||
|
const baseUrl = `https://resources.tidal.com/images/${formattedId}`;
|
||||||
|
return `${baseUrl}/160x160.jpg 160w, ${baseUrl}/320x320.jpg 320w, ${baseUrl}/640x640.jpg 640w`;
|
||||||
|
}
|
||||||
|
|
||||||
getArtistPictureUrl(id, size = '320') {
|
getArtistPictureUrl(id, size = '320') {
|
||||||
if (!id) {
|
if (!id) {
|
||||||
return `https://picsum.photos/seed/${Math.random()}/${size}`;
|
return `https://picsum.photos/seed/${Math.random()}/${size}`;
|
||||||
|
|
@ -1965,6 +1978,16 @@ export class LosslessAPI {
|
||||||
return `https://resources.tidal.com/images/${formattedId}/${size}x${size}.jpg`;
|
return `https://resources.tidal.com/images/${formattedId}/${size}x${size}.jpg`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getArtistPictureSrcset(id) {
|
||||||
|
if (!id || (typeof id === 'string' && (id.startsWith('blob:') || id.startsWith('assets/')))) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
const formattedId = String(id).replace(/-/g, '/');
|
||||||
|
const baseUrl = `https://resources.tidal.com/images/${formattedId}`;
|
||||||
|
return `${baseUrl}/160x160.jpg 160w, ${baseUrl}/320x320.jpg 320w, ${baseUrl}/640x640.jpg 640w`;
|
||||||
|
}
|
||||||
|
|
||||||
getVideoCoverUrl(imageId, size = '1280') {
|
getVideoCoverUrl(imageId, size = '1280') {
|
||||||
if (!imageId) {
|
if (!imageId) {
|
||||||
return null;
|
return null;
|
||||||
|
|
|
||||||
|
|
@ -223,6 +223,13 @@ export class MusicAPI {
|
||||||
return this.tidalAPI.getCoverUrl(this.stripProviderPrefix(id), size);
|
return this.tidalAPI.getCoverUrl(this.stripProviderPrefix(id), size);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getCoverSrcset(id) {
|
||||||
|
if (typeof id === 'string' && id.startsWith('blob:')) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
return this.tidalAPI.getCoverSrcset(this.stripProviderPrefix(id));
|
||||||
|
}
|
||||||
|
|
||||||
getVideoCoverUrl(imageId, size = '1280') {
|
getVideoCoverUrl(imageId, size = '1280') {
|
||||||
if (!imageId) {
|
if (!imageId) {
|
||||||
return null;
|
return null;
|
||||||
|
|
@ -260,6 +267,10 @@ export class MusicAPI {
|
||||||
return this.tidalAPI.getArtistPictureUrl(this.stripProviderPrefix(id), size);
|
return this.tidalAPI.getArtistPictureUrl(this.stripProviderPrefix(id), size);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getArtistPictureSrcset(id) {
|
||||||
|
return this.tidalAPI.getArtistPictureSrcset(this.stripProviderPrefix(id));
|
||||||
|
}
|
||||||
|
|
||||||
extractStreamUrlFromManifest(manifest) {
|
extractStreamUrlFromManifest(manifest) {
|
||||||
return this.tidalAPI.extractStreamUrlFromManifest(manifest);
|
return this.tidalAPI.extractStreamUrlFromManifest(manifest);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
34
js/player.js
34
js/player.js
|
|
@ -307,8 +307,9 @@ export class Player {
|
||||||
|
|
||||||
if (coverEl) {
|
if (coverEl) {
|
||||||
const videoCoverUrl = track.videoUrl || track.videoCoverUrl || track.album?.videoCoverUrl || null;
|
const videoCoverUrl = track.videoUrl || track.videoCoverUrl || track.album?.videoCoverUrl || null;
|
||||||
const coverUrl =
|
const coverId = track.image || track.cover || track.album?.cover;
|
||||||
videoCoverUrl || this.api.getCoverUrl(track.image || track.cover || track.album?.cover);
|
const coverUrl = videoCoverUrl || this.api.getCoverUrl(coverId);
|
||||||
|
const coverSrcset = videoCoverUrl ? null : this.api.getCoverSrcset(coverId);
|
||||||
|
|
||||||
if (videoCoverUrl) {
|
if (videoCoverUrl) {
|
||||||
if (coverEl.tagName === 'IMG') {
|
if (coverEl.tagName === 'IMG') {
|
||||||
|
|
@ -326,14 +327,24 @@ export class Player {
|
||||||
coverEl.src = videoCoverUrl;
|
coverEl.src = videoCoverUrl;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
const setImgSrcset = (img) => {
|
||||||
|
if (img.getAttribute('src') !== coverUrl) img.src = coverUrl;
|
||||||
|
if (coverSrcset) {
|
||||||
|
img.setAttribute('srcset', coverSrcset);
|
||||||
|
img.setAttribute('sizes', '(max-width: 640px) 160px, (max-width: 1024px) 320px, 640px');
|
||||||
|
} else {
|
||||||
|
img.removeAttribute('srcset');
|
||||||
|
img.removeAttribute('sizes');
|
||||||
|
}
|
||||||
|
};
|
||||||
if (coverEl.tagName === 'VIDEO') {
|
if (coverEl.tagName === 'VIDEO') {
|
||||||
const img = document.createElement('img');
|
const img = document.createElement('img');
|
||||||
img.src = coverUrl;
|
|
||||||
img.className = coverEl.className;
|
img.className = coverEl.className;
|
||||||
img.id = coverEl.id;
|
img.id = coverEl.id;
|
||||||
|
setImgSrcset(img);
|
||||||
coverEl.replaceWith(img);
|
coverEl.replaceWith(img);
|
||||||
} else {
|
} else {
|
||||||
coverEl.src = coverUrl;
|
setImgSrcset(coverEl);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -870,8 +881,19 @@ export class Player {
|
||||||
} else {
|
} else {
|
||||||
if (coverEl) {
|
if (coverEl) {
|
||||||
coverEl.style.display = 'block';
|
coverEl.style.display = 'block';
|
||||||
const coverUrl = this.api.getCoverUrl(track.image || track.cover || track.album?.cover);
|
const coverId = track.image || track.cover || track.album?.cover;
|
||||||
if (coverEl.src !== coverUrl) coverEl.src = coverUrl;
|
const coverUrl = this.api.getCoverUrl(coverId);
|
||||||
|
const coverSrcset = this.api.getCoverSrcset(coverId);
|
||||||
|
if (coverEl.getAttribute('src') !== coverUrl) {
|
||||||
|
coverEl.src = coverUrl;
|
||||||
|
if (coverSrcset) {
|
||||||
|
coverEl.setAttribute('srcset', coverSrcset);
|
||||||
|
coverEl.setAttribute('sizes', '(max-width: 640px) 160px, (max-width: 1024px) 320px, 640px');
|
||||||
|
} else {
|
||||||
|
coverEl.removeAttribute('srcset');
|
||||||
|
coverEl.removeAttribute('sizes');
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (this.audio) {
|
if (this.audio) {
|
||||||
const isInFullscreen = document.getElementById('fullscreen-cover-overlay')?.style.display === 'flex';
|
const isInFullscreen = document.getElementById('fullscreen-cover-overlay')?.style.display === 'flex';
|
||||||
|
|
|
||||||
73
js/ui.js
73
js/ui.js
|
|
@ -543,11 +543,43 @@ export class UIRenderer {
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
getCoverHTML(cover, alt, className = 'card-image', loading = 'lazy', videoCoverUrl = null) {
|
getCoverHTML(
|
||||||
const imageUrl = this.api.getCoverUrl(cover);
|
cover,
|
||||||
|
alt,
|
||||||
|
className = 'card-image',
|
||||||
|
loading = 'lazy',
|
||||||
|
videoCoverUrl = null,
|
||||||
|
isEditorsPick = false,
|
||||||
|
type = 'album'
|
||||||
|
) {
|
||||||
|
let size = '320';
|
||||||
|
if (this.currentPage === 'search' || className === 'track-item-cover') {
|
||||||
|
size = '80';
|
||||||
|
} else if (type === 'artist') {
|
||||||
|
size = '160';
|
||||||
|
}
|
||||||
|
|
||||||
|
const imageUrl =
|
||||||
|
type === 'artist' ? this.api.getArtistPictureUrl(cover, size) : this.api.getCoverUrl(cover, size);
|
||||||
|
|
||||||
if (videoCoverUrl) {
|
if (videoCoverUrl) {
|
||||||
return `<video src="${videoCoverUrl}" poster="${imageUrl}" class="${className}" alt="${alt}" preload="metadata" playsinline muted></video>`;
|
return `<video src="${videoCoverUrl}" poster="${imageUrl}" class="${className}" alt="${alt}" preload="metadata" playsinline muted></video>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
isEditorsPick &&
|
||||||
|
cover &&
|
||||||
|
typeof cover === 'string' &&
|
||||||
|
!cover.startsWith('http') &&
|
||||||
|
!cover.startsWith('blob:') &&
|
||||||
|
!cover.startsWith('assets/')
|
||||||
|
) {
|
||||||
|
const formattedId = String(cover).replace(/-/g, '/');
|
||||||
|
const tidalUrl = `https://resources.tidal.com/images/${formattedId}/320x320.jpg`;
|
||||||
|
const wsrvUrl = `https://wsrv.nl/?url=${encodeURIComponent(tidalUrl)}&w=250&h=250&output=webp`;
|
||||||
|
return `<img src="${wsrvUrl}" class="${className}" alt="${alt}" loading="${loading}">`;
|
||||||
|
}
|
||||||
|
|
||||||
return `<img src="${imageUrl}" class="${className}" alt="${alt}" loading="${loading}">`;
|
return `<img src="${imageUrl}" class="${className}" alt="${alt}" loading="${loading}">`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -658,7 +690,15 @@ export class UIRenderer {
|
||||||
createUserPlaylistCardHTML(playlist, customSubtitle = null) {
|
createUserPlaylistCardHTML(playlist, customSubtitle = null) {
|
||||||
let imageHTML = '';
|
let imageHTML = '';
|
||||||
if (playlist.cover) {
|
if (playlist.cover) {
|
||||||
imageHTML = `<img src="${playlist.cover}" alt="${playlist.name}" class="card-image" loading="lazy">`;
|
imageHTML = this.getCoverHTML(
|
||||||
|
playlist.cover,
|
||||||
|
escapeHtml(playlist.name),
|
||||||
|
'card-image',
|
||||||
|
playlist._lazy === false ? 'eager' : 'lazy',
|
||||||
|
null,
|
||||||
|
playlist._isEditorsPick || false,
|
||||||
|
'album'
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
const tracks = playlist.tracks || [];
|
const tracks = playlist.tracks || [];
|
||||||
let uniqueCovers = playlist.images || [];
|
let uniqueCovers = playlist.images || [];
|
||||||
|
|
@ -750,7 +790,9 @@ export class UIRenderer {
|
||||||
escapeHtml(album.title),
|
escapeHtml(album.title),
|
||||||
'card-image',
|
'card-image',
|
||||||
album._lazy === false ? 'eager' : 'lazy',
|
album._lazy === false ? 'eager' : 'lazy',
|
||||||
album.videoCoverUrl
|
album.videoCoverUrl,
|
||||||
|
album._isEditorsPick || false,
|
||||||
|
'album'
|
||||||
),
|
),
|
||||||
actionButtonsHTML: `
|
actionButtonsHTML: `
|
||||||
<button class="like-btn card-like-btn" data-action="toggle-like" data-type="album" title="Add to Liked">
|
<button class="like-btn card-like-btn" data-action="toggle-like" data-type="album" title="Add to Liked">
|
||||||
|
|
@ -821,7 +863,15 @@ export class UIRenderer {
|
||||||
href: `/artist/${artist.id}`,
|
href: `/artist/${artist.id}`,
|
||||||
title: escapeHtml(artist.name),
|
title: escapeHtml(artist.name),
|
||||||
subtitle: '',
|
subtitle: '',
|
||||||
imageHTML: `<img src="${this.api.getArtistPictureUrl(artist.picture)}" alt="${escapeHtml(artist.name)}" class="card-image" loading="${artist._lazy === false ? 'eager' : 'lazy'}">`,
|
imageHTML: this.getCoverHTML(
|
||||||
|
artist.picture,
|
||||||
|
escapeHtml(artist.name),
|
||||||
|
'card-image',
|
||||||
|
artist._lazy === false ? 'eager' : 'lazy',
|
||||||
|
null,
|
||||||
|
artist._isEditorsPick || false,
|
||||||
|
'artist'
|
||||||
|
),
|
||||||
actionButtonsHTML: `
|
actionButtonsHTML: `
|
||||||
<button class="like-btn card-like-btn" data-action="toggle-like" data-type="artist" title="Add to Liked">
|
<button class="like-btn card-like-btn" data-action="toggle-like" data-type="artist" title="Add to Liked">
|
||||||
${this.createHeartIcon(false)}
|
${this.createHeartIcon(false)}
|
||||||
|
|
@ -2622,6 +2672,7 @@ export class UIRenderer {
|
||||||
mediaMetadata: item.mediaMetadata,
|
mediaMetadata: item.mediaMetadata,
|
||||||
type: 'ALBUM',
|
type: 'ALBUM',
|
||||||
_lazy: cardsHTML.length >= 6,
|
_lazy: cardsHTML.length >= 6,
|
||||||
|
_isEditorsPick: true,
|
||||||
};
|
};
|
||||||
cardsHTML.push(this.createAlbumCardHTML(album));
|
cardsHTML.push(this.createAlbumCardHTML(album));
|
||||||
itemsToStore.push({ el: null, data: album, type: 'album' });
|
itemsToStore.push({ el: null, data: album, type: 'album' });
|
||||||
|
|
@ -2630,6 +2681,7 @@ export class UIRenderer {
|
||||||
const result = await this.api.getAlbum(item.id);
|
const result = await this.api.getAlbum(item.id);
|
||||||
if (result && result.album) {
|
if (result && result.album) {
|
||||||
result.album._lazy = cardsHTML.length >= 6;
|
result.album._lazy = cardsHTML.length >= 6;
|
||||||
|
result.album._isEditorsPick = true;
|
||||||
cardsHTML.push(this.createAlbumCardHTML(result.album));
|
cardsHTML.push(this.createAlbumCardHTML(result.album));
|
||||||
itemsToStore.push({ el: null, data: result.album, type: 'album' });
|
itemsToStore.push({ el: null, data: result.album, type: 'album' });
|
||||||
}
|
}
|
||||||
|
|
@ -2653,6 +2705,7 @@ export class UIRenderer {
|
||||||
type: 'ALBUM',
|
type: 'ALBUM',
|
||||||
_href: `/userplaylist/${item.id}`,
|
_href: `/userplaylist/${item.id}`,
|
||||||
_lazy: cardsHTML.length >= 6,
|
_lazy: cardsHTML.length >= 6,
|
||||||
|
_isEditorsPick: true,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
itemsToStore.push({ el: null, data: playlist, type: 'user-playlist' });
|
itemsToStore.push({ el: null, data: playlist, type: 'user-playlist' });
|
||||||
|
|
@ -2665,6 +2718,7 @@ export class UIRenderer {
|
||||||
name: item.name,
|
name: item.name,
|
||||||
picture: item.picture,
|
picture: item.picture,
|
||||||
_lazy: cardsHTML.length >= 6,
|
_lazy: cardsHTML.length >= 6,
|
||||||
|
_isEditorsPick: true,
|
||||||
};
|
};
|
||||||
cardsHTML.push(this.createArtistCardHTML(artist));
|
cardsHTML.push(this.createArtistCardHTML(artist));
|
||||||
itemsToStore.push({ el: null, data: artist, type: 'artist' });
|
itemsToStore.push({ el: null, data: artist, type: 'artist' });
|
||||||
|
|
@ -2673,6 +2727,7 @@ export class UIRenderer {
|
||||||
const artist = await this.api.getArtist(item.id);
|
const artist = await this.api.getArtist(item.id);
|
||||||
if (artist) {
|
if (artist) {
|
||||||
artist._lazy = cardsHTML.length >= 6;
|
artist._lazy = cardsHTML.length >= 6;
|
||||||
|
artist._isEditorsPick = true;
|
||||||
cardsHTML.push(this.createArtistCardHTML(artist));
|
cardsHTML.push(this.createArtistCardHTML(artist));
|
||||||
itemsToStore.push({ el: null, data: artist, type: 'artist' });
|
itemsToStore.push({ el: null, data: artist, type: 'artist' });
|
||||||
}
|
}
|
||||||
|
|
@ -2689,6 +2744,8 @@ export class UIRenderer {
|
||||||
audioQuality: item.audioQuality,
|
audioQuality: item.audioQuality,
|
||||||
mediaMetadata: item.mediaMetadata,
|
mediaMetadata: item.mediaMetadata,
|
||||||
duration: item.duration,
|
duration: item.duration,
|
||||||
|
_lazy: cardsHTML.length >= 6,
|
||||||
|
_isEditorsPick: true,
|
||||||
};
|
};
|
||||||
cardsHTML.push(this.createTrackCardHTML(track));
|
cardsHTML.push(this.createTrackCardHTML(track));
|
||||||
itemsToStore.push({ el: null, data: track, type: 'track' });
|
itemsToStore.push({ el: null, data: track, type: 'track' });
|
||||||
|
|
@ -2696,6 +2753,8 @@ export class UIRenderer {
|
||||||
// Fall back to API call
|
// Fall back to API call
|
||||||
const track = await this.api.getTrackMetadata(item.id);
|
const track = await this.api.getTrackMetadata(item.id);
|
||||||
if (track) {
|
if (track) {
|
||||||
|
track._lazy = cardsHTML.length >= 6;
|
||||||
|
track._isEditorsPick = true;
|
||||||
cardsHTML.push(this.createTrackCardHTML(track));
|
cardsHTML.push(this.createTrackCardHTML(track));
|
||||||
itemsToStore.push({ el: null, data: track, type: 'track' });
|
itemsToStore.push({ el: null, data: track, type: 'track' });
|
||||||
}
|
}
|
||||||
|
|
@ -2708,6 +2767,8 @@ export class UIRenderer {
|
||||||
cover: item.cover,
|
cover: item.cover,
|
||||||
tracks: item.tracks || [],
|
tracks: item.tracks || [],
|
||||||
numberOfTracks: item.numberOfTracks || (item.tracks ? item.tracks.length : 0),
|
numberOfTracks: item.numberOfTracks || (item.tracks ? item.tracks.length : 0),
|
||||||
|
_lazy: cardsHTML.length >= 6,
|
||||||
|
_isEditorsPick: true,
|
||||||
};
|
};
|
||||||
const subtitle = item.username ? `by ${item.username}` : null;
|
const subtitle = item.username ? `by ${item.username}` : null;
|
||||||
cardsHTML.push(this.createUserPlaylistCardHTML(playlist, subtitle));
|
cardsHTML.push(this.createUserPlaylistCardHTML(playlist, subtitle));
|
||||||
|
|
@ -2715,6 +2776,8 @@ export class UIRenderer {
|
||||||
} else {
|
} else {
|
||||||
const playlist = await syncManager.getPublicPlaylist(item.id);
|
const playlist = await syncManager.getPublicPlaylist(item.id);
|
||||||
if (playlist) {
|
if (playlist) {
|
||||||
|
playlist._lazy = cardsHTML.length >= 6;
|
||||||
|
playlist._isEditorsPick = true;
|
||||||
const subtitle = item.username ? `by ${item.username}` : null;
|
const subtitle = item.username ? `by ${item.username}` : null;
|
||||||
cardsHTML.push(this.createUserPlaylistCardHTML(playlist, subtitle));
|
cardsHTML.push(this.createUserPlaylistCardHTML(playlist, subtitle));
|
||||||
itemsToStore.push({ el: null, data: playlist, type: 'user-playlist' });
|
itemsToStore.push({ el: null, data: playlist, type: 'user-playlist' });
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue