// ============================================================================ // LAUNCHER — VERSIONE COMPLETA E OTTIMIZZATA (A) — BLOCCO 1/6 // Sezione: Variabili globali + Storage + Config + Setup Page // ============================================================================ // --------------------------------------------------------------------------- // VARIABILI GLOBALI // --------------------------------------------------------------------------- let URI; let USER; let PASSW; let appsData = []; // Lista completa delle app (nome, icona, url) let appsOrder = []; // Ordine delle icone nella griglia let editMode = false; // Modalità wiggle stile iOS // Zoom let zoomLevel; let zoomMax; let initialPinchDistance = null; let lastTapTime = 0; let zoomAnimFrame = null; // Long‑press / drag let longPressTimer = null; let longPressTarget = null; let contextMenuTargetId = null; let draggingIcon = null; let draggingId = null; let dragOffsetX = 0; let dragOffsetY = 0; let dragStartX = 0; let dragStartY = 0; // --------------------------------------------------------------------------- // CRITTOGRAFIA E STORAGE // --------------------------------------------------------------------------- const SECRET_KEY = "chiave-super-segreta-123"; // Salva configurazione (URL, user, pass) function saveConfig(url, user, password) { const data = { url, user, password }; URI = url; USER = user; PASSW = password; const encrypted = CryptoJS.AES.encrypt( JSON.stringify(data), SECRET_KEY ).toString(); localStorage.setItem("launcherConfig", encrypted); } // Carica configurazione function loadConfig() { const encrypted = localStorage.getItem("launcherConfig"); if (!encrypted) return null; try { const bytes = CryptoJS.AES.decrypt(encrypted, SECRET_KEY); const obj = JSON.parse(bytes.toString(CryptoJS.enc.Utf8)); URI = obj.url; USER = obj.user; PASSW = obj.password; return obj; } catch { return null; } } // Salva apps scaricate dal server function saveApps(jsonApps) { const encrypted = CryptoJS.AES.encrypt( JSON.stringify(jsonApps), SECRET_KEY ).toString(); localStorage.setItem("jsonApps", encrypted); } // Carica apps salvate in locale function loadApps() { const encrypted = localStorage.getItem("jsonApps"); if (!encrypted) return null; try { const bytes = CryptoJS.AES.decrypt(encrypted, SECRET_KEY); return JSON.parse(bytes.toString(CryptoJS.enc.Utf8)); } catch { return null; } } // --------------------------------------------------------------------------- // SETUP PAGE (6 TAP PER APRIRE + AUTOCOMPILAZIONE) // --------------------------------------------------------------------------- /*function showSetupPage() { const cfg = loadConfig(); if (cfg) { document.getElementById("cfg-url").value = cfg.url; document.getElementById("cfg-user").value = cfg.user; document.getElementById("cfg-pass").value = cfg.password; } document.getElementById("setup-page").classList.remove("hidden"); }*/ function showSetupPage() { const cfg = loadConfig(); if (cfg) { // Popola i campi document.getElementById("cfg-url").value = cfg.url; document.getElementById("cfg-user").value = cfg.user; document.getElementById("cfg-pass").value = cfg.password; // Mostra il pulsante "Aggiorna ora" document.getElementById("cfg-refresh").style.display = "block"; } else { // Nessuna config → nascondi il pulsante document.getElementById("cfg-refresh").style.display = "none"; } document.getElementById("setup-page").classList.remove("hidden"); } function hideSetupPage() { document.getElementById("setup-page").classList.add("hidden"); } // 6 tap per aprire la setup page let tapCount = 0; let tapTimer = null; document.addEventListener("click", () => { tapCount++; if (tapTimer) clearTimeout(tapTimer); tapTimer = setTimeout(() => { tapCount = 0; }, 600); if (tapCount >= 6) { tapCount = 0; showSetupPage(); } }); // ============================================================================ // LAUNCHER — VERSIONE COMPLETA E OTTIMIZZATA (A) — BLOCCO 2/6 // Sezione: API login, getLinks, ordine apps, render, startLauncher // ============================================================================ // --------------------------------------------------------------------------- // LOGIN API // --------------------------------------------------------------------------- async function login(email, password) { try { const res = await fetch(`${URI}/auth/login`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ email, password }) }); if (!res.ok) { const text = await res.text(); throw new Error(`HTTP ${res.status}: ${text}`); } const data = await res.json(); return data.token; } catch (err) { alert(err); return null; } } // --------------------------------------------------------------------------- // GET LINKS (scarica apps dal server + salva + aggiorna ordine) // --------------------------------------------------------------------------- async function getLinks() { try { const token = await login(USER, PASSW); if (!token) throw new Error("User o Password errati"); const res = await fetch(`${URI}/links`, { headers: { "Authorization": `Bearer ${token}`, "Accept": "application/json" } }); if (!res.ok) throw new Error("Server errato o non risponde"); const json = await res.json(); // Normalizza apps appsData = json.map((a, i) => ({ id: a.id || `app-${i}`, name: a.name, url: a.url, icon: `${URI}${a.icon}` })); // Salva in locale saveApps(appsData); // Aggiorna ordine loadAppOrder(); // Render renderApps(); return true; } catch (err) { console.error(err); return null; } } // --------------------------------------------------------------------------- // CARICAMENTO ORDINE APPS // --------------------------------------------------------------------------- function loadAppOrder() { const stored = localStorage.getItem("appsOrder"); if (stored) { const parsed = JSON.parse(stored); // Mantieni solo ID validi appsOrder = parsed.filter(id => appsData.some(a => a.id === id)); // Aggiungi eventuali nuove app non presenti nell'ordine salvato appsData.forEach(a => { if (!appsOrder.includes(a.id)) appsOrder.push(a.id); }); } else { // Primo avvio → ordine naturale appsOrder = appsData.map(a => a.id); } } function saveOrder() { localStorage.setItem("appsOrder", JSON.stringify(appsOrder)); } // --------------------------------------------------------------------------- // RENDER DELLA GRIGLIA // --------------------------------------------------------------------------- function renderApps() { const folderEl = document.getElementById("folder"); folderEl.innerHTML = ""; appsOrder.forEach(id => { const app = appsData.find(a => a.id === id); if (!app) return; const div = document.createElement("div"); div.className = "app-icon"; div.dataset.id = app.id; div.innerHTML = ` ${app.name} ${app.name} `; div.addEventListener("click", () => { if (!editMode) window.open(app.url, "_blank", "noopener"); }); folderEl.appendChild(div); }); } // --------------------------------------------------------------------------- // START LAUNCHER (carica locale → render → aggiorna server → init UI) // --------------------------------------------------------------------------- async function startLauncher() { // 1️⃣ Carica apps salvate in locale const saved = loadApps(); if (saved) { appsData = saved; //console.log("Apps caricate da localStorage:", appsData); } // 2️⃣ Carica ordine loadAppOrder(); // 3️⃣ Render immediato (istantaneo) renderApps(); // 4️⃣ Aggiorna in background dal server //getLinks(); // 5️⃣ Inizializza UI (zoom, drag, wiggle, menu…) initZoomHandlers(); initLongPressHandlers(); initDragHandlers(); initContextMenuActions(); initGlobalCloseHandlers(); } // ============================================================================ // LAUNCHER — VERSIONE COMPLETA E OTTIMIZZATA (A) — BLOCCO 3/6 // Sezione: Zoom stile iPhone (pinch, elasticità, wheel) // ============================================================================ // --------------------------------------------------------------------------- // Calcolo dinamico dello zoom massimo // (dipende dalla larghezza dello schermo e dalla dimensione delle icone) // --------------------------------------------------------------------------- function computeDynamicMaxZoom() { return Math.min(window.innerWidth / 85, 4.0); } // --------------------------------------------------------------------------- // Carica lo zoom salvato in locale // --------------------------------------------------------------------------- function loadInitialZoom() { const v = parseFloat(localStorage.getItem("zoomLevel")); if (!isFinite(v) || v <= 0) return 1; return Math.min(Math.max(v, 0.5), computeDynamicMaxZoom()); } // --------------------------------------------------------------------------- // Applica lo zoom (aggiorna CSS + salva in locale) // --------------------------------------------------------------------------- function applyZoom(z) { zoomLevel = (!isFinite(z) || z <= 0) ? 1 : z; document.documentElement.style.setProperty("--zoom", zoomLevel); localStorage.setItem("zoomLevel", String(zoomLevel)); } // --------------------------------------------------------------------------- // Distanza tra due dita (pinch) // --------------------------------------------------------------------------- function getPinchDistance(touches) { const [a, b] = touches; return Math.hypot(a.clientX - b.clientX, a.clientY - b.clientY); } // --------------------------------------------------------------------------- // Elasticità ai limiti (effetto iOS) // --------------------------------------------------------------------------- function elasticEase(x) { return Math.sin(x * Math.PI * 0.5) * 1.05; } // --------------------------------------------------------------------------- // Inizializzazione completa dello zoom // --------------------------------------------------------------------------- function initZoomHandlers() { zoomMax = computeDynamicMaxZoom(); zoomLevel = loadInitialZoom(); applyZoom(zoomLevel); // --------------------------------------------------------- // PINCH SU MOBILE // --------------------------------------------------------- // Previeni scroll durante pinch document.addEventListener("touchmove", e => { if (e.touches.length === 2) e.preventDefault(); }, { passive: false }); // Inizio pinch o doppio tap document.addEventListener("touchstart", e => { // Inizio pinch if (e.touches.length === 2) { initialPinchDistance = getPinchDistance(e.touches); if (zoomAnimFrame) cancelAnimationFrame(zoomAnimFrame); } // Doppio tap → zoom rapido /*const now = Date.now(); if (e.touches.length === 1 && now - lastTapTime < 300) { zoomMax = computeDynamicMaxZoom(); applyZoom(Math.min(zoomLevel * 1.15, zoomMax)); } lastTapTime = now;*/ lastTapTime = Date.now(); }); // Pinch in corso document.addEventListener("touchmove", e => { if (e.touches.length === 2 && initialPinchDistance) { const newDist = getPinchDistance(e.touches); const scale = newDist / initialPinchDistance; let newZoom = zoomLevel * scale; zoomMax = computeDynamicMaxZoom(); // Elasticità ai limiti if (newZoom > zoomMax) newZoom = zoomMax + (newZoom - zoomMax) * 0.25; if (newZoom < 0.5) newZoom = 0.5 - (0.5 - newZoom) * 0.25; applyZoom(newZoom); initialPinchDistance = newDist; e.preventDefault(); } }, { passive: false }); // Fine pinch → animazione elastica verso limite document.addEventListener("touchend", e => { if (e.touches.length < 2 && initialPinchDistance) { initialPinchDistance = null; zoomMax = computeDynamicMaxZoom(); const target = Math.min(Math.max(zoomLevel, 0.5), zoomMax); const start = zoomLevel; const duration = 250; const startTime = performance.now(); function animate(t) { const p = Math.min((t - startTime) / duration, 1); const eased = start + (target - start) * elasticEase(p); applyZoom(eased); if (p < 1) zoomAnimFrame = requestAnimationFrame(animate); } zoomAnimFrame = requestAnimationFrame(animate); } }); // --------------------------------------------------------- // ZOOM CON WHEEL SU DESKTOP // --------------------------------------------------------- document.addEventListener("wheel", e => { e.preventDefault(); zoomMax = computeDynamicMaxZoom(); const direction = e.deltaY < 0 ? 1 : -1; const factor = 1 + direction * 0.1; let newZoom = zoomLevel * factor; // Elasticità if (newZoom > zoomMax) newZoom = zoomMax + (newZoom - zoomMax) * 0.25; if (newZoom < 0.5) newZoom = 0.5 - (0.5 - newZoom) * 0.25; applyZoom(newZoom); }, { passive: false }); } // ============================================================================ // LAUNCHER — VERSIONE COMPLETA E OTTIMIZZATA (A) — BLOCCO 4/6 // Sezione: Long‑press, Edit Mode, Context Menu, Global Close // ============================================================================ // --------------------------------------------------------------------------- // EDIT MODE (wiggle stile iOS) // --------------------------------------------------------------------------- function enterEditMode() { editMode = true; document.body.classList.add("edit-mode"); } function exitEditMode() { editMode = false; document.body.classList.remove("edit-mode"); hideContextMenu(); } // --------------------------------------------------------------------------- // MENU CONTESTUALE // --------------------------------------------------------------------------- function showContextMenuFor(id, x, y) { contextMenuTargetId = id; const menu = document.getElementById("context-menu"); menu.style.left = `${x}px`; menu.style.top = `${y}px`; menu.classList.remove("hidden"); } function hideContextMenu() { const menu = document.getElementById("context-menu"); menu.classList.add("hidden"); contextMenuTargetId = null; } // --------------------------------------------------------------------------- // LONG PRESS HANDLERS (TOUCH + MOUSE) // --------------------------------------------------------------------------- function initLongPressHandlers() { // --------------------------------------------------------- // TOUCH LONG PRESS // --------------------------------------------------------- document.addEventListener("touchstart", e => { if (e.touches.length !== 1) return; const touch = e.touches[0]; const icon = touch.target.closest(".app-icon"); if (icon) { longPressTarget = icon; longPressTimer = setTimeout(() => { // Primo long‑press → entra in edit mode if (!editMode) { enterEditMode(); if (navigator.vibrate) navigator.vibrate(10); return; } // Se già in edit mode → apri menu contestuale const r = icon.getBoundingClientRect(); showContextMenuFor( icon.dataset.id, r.left + r.width / 2, r.top + r.height ); if (navigator.vibrate) navigator.vibrate(10); }, 350); return; } // Long press fuori dalle icone → esci da edit mode longPressTimer = setTimeout(() => { if (editMode) exitEditMode(); }, 350); }, { passive: true }); // Cancella long‑press se l’utente si muove troppo document.addEventListener("touchmove", e => { if (!longPressTimer) return; const touch = e.touches[0]; const r = longPressTarget?.getBoundingClientRect(); const dx = touch.clientX - (r?.left ?? touch.clientX); const dy = touch.clientY - (r?.top ?? touch.clientY); if (Math.hypot(dx, dy) > 15) { clearTimeout(longPressTimer); longPressTimer = null; longPressTarget = null; } }, { passive: true }); // Fine touch → cancella long‑press document.addEventListener("touchend", () => { if (longPressTimer) { clearTimeout(longPressTimer); longPressTimer = null; longPressTarget = null; } }, { passive: true }); // --------------------------------------------------------- // MOUSE LONG PRESS // --------------------------------------------------------- document.addEventListener("mousedown", e => { if (e.button !== 0) return; const icon = e.target.closest(".app-icon"); longPressTarget = icon ?? null; longPressTimer = setTimeout(() => { if (!editMode) { enterEditMode(); return; } if (icon) { const r = icon.getBoundingClientRect(); showContextMenuFor( icon.dataset.id, r.left + r.width / 2, r.top + r.height ); } }, 350); }); // Cancella long‑press se il mouse si muove troppo document.addEventListener("mousemove", e => { if (!longPressTimer) return; if (longPressTarget) { const r = longPressTarget.getBoundingClientRect(); const dx = e.clientX - (r.left + r.width / 2); const dy = e.clientY - (r.top + r.height / 2); if (Math.hypot(dx, dy) > 15) { clearTimeout(longPressTimer); longPressTimer = null; longPressTarget = null; } } }); // Mouse up → cancella long‑press document.addEventListener("mouseup", () => { if (longPressTimer) { clearTimeout(longPressTimer); longPressTimer = null; longPressTarget = null; } }); } // --------------------------------------------------------------------------- // CHIUSURA MENU E USCITA DA EDIT MODE // --------------------------------------------------------------------------- function initGlobalCloseHandlers() { document.addEventListener("pointerdown", e => { const isIcon = e.target.closest(".app-icon"); const isMenu = e.target.closest("#context-menu"); // 1️⃣ Clic fuori dal menu → chiudi menu if (!isMenu && !isIcon && !document.getElementById("context-menu").classList.contains("hidden")) { hideContextMenu(); } // 2️⃣ Clic fuori dalle icone → esci da edit mode if (!isIcon && editMode) { exitEditMode(); } }); } // ============================================================================ // LAUNCHER — VERSIONE COMPLETA E OTTIMIZZATA (A) — BLOCCO 5/6 // Sezione: Drag & Drop stile iPhone // ============================================================================ // --------------------------------------------------------------------------- // Utility per ottenere posizione del puntatore (touch + mouse) // --------------------------------------------------------------------------- function getPointerPosition(e) { if (e.touches && e.touches.length > 0) { return { pageX: e.touches[0].pageX, pageY: e.touches[0].pageY, clientX: e.touches[0].clientX, clientY: e.touches[0].clientY }; } return { pageX: e.pageX, pageY: e.pageY, clientX: e.clientX, clientY: e.clientY }; } // --------------------------------------------------------------------------- // Inizio drag: crea icona flottante + placeholder invisibile // --------------------------------------------------------------------------- function startDrag(icon, pos) { draggingId = icon.dataset.id; const r = icon.getBoundingClientRect(); dragOffsetX = pos.pageX - r.left; dragOffsetY = pos.pageY - r.top; draggingIcon = icon; draggingIcon.classList.add("dragging"); draggingIcon.style.position = "fixed"; draggingIcon.style.left = `${r.left}px`; draggingIcon.style.top = `${r.top}px`; draggingIcon.style.width = `${r.width}px`; draggingIcon.style.height = `${r.height}px`; draggingIcon.style.zIndex = "1000"; draggingIcon.style.pointerEvents = "none"; draggingIcon.style.transform = "translate3d(0,0,0)"; // Placeholder invisibile che mantiene lo spazio const placeholder = icon.cloneNode(true); placeholder.classList.add("placeholder"); placeholder.style.visibility = "hidden"; icon.parentNode.insertBefore(placeholder, icon); hideContextMenu(); } // --------------------------------------------------------------------------- // Aggiorna posizione dell’icona trascinata + reorder dinamico // --------------------------------------------------------------------------- function updateDragPosition(pos) { if (!draggingIcon) return; const x = pos.pageX - dragOffsetX; const y = pos.pageY - dragOffsetY; draggingIcon.style.left = `${x}px`; draggingIcon.style.top = `${y}px`; const elem = document.elementFromPoint(pos.clientX, pos.clientY); const targetIcon = elem && elem.closest(".app-icon:not(.dragging):not(.placeholder)"); if (!targetIcon) return; const from = appsOrder.indexOf(draggingId); const to = appsOrder.indexOf(targetIcon.dataset.id); if (from === -1 || to === -1 || from === to) return; appsOrder.splice(from, 1); appsOrder.splice(to, 0, draggingId); saveOrder(); } // --------------------------------------------------------------------------- // Fine drag: drop preciso nella cella corretta // --------------------------------------------------------------------------- function endDrag() { if (!draggingIcon) return; const icon = draggingIcon; draggingIcon = null; // Rimuovi placeholder const placeholder = document.querySelector(".app-icon.placeholder"); if (placeholder) placeholder.remove(); // Calcola punto centrale dell’icona trascinata const left = parseFloat(icon.style.left) || 0; const top = parseFloat(icon.style.top) || 0; const dropXClient = left + icon.offsetWidth / 2; const dropYClient = top + icon.offsetHeight / 2; const elem = document.elementFromPoint(dropXClient, dropYClient); const targetIcon = elem && elem.closest(".app-icon:not(.dragging)"); if (targetIcon) { const from = appsOrder.indexOf(icon.dataset.id); const to = appsOrder.indexOf(targetIcon.dataset.id); if (from !== -1 && to !== -1 && from !== to) { appsOrder.splice(from, 1); appsOrder.splice(to, 0, icon.dataset.id); saveOrder(); } } // Ripristina icona icon.classList.remove("dragging"); icon.style.position = ""; icon.style.left = ""; icon.style.top = ""; icon.style.width = ""; icon.style.height = ""; icon.style.zIndex = ""; icon.style.pointerEvents = ""; icon.style.transform = ""; renderApps(); } // --------------------------------------------------------------------------- // Inizializzazione Drag & Drop (touch + mouse) // --------------------------------------------------------------------------- function initDragHandlers() { // --------------------------------------------------------- // TOUCH DRAG // --------------------------------------------------------- document.addEventListener("touchstart", e => { if (!editMode) return; if (e.touches.length !== 1) return; if (contextMenuTargetId) return; const pos = getPointerPosition(e); const icon = e.touches[0].target.closest(".app-icon"); if (!icon) return; dragStartX = pos.clientX; dragStartY = pos.clientY; draggingIcon = null; draggingId = null; }, { passive: true }); document.addEventListener("touchmove", e => { if (!editMode) return; if (e.touches.length !== 1) return; const pos = getPointerPosition(e); // Inizio drag if (!draggingIcon) { const dx = pos.clientX - dragStartX; const dy = pos.clientY - dragStartY; if (Math.hypot(dx, dy) > 10) { const icon = e.touches[0].target.closest(".app-icon"); if (icon) { if (longPressTimer) { clearTimeout(longPressTimer); longPressTimer = null; longPressTarget = null; } startDrag(icon, pos); } } } else { updateDragPosition(pos); e.preventDefault(); } }, { passive: false }); document.addEventListener("touchend", e => { if (!editMode) return; if (draggingIcon && (!e.touches || e.touches.length === 0)) { endDrag(); } }, { passive: true }); // --------------------------------------------------------- // MOUSE DRAG // --------------------------------------------------------- document.addEventListener("mousedown", e => { if (!editMode) return; if (e.button !== 0) return; if (contextMenuTargetId) return; const icon = e.target.closest(".app-icon"); if (!icon) return; const pos = getPointerPosition(e); dragStartX = pos.clientX; dragStartY = pos.clientY; draggingIcon = null; draggingId = null; }); document.addEventListener("mousemove", e => { if (!editMode) return; const pos = getPointerPosition(e); if (!draggingIcon) { if (!dragStartX && !dragStartY) return; const dx = pos.clientX - dragStartX; const dy = pos.clientY - dragStartY; if (Math.hypot(dx, dy) > 10) { const icon = e.target.closest(".app-icon"); if (icon) { if (longPressTimer) { clearTimeout(longPressTimer); longPressTimer = null; longPressTarget = null; } startDrag(icon, pos); } } } else { updateDragPosition(pos); } }); document.addEventListener("mouseup", () => { if (!editMode) return; dragStartX = 0; dragStartY = 0; if (draggingIcon) { endDrag(); } draggingIcon = null; draggingId = null; }); document.addEventListener("mousemove", e => { if (!editMode) return; const pos = getPointerPosition(e); if (!draggingIcon) { if (!dragStartX && !dragStartY) return; const dx = pos.clientX - dragStartX; const dy = pos.clientY - dragStartY; if (Math.hypot(dx, dy) > 10) { const icon = e.target.closest(".app-icon"); if (icon) { if (longPressTimer) { clearTimeout(longPressTimer); longPressTimer = null; longPressTarget = null; } startDrag(icon, pos); } } } else { updateDragPosition(pos); } }); document.addEventListener("mouseup", () => { if (!editMode) return; dragStartX = 0; dragStartY = 0; if (draggingIcon) { endDrag(); } }); } // ============================================================================ // LAUNCHER — VERSIONE COMPLETA E OTTIMIZZATA (A) — BLOCCO 6/6 (FINALE) // Sezione: Context Menu Actions + Config Save + Init Globale // ============================================================================ // --------------------------------------------------------------------------- // MENU CONTESTUALE — AZIONI (rename, change icon, remove) // --------------------------------------------------------------------------- function initContextMenuActions() { const menu = document.getElementById("context-menu"); menu.addEventListener("click", e => { const btn = e.target.closest("button"); if (!btn || !contextMenuTargetId) return; const action = btn.dataset.action; const app = appsData.find(a => a.id === contextMenuTargetId); if (!app) return; // --------------------------------------------------------- // RINOMINA APP // --------------------------------------------------------- if (action === "rename") { const nuovoNome = prompt("Nuovo nome app:", app.name); if (nuovoNome && nuovoNome.trim()) { app.name = nuovoNome.trim(); renderApps(); saveOrder(); } } // --------------------------------------------------------- // CAMBIA ICONA // --------------------------------------------------------- if (action === "change-icon") { const nuovaIcona = prompt("URL nuova icona:", app.icon); if (nuovaIcona && nuovaIcona.trim()) { app.icon = nuovaIcona.trim(); renderApps(); saveOrder(); } } // --------------------------------------------------------- // RIMUOVI APP DALLA GRIGLIA // --------------------------------------------------------- if (action === "remove") { if (confirm("Rimuovere questa app dalla griglia?")) { appsOrder = appsOrder.filter(id => id !== app.id); saveOrder(); renderApps(); } } hideContextMenu(); }); } // --------------------------------------------------------------------------- // AGGIORNA ORA — aggiorna apps dal server senza cambiare config // --------------------------------------------------------------------------- document.getElementById("cfg-refresh").addEventListener("click", async () => { // Carica config attuale const cfg = loadConfig(); if (!cfg) { alert("Config mancante. Inserisci URL, user e password."); return; } // Aggiorna apps dal server const ok = await getLinks(); if (ok) { hideSetupPage(); startLauncher(); // Ritorna subito alla schermata principale } else { alert("Impossibile aggiornare le app dal server."); } }); // --------------------------------------------------------------------------- // SALVATAGGIO CONFIG + RESTART COMPLETO // --------------------------------------------------------------------------- document.getElementById("cfg-save").addEventListener("click", async () => { const url = document.getElementById("cfg-url").value; const user = document.getElementById("cfg-user").value; const pass = document.getElementById("cfg-pass").value; // Salva configurazione saveConfig(url, user, pass); // Scarica apps dal server const ok = await getLinks(); if (ok) { hideSetupPage(); // Restart completo del launcher startLauncher(); } }); // --------------------------------------------------------------------------- // INIT GLOBALE — DOMContentLoaded // --------------------------------------------------------------------------- document.addEventListener("DOMContentLoaded", () => { const cfg = loadConfig(); if (!cfg) { // Primo avvio → mostra setup showSetupPage(); } else { // Config presente → avvia launcher hideSetupPage(); startLauncher(); } });