// Goondex Web UI JavaScript // Modal handling function openModal(modalId) { const modal = document.getElementById(modalId); if (modal) { modal.classList.add('active'); } } } // ============================================================================ // Logo Animation for Loading Screens // ============================================================================ let logoAnimator = null; function startLogoAnimation() { // Find logo in loader or main content const logoElement = document.querySelector('#global-loader .logo img, #global-loader .logo svg, .logo img, .logo svg'); if (logoElement && !logoAnimator) { // Add CSS if not already loaded if (!document.querySelector('#logo-animation-css')) { const css = document.createElement('link'); css.id = 'logo-animation-css'; css.rel = 'stylesheet'; css.href = '/static/css/logo-animation.css'; document.head.appendChild(css); } // Initialize animator logoAnimator = new LogoAnimator(); logoAnimator.init(logoElement); logoAnimator.startBounce(); } } function stopLogoAnimation() { if (logoAnimator) { logoAnimator.stopBounce(); logoAnimator = null; } } } // Import functions // Global Search let searchTimeout; document.addEventListener('DOMContentLoaded', function() { const searchInput = document.getElementById('global-search'); if (searchInput) { searchInput.addEventListener('input', function() { clearTimeout(searchTimeout); const query = this.value.trim(); if (query.length < 2) { document.getElementById('global-search-results').style.display = 'none'; return; } searchTimeout = setTimeout(() => globalSearch(query), 300); }); } }); async function globalSearch(query) { try { const response = await fetch(`/api/search?q=${encodeURIComponent(query)}`); const result = await response.json(); if (result.success) { displayGlobalSearchResults(result.data); } } catch (error) { console.error('Search failed:', error); } } function displayGlobalSearchResults(data) { const resultsDiv = document.getElementById('global-search-results'); let html = '
'; if (data.total === 0) { html += '

No results found

'; } else { html += `

Found ${data.total} results

`; if (data.performers && data.performers.length > 0) { html += '

Performers

'; } if (data.studios && data.studios.length > 0) { html += '

Studios

'; } if (data.scenes && data.scenes.length > 0) { html += '

Scenes

'; } if (data.tags && data.tags.length > 0) { html += '

Tags

'; data.tags.slice(0, 10).forEach(t => { html += `${t.name}`; }); html += '
'; } } html += '
'; resultsDiv.innerHTML = html; resultsDiv.style.display = 'block'; } // Bulk Import Functions async function bulkImportAll() { showLoader('Importing everything...'); startJobProgress('Full library import'); try { await importWithProgress('/api/import/all-performers/progress', 'Performers'); await importWithProgress('/api/import/all-studios/progress', 'Studios'); await importWithProgress('/api/import/all-scenes/progress', 'Scenes'); setImportStatus('import-all', 'Import complete', true); setTimeout(() => location.reload(), 1500); } catch (err) { setImportStatus('import-all', `Import error: ${err.message}`, false); } finally { stopJobProgress(); hideLoader(); } } async function bulkImportPerformers() { showLoader('Importing performers...'); startJobProgress('Importing performers'); try { await importWithProgress('/api/import/all-performers/progress', 'Performers'); setTimeout(() => location.reload(), 1000); } catch (err) { setImportStatus('performer', `Error: ${err.message}`, false); } finally { stopJobProgress(); hideLoader(); } } async function bulkImportStudios() { showLoader('Importing studios...'); startJobProgress('Importing studios'); try { await importWithProgress('/api/import/all-studios/progress', 'Studios'); setTimeout(() => location.reload(), 1000); } catch (err) { setImportStatus('studio', `Error: ${err.message}`, false); } finally { stopJobProgress(); hideLoader(); } } async function bulkImportScenes() { showLoader('Importing scenes...'); startJobProgress('Importing scenes'); try { await importWithProgress('/api/import/all-scenes/progress', 'Scenes'); setTimeout(() => location.reload(), 1000); } catch (err) { setImportStatus('scene', `Error: ${err.message}`, false); } finally { stopJobProgress(); hideLoader(); } } function bulkImportMovies() { alert('Bulk movie import is not implemented yet. Use the Adult Empire CLI to scrape movies individually for now.'); } function toggleFilterbar() { document.getElementById('filterbar').classList.toggle('open'); } function showTPDBDisabled() { alert('TPDB import/sync is temporarily disabled. Use Adult Empire CLI commands instead:\n\n' + ' ./goondex adultemp search-performer "Name"\n' + ' ./goondex adultemp scrape-performer \n' + ' ./goondex adultemp search-scene "Title"\n' + ' ./goondex adultemp scrape-scene '); } function scrollToAEImport() { const section = document.getElementById('ae-import'); if (section) { section.scrollIntoView({ behavior: 'smooth' }); } } function copyToClipboard(text) { navigator.clipboard.writeText(text).then(() => { alert('Copied to clipboard:\n' + text); }).catch(() => { alert('Could not copy. Please copy manually:\n' + text); }); } // ============================================================================ // Adult Empire UI helpers // ============================================================================ function setAEStatus(msg, isError = false) { const el = document.getElementById('ae-status'); if (!el) return; el.textContent = msg; el.classList.toggle('error', !!isError); el.style.display = msg ? 'block' : 'none'; } async function aeImportPerformerByName() { const name = prompt('Import performer by name (Adult Empire):'); if (!name) return; setAEStatus(`Searching Adult Empire for "${name}"...`); showLoader(`Importing performer "${name}" from Adult Empire...`); try { const res = await fetch('/api/ae/import/performer', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ query: name }) }); const result = await res.json(); if (result.success) { setAEStatus(result.message); setTimeout(() => location.reload(), 1500); } else { setAEStatus(result.message || 'Import failed', true); } } catch (err) { setAEStatus(`Error: ${err.message}`, true); } finally { hideLoader(); } } async function aeImportPerformerByURL() { const url = prompt('Paste Adult Empire performer URL:'); if (!url) return; setAEStatus('Importing performer from Adult Empire URL...'); showLoader('Importing performer from Adult Empire URL...'); try { const res = await fetch('/api/ae/import/performer-by-url', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ url }) }); const result = await res.json(); if (result.success) { setAEStatus(result.message); setTimeout(() => location.reload(), 1500); } else { setAEStatus(result.message || 'Import failed', true); } } catch (err) { setAEStatus(`Error: ${err.message}`, true); } finally { hideLoader(); } } async function aeImportSceneByName() { const title = prompt('Import scene by title (Adult Empire):'); if (!title) return; setAEStatus(`Searching Adult Empire for "${title}"...`); showLoader(`Importing scene "${title}" from Adult Empire...`); try { const res = await fetch('/api/ae/import/scene', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ query: title }) }); const result = await res.json(); if (result.success) { setAEStatus(result.message); setTimeout(() => location.reload(), 1500); } else { setAEStatus(result.message || 'Import failed', true); } } catch (err) { setAEStatus(`Error: ${err.message}`, true); } finally { hideLoader(); } } async function aeImportSceneByURL() { const url = prompt('Paste Adult Empire scene URL:'); if (!url) return; setAEStatus('Importing scene from Adult Empire URL...'); showLoader('Importing scene from Adult Empire URL...'); try { const res = await fetch('/api/ae/import/scene-by-url', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ url }) }); const result = await res.json(); if (result.success) { setAEStatus(result.message); setTimeout(() => location.reload(), 1500); } else { setAEStatus(result.message || 'Import failed', true); } } catch (err) { setAEStatus(`Error: ${err.message}`, true); } finally { hideLoader(); } } function applyFilters() { // Hook for your search/filter logic console.log("Applying filters…"); } function sortTable(columnIndex) { const table = document.querySelector(".gx-table tbody"); const rows = Array.from(table.querySelectorAll("tr")); const asc = table.getAttribute("data-sort") !== "asc"; table.setAttribute("data-sort", asc ? "asc" : "desc"); rows.sort((a, b) => { const A = a.children[columnIndex].innerText.trim().toLowerCase(); const B = b.children[columnIndex].innerText.trim().toLowerCase(); if (!isNaN(A) && !isNaN(B)) return asc ? A - B : B - A; return asc ? A.localeCompare(B) : B.localeCompare(A); }); rows.forEach(r => table.appendChild(r)); } // Search-based Import Functions async function importPerformer() { const query = document.getElementById('performer-query').value; if (!query) { alert('Please enter a performer name'); return; } setImportStatus('performer', 'Searching...', false); try { const response = await fetch('/api/import/performer', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ query }) }); const result = await response.json(); if (result.success) { setImportStatus('performer', result.message, true); setTimeout(() => { closeModal('search-performer-modal'); location.reload(); }, 1500); } else { setImportStatus('performer', result.message, false); } } catch (error) { setImportStatus('performer', 'Error: ' + error.message, false); } } async function importStudio() { const query = document.getElementById('studio-query').value; if (!query) { alert('Please enter a studio name'); return; } setImportStatus('studio', 'Searching...', false); try { const response = await fetch('/api/import/studio', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ query }) }); const result = await response.json(); if (result.success) { setImportStatus('studio', result.message, true); setTimeout(() => { closeModal('search-studio-modal'); location.reload(); }, 1500); } else { setImportStatus('studio', result.message, false); } } catch (error) { setImportStatus('studio', 'Error: ' + error.message, false); } } async function importScene() { const query = document.getElementById('scene-query').value; if (!query) { alert('Please enter a scene title'); return; } setImportStatus('scene', 'Searching...', false); try { const response = await fetch('/api/import/scene', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ query }) }); const result = await response.json(); if (result.success) { setImportStatus('scene', result.message, true); setTimeout(() => { closeModal('search-scene-modal'); location.reload(); }, 1500); } else { setImportStatus('scene', result.message, false); } } catch (error) { setImportStatus('scene', 'Error: ' + error.message, false); } } async function syncAll() { const forceEl = document.getElementById('sync-force'); const force = forceEl ? forceEl.checked : false; setImportStatus('sync', 'Syncing all data from TPDB...', false); try { const response = await fetch('/api/sync', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ force }) }); const result = await response.json(); if (result.success) { let message = result.message + '\n\n'; if (result.data) { result.data.forEach(r => { message += `${r.EntityType}: ${r.Updated} updated, ${r.Failed} failed\n`; }); } setImportStatus('sync', message, true); setTimeout(() => { closeModal('sync-modal'); location.reload(); }, 2000); } else { setImportStatus('sync', result.message, false); } } catch (error) { setImportStatus('sync', 'Error: ' + error.message, false); } } function setImportStatus(type, message, success) { const statusEl = document.getElementById(`${type}-import-status`); if (statusEl) { statusEl.textContent = message; statusEl.className = 'result-message ' + (success ? 'success' : 'error'); statusEl.style.display = 'block'; } } // Close modals when clicking outside // Global loader helpers function showLoader(msg) { const overlay = document.getElementById('global-loader'); const text = document.getElementById('global-loader-text'); if (overlay) { overlay.style.display = 'flex'; // Start logo animation when loader shows startLogoAnimation(); } if (text && msg) { text.textContent = msg; } } function hideLoader() { const overlay = document.getElementById('global-loader'); if (overlay) { overlay.style.display = 'none'; // Stop logo animation when loader hides stopLogoAnimation(); } } // Unified SSE import helper with progress bar function importWithProgress(url, label) { return new Promise((resolve, reject) => { const eventSource = new EventSource(url); startJobProgress(label); eventSource.onmessage = function(event) { const data = JSON.parse(event.data); if (data.error) { updateJobProgress(0, 0, data.error, true); eventSource.close(); reject(new Error(data.error)); return; } if (data.complete) { updateJobProgress(data.result.Imported, data.result.Total, `${label} complete (${data.result.Imported}/${data.result.Total})`, false); eventSource.close(); resolve(data.result); return; } updateJobProgress(data.current, data.total, data.message, false); }; eventSource.onerror = function() { updateJobProgress(0, 0, `${label} connection error`, true); eventSource.close(); reject(new Error('Connection error')); }; }); } function startJobProgress(label) { const container = document.getElementById('job-progress'); const lbl = document.getElementById('job-progress-label'); const msg = document.getElementById('job-progress-message'); const fill = document.getElementById('job-progress-fill'); const count = document.getElementById('job-progress-count'); if (container && lbl && msg && fill && count) { container.style.display = 'block'; lbl.textContent = label || 'Working...'; msg.textContent = ''; count.textContent = ''; fill.style.width = '0%'; } } function updateJobProgress(current, total, message, isError) { const container = document.getElementById('job-progress'); const msg = document.getElementById('job-progress-message'); const fill = document.getElementById('job-progress-fill'); const count = document.getElementById('job-progress-count'); if (container && fill && msg && count) { const percent = total > 0 ? Math.min(100, (current / total) * 100) : 0; fill.style.width = `${percent}%`; count.textContent = total > 0 ? `${current}/${total}` : ''; msg.textContent = message || ''; if (isError) { fill.style.background = '#ff8a8a'; msg.style.color = '#ff8a8a'; } else { fill.style.background = 'linear-gradient(135deg, var(--color-brand) 0%, var(--color-keypoint) 100%)'; msg.style.color = 'var(--color-text-secondary)'; } } } function stopJobProgress() { const container = document.getElementById('job-progress'); if (container) container.style.display = 'none'; } window.onclick = function(event) { if (event.target.classList.contains('modal')) { event.target.classList.remove('active'); } } // Image error handling function handleImageError(img) { img.style.display = 'none'; } // Progress Modal Functions function showProgressModal(entityType) { const modal = document.getElementById('progress-modal'); if (!modal) { // Create progress modal if it doesn't exist const modalHTML = ` `; document.body.insertAdjacentHTML('beforeend', modalHTML); } else { modal.classList.add('active'); } } function closeProgressModal() { const modal = document.getElementById('progress-modal'); if (modal) { modal.classList.remove('active'); } } function updateProgress(entityType, current, total, message, isError) { const progressFill = document.getElementById('progress-fill'); const progressText = document.getElementById('progress-text'); if (progressFill && progressText) { const percent = total > 0 ? (current / total * 100) : 0; progressFill.style.width = percent + '%'; progressText.textContent = message; if (isError) { progressFill.style.background = 'var(--color-warning)'; progressText.style.color = 'var(--color-warning)'; } else { progressFill.style.background = 'linear-gradient(135deg, var(--color-brand) 0%, var(--color-keypoint) 100%)'; progressText.style.color = 'var(--color-text-primary)'; } } }