From cc0b0a891e3ebb1f54316daf21a9374e72d50982 Mon Sep 17 00:00:00 2001 From: lenafx Date: Sat, 27 Dec 2025 18:48:53 +0100 Subject: [PATCH] wip settings section --- desktop/src/api/local/local.controller.ts | 21 -- desktop/src/api/local/local.routes.ts | 2 - desktop/src/api/local/local.service.ts | 0 desktop/src/scripts/auth-guard.js | 37 +++ desktop/src/scripts/settings.js | 218 +++++++++++++++ desktop/views/anime/animes.html | 9 + desktop/views/components/navbar.html | 62 +++++ desktop/views/components/settings-modal.html | 278 +++++++++++++++++++ 8 files changed, 604 insertions(+), 23 deletions(-) delete mode 100644 desktop/src/api/local/local.service.ts create mode 100644 desktop/src/scripts/settings.js create mode 100644 desktop/views/components/navbar.html create mode 100644 desktop/views/components/settings-modal.html diff --git a/desktop/src/api/local/local.controller.ts b/desktop/src/api/local/local.controller.ts index 76b8094..3c2dad8 100644 --- a/desktop/src/api/local/local.controller.ts +++ b/desktop/src/api/local/local.controller.ts @@ -65,27 +65,6 @@ async function resolveEntryMetadata(entry: any, type: string) { }; } - -export async function getConfig(_request: FastifyRequest, reply: FastifyReply) { - try { - return loadConfig(); - } catch { - return reply.status(500).send({ error: 'FAILED_TO_LOAD_CONFIG' }); - } -} - -export async function setConfig(request: FastifyRequest<{ Body: SetConfigBody }>, reply: FastifyReply) { - try { - const { body } = request; - if (!body || typeof body !== 'object') { - return reply.status(400).send({ error: 'INVALID_BODY' }); - } - return saveConfig(body); - } catch { - return reply.status(500).send({ error: 'FAILED_TO_SAVE_CONFIG' }); - } -} - export async function scanLibrary(request: FastifyRequest<{ Querystring: ScanQuery }>, reply: FastifyReply) { try { const mode = request.query.mode || 'incremental'; diff --git a/desktop/src/api/local/local.routes.ts b/desktop/src/api/local/local.routes.ts index 3cf8eea..5924812 100644 --- a/desktop/src/api/local/local.routes.ts +++ b/desktop/src/api/local/local.routes.ts @@ -2,8 +2,6 @@ import { FastifyInstance } from 'fastify'; import * as controller from './local.controller'; async function localRoutes(fastify: FastifyInstance) { - fastify.get('/library/config', controller.getConfig); - fastify.post('/library/config', controller.setConfig); fastify.post('/library/scan', controller.scanLibrary); fastify.get('/library/:type', controller.listEntries); fastify.get('/library/:type/:id', controller.getEntry); diff --git a/desktop/src/api/local/local.service.ts b/desktop/src/api/local/local.service.ts deleted file mode 100644 index e69de29..0000000 diff --git a/desktop/src/scripts/auth-guard.js b/desktop/src/scripts/auth-guard.js index ae6a220..e0cfd85 100644 --- a/desktop/src/scripts/auth-guard.js +++ b/desktop/src/scripts/auth-guard.js @@ -43,6 +43,43 @@ async function loadMeUI() { } } +// Variable para saber si el modal ya fue cargado +let settingsModalLoaded = false; + +document.getElementById('nav-settings').addEventListener('click', openSettings) + +async function openSettings() { + if (!settingsModalLoaded) { + try { + const res = await fetch('/views/components/settings-modal.html') + const html = await res.text() + document.body.insertAdjacentHTML('beforeend', html) + settingsModalLoaded = true; + + // Esperar un momento para que el DOM se actualice + await new Promise(resolve => setTimeout(resolve, 50)); + + // Ahora cargar los settings + if (window.toggleSettingsModal) { + await window.toggleSettingsModal(false); + } + } catch (err) { + console.error('Error loading settings modal:', err); + } + } else { + if (window.toggleSettingsModal) { + await window.toggleSettingsModal(false); + } + } +} + +function closeSettings() { + const modal = document.getElementById('settings-modal'); + if (modal) { + modal.classList.add('hidden'); + } +} + function setupDropdown() { const userAvatarBtn = document.querySelector(".user-avatar-btn") const navDropdown = document.getElementById("nav-dropdown") diff --git a/desktop/src/scripts/settings.js b/desktop/src/scripts/settings.js new file mode 100644 index 0000000..d1bb295 --- /dev/null +++ b/desktop/src/scripts/settings.js @@ -0,0 +1,218 @@ +const API_BASE = '/api/config'; +let currentConfig = {}; +let activeSection = ''; +let modal, navContainer, formContent, form; + +window.toggleSettingsModal = async (forceClose = false) => { + modal = document.getElementById('settings-modal'); + navContainer = document.getElementById('config-nav'); + formContent = document.getElementById('config-section-content'); + form = document.getElementById('config-form'); + + if (!modal) { + console.error('Modal not found'); + return; + } + + if (forceClose) { + modal.classList.add('hidden'); + } else { + const isHidden = modal.classList.contains('hidden'); + + if (isHidden) { + // Abrir modal + modal.classList.remove('hidden'); + await loadSettings(); + } else { + // Cerrar modal + modal.classList.add('hidden'); + } + } +}; + +async function loadSettings() { + if (!formContent) { + console.error('Form content not found'); + return; + } + + // Mostrar loading + formContent.innerHTML = ` +
+
+
+
+
+ `; + + try { + const res = await fetch(API_BASE); + + if (!res.ok) { + throw new Error(`HTTP error! status: ${res.status}`); + } + + const data = await res.json(); + + if (data.error) throw new Error(data.error); + + currentConfig = data; + renderNav(); + + // Seleccionar la primera sección si no hay ninguna activa + if (!activeSection || !currentConfig[activeSection]) { + activeSection = Object.keys(currentConfig)[0]; + } + + switchSection(activeSection); + } catch (err) { + console.error('Error loading settings:', err); + formContent.innerHTML = ` +
+

Failed to load settings

+

${err.message}

+
+ `; + } +} + +function renderNav() { + if (!navContainer) return; + + navContainer.innerHTML = ''; + Object.keys(currentConfig).forEach(section => { + const btn = document.createElement('div'); + btn.className = `nav-item ${section === activeSection ? 'active' : ''}`; + btn.textContent = section; + btn.onclick = () => switchSection(section); + navContainer.appendChild(btn); + }); +} + +function switchSection(section) { + if (!currentConfig[section]) return; + + activeSection = section; + renderNav(); + + const sectionData = currentConfig[section]; + + formContent.innerHTML = ` +

+ ${section.replace(/_/g, ' ')} +

+ `; + + Object.entries(sectionData).forEach(([key, value]) => { + const group = document.createElement('div'); + group.className = 'config-group'; + + const isBool = typeof value === 'boolean'; + const inputId = `input-${section}-${key}`; + const label = key.replace(/_/g, ' '); + + if (isBool) { + group.innerHTML = ` +
+ + +
+ `; + } else { + group.innerHTML = ` + + + `; + } + + formContent.appendChild(group); + }); +} + +// Setup form submit handler +document.addEventListener('DOMContentLoaded', () => { + // Usar delegación de eventos ya que el form se carga dinámicamente + document.addEventListener('submit', async (e) => { + if (e.target.id === 'config-form') { + e.preventDefault(); + await saveSettings(); + } + }); +}); + +async function saveSettings() { + if (!form || !activeSection) return; + + const updatedData = {}; + + Object.keys(currentConfig[activeSection]).forEach(key => { + const input = form.elements[key]; + if (!input) return; + + if (input.type === 'checkbox') { + updatedData[key] = input.checked; + } else if (input.type === 'number') { + updatedData[key] = Number(input.value); + } else { + updatedData[key] = input.value; + } + }); + + try { + const res = await fetch(`${API_BASE}/${activeSection}`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(updatedData) + }); + + if (res.ok) { + currentConfig[activeSection] = updatedData; + + // Mostrar notificación de éxito + const notification = document.createElement('div'); + notification.style.cssText = ` + position: fixed; + top: 20px; + right: 20px; + background: var(--color-success, #10b981); + color: white; + padding: 1rem 1.5rem; + border-radius: 8px; + box-shadow: 0 4px 12px rgba(0,0,0,0.3); + z-index: 10000; + animation: slideIn 0.3s ease-out; + `; + notification.textContent = 'Settings saved successfully!'; + document.body.appendChild(notification); + + setTimeout(() => { + notification.style.animation = 'slideOut 0.3s ease-out'; + setTimeout(() => notification.remove(), 300); + }, 2000); + } else { + throw new Error('Failed to save settings'); + } + } catch (err) { + console.error('Error saving settings:', err); + alert('Error saving settings: ' + err.message); + } +} + +// Añadir estilos para las animaciones (solo si no existen) +if (!document.getElementById('settings-animations')) { + const animationStyles = document.createElement('style'); + animationStyles.id = 'settings-animations'; + animationStyles.textContent = ` + @keyframes slideIn { + from { transform: translateX(400px); opacity: 0; } + to { transform: translateX(0); opacity: 1; } + } + @keyframes slideOut { + from { transform: translateX(0); opacity: 1; } + to { transform: translateX(400px); opacity: 0; } + } + `; + document.head.appendChild(animationStyles); +} \ No newline at end of file diff --git a/desktop/views/anime/animes.html b/desktop/views/anime/animes.html index dcaaf47..6cb3860 100644 --- a/desktop/views/anime/animes.html +++ b/desktop/views/anime/animes.html @@ -68,6 +68,14 @@ + + @@ -303,5 +311,6 @@ + \ No newline at end of file diff --git a/desktop/views/components/navbar.html b/desktop/views/components/navbar.html new file mode 100644 index 0000000..f9d0b2d --- /dev/null +++ b/desktop/views/components/navbar.html @@ -0,0 +1,62 @@ +
+ WF Logo +
+ WaifuBoard +
+ + \ No newline at end of file diff --git a/desktop/views/components/settings-modal.html b/desktop/views/components/settings-modal.html new file mode 100644 index 0000000..3dae9fa --- /dev/null +++ b/desktop/views/components/settings-modal.html @@ -0,0 +1,278 @@ + + + \ No newline at end of file