1+ // Define the elements
2+ const iframe = document . getElementById ( 'videoFrame' ) ;
3+ const title = document . getElementById ( 'title' ) ;
4+ const server_buttons = document . querySelectorAll ( '.server-grid button' ) ;
5+ const nextEpButton = document . getElementById ( 'nextep-button' ) ;
6+ const epSelectButton = document . querySelector ( '.epselect-button' ) ;
7+ const popoverContainer = document . querySelector ( '.popover-container' ) ;
8+ const popoverContent = document . querySelector ( '.popover-content' ) ;
9+ const seasonsList = document . querySelector ( '.seasons-list' ) ;
10+ const episodesList = document . querySelector ( '.episodes-list' ) ;
11+ const popoverTitle = document . querySelector ( '.popover-header-title' ) ;
12+ const popoverBackButton = document . querySelector ( '.popover-back-button' ) ;
13+ const popoverCloseButton = document . querySelector ( '.popover-close-button' ) ;
14+ const popoverListContainer = document . querySelector ( '.popover-list-container' ) ;
15+
16+ // Utility Functions
117function getURLParams ( ) {
218 const params = new URLSearchParams ( window . location . search ) ;
319 const type = params . get ( 'type' ) ;
@@ -27,11 +43,8 @@ function getURLParams() {
2743}
2844
2945function getSelectedServerButtonId ( ) {
30- // Get all buttons in the server grid
31- const buttons = document . querySelectorAll ( '.server-grid button' ) ;
32-
3346 // Loop through the buttons to find the one with the 'selected' class
34- for ( const button of buttons ) {
47+ for ( const button of server_buttons ) {
3548 if ( button . classList . contains ( 'selected' ) ) {
3649 const id = button . id . replace ( 'server' , '' ) ;
3750 return parseInt ( id , 10 ) ; // Convert the extracted string to a number
@@ -41,15 +54,10 @@ function getSelectedServerButtonId() {
4154 return null ; // Return null if no button is selected
4255}
4356
44- function redirectTowebsite ( ) {
45- window . location . href = "https://github.com/TomasTNunes/TMDB-Player?tab=readme-ov-file#tmdb-player" ;
46- }
47-
4857function changeServer ( serverNumber ) {
4958 const params = getURLParams ( ) ;
5059 if ( ! params ) return ;
5160
52- const iframe = document . getElementById ( 'videoFrame' ) ;
5361 iframe . src = '' ;
5462
5563 let src = '' ;
@@ -76,8 +84,7 @@ function changeServer(serverNumber) {
7684 iframe . src = src ;
7785
7886 // Highlight the selected server button
79- const buttons = document . querySelectorAll ( '.server-grid button' ) ;
80- buttons . forEach ( button => button . classList . remove ( 'selected' ) ) ;
87+ server_buttons . forEach ( button => button . classList . remove ( 'selected' ) ) ;
8188 document . getElementById ( `server${ serverNumber } ` ) . classList . add ( 'selected' ) ;
8289}
8390
@@ -106,10 +113,15 @@ async function fetchTMDBData(params) {
106113 throw new Error ( 'Network response was not ok' ) ;
107114 }
108115 const data = await response . json ( ) ;
109- result [ ' title' ] = data . name ;
116+ result . title = data . name ;
110117 const seasons = data . seasons ;
118+ result . seasons = [ ] ;
111119 for ( const season of seasons ) {
112120 result [ season . season_number ] = season . episode_count ;
121+ // Exclude Season 0 (Specials) from the list
122+ if ( season . season_number !== 0 ) {
123+ result . seasons . push ( season . season_number ) ;
124+ }
113125 }
114126 } else {
115127 throw new Error ( 'Invalid type specified' ) ;
@@ -133,16 +145,116 @@ function getNextEp(currentSeason, currentEpisode, tmdbData) {
133145 return [ null , null ] ;
134146}
135147
148+ // Fetch TV show episodes data from TMDB API
149+ async function fetchEpSelectionData ( params , tmdbData ) {
150+ const result = { } ;
151+ let url ;
152+ const headers = {
153+ 'Authorization' : `Bearer eyJhbGciOiJIUzI1NiJ9.eyJhdWQiOiIwYTk1NzRmZDcxMjRkNmI5ZTUyNjA4ZWEzNWQ2NzdiNCIsIm5iZiI6MTczNzU5MDQ2NC4zMjUsInN1YiI6IjY3OTE4NmMwZThiNjdmZjgzM2ZhNjM4OCIsInNjb3BlcyI6WyJhcGlfcmVhZCJdLCJ2ZXJzaW9uIjoxfQ.kWqK74FSN41PZO7_ENZelydTtX0u2g6dCkAW0vFs4jU` ,
154+ 'accept' : 'application/json'
155+ } ;
156+
157+ for ( const season of tmdbData . seasons ) {
158+ url = `https://api.themoviedb.org/3/tv/${ params . id } /season/${ season } ?language=en-US` ;
159+ const response = await fetch ( url , { method : 'GET' , headers : headers } ) ;
160+ if ( ! response . ok ) {
161+ throw new Error ( 'Network response was not ok' ) ;
162+ }
163+ const seasonData = await response . json ( ) ;
164+
165+ result [ season ] = { } ;
166+ result [ season ] . name = seasonData . name ;
167+ result [ season ] . air_date = seasonData . air_date ;
168+ result [ season ] . poster_path = seasonData . poster_path ;
169+ result [ season ] . episodes = [ ] ;
170+
171+ for ( const ep of seasonData . episodes ) {
172+ const episode = { } ;
173+ episode . name = ep . name ;
174+ episode . episode_number = ep . episode_number ;
175+ episode . season_number = ep . season_number ;
176+ episode . air_date = ep . air_date ;
177+ episode . runtime = ep . runtime ;
178+ episode . still_path = ep . still_path ;
179+ result [ season ] . episodes . push ( episode ) ;
180+ }
181+ }
182+ return result ;
183+ }
184+
185+ // Episode Selection Popover show: seasons list
186+ function showSeasons ( tvShowTitle ) {
187+ seasonsList . style . display = 'block' ;
188+ episodesList . style . display = 'none' ;
189+ popoverBackButton . style . display = 'none' ;
190+ popoverTitle . innerText = tvShowTitle ;
191+ popoverListContainer . scrollTop = 0 ;
192+ }
193+
194+ // Episode Selection Popover show: episodes list
195+ function showEpisodes ( seasonName ) {
196+ seasonsList . style . display = 'none' ;
197+ episodesList . style . display = 'block' ;
198+ popoverBackButton . style . display = 'block' ;
199+ popoverTitle . innerText = seasonName ;
200+ popoverListContainer . scrollTop = 0 ;
201+ }
202+
203+ // Load Popover container with seasons and episodes
204+ async function loadPopoverSelectEpisode ( params , tmdbData ) {
205+ // Get episode data
206+ const epSelectionData = await fetchEpSelectionData ( params , tmdbData ) ;
207+
208+ // Populate seasons list
209+ seasonsList . innerHTML = tmdbData . seasons . map ( season => `
210+ <li data-season="${ season } ">
211+ <div class="season-name">${ epSelectionData [ season ] . name } </div>
212+ <div class="season-details">${ epSelectionData [ season ] . air_date ? epSelectionData [ season ] . air_date : "" } </div>
213+ </li>
214+ ` ) . join ( '' ) ;
215+
216+ // Handle season click
217+ seasonsList . addEventListener ( 'click' , ( e ) => {
218+ const li = e . target . closest ( 'li' ) ;
219+ if ( li ) {
220+ const season = li . getAttribute ( 'data-season' ) ;
221+ const episodes = epSelectionData [ season ] . episodes ;
222+ episodesList . innerHTML = episodes . map ( ep => `
223+ <li data-season="${ season } " data-episode="${ ep . episode_number } ">
224+ <div class="episode-name">E${ ep . episode_number } - ${ ep . name } </div>
225+ <div class="episode-details">${ ep . air_date ? ep . air_date : "" } ${ ep . runtime ? `(${ ep . runtime } m)` : "" } </div>
226+ </li>
227+ ` ) . join ( '' ) ;
228+ showEpisodes ( epSelectionData [ season ] . name ) ;
229+ }
230+ } ) ;
231+
232+ // Handle episode click
233+ episodesList . addEventListener ( 'click' , ( e ) => {
234+ const li = e . target . closest ( 'li' ) ;
235+ if ( li ) {
236+ const season = li . getAttribute ( 'data-season' ) ;
237+ const episode = li . getAttribute ( 'data-episode' ) ;
238+ const currentUrl = new URL ( window . location . href ) ;
239+ currentUrl . searchParams . set ( 's' , season ) ;
240+ currentUrl . searchParams . set ( 'e' , episode ) ;
241+ currentUrl . searchParams . set ( 'server' , getSelectedServerButtonId ( ) ) ;
242+ window . location . href = currentUrl . toString ( ) ;
243+ }
244+ } ) ;
245+ showSeasons ( tmdbData . title ) ;
246+ }
247+
248+ // Initialize popover data
136249window . onload = async ( ) => {
137250 const params = getURLParams ( ) ;
138251 if ( ! params ) {
139- redirectTowebsite ( ) ;
252+ window . location . href = "https://github.com/TomasTNunes/TMDB-Player?tab=readme-ov-file#tmdb-player" ;
140253 return ;
141254 }
142255
143256 try {
144257 const tmdbData = await fetchTMDBData ( params ) ;
145- const title = document . getElementById ( 'title' ) ;
146258
147259 title . addEventListener ( 'click' , ( ) => {
148260 window . location . href = `https://www.themoviedb.org/${ params . type } /${ params . id } ` ;
@@ -153,13 +265,13 @@ window.onload = async () => {
153265 } else {
154266 title . innerText = `${ tmdbData . title } S${ params . season } E${ params . episode } ` ;
155267
268+ // Next Episode
156269 const [ nextEpS , nextEpE ] = getNextEp ( params . season , params . episode , tmdbData ) ;
157270 if ( nextEpS !== null ) {
158- const nextEpButton = document . getElementById ( 'nextep-button' ) ;
159271 nextEpButton . title = `Next Episode: S${ nextEpS } E${ nextEpE } ` ;
160- nextEpButton . style . display = 'flex' ;
272+ nextEpButton . style . display = 'flex' ;
161273 nextEpButton . style . cursor = 'pointer' ;
162- nextEpButton . style . opacity = 1 ;
274+ nextEpButton . style . visibility = 'visible' ;
163275 nextEpButton . disabled = false ;
164276 nextEpButton . addEventListener ( 'click' , ( ) => {
165277 const currentUrl = new URL ( window . location . href ) ;
@@ -168,20 +280,46 @@ window.onload = async () => {
168280 currentUrl . searchParams . set ( 'server' , getSelectedServerButtonId ( ) ) ;
169281 window . location . href = currentUrl . toString ( ) ;
170282 } ) ;
171- }
172- else {
173- const nextEpButton = document . getElementById ( 'nextep-button' ) ;
283+ } else {
174284 nextEpButton . title = `No Next Episode` ;
175- nextEpButton . style . display = 'flex' ; // Ensure the button is visible
176- nextEpButton . disabled = true ; // Disable the button
285+ nextEpButton . style . display = 'flex' ;
286+ nextEpButton . style . visibility = 'visible' ;
287+ nextEpButton . disabled = true ;
177288 }
178- }
179289
290+ // Episode Selection
291+ epSelectButton . style . display = 'flex' ;
292+ epSelectButton . style . cursor = 'pointer' ;
293+ epSelectButton . style . visibility = 'visible' ;
294+ epSelectButton . disabled = false ;
295+ // Open popover in current season when clicking the button
296+ epSelectButton . addEventListener ( 'click' , ( e ) => {
297+ e . stopPropagation ( ) ;
298+ const currentSeasonLi = seasonsList . querySelector ( `li[data-season="${ params . season } "]` ) ;
299+ if ( currentSeasonLi ) {
300+ currentSeasonLi . click ( ) ;
301+ }
302+ popoverContainer . classList . toggle ( 'active' ) ;
303+ } ) ;
304+ // Close popover when clicking outside
305+ document . addEventListener ( 'click' , ( e ) => {
306+ if ( ! popoverContainer . contains ( e . target ) ) {
307+ popoverContainer . classList . remove ( 'active' ) ;
308+ showSeasons ( tmdbData . title ) ;
309+ }
310+ } ) ;
311+ // Show seasons list when click Back Button
312+ popoverBackButton . addEventListener ( 'click' , ( e ) => {
313+ showSeasons ( tmdbData . title ) ;
314+ } ) ;
315+ loadPopoverSelectEpisode ( params , tmdbData ) ; // dont await to not block the page load
316+
317+ }
180318 } catch ( error ) {
181319 console . error ( 'Error loading data:' , error ) ;
182- // Optionally, display an error message to the user
183- document . getElementById ( 'title' ) . innerText = 'Title' ;
320+ title . innerText = 'Title' ;
184321 }
322+
185323 if ( params . server ) {
186324 changeServer ( parseInt ( params . server ) ) ;
187325 } else {
0 commit comments