From 045977f9fb08bb8bacd36954146316eead39684d Mon Sep 17 00:00:00 2001 From: Fabio Date: Thu, 1 Jan 2026 22:32:35 +0100 Subject: [PATCH] 3rd ed. --- .gitignore | 6 +- app/app.js | 1561 +++++++++++++++++++++--------------- app/index.html | 7 + app/start.sh | 4 + app/style.css | 3 + server/backend/.env | 16 + server/frontend/app.js.old | 98 --- 7 files changed, 933 insertions(+), 762 deletions(-) create mode 100755 app/start.sh create mode 100644 server/backend/.env delete mode 100644 server/frontend/app.js.old diff --git a/.gitignore b/.gitignore index 7677172..ab81324 100644 --- a/.gitignore +++ b/.gitignore @@ -18,9 +18,9 @@ ios/ www/ # Environment files -.env -.env.* -!.env.example +#.env +#.env.* +#!.env.example # System files .DS_Store diff --git a/app/app.js b/app/app.js index 948d80e..4bd111f 100644 --- a/app/app.js +++ b/app/app.js @@ -1,15 +1,49 @@ -//const URI = "https://my.patachina2.casacam.net"; -//const USER = "fabio.micheluz@gmail.com"; -//const PASSW = "master66"; +// ============================================================================ +// LAUNCHER — VERSIONE COMPLETA E OTTIMIZZATA (A) — BLOCCO 1/6 +// Sezione: Variabili globali + Storage + Config + Setup Page +// ============================================================================ - // ========================================================================== - // Salvataggio dati - // ========================================================================== +// --------------------------------------------------------------------------- +// VARIABILI GLOBALI +// --------------------------------------------------------------------------- +let URI; +let USER; +let PASSW; -const SECRET_KEY = "chiave-super-segreta-123"; // puoi cambiarla +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), @@ -19,10 +53,40 @@ function saveConfig(url, user, password) { 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)); @@ -31,7 +95,36 @@ function loadConfig() { } } +// --------------------------------------------------------------------------- +// 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"); } @@ -39,6 +132,7 @@ function hideSetupPage() { document.getElementById("setup-page").classList.add("hidden"); } +// 6 tap per aprire la setup page let tapCount = 0; let tapTimer = null; @@ -56,734 +150,879 @@ document.addEventListener("click", () => { showSetupPage(); } }); +// ============================================================================ +// LAUNCHER — VERSIONE COMPLETA E OTTIMIZZATA (A) — BLOCCO 2/6 +// Sezione: API login, getLinks, ordine apps, render, startLauncher +// ============================================================================ - - -document.addEventListener("DOMContentLoaded", () => { - - // ========================================================================== - // Salva config - // ========================================================================== - document.getElementById("cfg-save").addEventListener("click", () => { - const url = document.getElementById("cfg-url").value; - const user = document.getElementById("cfg-user").value; - const pass = document.getElementById("cfg-pass").value; - - saveConfig(url, user, pass); - hideSetupPage(); - startLauncher(); - }); - - - - - // Blocca il menu contestuale nativo - document.addEventListener("contextmenu", e => e.preventDefault()); - - // ========================================================================== - // RIFERIMENTI DOM - // ========================================================================== - const folderEl = document.getElementById("folder"); - const contextMenuEl = document.getElementById("context-menu"); - - // ========================================================================== - // STATO GLOBALE - // ========================================================================== - let appsData = []; - let appsOrder = []; - let editMode = false; - - // 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; - const MOVE_TOLERANCE = 18; - - let draggingIcon = null; - let draggingId = null; - let dragOffsetX = 0; - let dragOffsetY = 0; - let dragStartX = 0; - let dragStartY = 0; - - // ========================================================================== - // CARICAMENTO APPS - // ========================================================================== - function loadOrder() { - try { - const val = localStorage.getItem("appsOrder"); - if (!val) return null; - const parsed = JSON.parse(val); - return Array.isArray(parsed) ? parsed : null; - } catch { - return null; - } - } - - function saveOrder() { - localStorage.setItem("appsOrder", JSON.stringify(appsOrder)); - } - - function renderApps() { - 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); +// --------------------------------------------------------------------------- +// 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 }) }); - } - async function loadApps() { - const apps = await fetch("apps.json").then(r => r.json()); - console.log(apps); - appsData = apps.map((app, i) => ({ - id: app.id || `app-${i}`, - name: app.name, - url: app.url, - icon: app.icon + 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}` })); - const stored = loadOrder(); - if (stored) { - appsOrder = stored.filter(id => appsData.some(a => a.id === id)); - appsData.forEach(a => { - if (!appsOrder.includes(a.id)) appsOrder.push(a.id); - }); - } else { - appsOrder = appsData.map(a => a.id); - } + // 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); } - // ========================================================================== - // UTILITY POINTER (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 - }; + // 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); } - return { - pageX: e.pageX, - pageY: e.pageY, - clientX: e.clientX, - clientY: e.clientY - }; - } - // ========================================================================== - // ZOOM STILE IPHONE (PINCH ELASTICO) + WHEEL SU PC - // ========================================================================== - function computeDynamicMaxZoom() { - return Math.min(window.innerWidth / 85, 4.0); - } + // 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(); + }); - function loadInitialZoom() { - const v = parseFloat(localStorage.getItem("zoomLevel")); - if (!isFinite(v) || v <= 0) return 1; - return Math.min(Math.max(v, 0.5), computeDynamicMaxZoom()); - } - - function applyZoom(z) { - zoomLevel = (!isFinite(z) || z <= 0) ? 1 : z; - document.documentElement.style.setProperty("--zoom", zoomLevel); - localStorage.setItem("zoomLevel", String(zoomLevel)); - } - - function getPinchDistance(touches) { - const [a, b] = touches; - return Math.hypot(a.clientX - b.clientX, a.clientY - b.clientY); - } - - function elasticEase(x) { - return Math.sin(x * Math.PI * 0.5) * 1.05; - } - - function initZoomHandlers() { - zoomMax = computeDynamicMaxZoom(); - zoomLevel = loadInitialZoom(); - applyZoom(zoomLevel); - - // Pinch su mobile - document.addEventListener("touchmove", e => { - if (e.touches.length === 2) e.preventDefault(); - }, { passive: false }); - - document.addEventListener("touchstart", e => { - if (e.touches.length === 2) { - initialPinchDistance = getPinchDistance(e.touches); - if (zoomAnimFrame) cancelAnimationFrame(zoomAnimFrame); - } - - const now = Date.now(); - if (e.touches.length === 1 && now - lastTapTime < 300) { - zoomMax = computeDynamicMaxZoom(); - applyZoom(Math.min(zoomLevel * 1.15, zoomMax)); - } - lastTapTime = now; - }); - - 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(); - - 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 }); - - 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 PC - document.addEventListener("wheel", e => { - // Se vuoi zoomare solo con CTRL, scommenta: - // if (!e.ctrlKey) return; - - e.preventDefault(); + // 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(); - const direction = e.deltaY < 0 ? 1 : -1; - const factor = 1 + direction * 0.1; - - let newZoom = zoomLevel * factor; - + // 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); - }, { passive: false }); - } + initialPinchDistance = newDist; + e.preventDefault(); + } + }, { passive: false }); - // ========================================================================== - // EDIT MODE + MENU CONTESTUALE + WIGGLE - // ========================================================================== - function enterEditMode() { - editMode = true; - document.body.classList.add("edit-mode"); - } + // Fine pinch → animazione elastica verso limite + document.addEventListener("touchend", e => { + if (e.touches.length < 2 && initialPinchDistance) { + initialPinchDistance = null; - function exitEditMode() { - editMode = false; - document.body.classList.remove("edit-mode"); - hideContextMenu(); - } + zoomMax = computeDynamicMaxZoom(); + const target = Math.min(Math.max(zoomLevel, 0.5), zoomMax); + const start = zoomLevel; + const duration = 250; + const startTime = performance.now(); - function showContextMenuFor(id, x, y) { - contextMenuTargetId = id; - contextMenuEl.style.left = `${x}px`; - contextMenuEl.style.top = `${y}px`; - contextMenuEl.classList.remove("hidden"); - } + 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); + } - function hideContextMenu() { - contextMenuEl.classList.add("hidden"); - contextMenuTargetId = null; - } + zoomAnimFrame = requestAnimationFrame(animate); + } + }); - // ========================================================================== - // LONG PRESS MOBILE + PC - // ========================================================================== - function initLongPressHandlers() { - // --- TOUCH --- - document.addEventListener("touchstart", e => { - if (e.touches.length !== 1) return; + // --------------------------------------------------------- + // ZOOM CON WHEEL SU DESKTOP + // --------------------------------------------------------- + document.addEventListener("wheel", e => { + e.preventDefault(); - const touch = e.touches[0]; - const icon = touch.target.closest(".app-icon"); + zoomMax = computeDynamicMaxZoom(); + const direction = e.deltaY < 0 ? 1 : -1; + const factor = 1 + direction * 0.1; - if (icon) { - longPressTarget = icon; + let newZoom = zoomLevel * factor; - longPressTimer = setTimeout(() => { - if (!editMode) { - enterEditMode(); - if (navigator.vibrate) navigator.vibrate(10); - return; - } + // Elasticità + if (newZoom > zoomMax) newZoom = zoomMax + (newZoom - zoomMax) * 0.25; + if (newZoom < 0.5) newZoom = 0.5 - (0.5 - newZoom) * 0.25; - const r = icon.getBoundingClientRect(); - showContextMenuFor( - icon.dataset.id, - r.left + r.width / 2, - r.top + r.height - ); + 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); - }, 350); + 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; } - // Long press fuori icone → esce da edit mode - longPressTimer = setTimeout(() => { - if (editMode) exitEditMode(); - }, 350); + if (icon) { + const r = icon.getBoundingClientRect(); + showContextMenuFor( + icon.dataset.id, + r.left + r.width / 2, + r.top + r.height + ); + } - }, { passive: true }); + }, 350); + }); - document.addEventListener("touchmove", e => { - if (!longPressTimer) return; + // Cancella long‑press se il mouse si muove troppo + document.addEventListener("mousemove", e => { + if (!longPressTimer) return; - const touch = e.touches[0]; - const dx = touch.clientX - (longPressTarget?.getBoundingClientRect().left ?? touch.clientX); - const dy = touch.clientY - (longPressTarget?.getBoundingClientRect().top ?? touch.clientY); + 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; } - }, { passive: true }); - - document.addEventListener("touchend", () => { - if (longPressTimer) { - clearTimeout(longPressTimer); - longPressTimer = null; - longPressTarget = null; - } - }, { passive: true }); - - // --- MOUSE --- - 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); - }); - - 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; - } - } - }); - - document.addEventListener("mouseup", () => { - if (longPressTimer) { - clearTimeout(longPressTimer); - longPressTimer = null; - longPressTarget = null; - } - }); - } - - // ========================================================================== - // DRAG FLUIDO STILE IPHONE CON PLACEHOLDER + FIX "SOTTO IL DITO" - // ========================================================================== - 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)"; - - const placeholder = icon.cloneNode(true); - placeholder.classList.add("placeholder"); - placeholder.style.visibility = "hidden"; - icon.parentNode.insertBefore(placeholder, icon); - - hideContextMenu(); - } - - 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(); - } - - // ========================================================================== - // DROP PRECISO NELLA CELLA CORRETTA - // ========================================================================== - function endDrag() { - if (!draggingIcon) return; - - const icon = draggingIcon; - draggingIcon = null; - - const placeholder = folderEl.querySelector(".app-icon.placeholder"); - if (placeholder) placeholder.remove(); - - 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(); - } } + }); - 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(); - } - - function initDragHandlers() { - // --- TOUCH --- - 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); - - 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 --- - 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(); - } - }); - } - - // ========================================================================== - // MENU CONTESTUALE: AZIONI - // ========================================================================== - function initContextMenuActions() { - contextMenuEl.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; - - if (action === "rename") { - const nuovoNome = prompt("Nuovo nome app:", app.name); - if (nuovoNome && nuovoNome.trim()) { - app.name = nuovoNome.trim(); - renderApps(); - saveOrder(); - } - } - - if (action === "change-icon") { - const nuovaIcona = prompt("URL nuova icona:", app.icon); - if (nuovaIcona && nuovaIcona.trim()) { - app.icon = nuovaIcona.trim(); - renderApps(); - saveOrder(); - } - } - - if (action === "remove") { - if (confirm("Rimuovere questa app dalla griglia?")) { - appsOrder = appsOrder.filter(id => id !== app.id); - saveOrder(); - renderApps(); - } - } - - hideContextMenu(); - }); - } + // 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 && !contextMenuEl.classList.contains("hidden")) { + if (!isMenu && !isIcon && !document.getElementById("context-menu").classList.contains("hidden")) { hideContextMenu(); } - // 2️⃣ Clic fuori dalle icone → esci da wiggle mode + // 2️⃣ Clic fuori dalle icone → esci da edit mode if (!isIcon && editMode) { exitEditMode(); } }); } - // ========================================================================== - // LOAD APPS - // ========================================================================== +// ============================================================================ +// LAUNCHER — VERSIONE COMPLETA E OTTIMIZZATA (A) — BLOCCO 5/6 +// Sezione: Drag & Drop stile iPhone +// ============================================================================ -async function login(email, password) { - const res = await fetch(`${URI}/auth/login`, { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ email, password }) - }); - - const data = await res.json(); - return data.token; +// --------------------------------------------------------------------------- +// 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 + }; } -async function getLinks() { - const token = await login(USER, PASSW); +// --------------------------------------------------------------------------- +// Inizio drag: crea icona flottante + placeholder invisibile +// --------------------------------------------------------------------------- +function startDrag(icon, pos) { + draggingId = icon.dataset.id; - const res = await fetch(`${URI}/links`, { - headers: { - "Authorization": `Bearer ${token}`, - "Accept": "application/json" + 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(); } - }); + } - const json = await res.json(); - //console.log(json); - appsData = json.map((json, i) => ({ - id: json.id || `app-${i}`, - name: json.name, - url: json.url, - icon: `${URI}${json.icon}` - })); - console.log(appsData); + // 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 = ""; - const stored = loadOrder(); - if (stored) { - appsOrder = stored.filter(id => appsData.some(a => a.id === id)); - appsData.forEach(a => { - if (!appsOrder.includes(a.id)) appsOrder.push(a.id); - }); + 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 { - appsOrder = appsData.map(a => a.id); + 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(); + } +StartY = 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(); + } + }); +} + +// ============================================================================ +// 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(); + } } - renderApps(); + // --------------------------------------------------------- + // 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 () => { -const config = loadConfig(); -let URI; -let USER; -let PASSW; + // 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 (!config) { - showSetupPage(); -} else { - hideSetupPage(); - startLauncher(); // la tua funzione -} - - - // ========================================================================== - // INIT GLOBALE - // ========================================================================== - - - async function startLauncher() { - //(async function init() { - //await loadApps(); - const conf = loadConfig(); - URI = conf.url; - USER = conf.user; - PASSW = conf.password - await getLinks(); - initZoomHandlers(); - initLongPressHandlers(); - initDragHandlers(); - initContextMenuActions(); - initGlobalCloseHandlers(); - // })(); + 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(); } }); diff --git a/app/index.html b/app/index.html index dbd7eb8..ba09ee9 100644 --- a/app/index.html +++ b/app/index.html @@ -14,6 +14,8 @@ + + + diff --git a/app/start.sh b/app/start.sh new file mode 100755 index 0000000..6c32c02 --- /dev/null +++ b/app/start.sh @@ -0,0 +1,4 @@ +#!/bin/bash + +# Avvia un server HTTP sulla porta 11002 senza cache +npx http-server . -c-1 -p 11002 diff --git a/app/style.css b/app/style.css index 4738a99..1b7b449 100644 --- a/app/style.css +++ b/app/style.css @@ -254,3 +254,6 @@ html, body { border: none; } +#cfg-refresh { + display: none; +} diff --git a/server/backend/.env b/server/backend/.env new file mode 100644 index 0000000..2e946e6 --- /dev/null +++ b/server/backend/.env @@ -0,0 +1,16 @@ +# === SERVER CONFIG === +PORT=11001 + +# === JWT CONFIG === +# Cambialo SEMPRE in produzione +JWT_SECRET=master66 + +# === MONGO CONFIG === +# In locale: +# MONGO_URI=mongodb://localhost:27017/mydb +# +# In Docker (usato dal docker-compose): +MONGO_URI=mongodb://root:example@192.168.1.3:27017/myapphttps?authSource=admin +# === UPLOADS === +# Cartella dove Express serve le icone +UPLOAD_DIR=uploads diff --git a/server/frontend/app.js.old b/server/frontend/app.js.old deleted file mode 100644 index be97267..0000000 --- a/server/frontend/app.js.old +++ /dev/null @@ -1,98 +0,0 @@ -import { - login, - register, - getLinks, - createLink, - deleteLink, - updateLink -} from "./api.js"; - -const authSection = document.getElementById("authSection"); -const linkSection = document.getElementById("linkSection"); -const authStatus = document.getElementById("authStatus"); -const list = document.getElementById("list"); - -let token = null; - -// ------------------------------ -// AUTH -// ------------------------------ - -function setToken(t) { - token = t; - if (token) { - authSection.style.display = "none"; - linkSection.style.display = "block"; - loadLinks(); - } else { - authSection.style.display = "block"; - linkSection.style.display = "none"; - } -} - -document.getElementById("loginForm").addEventListener("submit", async e => { - e.preventDefault(); - const email = e.target.email.value; - const password = e.target.password.value; - - try { - const t = await login(email, password); - setToken(t); - } catch (err) { - authStatus.textContent = err.message; - } -}); - -document.getElementById("registerForm").addEventListener("submit", async e => { - e.preventDefault(); - const email = e.target.email.value; - const password = e.target.password.value; - - try { - await register(email, password); - authStatus.textContent = "Registrato! Ora effettua il login."; - } catch (err) { - authStatus.textContent = err.message; - } -}); - -// ------------------------------ -// LINKS -// ------------------------------ - -async function loadLinks() { - const links = await getLinks(token); - - list.innerHTML = links - .map( - link => ` -
- ${link.icon ? `` : ""} -
- ${link.name}
- ${link.url} -
-
- ` - ) - .join(""); -} - -document.getElementById("linkForm").addEventListener("submit", async e => { - e.preventDefault(); - - const formData = new FormData(e.target); - const iconFile = formData.get("icon"); - - await createLink(token, { - name: formData.get("name"), - url: formData.get("url"), - iconFile: iconFile.size > 0 ? iconFile : null - }); - - e.target.reset(); - loadLinks(); -}); - -// Init -setToken(null);