PageSourceSearch

https://diccionario-moderno-2--rivera.on.websim.com/video-player-ui.js

js diccionario-moderno-2--rivera.on.websim.com collected 2026-10-03 05:06:29 UTC 20,155 bytes, 438 lines download raw bytes

1export class VideoPlayerUI {
2    constructor() {
3        this.modal = document.getElementById('video-player-modal');
4        this.scriptPanel = document.getElementById('video-script-panel');
5        this.eventListeners = {};
6        this.buildInitialDOM();
7    }
8
9    on(event, callback) {
10        if (!this.eventListeners[event]) {
11            this.eventListeners[event] = [];
12        }
13        this.eventListeners[event].push(callback);
14    }
15
16    emit(event, data) {
17        if (this.eventListeners[event]) {
18            this.eventListeners[event].forEach(callback => callback(data));
19        }
20    }
21
22    buildInitialDOM() {
23        this.modal.innerHTML = `
24            <div class="video-player-container">
25                 <div class="video-script-selection-container">
26                    <!-- Script selection UI will be injected here -->
27                 </div>
28                 <div class="video-player-main-area" style="display: none;">
29                    <div class="video-player-content">
30                        <div class="video-player-top-bar">
31                            <h2 class="video-title"></h2>
32                            <div class="video-player-top-controls">
33                                <button class="video-script-toggle-btn" title="Ver Guion Completo"><i class="fas fa-file-alt"></i></button>
34                                <button class="video-close-btn" title="Cerrar Reproductor"><i class="fas fa-times"></i></button>
35                            </div>
36                        </div>
37                        <div class="teleprompter-container">
38                            <div class="teleprompter-live-indicator"></div>
39                            <p class="teleprompter-text"></p>
40                        </div>
41                        <div class="video-player-bottom-bar">
42                            <div class="video-controls">
43                                <button class="video-play-btn" title="Reproducir"><i class="fas fa-play"></i></button>
44                                <button class="video-pause-btn" title="Pausa" style="display: none;"><i class="fas fa-pause"></i></button>
45                                <button class="video-stop-btn" title="Detener y Rebobinar"><i class="fas fa-stop"></i></button>
46                            </div>
47                            <div class="video-progress-container" title="Buscar en el guion">
48                                <div class="video-progress-bar"></div>
49                            </div>
50                        </div>
51                        <div class="video-player-loader">
52                            <div class="loader-spinner"></div>
53                            <p class="loader-text"></p>
54                        </div>
55                        <div class="video-player-error" style="display: none;">
56                            <p class="video-player-error-message"></p>
57                            <button class="video-error-exit-btn"><i class="fas fa-times-circle"></i> Salir</button>
58                        </div>
59                        <div class="video-player-end-screen" style="display: none;">
60                            <h3>¡Guion Terminado!</h3>
61                            <div class="end-screen-controls">
62                                <button class="video-replay-btn"><i class="fas fa-redo"></i> Repetir</button>
63                                <button class="video-elaborate-btn"><i class="fas fa-rocket"></i> Elaborar</button>
64                                <button class="video-exit-btn"><i class="fas fa-times-circle"></i> Salir</button>
65                            </div>
66                            <div class="video-explore-next-container">
67                                 <h4>Explorar Siguiente</h4>
68                                 <div class="video-explore-next-words"></div>
69                            </div>
70                        </div>
71                    </div>
72                    <div class="video-chapters-panel-container">
73                        <div class="video-chapters-header">
74                            <h3>Capítulos</h3>
75                        </div>
76                        <div class="video-chapters-content">
77                            <ol class="video-chapters-list"></ol>
78                        </div>
79                    </div>
80                </div>
81            </div>
82        `;
83
84        this.scriptPanel.innerHTML = `
85            <div class="video-script-header">
86                <h3>Guion Completo</h3>
87                <div class="header-controls">
88                    <button class="video-script-copy-btn" title="Copiar Guion"><i class="fas fa-copy"></i></button>
89                    <button class="video-script-close-btn" title="Cerrar Guion"><i class="fas fa-times"></i></button>
90                </div>
91            </div>
92            <div class="video-script-content">
93                <ul class="video-script-list"></ul>
94            </div>
95        `;
96        
97        // Cache DOM elements
98        this.selectionContainer = this.modal.querySelector('.video-script-selection-container');
99        this.mainPlayerArea = this.modal.querySelector('.video-player-main-area');
100
101        this.titleEl = this.modal.querySelector('.video-title');
102        this.teleprompterTextEl = this.modal.querySelector('.teleprompter-text');
103        this.playBtn = this.modal.querySelector('.video-play-btn');
104        this.pauseBtn = this.modal.querySelector('.video-pause-btn');
105        this.stopBtn = this.modal.querySelector('.video-stop-btn');
106        this.progressContainer = this.modal.querySelector('.video-progress-container');
107        this.progressBar = this.modal.querySelector('.video-progress-bar');
108        this.loader = this.modal.querySelector('.video-player-loader');
109        this.loaderText = this.modal.querySelector('.loader-text');
110        this.errorEl = this.modal.querySelector('.video-player-error');
111        this.errorMessageEl = this.errorEl.querySelector('.video-player-error-message');
112        this.endScreen = this.modal.querySelector('.video-player-end-screen');
113        this.exploreNextWordsEl = this.modal.querySelector('.video-explore-next-words');
114        this.scriptListEl = this.scriptPanel.querySelector('.video-script-list');
115        this.chaptersListEl = this.modal.querySelector('.video-chapters-list');
116        this.copyBtn = this.scriptPanel.querySelector('.video-script-copy-btn');
117
118
119        // Attach event listeners
120        this.modal.querySelector('.video-close-btn').addEventListener('click', () => this.emit('close'));
121        this.scriptPanel.querySelector('.video-script-close-btn').addEventListener('click', () => this.closeScriptPanel());
122        this.modal.querySelector('.video-script-toggle-btn').addEventListener('click', () => this.toggleScriptPanel());
123        this.playBtn.addEventListener('click', () => this.emit('play'));
124        this.pauseBtn.addEventListener('click', () => this.emit('pause'));
125        this.stopBtn.addEventListener('click', () => this.emit('stop'));
126        this.endScreen.querySelector('.video-replay-btn').addEventListener('click', () => this.emit('replay'));
127        this.endScreen.querySelector('.video-elaborate-btn').addEventListener('click', () => this.emit('elaborate'));
128        this.endScreen.querySelector('.video-exit-btn').addEventListener('click', () => this.emit('close'));
129        this.errorEl.querySelector('.video-error-exit-btn').addEventListener('click', () => this.emit('close'));
130
131        this.copyBtn.addEventListener('click', () => {
132            this.emit('copy');
133            // Visual feedback for copy
134            const icon = this.copyBtn.querySelector('i');
135            icon.classList.remove('fa-copy');
136            icon.classList.add('fa-check');
137            this.copyBtn.title = "¡Copiado!";
138            setTimeout(() => {
139                icon.classList.remove('fa-check');
140                icon.classList.add('fa-copy');
141                this.copyBtn.title = "Copiar Guion";
142            }, 2000);
143        });
144
145        this.progressContainer.addEventListener('click', (e) => {
146            const rect = this.progressContainer.getBoundingClientRect();
147            const clickX = e.clientX - rect.left;
148            const width = this.progressContainer.clientWidth;
149            const percentage = Math.max(0, Math.min(1, clickX / width));
150            this.emit('seek', percentage);
151        });
152
153        this.scriptListEl.addEventListener('click', (e) => {
154            const li = e.target.closest('li[data-index]');
155            if (li) {
156                const index = parseInt(li.dataset.index, 10);
157                if (!isNaN(index)) {
158                    this.emit('jumpToSentence', index);
159                }
160            }
161        });
162
163        this.chaptersListEl.addEventListener('click', (e) => {
164            const li = e.target.closest('li[data-index]');
165            if (li) {
166                const index = parseInt(li.dataset.index, 10);
167                if (!isNaN(index)) {
168                    this.emit('jumpToChapter', index);
169                }
170            }
171        });
172    }
173
174    openModal() {
175        this.modal.style.display = 'flex';
176        document.body.classList.add('video-player-open');
177        setTimeout(() => this.modal.classList.add('open'), 10);
178        this.resetUI();
179    }
180
181    closeModal() {
182        this.modal.classList.remove('open');
183        document.body.classList.remove('video-player-open');
184        this.closeScriptPanel();
185        setTimeout(() => {
186            if(this.modal) this.modal.style.display = 'none';
187        }, 500);
188    }
189    
190    toggleScriptPanel() {
191        if (this.scriptPanel.classList.contains('open')) {
192            this.closeScriptPanel();
193        } else {
194            this.openScriptPanel();
195        }
196    }
197
198    openScriptPanel() {
199        this.scriptPanel.style.display = 'flex';
200        setTimeout(() => this.scriptPanel.classList.add('open'), 10);
201    }
202    
203    closeScriptPanel() {
204        this.scriptPanel.classList.remove('open');
205        setTimeout(() => {
206            if(this.scriptPanel) this.scriptPanel.style.display = 'none';
207        }, 500);
208    }
209
210    toggleChaptersPanel() {
211        if (this.chaptersPanel.classList.contains('open')) {
212            this.closeChaptersPanel();
213        } else {
214            this.openChaptersPanel();
215        }
216    }
217
218    openChaptersPanel() {
219        this.closeScriptPanel(); // Close other panel
220        this.chaptersPanel.style.display = 'flex';
221        setTimeout(() => this.chaptersPanel.classList.add('open'), 10);
222    }
223    
224    closeChaptersPanel() {
225        this.chaptersPanel.classList.remove('open');
226        setTimeout(() => {
227            if(this.chaptersPanel) this.chaptersPanel.style.display = 'none';
228        }, 500);
229    }
230
231
232    resetUI() {
233        this.titleEl.textContent = '';
234        this.teleprompterTextEl.innerHTML = '...';
235        this.progressBar.style.width = '0%';
236        this.scriptListEl.innerHTML = '';
237        this.chaptersListEl.innerHTML = '';
238        this.exploreNextWordsEl.innerHTML = '';
239        this.loader.style.display = 'none';
240        this.errorEl.style.display = 'none';
241        this.endScreen.style.display = 'none';
242        this.setPlayerState('stopped');
243
244        this.mainPlayerArea.style.display = 'none';
245        this.selectionContainer.style.display = 'flex';
246        this.selectionContainer.innerHTML = '';
247    }
248
249    showPlayer() {
250        this.selectionContainer.style.display = 'none';
251        this.mainPlayerArea.style.display = 'flex';
252    }
253
254    showScriptSelection(word) {
255        this.selectionContainer.innerHTML = `
256            <button class="close-selection-btn" title="Cerrar"><i class="fas fa-times"></i></button>
257            <h2>Elige un guion para "<span class="selection-word">${word}</span>"</h2>
258            <p>Selecciona el tipo de video generado por IA que te gustaría crear.</p>
259            <div class="video-script-options">
260                <button id="explanation-btn" class="video-script-option-btn" data-script-type="explanation">
261                    <div class="icon-container"><i class="fas fa-lightbulb"></i></div>
262                    <h4>Explicación</h4>
263                    <p>Un análisis profundo del significado, uso e importancia de la palabra.</p>
264                </button>
265                <button id="comparison-btn" class="video-script-option-btn" data-script-type="comparison">
266                    <div class="icon-container"><i class="fas fa-balance-scale"></i></div>
267                    <h4>Comparación</h4>
268                    <p>Compara la palabra con sus sinónimos más cercanos para entender los matices.</p>
269                </button>
270                <button id="thematic-btn" class="video-script-option-btn" data-script-type="thematic">
271                    <div class="icon-container"><i class="fas fa-cubes"></i></div>
272                    <h4>Temático</h4>
273                    <p>Explora una red de ideas, temas y palabras conectadas interesantes.</p>
274                </button>
275            </div>
276        `;
277        
278        this.selectionContainer.querySelector('.close-selection-btn').addEventListener('click', () =>
278 this.emit('close'));
279        this.selectionContainer.querySelector('#explanation-btn').addEventListener('click', () => this.emit('scriptSelect', { type: 'explanation' }));
280        this.selectionContainer.querySelector('#comparison-btn').addEventListener('click', () => this.emit('scriptSelect', { type: 'comparison' }));
281        this.selectionContainer.querySelector('#thematic-btn').addEventListener('click', () => this.emit('scriptSelect', { type: 'thematic' }));
282    }
283
284    showComparisonChoice(word) {
285        this.selectionContainer.innerHTML = `
286            <button class="close-selection-btn" title="Cerrar"><i class="fas fa-times"></i></button>
287            <div class="video-comparison-subflow">
288                <h3>¿Te gustaría elegir con qué palabras comparar <span class="selection-word">${word}</span>?</h3>
289                <p>Si no, la IA elegirá sinónimos interesantes automáticamente.</p>
290                <div class="controls">
291                     <button class="yes-btn" title="Sí, déjame elegir"><i class="fas fa-check"></i></button>
292                     <button class="no-btn" title="No, que elija la IA"><i class="fas fa-times"></i></button>
293                </div>
294            </div>
295        `;
296        this.selectionContainer.querySelector('.close-selection-btn').addEventListener('click', () => this.emit('close'));
297        this.selectionContainer.querySelector('.yes-btn').addEventListener('click', () => this.emit('comparisonChoice', { userSelectsWords: true }));
298        this.selectionContainer.querySelector('.no-btn').addEventListener('click', () => this.emit('comparisonChoice', { userSelectsWords: false }));
299    }
300
301    showComparisonInput(suggestions = []) {
302        this.selectionContainer.innerHTML = `
303             <button class="close-selection-btn" title="Cerrar"><i class="fas fa-times"></i></button>
304             <div class="video-comparison-subflow">
305                <h3>¿Con qué palabra(s) la compararás?</h3>
306                <div class="video-comparison-input-area">
307                    <div class="input-group">
308                        <input type="text" class="comparison-input" placeholder="Introduce palabra(s), separadas por comas...">
309                        <button class="submit-comparison-btn" title="Generar Guion"><i class="fas fa-arrow-right"></i></button>
310                    </div>
311                    <div class="video-comparison-suggestions">
312                        ${suggestions.map(s => `<button class="suggestion-chip" data-word="${s}">${s}</button>`).join('')}
313                    </div>
314                </div>
315            </div>
316        `;
317
318        this.selectionContainer.querySelector('.close-selection-btn').addEventListener('click', () => this.emit('close'));
319
320        const input = this.selectionContainer.querySelector('.comparison-input');
321        const submitBtn = this.selectionContainer.querySelector('.submit-comparison-btn');
322        
323        const handleSubmit = () => {
324            const words = input.value.split(',').map(w => w.trim()).filter(Boolean);
325            if (words.length > 0) {
326                this.emit('comparisonSubmit', { words });
327            }
328        };
329
330        submitBtn.addEventListener('click', handleSubmit);
331        input.addEventListener('keyup', (e) => {
332            if (e.key === 'Enter') handleSubmit();
333        });
334
335        this.selectionContainer.querySelectorAll('.suggestion-chip').forEach(chip => {
336            chip.addEventListener('click', () => {
337                const word = chip.dataset.word;
338                const currentVal = input.value.trim();
339                if (currentVal) {
340                    input.value = `${currentVal}, ${word}`;
341                } else {
342                    input.value = word;
343                }
344                input.focus();
345            });
346        });
347    }
348
349    showLoading(show, text = "Cargando...") {
350        if (show) {
351            this.showPlayer();
352        }
353        this.loader.style.display = show ? 'flex' : 'none';
354        if (show) {
355            this.loaderText.textContent = text;
356        }
357    }
358
359    showError(message) {
360        this.showPlayer();
361        this.errorMessageEl.textContent = message;
362        this.errorEl.style.display = 'flex';
363    }
364    
365    setScript(title, sentences, chapters, exploreNextWords = []) {
366        this.titleEl.textContent = title;
367        this.scriptListEl.innerHTML = sentences.map((s, i) => `<li data-index="${i}">${s}</li>`).join('');
368        this.chaptersListEl.innerHTML = chapters.map((c, i) => `<li data-index="${i}">${c}</li>`).join('');
369        this.setExploreNext(exploreNextWords);
370    }
371
372    setExploreNext(words) {
373        this.exploreNextWordsEl.innerHTML = '';
374        if (!words || words.length === 0) return;
375
376        words.forEach(word => {
377            const wordEl = document.createElement('button');
378            wordEl.className = 'explore-next-word';
379            wordEl.textContent = word;
380            wordEl.title = `Explorar "${word}"`;
381            wordEl.addEventListener('click', () => {
382                this.emit('exploreNext', word);
383            });
384            this.exploreNextWordsEl.appendChild(wordEl);
385        });
386    }
387
388    updateTeleprompter(html) {
389        this.teleprompterTextEl.classList.remove('visible');
390        setTimeout(() => {
391            this.teleprompterTextEl.innerHTML = html;
392            this.teleprompterTextEl.classList.add('visible');
393        }, 150);
394    }
395
396    highlightSentenceInPanel(index) {
397        this.scriptListEl.querySelectorAll('li').forEach(li => li.classList.remove('active'));
398        const activeLi = this.scriptListEl.querySelector(`li[data-index="${index}"]`);
399        if (activeLi) {
400            activeLi.classList.add('active');
401            activeLi.scrollIntoView({ behavior: 'smooth', block: 'center' });
402        }
403    }
404
405    highlightChapter(index) {
406        this.chaptersListEl.querySelectorAll('li').forEach(li => li.classList.remove('active'));
407        const activeLi = this.chaptersListEl.querySelector(`li[data-index="${index}"]`);
408        if (activeLi) {
409            activeLi.classList.add('active');
410             activeLi.scrollIntoView({ behavior: 'smooth', block: 'center' });
411        }
412    }
413    
414    updateProgress(current, total) {
415        const percentage = total > 0 ? (current / total) * 100 : 0;
416        this.progressBar.style.width = `${percentage}%`;
417    }
418
419    setPlayerState(state) { // 'playing', 'paused', 'stopped'
420        this.playBtn.style.display = state === 'playing' ? 'none' : 'block';
421        this.pauseBtn.style.display = state === 'playing' ? 'block' : 'none';
422        if(state === 'stopped') {
423            this.updateProgress(0, 1);
424            this.updateTeleprompter('Presiona Reproducir para empezar.');
425            this.hideEndScreen();
426            this.highlightChapter(-1); // Deselect all chapters
427            this.highlightSentenceInPanel(-1); // Deselect all sentences
428        }
429    }
430
431    showEndScreen() {
432        this.endScreen.style.display = 'flex';
433    }
434    
435    hideEndScreen() {
436        this.endScreen.style.display = 'none';
437    }
438}

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.