From 536365e7497260c59fb067855d37856468beccc8 Mon Sep 17 00:00:00 2001 From: Admin9705 <9705@duck.com> Date: Fri, 6 Feb 2026 22:58:26 -0500 Subject: [PATCH] update --- frontend/static/css/movie-hunt-detail.css | 5 +- .../js/modules/features/movie-hunt-detail.js | 138 ++--- .../features/requestarr/requestarr-content.js | 7 +- .../features/requestarr/requestarr-detail.js | 533 ++++++++++++++++++ frontend/templates/components/scripts.html | 1 + 5 files changed, 611 insertions(+), 73 deletions(-) create mode 100644 frontend/static/js/modules/features/requestarr/requestarr-detail.js diff --git a/frontend/static/css/movie-hunt-detail.css b/frontend/static/css/movie-hunt-detail.css index beccd914..1595cb0d 100644 --- a/frontend/static/css/movie-hunt-detail.css +++ b/frontend/static/css/movie-hunt-detail.css @@ -208,7 +208,7 @@ /* Instance Selector */ .movie-detail-instance-selector { - display: flex; + display: inline-flex; align-items: center; gap: 12px; margin-bottom: 16px; @@ -217,6 +217,7 @@ border-radius: 8px; border: 1px solid rgba(255, 255, 255, 0.15); backdrop-filter: blur(10px); + max-width: 400px; } .movie-detail-instance-selector label { @@ -227,6 +228,7 @@ align-items: center; gap: 8px; margin: 0; + white-space: nowrap; } .movie-detail-instance-selector label i { @@ -243,6 +245,7 @@ font-weight: 500; cursor: pointer; transition: all 0.3s ease; + min-width: 200px; } .movie-detail-select:hover { diff --git a/frontend/static/js/modules/features/movie-hunt-detail.js b/frontend/static/js/modules/features/movie-hunt-detail.js index 67d493cd..ef865e47 100644 --- a/frontend/static/js/modules/features/movie-hunt-detail.js +++ b/frontend/static/js/modules/features/movie-hunt-detail.js @@ -8,8 +8,8 @@ window.MovieHuntDetail = { currentMovie: null, tmdbApiKey: null, - radarrInstances: [], - selectedInstance: null, + movieHuntInstances: [], + selectedInstanceId: null, init() { console.log('[MovieHuntDetail] Module initialized'); @@ -18,9 +18,9 @@ window.addEventListener('popstate', (e) => { if (e.state && e.state.movieDetail) { // User navigated to a movie detail via back/forward - this.openDetail(e.state.movieDetail, e.state.options || {}, true); - } else { - // User navigated away from movie detail + this.openDetail(e.state.movieDetail, {}, true); + } else if (!e.state || !e.state.requestarrMovieDetail) { + // User navigated away from movie detail (and not to Requestarr detail) this.closeDetail(true); } }); @@ -68,21 +68,20 @@ }, /** - * Open detail view for a movie + * Open detail view for a movie from Movie Hunt * @param {Object} movie - Movie data with at least title, year, tmdb_id - * @param {Object} options - Optional config: { source: 'movie-hunt' | 'requestarr', suggestedInstance: string } + * @param {Object} options - Optional config (unused, kept for compatibility) * @param {Boolean} fromHistory - True if opened from browser history (don't push state again) */ async openDetail(movie, options = {}, fromHistory = false) { if (!movie) return; this.currentMovie = movie; - this.options = options || {}; - console.log('[MovieHuntDetail] Opening detail for:', movie.title, 'from', this.options.source || 'unknown'); + console.log('[MovieHuntDetail] Opening detail for:', movie.title); - // Load Radarr instances if not already loaded - if (this.radarrInstances.length === 0) { - await this.loadRadarrInstances(); + // Load Movie Hunt instances if not already loaded + if (this.movieHuntInstances.length === 0) { + await this.loadMovieHuntInstances(); } // Get or create detail view container @@ -104,7 +103,7 @@ const tmdbId = movie.tmdb_id || movie.id; const url = `${window.location.pathname}${window.location.search}#movie/${tmdbId}`; history.pushState( - { movieDetail: movie, options: this.options }, + { movieDetail: movie }, movie.title, url ); @@ -124,6 +123,7 @@ const details = await this.fetchMovieDetails(tmdbId); if (details) { + console.log('[MovieHuntDetail] Rendering detail with instances:', this.movieHuntInstances.length); detailView.innerHTML = this.renderMovieDetail(details, movie); this.setupDetailInteractions(); } else { @@ -246,20 +246,30 @@ } } - // Build instance selector HTML + // Build Movie Hunt instance selector (always show, even if loading) let instanceSelectorHTML = ''; - if (this.radarrInstances.length > 0) { + if (this.movieHuntInstances.length > 0) { instanceSelectorHTML = `
`; + } else { + // Show loading state if instances haven't loaded yet + instanceSelectorHTML = ` +
+ + +
+ `; } return ` @@ -395,8 +405,8 @@ const instanceSelect = document.getElementById('movie-detail-instance-select'); if (instanceSelect) { instanceSelect.addEventListener('change', async () => { - this.selectedInstance = instanceSelect.value; - console.log('[MovieHuntDetail] Instance changed to:', this.selectedInstance); + this.selectedInstanceId = parseInt(instanceSelect.value); + console.log('[MovieHuntDetail] Instance changed to:', this.selectedInstanceId); // Update movie status for new instance await this.updateMovieStatus(); @@ -410,18 +420,7 @@ const requestBtn = document.getElementById('movie-detail-request-btn'); if (requestBtn && this.currentMovie) { requestBtn.addEventListener('click', () => { - // Don't close detail page - just open the modal on top of it - - // If called from Requestarr, use Requestarr modal with selected instance - if (this.options.source === 'requestarr' && window.RequestarrDiscover && window.RequestarrDiscover.modal) { - window.RequestarrDiscover.modal.openModal( - this.currentMovie.tmdb_id, - 'movie', - this.selectedInstance - ); - } - // Otherwise use Movie Hunt request modal - else if (window.MovieHunt && window.MovieHunt.openMovieHuntRequestModal) { + if (window.MovieHunt && window.MovieHunt.openMovieHuntRequestModal) { window.MovieHunt.openMovieHuntRequestModal(this.currentMovie); } }); @@ -446,11 +445,10 @@ backdrop_path: details.backdrop_path, overview: details.overview, vote_average: details.vote_average, - in_library: false, - in_cooldown: false + in_library: false }; // Open detail and update URL (fromHistory = false) - this.openDetail(movieData, this.options || {}, false); + this.openDetail(movieData, {}, false); } } catch (error) { console.error('[MovieHuntDetail] Error opening similar movie:', error); @@ -469,55 +467,67 @@ document.addEventListener('keydown', escHandler); }, - async loadRadarrInstances() { + async loadMovieHuntInstances() { + console.log('[MovieHuntDetail] Starting to load Movie Hunt instances...'); try { - const response = await fetch('./api/requestarr/instances/radarr'); + const response = await fetch('./api/movie-hunt/instances'); const data = await response.json(); + console.log('[MovieHuntDetail] Instances API response:', data); if (data.instances && data.instances.length > 0) { - this.radarrInstances = data.instances; + this.movieHuntInstances = data.instances; - // Set initial selected instance (prioritize suggested instance from options) - if (this.options.suggestedInstance) { - this.selectedInstance = this.options.suggestedInstance; - } else if (!this.selectedInstance) { - this.selectedInstance = this.radarrInstances[0].name; + // Set initial selected instance (Movie Hunt uses integer IDs) + if (!this.selectedInstanceId) { + // Get current instance from server + const currentResponse = await fetch('./api/movie-hunt/current-instance'); + const currentData = await currentResponse.json(); + this.selectedInstanceId = currentData.instance_id || this.movieHuntInstances[0].id; } - console.log('[MovieHuntDetail] Loaded', this.radarrInstances.length, 'Radarr instances, selected:', this.selectedInstance); + console.log('[MovieHuntDetail] Loaded', this.movieHuntInstances.length, 'Movie Hunt instances, selected:', this.selectedInstanceId); } else { - this.radarrInstances = []; - this.selectedInstance = null; + console.log('[MovieHuntDetail] No instances found in response'); + this.movieHuntInstances = []; + this.selectedInstanceId = null; } } catch (error) { - console.error('[MovieHuntDetail] Error loading Radarr instances:', error); - this.radarrInstances = []; - this.selectedInstance = null; + console.error('[MovieHuntDetail] Error loading Movie Hunt instances:', error); + this.movieHuntInstances = []; + this.selectedInstanceId = null; } }, - async checkMovieStatus(tmdbId, instanceName) { - if (!instanceName) return { in_library: false, in_cooldown: false }; + async checkMovieStatus(tmdbId, instanceId) { + if (!instanceId) return { in_library: false }; try { - const response = await fetch(`./api/requestarr/movie-status?tmdb_id=${tmdbId}&instance=${encodeURIComponent(instanceName)}`); + // Check Movie Hunt collection for this instance + const response = await fetch(`./api/movie-hunt/collection?instance_id=${instanceId}`); const data = await response.json(); - return { - in_library: data.in_library || false, - in_cooldown: (data.cooldown_status && data.cooldown_status.in_cooldown) || false - }; + // Find this movie in the collection + const items = data.items || []; + const movie = items.find(item => item.tmdb_id === tmdbId); + + if (movie) { + return { + in_library: movie.status === 'available' + }; + } + + return { in_library: false }; } catch (error) { console.error('[MovieHuntDetail] Error checking movie status:', error); - return { in_library: false, in_cooldown: false }; + return { in_library: false }; } }, async updateMovieStatus() { - if (!this.currentMovie || !this.selectedInstance) return; + if (!this.currentMovie || !this.selectedInstanceId) return; const tmdbId = this.currentMovie.tmdb_id || this.currentMovie.id; - const status = await this.checkMovieStatus(tmdbId, this.selectedInstance); + const status = await this.checkMovieStatus(tmdbId, this.selectedInstanceId); // Update the action button based on new status const actionsContainer = document.querySelector('.movie-detail-actions'); @@ -526,8 +536,6 @@ if (status.in_library) { actionButton = ''; - } else if (status.in_cooldown) { - actionButton = ''; } else { actionButton = ''; } @@ -538,13 +546,7 @@ const requestBtn = document.getElementById('movie-detail-request-btn'); if (requestBtn) { requestBtn.addEventListener('click', () => { - if (this.options.source === 'requestarr' && window.RequestarrDiscover && window.RequestarrDiscover.modal) { - window.RequestarrDiscover.modal.openModal( - this.currentMovie.tmdb_id, - 'movie', - this.selectedInstance - ); - } else if (window.MovieHunt && window.MovieHunt.openMovieHuntRequestModal) { + if (window.MovieHunt && window.MovieHunt.openMovieHuntRequestModal) { window.MovieHunt.openMovieHuntRequestModal(this.currentMovie); } }); diff --git a/frontend/static/js/modules/features/requestarr/requestarr-content.js b/frontend/static/js/modules/features/requestarr/requestarr-content.js index 540fa180..968b8bca 100644 --- a/frontend/static/js/modules/features/requestarr/requestarr-content.js +++ b/frontend/static/js/modules/features/requestarr/requestarr-content.js @@ -982,8 +982,8 @@ export class RequestarrContent { return; } - // For movies, open detail page if available - if (item.media_type === 'movie' && window.MovieHuntDetail && window.MovieHuntDetail.openDetail) { + // For movies, open Requestarr detail page if available + if (item.media_type === 'movie' && window.RequestarrDetail && window.RequestarrDetail.openDetail) { const movieData = { tmdb_id: item.tmdb_id, id: item.tmdb_id, @@ -996,8 +996,7 @@ export class RequestarrContent { in_library: inLibrary, in_cooldown: inCooldown }; - window.MovieHuntDetail.openDetail(movieData, { - source: 'requestarr', + window.RequestarrDetail.openDetail(movieData, { suggestedInstance: card.suggestedInstance }); } else { diff --git a/frontend/static/js/modules/features/requestarr/requestarr-detail.js b/frontend/static/js/modules/features/requestarr/requestarr-detail.js new file mode 100644 index 00000000..fdc4a738 --- /dev/null +++ b/frontend/static/js/modules/features/requestarr/requestarr-detail.js @@ -0,0 +1,533 @@ +/** + * Requestarr Movie Detail Page - Detail view for Requestarr movies + * Handles Radarr instances and movie status checking + */ +(function() { + 'use strict'; + + window.RequestarrDetail = { + currentMovie: null, + tmdbApiKey: null, + radarrInstances: [], + selectedInstanceName: null, + + init() { + console.log('[RequestarrDetail] Module initialized'); + + // Listen for browser back/forward buttons + window.addEventListener('popstate', (e) => { + if (e.state && e.state.requestarrMovieDetail) { + // User navigated to a movie detail via back/forward + this.openDetail(e.state.requestarrMovieDetail, e.state.options || {}, true); + } else if (e.state && !e.state.movieDetail) { + // User navigated away from movie detail (but not to Movie Hunt detail) + this.closeDetail(true); + } + }); + }, + + /** + * Open detail view for a movie from Requestarr + * @param {Object} movie - Movie data with at least title, year, tmdb_id + * @param {Object} options - Optional config: { suggestedInstance: string } + * @param {Boolean} fromHistory - True if opened from browser history (don't push state again) + */ + async openDetail(movie, options = {}, fromHistory = false) { + if (!movie) return; + + this.currentMovie = movie; + this.options = options || {}; + console.log('[RequestarrDetail] Opening detail for:', movie.title); + + // Load Radarr instances if not already loaded + if (this.radarrInstances.length === 0) { + await this.loadRadarrInstances(); + } + + // Get or create detail view container + let detailView = document.getElementById('requestarr-detail-view'); + if (!detailView) { + detailView = document.createElement('div'); + detailView.id = 'requestarr-detail-view'; + detailView.className = 'movie-detail-view'; + document.body.appendChild(detailView); + } + + // Show loading state + detailView.innerHTML = this.getLoadingHTML(); + detailView.classList.add('active'); + + // Add to browser history (so back button works) + if (!fromHistory) { + const tmdbId = movie.tmdb_id || movie.id; + const url = `${window.location.pathname}${window.location.search}#requestarr-movie/${tmdbId}`; + history.pushState( + { requestarrMovieDetail: movie, options: this.options }, + movie.title, + url + ); + } + + // Setup close button + setTimeout(() => { + const closeBtn = detailView.querySelector('.movie-detail-close'); + if (closeBtn) { + closeBtn.addEventListener('click', () => this.closeDetail()); + } + }, 0); + + try { + // Fetch full movie details from TMDB + const tmdbId = movie.tmdb_id || movie.id; + const details = await this.fetchMovieDetails(tmdbId); + + if (details) { + detailView.innerHTML = this.renderMovieDetail(details, movie); + this.setupDetailInteractions(); + } else { + detailView.innerHTML = this.getErrorHTML('Failed to load movie details'); + } + } catch (error) { + console.error('[RequestarrDetail] Error loading details:', error); + detailView.innerHTML = this.getErrorHTML('Failed to load movie details'); + } + }, + + closeDetail(fromHistory = false) { + const detailView = document.getElementById('requestarr-detail-view'); + if (detailView) { + detailView.classList.remove('active'); + } + + // Remove from browser history if not already navigating + if (!fromHistory && detailView && detailView.classList.contains('active')) { + history.back(); + } + }, + + async fetchMovieDetails(tmdbId) { + if (!tmdbId) return null; + + try { + // Get TMDB API key from Movie Hunt (shared endpoint) + if (!this.tmdbApiKey) { + const keyResponse = await fetch('./api/movie-hunt/tmdb-key'); + if (!keyResponse.ok) throw new Error('TMDB key endpoint failed: ' + keyResponse.status); + const keyData = await keyResponse.json(); + this.tmdbApiKey = keyData.api_key; + } + + if (!this.tmdbApiKey) { + console.error('[RequestarrDetail] No TMDB API key available'); + return null; + } + + // Fetch movie details with credits and similar movies + const url = `https://api.themoviedb.org/3/movie/${tmdbId}?api_key=${this.tmdbApiKey}&append_to_response=credits,similar,videos,release_dates`; + const response = await fetch(url); + + if (!response.ok) { + throw new Error(`TMDB API returned ${response.status}`); + } + + const data = await response.json(); + return data; + } catch (error) { + console.error('[RequestarrDetail] Error fetching from TMDB:', error); + return null; + } + }, + + async loadRadarrInstances() { + try { + const response = await fetch('./api/requestarr/instances/radarr'); + const data = await response.json(); + + if (data.instances && data.instances.length > 0) { + this.radarrInstances = data.instances; + + // Set initial selected instance (prioritize suggested instance from options) + if (this.options.suggestedInstance) { + this.selectedInstanceName = this.options.suggestedInstance; + } else if (!this.selectedInstanceName) { + this.selectedInstanceName = this.radarrInstances[0].name; + } + + console.log('[RequestarrDetail] Loaded', this.radarrInstances.length, 'Radarr instances, selected:', this.selectedInstanceName); + } else { + this.radarrInstances = []; + this.selectedInstanceName = null; + } + } catch (error) { + console.error('[RequestarrDetail] Error loading Radarr instances:', error); + this.radarrInstances = []; + this.selectedInstanceName = null; + } + }, + + async checkMovieStatus(tmdbId, instanceName) { + if (!instanceName) return { in_library: false, in_cooldown: false }; + + try { + const response = await fetch(`./api/requestarr/movie-status?tmdb_id=${tmdbId}&instance=${encodeURIComponent(instanceName)}`); + const data = await response.json(); + + return { + in_library: data.in_library || false, + in_cooldown: (data.cooldown_status && data.cooldown_status.in_cooldown) || false + }; + } catch (error) { + console.error('[RequestarrDetail] Error checking movie status:', error); + return { in_library: false, in_cooldown: false }; + } + }, + + async updateMovieStatus() { + if (!this.currentMovie || !this.selectedInstanceName) return; + + const tmdbId = this.currentMovie.tmdb_id || this.currentMovie.id; + const status = await this.checkMovieStatus(tmdbId, this.selectedInstanceName); + + // Update the action button based on new status + const actionsContainer = document.querySelector('.movie-detail-actions'); + if (actionsContainer) { + let actionButton = ''; + + if (status.in_library) { + actionButton = ''; + } else if (status.in_cooldown) { + actionButton = ''; + } else { + actionButton = ''; + } + + actionsContainer.innerHTML = actionButton; + + // Re-setup request button if it exists + const requestBtn = document.getElementById('requestarr-detail-request-btn'); + if (requestBtn) { + requestBtn.addEventListener('click', () => { + if (window.RequestarrDiscover && window.RequestarrDiscover.modal) { + window.RequestarrDiscover.modal.openModal( + this.currentMovie.tmdb_id, + 'movie', + this.selectedInstanceName + ); + } + }); + } + } + }, + + renderMovieDetail(details, originalMovie) { + const backdropUrl = details.backdrop_path + ? `https://image.tmdb.org/t/p/original${details.backdrop_path}` + : (details.poster_path ? `https://image.tmdb.org/t/p/original${details.poster_path}` : ''); + + const posterUrl = details.poster_path + ? `https://image.tmdb.org/t/p/w500${details.poster_path}` + : './static/images/blackout.jpg'; + + const rating = details.vote_average ? Number(details.vote_average).toFixed(1) : 'N/A'; + const year = details.release_date ? new Date(details.release_date).getFullYear() : 'N/A'; + const runtime = details.runtime ? `${Math.floor(details.runtime / 60)}h ${details.runtime % 60}m` : 'N/A'; + + const genres = details.genres && details.genres.length > 0 + ? details.genres.map(g => `${this.escapeHtml(g.name)}`).join('') + : 'Unknown'; + + const overview = details.overview || 'No overview available.'; + + // Check status from original movie data + const inLibrary = originalMovie.in_library || false; + const inCooldown = originalMovie.in_cooldown || false; + + let actionButton = ''; + + if (inLibrary) { + actionButton = ''; + } else if (inCooldown) { + actionButton = ''; + } else { + actionButton = ''; + } + + // Director and main cast + let director = 'N/A'; + let mainCast = []; + + if (details.credits) { + if (details.credits.crew) { + const directorObj = details.credits.crew.find(c => c.job === 'Director'); + if (directorObj) director = directorObj.name; + } + if (details.credits.cast) { + mainCast = details.credits.cast.slice(0, 10); + } + } + + // Similar movies + let similarMovies = []; + if (details.similar && details.similar.results) { + similarMovies = details.similar.results.slice(0, 6); + } + + // Certification/Rating + let certification = 'Not Rated'; + if (details.release_dates && details.release_dates.results) { + const usRelease = details.release_dates.results.find(r => r.iso_3166_1 === 'US'); + if (usRelease && usRelease.release_dates && usRelease.release_dates.length > 0) { + const cert = usRelease.release_dates[0].certification; + if (cert) certification = cert; + } + } + + // Build Radarr instance selector + let instanceSelectorHTML = ''; + if (this.radarrInstances.length > 0) { + instanceSelectorHTML = ` +
+ + +
+ `; + } + + return ` + + +
+
+
+ ${this.escapeHtml(details.title)} +
+
+

${this.escapeHtml(details.title)}

+ ${instanceSelectorHTML} +
+
${year}
+
${runtime}
+
${rating}
+
+
${genres}
+

${this.escapeHtml(overview)}

+
+ ${actionButton} +
+
+
+
+ +
+ +
+

Movie Details

+
+
+
Director
+
${this.escapeHtml(director)}
+
+
+
Release Date
+
${details.release_date || 'N/A'}
+
+
+
Rating
+
${certification}
+
+
+
Budget
+
${details.budget ? '$' + (details.budget / 1000000).toFixed(1) + 'M' : 'N/A'}
+
+
+
Revenue
+
${details.revenue ? '$' + (details.revenue / 1000000).toFixed(1) + 'M' : 'N/A'}
+
+
+
Language
+
${details.original_language ? details.original_language.toUpperCase() : 'N/A'}
+
+
+
+ + ${mainCast.length > 0 ? ` + +
+

Cast

+
+ ${mainCast.map(actor => this.renderCastCard(actor)).join('')} +
+
+ ` : ''} + + ${similarMovies.length > 0 ? ` + +
+

Similar Movies

+
+ ${similarMovies.map(movie => this.renderSimilarCard(movie)).join('')} +
+
+ ` : ''} +
+ `; + }, + + renderCastCard(actor) { + const photoUrl = actor.profile_path + ? `https://image.tmdb.org/t/p/w185${actor.profile_path}` + : './static/images/blackout.jpg'; + + return ` +
+
+ ${this.escapeHtml(actor.name)} +
+
+
${this.escapeHtml(actor.name)}
+
${this.escapeHtml(actor.character || 'Unknown')}
+
+
+ `; + }, + + renderSimilarCard(movie) { + const posterUrl = movie.poster_path + ? `https://image.tmdb.org/t/p/w185${movie.poster_path}` + : './static/images/blackout.jpg'; + + return ` +
+
+ ${this.escapeHtml(movie.title)} +
+
${this.escapeHtml(movie.title)}
+
+
+
+
${this.escapeHtml(movie.title)}
+
+ ${movie.release_date ? new Date(movie.release_date).getFullYear() : 'N/A'} + ${movie.vote_average ? Number(movie.vote_average).toFixed(1) : 'N/A'} +
+
+
+ `; + }, + + setupDetailInteractions() { + // Close button + const closeBtn = document.querySelector('.movie-detail-close'); + if (closeBtn) { + closeBtn.addEventListener('click', () => this.closeDetail()); + } + + // Instance selector change handler + const instanceSelect = document.getElementById('requestarr-detail-instance-select'); + if (instanceSelect) { + instanceSelect.addEventListener('change', async () => { + this.selectedInstanceName = instanceSelect.value; + console.log('[RequestarrDetail] Instance changed to:', this.selectedInstanceName); + + // Update movie status for new instance + await this.updateMovieStatus(); + }); + + // Initial status check for selected instance + this.updateMovieStatus(); + } + + // Request button + const requestBtn = document.getElementById('requestarr-detail-request-btn'); + if (requestBtn && this.currentMovie) { + requestBtn.addEventListener('click', () => { + if (window.RequestarrDiscover && window.RequestarrDiscover.modal) { + window.RequestarrDiscover.modal.openModal( + this.currentMovie.tmdb_id, + 'movie', + this.selectedInstanceName + ); + } + }); + } + + // Similar movie cards - open their details + const similarCards = document.querySelectorAll('.movie-detail-similar .media-card'); + similarCards.forEach(card => { + card.addEventListener('click', async () => { + const tmdbId = card.getAttribute('data-tmdb-id'); + if (tmdbId) { + try { + const details = await this.fetchMovieDetails(tmdbId); + if (details) { + const movieData = { + tmdb_id: details.id, + id: details.id, + title: details.title, + year: details.release_date ? new Date(details.release_date).getFullYear() : null, + poster_path: details.poster_path, + backdrop_path: details.backdrop_path, + overview: details.overview, + vote_average: details.vote_average, + in_library: false, + in_cooldown: false + }; + this.openDetail(movieData, this.options || {}, false); + } + } catch (error) { + console.error('[RequestarrDetail] Error opening similar movie:', error); + } + } + }); + }); + + // ESC key to close + const escHandler = (e) => { + if (e.key === 'Escape') { + this.closeDetail(); + document.removeEventListener('keydown', escHandler); + } + }; + document.addEventListener('keydown', escHandler); + }, + + getLoadingHTML() { + return ` + +
+ +

Loading movie details...

+
+ `; + }, + + getErrorHTML(message) { + return ` + +
+ +

${this.escapeHtml(message)}

+
+ `; + }, + + escapeHtml(text) { + if (!text) return ''; + const div = document.createElement('div'); + div.textContent = text; + return div.innerHTML; + } + }; + + // Initialize on load + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', () => window.RequestarrDetail.init()); + } else { + window.RequestarrDetail.init(); + } +})(); diff --git a/frontend/templates/components/scripts.html b/frontend/templates/components/scripts.html index e3fa2fe8..6a832ce4 100644 --- a/frontend/templates/components/scripts.html +++ b/frontend/templates/components/scripts.html @@ -71,6 +71,7 @@ +