Refactor movie playback and add clickability to stats
Co-authored-by: mathieumoran1 <[email protected]>
This commit is contained in:
co-authored by
mathieumoran1
parent
d98c802a40
commit
c207a03f39
@@ -236,6 +236,15 @@ footer {
|
|||||||
border-radius: 14px;
|
border-radius: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.stats-movie-item--clickable {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stats-movie-item--clickable:hover {
|
||||||
|
background: rgba(0, 0, 0, 0.02);
|
||||||
|
border-color: rgba(0, 0, 0, 0.14);
|
||||||
|
}
|
||||||
|
|
||||||
.stats-movie-poster {
|
.stats-movie-poster {
|
||||||
width: 56px;
|
width: 56px;
|
||||||
height: 84px;
|
height: 84px;
|
||||||
|
|||||||
+30
-8
@@ -348,15 +348,26 @@ window.addEventListener('DOMContentLoaded', async function () {
|
|||||||
const isChecked = watchedMoviesInYear.some(watchedMovie => watchedMovie.imdb_id === movie.imdb_id);
|
const isChecked = watchedMoviesInYear.some(watchedMovie => watchedMovie.imdb_id === movie.imdb_id);
|
||||||
const watchedMovie = watchedMoviesInYear.find(wm => wm.imdb_id === movie.imdb_id);
|
const watchedMovie = watchedMoviesInYear.find(wm => wm.imdb_id === movie.imdb_id);
|
||||||
const watchedDate = watchedMovie ? new Date(watchedMovie.watchedDate).toLocaleString() : '';
|
const watchedDate = watchedMovie ? new Date(watchedMovie.watchedDate).toLocaleString() : '';
|
||||||
const hasVideoSrc = !!(movie && typeof movie.video_src === 'string' && movie.video_src.trim());
|
const cleanUrl = (v) => {
|
||||||
const hasEmbedSrc = !!(movie && typeof movie.embed_src === 'string' && movie.embed_src.trim());
|
const s = (typeof v === 'string' ? v.trim() : '');
|
||||||
|
// Treat placeholders as "no link" so you never need to store '#'
|
||||||
|
if (!s || s === '#' || s.toLowerCase() === 'about:blank') return '';
|
||||||
|
return s;
|
||||||
|
};
|
||||||
|
const hasVideoSrc = !!cleanUrl(movie?.video_src);
|
||||||
|
const hasEmbedSrc = !!cleanUrl(movie?.embed_src);
|
||||||
const hasNewPlayerSource = hasVideoSrc || hasEmbedSrc;
|
const hasNewPlayerSource = hasVideoSrc || hasEmbedSrc;
|
||||||
const hasLegacy = !!(movie && typeof movie.vod_link === 'string' && movie.vod_link.trim());
|
const legacyUrl = cleanUrl(movie?.vod_link);
|
||||||
|
const hasLegacy = !!legacyUrl;
|
||||||
|
|
||||||
// If the movie only has legacy `vod_link`, skip the player entirely and open the original URL in a new tab.
|
// Navigation:
|
||||||
|
// - Prefer in-app player when we have a non-legacy source
|
||||||
|
// - If only legacy exists, open the legacy URL
|
||||||
|
// - If there is no source at all, render as non-clickable (no "#")
|
||||||
const playerUrl = (movie && movie._id && hasNewPlayerSource)
|
const playerUrl = (movie && movie._id && hasNewPlayerSource)
|
||||||
? `/player.html?id=${encodeURIComponent(movie._id)}`
|
? `/player.html?id=${encodeURIComponent(movie._id)}`
|
||||||
: (hasLegacy ? movie.vod_link : (movie && movie._id ? `/player.html?id=${encodeURIComponent(movie._id)}` : '#'));
|
: (hasLegacy ? legacyUrl : '');
|
||||||
|
const isClickable = !!playerUrl;
|
||||||
|
|
||||||
const movieDiv = document.createElement('div');
|
const movieDiv = document.createElement('div');
|
||||||
movieDiv.classList.add('col-12', 'col-sm-6', 'col-lg-4', 'movie-card');
|
movieDiv.classList.add('col-12', 'col-sm-6', 'col-lg-4', 'movie-card');
|
||||||
@@ -364,15 +375,25 @@ window.addEventListener('DOMContentLoaded', async function () {
|
|||||||
movieDiv.innerHTML = `
|
movieDiv.innerHTML = `
|
||||||
<div class="card h-100 shadow-sm">
|
<div class="card h-100 shadow-sm">
|
||||||
${isChecked ? '<span class="badge bg-success position-absolute top-0 start-0 m-2 watched-badge">VISIONNÉ</span>' : ''}
|
${isChecked ? '<span class="badge bg-success position-absolute top-0 start-0 m-2 watched-badge">VISIONNÉ</span>' : ''}
|
||||||
<a href="${playerUrl}" target="_self" rel="noopener noreferrer" class="text-decoration-none">
|
${
|
||||||
|
isClickable
|
||||||
|
? `<a href="${playerUrl}" target="_self" rel="noopener noreferrer" class="text-decoration-none">
|
||||||
<div class="text-center pt-3 px-3">
|
<div class="text-center pt-3 px-3">
|
||||||
<img src="${movie.poster}" class="img-fluid rounded movie-poster" alt="${movie.title}">
|
<img src="${movie.poster}" class="img-fluid rounded movie-poster" alt="${movie.title}">
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>`
|
||||||
|
: `<div class="text-center pt-3 px-3">
|
||||||
|
<img src="${movie.poster}" class="img-fluid rounded movie-poster" alt="${movie.title}">
|
||||||
|
</div>`
|
||||||
|
}
|
||||||
<div class="card-body d-flex flex-column">
|
<div class="card-body d-flex flex-column">
|
||||||
<div class="d-flex justify-content-between align-items-start gap-2">
|
<div class="d-flex justify-content-between align-items-start gap-2">
|
||||||
<h5 class="card-title mb-1 flex-grow-1">
|
<h5 class="card-title mb-1 flex-grow-1">
|
||||||
<a href="${playerUrl}" target="_self" rel="noopener noreferrer" class="text-decoration-none text-dark">${movie.title}</a>
|
${
|
||||||
|
isClickable
|
||||||
|
? `<a href="${playerUrl}" target="_self" rel="noopener noreferrer" class="text-decoration-none text-dark">${movie.title}</a>`
|
||||||
|
: `<span class="text-dark">${movie.title}</span>`
|
||||||
|
}
|
||||||
</h5>
|
</h5>
|
||||||
<div class="text-nowrap small text-muted">
|
<div class="text-nowrap small text-muted">
|
||||||
⭐ ${movie.rating}
|
⭐ ${movie.rating}
|
||||||
@@ -381,6 +402,7 @@ window.addEventListener('DOMContentLoaded', async function () {
|
|||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
${!isClickable ? '<div class="text-muted small mb-1">Aucune source</div>' : ''}
|
||||||
<div class="text-muted small fw-semibold fst-italic mb-2">${movie.category}</div>
|
<div class="text-muted small fw-semibold fst-italic mb-2">${movie.category}</div>
|
||||||
<p class="card-text small flex-grow-1 mb-2">${movie.description}</p>
|
<p class="card-text small flex-grow-1 mb-2">${movie.description}</p>
|
||||||
${isChecked ? `<div class="text-muted small"><span class="fw-semibold text-dark">Regardé le:</span> ${watchedDate}</div>` : ''}
|
${isChecked ? `<div class="text-muted small"><span class="fw-semibold text-dark">Regardé le:</span> ${watchedDate}</div>` : ''}
|
||||||
|
|||||||
@@ -435,6 +435,7 @@ window.addEventListener('DOMContentLoaded', async function () {
|
|||||||
moviesEmptyEl.classList.add('d-none');
|
moviesEmptyEl.classList.add('d-none');
|
||||||
|
|
||||||
sorted.forEach((movie) => {
|
sorted.forEach((movie) => {
|
||||||
|
const movieId = safeStr(movie?.movieId).trim();
|
||||||
const imdbId = safeStr(movie?.imdb_id).trim();
|
const imdbId = safeStr(movie?.imdb_id).trim();
|
||||||
const title = safeStr(movie?.title).trim() || '(sans titre)';
|
const title = safeStr(movie?.title).trim() || '(sans titre)';
|
||||||
const category = safeStr(movie?.category).trim();
|
const category = safeStr(movie?.category).trim();
|
||||||
@@ -445,6 +446,12 @@ window.addEventListener('DOMContentLoaded', async function () {
|
|||||||
|
|
||||||
const item = document.createElement('div');
|
const item = document.createElement('div');
|
||||||
item.className = 'stats-movie-item';
|
item.className = 'stats-movie-item';
|
||||||
|
if (movieId) {
|
||||||
|
item.classList.add('stats-movie-item--clickable');
|
||||||
|
item.setAttribute('role', 'link');
|
||||||
|
item.setAttribute('tabindex', '0');
|
||||||
|
item.setAttribute('aria-label', `Ouvrir le lecteur: ${title}`);
|
||||||
|
}
|
||||||
|
|
||||||
const poster = document.createElement('img');
|
const poster = document.createElement('img');
|
||||||
poster.className = 'stats-movie-poster';
|
poster.className = 'stats-movie-poster';
|
||||||
@@ -461,6 +468,26 @@ window.addEventListener('DOMContentLoaded', async function () {
|
|||||||
try { poster.removeAttribute('src'); } catch (_) {}
|
try { poster.removeAttribute('src'); } catch (_) {}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function openPlayerIfPossible(evt) {
|
||||||
|
if (!movieId) return;
|
||||||
|
// Don't hijack clicks inside controls (IMDb button, details summary, etc.)
|
||||||
|
const target = evt?.target;
|
||||||
|
if (target && typeof target.closest === 'function') {
|
||||||
|
if (target.closest('a, button, summary, details')) return;
|
||||||
|
}
|
||||||
|
window.location.href = `/player.html?id=${encodeURIComponent(movieId)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (movieId) {
|
||||||
|
item.addEventListener('click', openPlayerIfPossible);
|
||||||
|
item.addEventListener('keydown', (e) => {
|
||||||
|
if (e.key === 'Enter' || e.key === ' ') {
|
||||||
|
e.preventDefault();
|
||||||
|
openPlayerIfPossible(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const main = document.createElement('div');
|
const main = document.createElement('div');
|
||||||
main.className = 'flex-grow-1';
|
main.className = 'flex-grow-1';
|
||||||
|
|
||||||
|
|||||||
@@ -361,16 +361,6 @@ router.post("/add", async (req, res) => {
|
|||||||
if (normalizedMode === null) {
|
if (normalizedMode === null) {
|
||||||
return res.status(400).json({ message: "player_mode invalide (auto|video|embed)" });
|
return res.status(400).json({ message: "player_mode invalide (auto|video|embed)" });
|
||||||
}
|
}
|
||||||
if (
|
|
||||||
!hasAnySourceIncludingFile({
|
|
||||||
vod_link: normalizedVod || "",
|
|
||||||
video_src: normalizedVideo || "",
|
|
||||||
embed_src: normalizedEmbed || "",
|
|
||||||
video_file: normalizedFile || "",
|
|
||||||
})
|
|
||||||
) {
|
|
||||||
return res.status(400).json({ message: "Ajoute au moins une source (VOD / video_src / embed_src)." });
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fetch movie details from OMDb API
|
// Fetch movie details from OMDb API
|
||||||
let movieDetails;
|
let movieDetails;
|
||||||
@@ -584,17 +574,6 @@ router.put("/:id", async (req, res) => {
|
|||||||
if (poster !== undefined) movie.poster = poster;
|
if (poster !== undefined) movie.poster = poster;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
|
||||||
!hasAnySourceIncludingFile({
|
|
||||||
vod_link: movie.vod_link || "",
|
|
||||||
video_src: movie.video_src || "",
|
|
||||||
embed_src: movie.embed_src || "",
|
|
||||||
video_file: movie.video_file || "",
|
|
||||||
})
|
|
||||||
) {
|
|
||||||
return res.status(400).json({ message: "Ajoute au moins une source (VOD / video_src / embed_src / video_file)." });
|
|
||||||
}
|
|
||||||
|
|
||||||
await movie.save();
|
await movie.save();
|
||||||
cacheClear();
|
cacheClear();
|
||||||
return res.status(200).json(movie);
|
return res.status(200).json(movie);
|
||||||
|
|||||||
@@ -151,6 +151,7 @@ router.get("/stats", verifyToken, async (req, res) => {
|
|||||||
const movie = movieByImdbId.get(imdbId);
|
const movie = movieByImdbId.get(imdbId);
|
||||||
if (!movie) return null;
|
if (!movie) return null;
|
||||||
return {
|
return {
|
||||||
|
movieId: movie?._id ? String(movie._id) : null,
|
||||||
imdb_id: movie.imdb_id,
|
imdb_id: movie.imdb_id,
|
||||||
title: movie.title || "",
|
title: movie.title || "",
|
||||||
poster: movie.poster || "",
|
poster: movie.poster || "",
|
||||||
|
|||||||
Reference in New Issue
Block a user