feat(reviews): detailed critics reviews

This commit is contained in:
Samidy 2026-04-06 19:56:01 +03:00
parent 11f66c1a28
commit db20643a61
2 changed files with 104 additions and 1 deletions

View file

@ -13,7 +13,9 @@ import {
calculateTotalDuration,
formatDuration,
escapeHtml,
decodeHtml,
getShareUrl,
createModal,
} from './utils.js';
import { openLyricsPanel, renderLyricsInFullscreen, clearFullscreenLyricsSync } from './lyrics.js';
import {
@ -3853,11 +3855,69 @@ export class UIRenderer {
);
const data = await response.json();
rateCriticsEl.innerHTML = `<a href="${data.url}" target="_blank" style="color: var(--muted-foreground);">Critic Score: <span style="text-decoration: underline;">${data.critic.score}</span>, Based on ${data.critic.count} reviews</a>`;
const critviews = data.critic.reviews || [];
rateCriticsEl.innerHTML = `<a href="javascript:void(0)" style="color: var(--muted-foreground); cursor: pointer;">Critic Score: ${data.critic.score}, Based on <span style="text-decoration: underline;">${data.critic.count} reviews</span></a>`;
if (data.critic.score == 'NR') {
rateCriticsEl.innerHTML = `<a style="color: var(--muted-foreground);">Critic Score Not Available Yet</a>`;
} else {
rateCriticsEl.querySelector('a').onclick = () => {
const con = document.createElement('div');
con.style.display = 'flex';
con.style.flexDirection = 'column';
con.style.gap = '1.5rem';
critviews.forEach((review) => {
const reviewdiv = document.createElement('div');
reviewdiv.style.display = 'flex';
reviewdiv.style.gap = '1rem';
reviewdiv.style.paddingBottom = '1rem';
reviewdiv.style.borderBottom = '1px solid var(--border)';
const publication = decodeHtml(review.publication || review.source || 'Unknown Publication');
const author = decodeHtml(review.author || '');
const quote = decodeHtml(review.text || review.quote || 'No review text available.');
reviewdiv.innerHTML = `
<img src="${review.image}" width="50" height="50" style="border-radius: 8px; object-fit: cover; background: var(--highlight);"
onerror="this.src='images/monochrome-logo.svg'; this.onerror=null;"
loading="lazy"
referrerpolicy="no-referrer">
<div style="flex: 1;">
<div style="display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 0.25rem;">
<div class="pub-name" style="font-weight: 600; color: var(--foreground);"></div>
<div style="font-weight: bold; color: var(--primary-foreground); background: var(--primary); padding: 2px 10px; border-radius: 6px; font-size: 0.85rem; box-shadow: 0 2px 4px rgba(0,0,0,0.1);">${review.score}</div>
</div>
<div class="author-name" style="font-size: 0.8rem; color: var(--muted-foreground); margin-bottom: 0.5rem;"></div>
<div class="quote-text" style="font-size: 0.95rem; line-height: 1.5; color: var(--muted-foreground); font-style: italic;"></div>
</div>
`;
reviewdiv.querySelector('.pub-name').textContent = publication;
if (author) {
reviewdiv.querySelector('.author-name').textContent = `By ${author}`;
} else {
reviewdiv.querySelector('.author-name').remove();
}
reviewdiv.querySelector('.quote-text').textContent = `"${quote}"`;
con.appendChild(reviewdiv);
});
if (critviews.length === 0) {
con.innerHTML =
'<div style="text-align: center; padding: 2rem; color: var(--muted-foreground);">No reviews found.</div>';
}
createModal({
title: 'Critics Reviews',
content: con,
className: 'extra-wide',
});
};
}
rateUsersEl.innerHTML = `<a href="${data.url}" target="_blank" style="color: var(--muted-foreground);">User Score: <span style="text-decoration: underline;">${data.user.score}</span>, Based on ${data.user.count} reviews</a>`;
} catch (e) {
rateCriticsEl.innerHTML = `<a style="color: var(--muted-foreground);">Unable to Fetch Critic Score</a>`;

View file

@ -381,6 +381,13 @@ export const escapeHtml = (unsafe) => {
.replace(/'/g, '&#039;');
};
export const decodeHtml = (html) => {
if (!html) return '';
const div = document.createElement('div');
div.innerHTML = html;
return div.textContent;
};
export const getTrackTitle = (track, { fallback = 'Unknown Title' } = {}) => {
if (!track?.title) return fallback;
return track?.version ? `${track.title} (${track.version})` : track.title;
@ -778,3 +785,39 @@ export function replaceTokens(template, tokens) {
return key in tokens ? tokens[key] : match;
});
}
export function createModal({ title, content, className = '', onClose }) {
const modal = document.createElement('div');
modal.className = 'modal active';
modal.style.zIndex = '10000';
modal.innerHTML = `
<div class="modal-overlay"></div>
<div class="modal-content ${className}" style="display: flex; flex-direction: column;">
<div class="modal-header" style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 1.5rem; border-bottom: 1px solid var(--border); padding-bottom: 1rem;">
<h3 style="margin: 0;">${title}</h3>
<button class="btn-close" style="background: none; border: none; font-size: 2rem; cursor: pointer; color: var(--foreground); padding: 0.2rem 0.5rem; line-height: 1;">&times;</button>
</div>
<div class="modal-body" style="max-height: 70vh; overflow-y: auto; padding-right: 0.5rem;"></div>
</div>
`;
const body = modal.querySelector('.modal-body');
if (typeof content === 'string') {
body.innerHTML = content;
} else if (content instanceof HTMLElement) {
body.appendChild(content);
}
document.body.appendChild(modal);
const close = () => {
modal.remove();
if (onClose) onClose();
};
modal.querySelector('.modal-overlay').onclick = close;
modal.querySelector('.btn-close').onclick = close;
return { modal, close };
}