my_app_remote_server_ui_app/app/app.js
2026-01-02 10:11:21 +01:00

1027 lines
30 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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