// Goondex Web UI JavaScript // Modal handling function openModal(modalId) { const modal = document.getElementById(modalId); if (modal) { modal.classList.add('active'); } } function closeModal(modalId) { const modal = document.getElementById(modalId); if (modal) { modal.classList.remove('active'); } } // 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() { if (!confirm('This will import ALL data from TPDB. This may take several hours. Continue?')) { return; } setImportStatus('import-all', 'Importing all data from TPDB... This may take a while.', false); try { const response = await fetch('/api/import/all', { method: 'POST' }); 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.Imported}/${r.Total} imported, ${r.Failed} failed\n`; }); } setImportStatus('import-all', message, true); setTimeout(() => { closeModal('import-all-modal'); location.reload(); }, 3000); } else { setImportStatus('import-all', result.message, false); } } catch (error) { setImportStatus('import-all', 'Error: ' + error.message, false); } } async function bulkImportPerformers() { if (!confirm('This will import ALL performers from TPDB. Continue?')) { return; } // Show progress modal showProgressModal('performers'); // Connect to SSE endpoint const eventSource = new EventSource('/api/import/all-performers/progress'); eventSource.onmessage = function(event) { const data = JSON.parse(event.data); if (data.error) { updateProgress('performers', 0, 0, data.error, true); eventSource.close(); return; } if (data.complete) { updateProgress('performers', 100, 100, `Complete! Imported ${data.result.Imported}/${data.result.Total} performers`, false); eventSource.close(); setTimeout(() => { closeProgressModal(); location.reload(); }, 2000); } else { updateProgress('performers', data.current, data.total, data.message, false); } }; eventSource.onerror = function() { updateProgress('performers', 0, 0, 'Connection error', true); eventSource.close(); }; } async function bulkImportStudios() { if (!confirm('This will import ALL studios from TPDB. Continue?')) { return; } // Show progress modal showProgressModal('studios'); // Connect to SSE endpoint const eventSource = new EventSource('/api/import/all-studios/progress'); eventSource.onmessage = function(event) { const data = JSON.parse(event.data); if (data.error) { updateProgress('studios', 0, 0, data.error, true); eventSource.close(); return; } if (data.complete) { updateProgress('studios', 100, 100, `Complete! Imported ${data.result.Imported}/${data.result.Total} studios`, false); eventSource.close(); setTimeout(() => { closeProgressModal(); location.reload(); }, 2000); } else { updateProgress('studios', data.current, data.total, data.message, false); } }; eventSource.onerror = function() { updateProgress('studios', 0, 0, 'Connection error', true); eventSource.close(); }; } async function bulkImportScenes() { if (!confirm('This will import ALL scenes from TPDB. Continue?')) { return; } // Show progress modal showProgressModal('scenes'); // Connect to SSE endpoint const eventSource = new EventSource('/api/import/all-scenes/progress'); eventSource.onmessage = function(event) { const data = JSON.parse(event.data); if (data.error) { updateProgress('scenes', 0, 0, data.error, true); eventSource.close(); return; } if (data.complete) { updateProgress('scenes', 100, 100, `Complete! Imported ${data.result.Imported}/${data.result.Total} scenes`, false); eventSource.close(); setTimeout(() => { closeProgressModal(); location.reload(); }, 2000); } else { updateProgress('scenes', data.current, data.total, data.message, false); } }; eventSource.onerror = function() { updateProgress('scenes', 0, 0, 'Connection error', true); eventSource.close(); }; } function toggleFilterbar() { document.getElementById('filterbar').classList.toggle('open'); } 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 force = document.getElementById('sync-force').checked; 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 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)'; } } }