mirror of
https://github.com/plexguide/Huntarr.io.git
synced 2026-04-22 04:36:53 -04:00
update
This commit is contained in:
@@ -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 {
|
||||
|
||||
@@ -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 = `
|
||||
<div class="movie-detail-instance-selector">
|
||||
<label for="movie-detail-instance-select"><i class="fas fa-server"></i> Instance:</label>
|
||||
<select id="movie-detail-instance-select" class="movie-detail-select">
|
||||
${this.radarrInstances.map(instance => {
|
||||
const selected = instance.name === this.selectedInstance ? 'selected' : '';
|
||||
return `<option value="${this.escapeHtml(instance.name)}" ${selected}>Radarr - ${this.escapeHtml(instance.name)}</option>`;
|
||||
${this.movieHuntInstances.map(instance => {
|
||||
const selected = instance.id === this.selectedInstanceId ? 'selected' : '';
|
||||
return `<option value="${instance.id}" ${selected}>Movie Hunt - ${this.escapeHtml(instance.name)}</option>`;
|
||||
}).join('')}
|
||||
</select>
|
||||
</div>
|
||||
`;
|
||||
} else {
|
||||
// Show loading state if instances haven't loaded yet
|
||||
instanceSelectorHTML = `
|
||||
<div class="movie-detail-instance-selector">
|
||||
<label for="movie-detail-instance-select"><i class="fas fa-server"></i> Instance:</label>
|
||||
<select id="movie-detail-instance-select" class="movie-detail-select" disabled>
|
||||
<option>Loading instances...</option>
|
||||
</select>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
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 = '<button class="movie-detail-btn movie-detail-btn-primary" disabled><i class="fas fa-check"></i> Already Available</button>';
|
||||
} else if (status.in_cooldown) {
|
||||
actionButton = '<button class="movie-detail-btn movie-detail-btn-primary" disabled><i class="fas fa-clock"></i> In Cooldown</button>';
|
||||
} else {
|
||||
actionButton = '<button class="movie-detail-btn movie-detail-btn-primary" id="movie-detail-request-btn"><i class="fas fa-download"></i> Request Movie</button>';
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 = '<button class="movie-detail-btn movie-detail-btn-primary" disabled><i class="fas fa-check"></i> Already Available</button>';
|
||||
} else if (status.in_cooldown) {
|
||||
actionButton = '<button class="movie-detail-btn movie-detail-btn-primary" disabled><i class="fas fa-clock"></i> In Cooldown</button>';
|
||||
} else {
|
||||
actionButton = '<button class="movie-detail-btn movie-detail-btn-primary" id="requestarr-detail-request-btn"><i class="fas fa-download"></i> Request Movie</button>';
|
||||
}
|
||||
|
||||
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 => `<span class="movie-detail-genre">${this.escapeHtml(g.name)}</span>`).join('')
|
||||
: '<span class="movie-detail-genre">Unknown</span>';
|
||||
|
||||
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 = '<button class="movie-detail-btn movie-detail-btn-primary" disabled><i class="fas fa-check"></i> Already Available</button>';
|
||||
} else if (inCooldown) {
|
||||
actionButton = '<button class="movie-detail-btn movie-detail-btn-primary" disabled><i class="fas fa-clock"></i> In Cooldown</button>';
|
||||
} else {
|
||||
actionButton = '<button class="movie-detail-btn movie-detail-btn-primary" id="requestarr-detail-request-btn"><i class="fas fa-download"></i> Request Movie</button>';
|
||||
}
|
||||
|
||||
// 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 = `
|
||||
<div class="movie-detail-instance-selector">
|
||||
<label for="requestarr-detail-instance-select"><i class="fas fa-server"></i> Instance:</label>
|
||||
<select id="requestarr-detail-instance-select" class="movie-detail-select">
|
||||
${this.radarrInstances.map(instance => {
|
||||
const selected = instance.name === this.selectedInstanceName ? 'selected' : '';
|
||||
return `<option value="${this.escapeHtml(instance.name)}" ${selected}>Radarr - ${this.escapeHtml(instance.name)}</option>`;
|
||||
}).join('')}
|
||||
</select>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
return `
|
||||
<button class="movie-detail-close"><i class="fas fa-times"></i></button>
|
||||
|
||||
<div class="movie-detail-hero" style="background-image: url('${backdropUrl}');">
|
||||
<div class="movie-detail-hero-content">
|
||||
<div class="movie-detail-poster">
|
||||
<img src="${posterUrl}" alt="${this.escapeHtml(details.title)}" onerror="this.src='./static/images/blackout.jpg'">
|
||||
</div>
|
||||
<div class="movie-detail-info">
|
||||
<h1 class="movie-detail-title">${this.escapeHtml(details.title)}</h1>
|
||||
${instanceSelectorHTML}
|
||||
<div class="movie-detail-meta">
|
||||
<div class="movie-detail-year"><i class="fas fa-calendar"></i> ${year}</div>
|
||||
<div class="movie-detail-runtime"><i class="fas fa-clock"></i> ${runtime}</div>
|
||||
<div class="movie-detail-rating"><i class="fas fa-star"></i> ${rating}</div>
|
||||
</div>
|
||||
<div class="movie-detail-genres">${genres}</div>
|
||||
<p class="movie-detail-overview">${this.escapeHtml(overview)}</p>
|
||||
<div class="movie-detail-actions">
|
||||
${actionButton}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="movie-detail-content">
|
||||
<!-- Movie Details -->
|
||||
<div class="movie-detail-section">
|
||||
<h2 class="movie-detail-section-title"><i class="fas fa-info-circle"></i> Movie Details</h2>
|
||||
<div class="movie-detail-grid">
|
||||
<div class="movie-detail-item">
|
||||
<div class="movie-detail-item-label">Director</div>
|
||||
<div class="movie-detail-item-value">${this.escapeHtml(director)}</div>
|
||||
</div>
|
||||
<div class="movie-detail-item">
|
||||
<div class="movie-detail-item-label">Release Date</div>
|
||||
<div class="movie-detail-item-value">${details.release_date || 'N/A'}</div>
|
||||
</div>
|
||||
<div class="movie-detail-item">
|
||||
<div class="movie-detail-item-label">Rating</div>
|
||||
<div class="movie-detail-item-value">${certification}</div>
|
||||
</div>
|
||||
<div class="movie-detail-item">
|
||||
<div class="movie-detail-item-label">Budget</div>
|
||||
<div class="movie-detail-item-value">${details.budget ? '$' + (details.budget / 1000000).toFixed(1) + 'M' : 'N/A'}</div>
|
||||
</div>
|
||||
<div class="movie-detail-item">
|
||||
<div class="movie-detail-item-label">Revenue</div>
|
||||
<div class="movie-detail-item-value">${details.revenue ? '$' + (details.revenue / 1000000).toFixed(1) + 'M' : 'N/A'}</div>
|
||||
</div>
|
||||
<div class="movie-detail-item">
|
||||
<div class="movie-detail-item-label">Language</div>
|
||||
<div class="movie-detail-item-value">${details.original_language ? details.original_language.toUpperCase() : 'N/A'}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
${mainCast.length > 0 ? `
|
||||
<!-- Cast -->
|
||||
<div class="movie-detail-section">
|
||||
<h2 class="movie-detail-section-title"><i class="fas fa-users"></i> Cast</h2>
|
||||
<div class="movie-detail-cast">
|
||||
${mainCast.map(actor => this.renderCastCard(actor)).join('')}
|
||||
</div>
|
||||
</div>
|
||||
` : ''}
|
||||
|
||||
${similarMovies.length > 0 ? `
|
||||
<!-- Similar Movies -->
|
||||
<div class="movie-detail-section">
|
||||
<h2 class="movie-detail-section-title"><i class="fas fa-film"></i> Similar Movies</h2>
|
||||
<div class="movie-detail-similar">
|
||||
${similarMovies.map(movie => this.renderSimilarCard(movie)).join('')}
|
||||
</div>
|
||||
</div>
|
||||
` : ''}
|
||||
</div>
|
||||
`;
|
||||
},
|
||||
|
||||
renderCastCard(actor) {
|
||||
const photoUrl = actor.profile_path
|
||||
? `https://image.tmdb.org/t/p/w185${actor.profile_path}`
|
||||
: './static/images/blackout.jpg';
|
||||
|
||||
return `
|
||||
<div class="movie-detail-cast-card">
|
||||
<div class="movie-detail-cast-photo">
|
||||
<img src="${photoUrl}" alt="${this.escapeHtml(actor.name)}" onerror="this.src='./static/images/blackout.jpg'">
|
||||
</div>
|
||||
<div class="movie-detail-cast-info">
|
||||
<div class="movie-detail-cast-name">${this.escapeHtml(actor.name)}</div>
|
||||
<div class="movie-detail-cast-character">${this.escapeHtml(actor.character || 'Unknown')}</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
},
|
||||
|
||||
renderSimilarCard(movie) {
|
||||
const posterUrl = movie.poster_path
|
||||
? `https://image.tmdb.org/t/p/w185${movie.poster_path}`
|
||||
: './static/images/blackout.jpg';
|
||||
|
||||
return `
|
||||
<div class="media-card" data-tmdb-id="${movie.id}">
|
||||
<div class="media-card-poster">
|
||||
<img src="${posterUrl}" alt="${this.escapeHtml(movie.title)}" onerror="this.src='./static/images/blackout.jpg'">
|
||||
<div class="media-card-overlay">
|
||||
<div class="media-card-overlay-title">${this.escapeHtml(movie.title)}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="media-card-info">
|
||||
<div class="media-card-title">${this.escapeHtml(movie.title)}</div>
|
||||
<div class="media-card-meta">
|
||||
<span class="media-card-year">${movie.release_date ? new Date(movie.release_date).getFullYear() : 'N/A'}</span>
|
||||
<span class="media-card-rating"><i class="fas fa-star"></i> ${movie.vote_average ? Number(movie.vote_average).toFixed(1) : 'N/A'}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
},
|
||||
|
||||
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 `
|
||||
<button class="movie-detail-close"><i class="fas fa-times"></i></button>
|
||||
<div class="movie-detail-loading">
|
||||
<i class="fas fa-spinner fa-spin"></i>
|
||||
<p>Loading movie details...</p>
|
||||
</div>
|
||||
`;
|
||||
},
|
||||
|
||||
getErrorHTML(message) {
|
||||
return `
|
||||
<button class="movie-detail-close"><i class="fas fa-times"></i></button>
|
||||
<div class="movie-detail-loading">
|
||||
<i class="fas fa-exclamation-triangle" style="color: #ef4444;"></i>
|
||||
<p style="color: #ef4444;">${this.escapeHtml(message)}</p>
|
||||
</div>
|
||||
`;
|
||||
},
|
||||
|
||||
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();
|
||||
}
|
||||
})();
|
||||
@@ -71,6 +71,7 @@
|
||||
<script src="./static/js/modules/utils/tmdb-image-cache-standalone.js"></script>
|
||||
|
||||
<!-- Requestarr System -->
|
||||
<script src="./static/js/modules/features/requestarr/requestarr-detail.js"></script>
|
||||
<script src="./static/js/modules/features/requestarr/requestarr-controller.js" type="module"></script>
|
||||
<script src="./static/js/modules/features/requestarr/requestarr-home.js" type="module"></script>
|
||||
<script src="./static/js/modules/features/requestarr/requestarr-content.js" type="module"></script>
|
||||
|
||||
Reference in New Issue
Block a user