feat(reviews): detailed critics reviews
This commit is contained in:
parent
11f66c1a28
commit
db20643a61
2 changed files with 104 additions and 1 deletions
62
js/ui.js
62
js/ui.js
|
|
@ -13,7 +13,9 @@ import {
|
||||||
calculateTotalDuration,
|
calculateTotalDuration,
|
||||||
formatDuration,
|
formatDuration,
|
||||||
escapeHtml,
|
escapeHtml,
|
||||||
|
decodeHtml,
|
||||||
getShareUrl,
|
getShareUrl,
|
||||||
|
createModal,
|
||||||
} from './utils.js';
|
} from './utils.js';
|
||||||
import { openLyricsPanel, renderLyricsInFullscreen, clearFullscreenLyricsSync } from './lyrics.js';
|
import { openLyricsPanel, renderLyricsInFullscreen, clearFullscreenLyricsSync } from './lyrics.js';
|
||||||
import {
|
import {
|
||||||
|
|
@ -3853,11 +3855,69 @@ export class UIRenderer {
|
||||||
);
|
);
|
||||||
const data = await response.json();
|
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') {
|
if (data.critic.score == 'NR') {
|
||||||
rateCriticsEl.innerHTML = `<a style="color: var(--muted-foreground);">Critic Score Not Available Yet</a>`;
|
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>`;
|
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) {
|
} catch (e) {
|
||||||
rateCriticsEl.innerHTML = `<a style="color: var(--muted-foreground);">Unable to Fetch Critic Score</a>`;
|
rateCriticsEl.innerHTML = `<a style="color: var(--muted-foreground);">Unable to Fetch Critic Score</a>`;
|
||||||
|
|
|
||||||
43
js/utils.js
43
js/utils.js
|
|
@ -381,6 +381,13 @@ export const escapeHtml = (unsafe) => {
|
||||||
.replace(/'/g, ''');
|
.replace(/'/g, ''');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
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' } = {}) => {
|
export const getTrackTitle = (track, { fallback = 'Unknown Title' } = {}) => {
|
||||||
if (!track?.title) return fallback;
|
if (!track?.title) return fallback;
|
||||||
return track?.version ? `${track.title} (${track.version})` : track.title;
|
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;
|
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;">×</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 };
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue