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}
- `;
-
- 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}
+ `;
+
+ 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 @@