myapps_macos/web/app.js
2026-01-07 15:52:21 +01:00

1006 lines
29 KiB
JavaScript
Raw Permalink 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 — 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;
let placeholderEl = null;
// PATCH MINIMA: flag per evitare che al primo longpress parta anche il menu
let justEnteredEditMode = false;
// ---------------------------------------------------------------------------
// 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 + "Aggiorna ora")
// ---------------------------------------------------------------------------
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;
// Mostra il pulsante "Aggiorna ora" solo se esiste già una config
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 — 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}`
}));
*/
appsData = json.map((a, i) => {
let icon = null;
if (a.icon && a.icon.data && a.icon.mime) {
const base64 = btoa(
String.fromCharCode(...a.icon.data.data)
);
icon = `data:${a.icon.mime};base64,${base64}`;
}
return {
id: a.id || `app-${i}`,
name: a.name,
url: a.url,
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 → 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();
// ❌ Nessun aggiornamento automatico dal server
// getLinks();
// 4⃣ Inizializza UI (zoom, drag, wiggle, menu…)
initZoomHandlers();
initLongPressHandlers();
initDragHandlers();
initContextMenuActions();
initGlobalCloseHandlers();
}
// ============================================================================
// LAUNCHER — VERSIONE COMPLETA E OTTIMIZZATA (A)
// BLOCCO 3/6 — Zoom stile iPhone (pinch, elasticità, wheel)
// ============================================================================
// ---------------------------------------------------------------------------
// Calcolo dinamico dello zoom massimo
// ---------------------------------------------------------------------------
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 (NO double tap zoom)
document.addEventListener("touchstart", e => {
// Inizio pinch
if (e.touches.length === 2) {
initialPinchDistance = getPinchDistance(e.touches);
if (zoomAnimFrame) cancelAnimationFrame(zoomAnimFrame);
}
// Nessuna azione sul doppio tap
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 — 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();
justEnteredEditMode = true; // PATCH: blocca il menu su questo primo long-press
if (navigator.vibrate) navigator.vibrate(10);
return;
}
// Se siamo appena entrati in edit mode con questo long-press → NON aprire menu
if (justEnteredEditMode) {
justEnteredEditMode = false;
return;
}
// Se già in edit mode da prima → 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;
}
// PATCH: reset del flag
justEnteredEditMode = false;
}, { 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 — Drag & Drop stile iPhone (FIXED)
// ============================================================================
// ============================================================================
// LAUNCHER — VERSIONE COMPLETA E OTTIMIZZATA (A)
// BLOCCO 5/6 — Drag & Drop stile iPhone (FIXED)
// ============================================================================
// ---------------------------------------------------------------------------
// 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: icona flottante + placeholder nel layout
// ---------------------------------------------------------------------------
function startDrag(icon, pos) {
const folderEl = document.getElementById("folder");
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 nel layout
placeholderEl = document.createElement("div");
placeholderEl.className = "app-icon placeholder";
placeholderEl.style.visibility = "hidden";
folderEl.insertBefore(placeholderEl, icon);
hideContextMenu();
}
// ---------------------------------------------------------------------------
// Aggiorna posizione icona trascinata + posizione placeholder
// ---------------------------------------------------------------------------
function updateDragPosition(pos) {
if (!draggingIcon || !placeholderEl) return;
const x = pos.pageX - dragOffsetX;
const y = pos.pageY - dragOffsetY;
draggingIcon.style.left = `${x}px`;
draggingIcon.style.top = `${y}px`;
const centerX = pos.clientX;
const centerY = pos.clientY;
const elem = document.elementFromPoint(centerX, centerY);
const targetIcon = elem && elem.closest(".app-icon:not(.dragging)");
if (!targetIcon || targetIcon === placeholderEl) return;
const folderEl = document.getElementById("folder");
const targetRect = targetIcon.getBoundingClientRect();
const isBefore = centerY < targetRect.top + targetRect.height / 2;
if (isBefore) {
folderEl.insertBefore(placeholderEl, targetIcon);
} else {
folderEl.insertBefore(placeholderEl, targetIcon.nextSibling);
}
}
// ---------------------------------------------------------------------------
// Fine drag: aggiorna appsOrder in base alla posizione del placeholder
// ---------------------------------------------------------------------------
function endDrag() {
if (!draggingIcon || !placeholderEl) {
draggingIcon = null;
placeholderEl = null;
dragStartX = 0;
dragStartY = 0;
return;
}
const folderEl = document.getElementById("folder");
const children = Array.from(folderEl.children);
const finalIndex = children.indexOf(placeholderEl);
draggingIcon.classList.remove("dragging");
draggingIcon.style.position = "";
draggingIcon.style.left = "";
draggingIcon.style.top = "";
draggingIcon.style.width = "";
draggingIcon.style.height = "";
draggingIcon.style.zIndex = "";
draggingIcon.style.pointerEvents = "";
draggingIcon.style.transform = "";
if (finalIndex !== -1) {
const currentIndex = appsOrder.indexOf(draggingId);
if (currentIndex !== -1 && currentIndex !== finalIndex) {
appsOrder.splice(currentIndex, 1);
appsOrder.splice(finalIndex, 0, draggingId);
saveOrder();
}
}
if (placeholderEl && placeholderEl.parentNode) {
placeholderEl.parentNode.removeChild(placeholderEl);
}
draggingIcon = null;
placeholderEl = null;
dragStartX = 0;
dragStartY = 0;
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);
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) {
dragStartX = 0;
dragStartY = 0;
return;
}
if (!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;
if (!draggingIcon) {
dragStartX = 0;
dragStartY = 0;
return;
}
endDrag();
});
}
// ============================================================================
// BLOCCO 6/6 — 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
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
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 cfg = loadConfig();
if (!cfg) {
alert("Config mancante. Inserisci URL, user e password.");
return;
}
const ok = await getLinks();
if (ok) {
hideSetupPage();
startLauncher();
} 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;
saveConfig(url, user, pass);
const ok = await getLinks();
if (ok) {
hideSetupPage();
startLauncher();
}
});
// ---------------------------------------------------------------------------
// INIT GLOBALE — DOMContentLoaded
// ---------------------------------------------------------------------------
document.addEventListener("DOMContentLoaded", () => {
const cfg = loadConfig();
if (!cfg) {
showSetupPage();
} else {
hideSetupPage();
startLauncher();
}
});