8th risolto bug menu
This commit is contained in:
parent
652d076065
commit
44412bc035
2 changed files with 1009 additions and 21 deletions
47
app/app.js
47
app/app.js
|
|
@ -34,6 +34,9 @@ let dragStartX = 0;
|
||||||
let dragStartY = 0;
|
let dragStartY = 0;
|
||||||
let placeholderEl = null;
|
let placeholderEl = null;
|
||||||
|
|
||||||
|
// PATCH MINIMA: flag per evitare che al primo long‑press parta anche il menu
|
||||||
|
let justEnteredEditMode = false;
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// CRITTOGRAFIA E STORAGE
|
// CRITTOGRAFIA E STORAGE
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
@ -492,11 +495,18 @@ function initLongPressHandlers() {
|
||||||
// Primo long‑press → entra in edit mode
|
// Primo long‑press → entra in edit mode
|
||||||
if (!editMode) {
|
if (!editMode) {
|
||||||
enterEditMode();
|
enterEditMode();
|
||||||
|
justEnteredEditMode = true; // PATCH: blocca il menu su questo primo long-press
|
||||||
if (navigator.vibrate) navigator.vibrate(10);
|
if (navigator.vibrate) navigator.vibrate(10);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Se già in edit mode → apri menu contestuale
|
// 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();
|
const r = icon.getBoundingClientRect();
|
||||||
showContextMenuFor(
|
showContextMenuFor(
|
||||||
icon.dataset.id,
|
icon.dataset.id,
|
||||||
|
|
@ -541,6 +551,8 @@ function initLongPressHandlers() {
|
||||||
longPressTimer = null;
|
longPressTimer = null;
|
||||||
longPressTarget = null;
|
longPressTarget = null;
|
||||||
}
|
}
|
||||||
|
// PATCH: reset del flag
|
||||||
|
justEnteredEditMode = false;
|
||||||
}, { passive: true });
|
}, { passive: true });
|
||||||
|
|
||||||
// ---------------------------------------------------------
|
// ---------------------------------------------------------
|
||||||
|
|
@ -623,6 +635,11 @@ function initGlobalCloseHandlers() {
|
||||||
// BLOCCO 5/6 — Drag & Drop stile iPhone (FIXED)
|
// 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)
|
// Utility per ottenere posizione del puntatore (touch + mouse)
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
@ -644,7 +661,7 @@ function getPointerPosition(e) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
/* Inizio drag: icona flottante + placeholder nel layout */
|
// Inizio drag: icona flottante + placeholder nel layout
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
function startDrag(icon, pos) {
|
function startDrag(icon, pos) {
|
||||||
const folderEl = document.getElementById("folder");
|
const folderEl = document.getElementById("folder");
|
||||||
|
|
@ -666,12 +683,11 @@ function startDrag(icon, pos) {
|
||||||
draggingIcon.style.pointerEvents = "none";
|
draggingIcon.style.pointerEvents = "none";
|
||||||
draggingIcon.style.transform = "translate3d(0,0,0)";
|
draggingIcon.style.transform = "translate3d(0,0,0)";
|
||||||
|
|
||||||
// Placeholder nel layout (slot vuoto)
|
// Placeholder nel layout
|
||||||
placeholderEl = document.createElement("div");
|
placeholderEl = document.createElement("div");
|
||||||
placeholderEl.className = "app-icon placeholder";
|
placeholderEl.className = "app-icon placeholder";
|
||||||
placeholderEl.style.visibility = "hidden";
|
placeholderEl.style.visibility = "hidden";
|
||||||
|
|
||||||
// Inserisci il placeholder dove stava l’icona
|
|
||||||
folderEl.insertBefore(placeholderEl, icon);
|
folderEl.insertBefore(placeholderEl, icon);
|
||||||
|
|
||||||
hideContextMenu();
|
hideContextMenu();
|
||||||
|
|
@ -721,11 +737,9 @@ function endDrag() {
|
||||||
|
|
||||||
const folderEl = document.getElementById("folder");
|
const folderEl = document.getElementById("folder");
|
||||||
|
|
||||||
// Tutti i figli, inclusa la placeholder
|
|
||||||
const children = Array.from(folderEl.children);
|
const children = Array.from(folderEl.children);
|
||||||
const finalIndex = children.indexOf(placeholderEl);
|
const finalIndex = children.indexOf(placeholderEl);
|
||||||
|
|
||||||
// Ripristina icona visuale
|
|
||||||
draggingIcon.classList.remove("dragging");
|
draggingIcon.classList.remove("dragging");
|
||||||
draggingIcon.style.position = "";
|
draggingIcon.style.position = "";
|
||||||
draggingIcon.style.left = "";
|
draggingIcon.style.left = "";
|
||||||
|
|
@ -754,7 +768,6 @@ function endDrag() {
|
||||||
dragStartX = 0;
|
dragStartX = 0;
|
||||||
dragStartY = 0;
|
dragStartY = 0;
|
||||||
|
|
||||||
// Ridisegna in base al nuovo ordine
|
|
||||||
renderApps();
|
renderApps();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -870,10 +883,8 @@ function initDragHandlers() {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// LAUNCHER — VERSIONE COMPLETA E
|
// BLOCCO 6/6 — Context Menu Actions + Config Save + Init Globale
|
||||||
// OTTIMIZZATA (A) BLOCCO 6/6 — Context Menu
|
|
||||||
// Actions + Config Save + Init Globale
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
@ -890,9 +901,7 @@ function initContextMenuActions() {
|
||||||
const app = appsData.find(a => a.id === contextMenuTargetId);
|
const app = appsData.find(a => a.id === contextMenuTargetId);
|
||||||
if (!app) return;
|
if (!app) return;
|
||||||
|
|
||||||
// ---------------------------------------------------------
|
// RINOMINA
|
||||||
// RINOMINA APP
|
|
||||||
// ---------------------------------------------------------
|
|
||||||
if (action === "rename") {
|
if (action === "rename") {
|
||||||
const nuovoNome = prompt("Nuovo nome app:", app.name);
|
const nuovoNome = prompt("Nuovo nome app:", app.name);
|
||||||
if (nuovoNome && nuovoNome.trim()) {
|
if (nuovoNome && nuovoNome.trim()) {
|
||||||
|
|
@ -902,9 +911,7 @@ function initContextMenuActions() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------
|
// CAMBIA ICONA
|
||||||
// CAMBIA ICONA
|
|
||||||
// ---------------------------------------------------------
|
|
||||||
if (action === "change-icon") {
|
if (action === "change-icon") {
|
||||||
const nuovaIcona = prompt("URL nuova icona:", app.icon);
|
const nuovaIcona = prompt("URL nuova icona:", app.icon);
|
||||||
if (nuovaIcona && nuovaIcona.trim()) {
|
if (nuovaIcona && nuovaIcona.trim()) {
|
||||||
|
|
@ -914,9 +921,7 @@ function initContextMenuActions() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------
|
// RIMUOVI
|
||||||
// RIMUOVI APP DALLA GRIGLIA
|
|
||||||
// ---------------------------------------------------------
|
|
||||||
if (action === "remove") {
|
if (action === "remove") {
|
||||||
if (confirm("Rimuovere questa app dalla griglia?")) {
|
if (confirm("Rimuovere questa app dalla griglia?")) {
|
||||||
appsOrder = appsOrder.filter(id => id !== app.id);
|
appsOrder = appsOrder.filter(id => id !== app.id);
|
||||||
|
|
@ -944,7 +949,7 @@ document.getElementById("cfg-refresh").addEventListener("click", async () => {
|
||||||
|
|
||||||
if (ok) {
|
if (ok) {
|
||||||
hideSetupPage();
|
hideSetupPage();
|
||||||
startLauncher(); // Torna subito alla schermata principale
|
startLauncher();
|
||||||
} else {
|
} else {
|
||||||
alert("Impossibile aggiornare le app dal server.");
|
alert("Impossibile aggiornare le app dal server.");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
983
app/app.js.old
Normal file
983
app/app.js.old
Normal file
|
|
@ -0,0 +1,983 @@
|
||||||
|
// ============================================================================
|
||||||
|
// 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;
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
let placeholderEl = null;
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// 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}`
|
||||||
|
}));
|
||||||
|
|
||||||
|
// 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 — Long‑press, Edit Mode, Context Menu, Global Close
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// EDIT MODE (wiggle stile iOS)
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
function enterEditMode() {
|
||||||
|
editMode = true;
|
||||||
|
document.body.classList.add("edit-mode");
|
||||||
|
}
|
||||||
|
|
||||||
|
function exitEditMode() {
|
||||||
|
editMode = false;
|
||||||
|
document.body.classList.remove("edit-mode");
|
||||||
|
hideContextMenu();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// MENU CONTESTUALE
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
function showContextMenuFor(id, x, y) {
|
||||||
|
contextMenuTargetId = id;
|
||||||
|
const menu = document.getElementById("context-menu");
|
||||||
|
menu.style.left = `${x}px`;
|
||||||
|
menu.style.top = `${y}px`;
|
||||||
|
menu.classList.remove("hidden");
|
||||||
|
}
|
||||||
|
|
||||||
|
function hideContextMenu() {
|
||||||
|
const menu = document.getElementById("context-menu");
|
||||||
|
menu.classList.add("hidden");
|
||||||
|
contextMenuTargetId = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// LONG PRESS HANDLERS (TOUCH + MOUSE)
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
function initLongPressHandlers() {
|
||||||
|
|
||||||
|
// ---------------------------------------------------------
|
||||||
|
// TOUCH LONG PRESS
|
||||||
|
// ---------------------------------------------------------
|
||||||
|
document.addEventListener("touchstart", e => {
|
||||||
|
if (e.touches.length !== 1) return;
|
||||||
|
|
||||||
|
const touch = e.touches[0];
|
||||||
|
const icon = touch.target.closest(".app-icon");
|
||||||
|
|
||||||
|
if (icon) {
|
||||||
|
longPressTarget = icon;
|
||||||
|
|
||||||
|
longPressTimer = setTimeout(() => {
|
||||||
|
|
||||||
|
// Primo long‑press → entra in edit mode
|
||||||
|
if (!editMode) {
|
||||||
|
enterEditMode();
|
||||||
|
if (navigator.vibrate) navigator.vibrate(10);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Se già in edit mode → apri menu contestuale
|
||||||
|
const r = icon.getBoundingClientRect();
|
||||||
|
showContextMenuFor(
|
||||||
|
icon.dataset.id,
|
||||||
|
r.left + r.width / 2,
|
||||||
|
r.top + r.height
|
||||||
|
);
|
||||||
|
if (navigator.vibrate) navigator.vibrate(10);
|
||||||
|
|
||||||
|
}, 350);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Long press fuori dalle icone → esci da edit mode
|
||||||
|
longPressTimer = setTimeout(() => {
|
||||||
|
if (editMode) exitEditMode();
|
||||||
|
}, 350);
|
||||||
|
|
||||||
|
}, { passive: true });
|
||||||
|
|
||||||
|
// Cancella long‑press se l’utente si muove troppo
|
||||||
|
document.addEventListener("touchmove", e => {
|
||||||
|
if (!longPressTimer) return;
|
||||||
|
|
||||||
|
const touch = e.touches[0];
|
||||||
|
const r = longPressTarget?.getBoundingClientRect();
|
||||||
|
|
||||||
|
const dx = touch.clientX - (r?.left ?? touch.clientX);
|
||||||
|
const dy = touch.clientY - (r?.top ?? touch.clientY);
|
||||||
|
|
||||||
|
if (Math.hypot(dx, dy) > 15) {
|
||||||
|
clearTimeout(longPressTimer);
|
||||||
|
longPressTimer = null;
|
||||||
|
longPressTarget = null;
|
||||||
|
}
|
||||||
|
}, { passive: true });
|
||||||
|
|
||||||
|
// Fine touch → cancella long‑press
|
||||||
|
document.addEventListener("touchend", () => {
|
||||||
|
if (longPressTimer) {
|
||||||
|
clearTimeout(longPressTimer);
|
||||||
|
longPressTimer = null;
|
||||||
|
longPressTarget = null;
|
||||||
|
}
|
||||||
|
}, { passive: true });
|
||||||
|
|
||||||
|
// ---------------------------------------------------------
|
||||||
|
// MOUSE LONG PRESS
|
||||||
|
// ---------------------------------------------------------
|
||||||
|
document.addEventListener("mousedown", e => {
|
||||||
|
if (e.button !== 0) return;
|
||||||
|
|
||||||
|
const icon = e.target.closest(".app-icon");
|
||||||
|
longPressTarget = icon ?? null;
|
||||||
|
|
||||||
|
longPressTimer = setTimeout(() => {
|
||||||
|
|
||||||
|
if (!editMode) {
|
||||||
|
enterEditMode();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (icon) {
|
||||||
|
const r = icon.getBoundingClientRect();
|
||||||
|
showContextMenuFor(
|
||||||
|
icon.dataset.id,
|
||||||
|
r.left + r.width / 2,
|
||||||
|
r.top + r.height
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}, 350);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Cancella long‑press se il mouse si muove troppo
|
||||||
|
document.addEventListener("mousemove", e => {
|
||||||
|
if (!longPressTimer) return;
|
||||||
|
|
||||||
|
if (longPressTarget) {
|
||||||
|
const r = longPressTarget.getBoundingClientRect();
|
||||||
|
const dx = e.clientX - (r.left + r.width / 2);
|
||||||
|
const dy = e.clientY - (r.top + r.height / 2);
|
||||||
|
|
||||||
|
if (Math.hypot(dx, dy) > 15) {
|
||||||
|
clearTimeout(longPressTimer);
|
||||||
|
longPressTimer = null;
|
||||||
|
longPressTarget = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Mouse up → cancella long‑press
|
||||||
|
document.addEventListener("mouseup", () => {
|
||||||
|
if (longPressTimer) {
|
||||||
|
clearTimeout(longPressTimer);
|
||||||
|
longPressTimer = null;
|
||||||
|
longPressTarget = null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// CHIUSURA MENU E USCITA DA EDIT MODE
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
function initGlobalCloseHandlers() {
|
||||||
|
document.addEventListener("pointerdown", e => {
|
||||||
|
const isIcon = e.target.closest(".app-icon");
|
||||||
|
const isMenu = e.target.closest("#context-menu");
|
||||||
|
|
||||||
|
// 1️⃣ Clic fuori dal menu → chiudi menu
|
||||||
|
if (!isMenu && !isIcon && !document.getElementById("context-menu").classList.contains("hidden")) {
|
||||||
|
hideContextMenu();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2️⃣ Clic fuori dalle icone → esci da edit mode
|
||||||
|
if (!isIcon && editMode) {
|
||||||
|
exitEditMode();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// LAUNCHER — VERSIONE COMPLETA E OTTIMIZZATA (A)
|
||||||
|
// BLOCCO 5/6 — 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 (slot vuoto)
|
||||||
|
placeholderEl = document.createElement("div");
|
||||||
|
placeholderEl.className = "app-icon placeholder";
|
||||||
|
placeholderEl.style.visibility = "hidden";
|
||||||
|
|
||||||
|
// Inserisci il placeholder dove stava l’icona
|
||||||
|
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");
|
||||||
|
|
||||||
|
// Tutti i figli, inclusa la placeholder
|
||||||
|
const children = Array.from(folderEl.children);
|
||||||
|
const finalIndex = children.indexOf(placeholderEl);
|
||||||
|
|
||||||
|
// Ripristina icona visuale
|
||||||
|
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;
|
||||||
|
|
||||||
|
// Ridisegna in base al nuovo ordine
|
||||||
|
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();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// LAUNCHER — VERSIONE COMPLETA E
|
||||||
|
// OTTIMIZZATA (A) 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 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 () => {
|
||||||
|
|
||||||
|
const cfg = loadConfig();
|
||||||
|
if (!cfg) {
|
||||||
|
alert("Config mancante. Inserisci URL, user e password.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ok = await getLinks();
|
||||||
|
|
||||||
|
if (ok) {
|
||||||
|
hideSetupPage();
|
||||||
|
startLauncher(); // Torna 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;
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
});
|
||||||
Loading…
Reference in a new issue