@sailingrotevista/rotevista-dash 2.0.2 → 2.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/app.js +75 -18
  2. package/package.json +1 -1
  3. package/style.css +7 -2
package/app.js CHANGED
@@ -283,39 +283,96 @@ function drawGraph(d, id, min, max, isTws, isHercules) {
283
283
  }
284
284
 
285
285
  // ==========================================================================
286
- // 8. EVENTI E INTERAZIONI (Touch, Focus e Fullscreen)
286
+ // 8. EVENTI E INTERAZIONI (Smart Touch Manager)
287
287
  // ==========================================================================
288
288
 
289
- // Blocco globale del menu contestuale per Android/iOS
290
- window.addEventListener('contextmenu', e => e.preventDefault());
289
+ // Uccide il menu contestuale di Android/iOS
290
+ window.addEventListener('contextmenu', e => e.preventDefault(), true);
291
291
 
292
292
  function toggleFocusMode(type, element) {
293
293
  const container = document.querySelector('.main-container');
294
294
  const parentPanel = element.closest('.side-panel');
295
295
  const isLeft = parentPanel.classList.contains('left-panel');
296
+
296
297
  isFocusActive = !isFocusActive;
297
- if (isFocusActive) { container.classList.add('focus-active'); container.classList.add(isLeft ? 'focus-side-left' : 'focus-side-right'); parentPanel.classList.add('has-focus'); element.classList.add('is-focused'); blockNextClick = true; }
298
- else { container.classList.remove('focus-active', 'focus-side-left', 'focus-side-right'); document.querySelectorAll('.side-panel').forEach(p => p.classList.remove('has-focus')); document.querySelectorAll('.data-box').forEach(b => b.classList.remove('is-focused')); }
298
+
299
+ if (isFocusActive) {
300
+ container.classList.add('focus-active');
301
+ container.classList.add(isLeft ? 'focus-side-left' : 'focus-side-right');
302
+ parentPanel.classList.add('has-focus');
303
+ element.classList.add('is-focused');
304
+ } else {
305
+ container.classList.remove('focus-active', 'focus-side-left', 'focus-side-right');
306
+ document.querySelectorAll('.side-panel').forEach(p => p.classList.remove('has-focus'));
307
+ document.querySelectorAll('.data-box').forEach(b => b.classList.remove('is-focused'));
308
+ }
299
309
  }
300
310
 
311
+ // Configurazione Interazioni per i 4 grafici principali
301
312
  ['stw', 'sog', 'tws', 'depth'].forEach(type => {
302
313
  const el = document.getElementById(type + '-graph').closest('.data-box');
303
314
 
304
- // Doppio Click -> Hercules Zoom
305
- el.addEventListener('dblclick', (e) => { if (isFocusActive) return; e.preventDefault(); graphModes[type] = graphModes[type] === 'standard' ? 'hercules' : 'standard'; localStorage.setItem('mode_' + type, graphModes[type]); el.style.backgroundColor = "rgba(255,255,255,0.15)"; setTimeout(() => el.style.backgroundColor = "", 200); });
306
-
307
- // Long Press -> Tactical Focus
308
- const startPress = () => { if (!isFocusActive) pressTimer = setTimeout(() => toggleFocusMode(type, el), 1000); };
309
- const cancelPress = () => { clearTimeout(pressTimer); };
310
- el.addEventListener('mousedown', startPress); el.addEventListener('touchstart', startPress, {passive: true});
311
- ['mouseup', 'mouseleave', 'touchend', 'touchcancel'].forEach(evt => el.addEventListener(evt, cancelPress));
312
-
313
- // Click -> Exit Focus o Ghost click filtering
314
- el.addEventListener('click', (e) => { if (blockNextClick) { blockNextClick = false; return; } if (isFocusActive && el.classList.contains('is-focused')) toggleFocusMode(type, el); });
315
+ let lastTapTime = 0;
316
+ let tapTimeout;
317
+
318
+ const handleInteraction = (e) => {
319
+ // Impedisce al browser di fare qualsiasi cosa (zoom, menu, click fantasma)
320
+ if (e.cancelable) e.preventDefault();
321
+
322
+ const currentTime = new Date().getTime();
323
+ const tapDelay = currentTime - lastTapTime;
324
+
325
+ // --- 1. RILEVAMENTO LONG PRESS ---
326
+ // Avviamo un timer per la pressione lunga
327
+ pressTimer = setTimeout(() => {
328
+ if (!isFocusActive) {
329
+ toggleFocusMode(type, el);
330
+ lastTapTime = 0; // Reset per non innescare click singoli al rilascio
331
+ }
332
+ }, 800); // 800ms per attivare il Focus
333
+
334
+ // --- 2. GESTIONE DOPPIO E SINGOLO TOCCO ---
335
+ // Se tocchiamo di nuovo entro 300ms è un DOUBLE TAP
336
+ if (tapDelay < 300 && tapDelay > 0) {
337
+ clearTimeout(tapTimeout); // Cancella l'azione del singolo tap
338
+ if (!isFocusActive) {
339
+ // Toggle Hercules Mode
340
+ graphModes[type] = graphModes[type] === 'standard' ? 'hercules' : 'standard';
341
+ localStorage.setItem('mode_' + type, graphModes[type]);
342
+ el.style.backgroundColor = "rgba(255,255,255,0.15)"; setTimeout(() => el.style.backgroundColor = "", 200);
343
+ }
344
+ lastTapTime = 0;
345
+ } else {
346
+ // Se è passato più tempo, potrebbe essere un SINGOLO TAP
347
+ lastTapTime = currentTime;
348
+ tapTimeout = setTimeout(() => {
349
+ // Se siamo in focus mode, il singolo tap esce
350
+ if (isFocusActive && el.classList.contains('is-focused')) {
351
+ toggleFocusMode(type, el);
352
+ }
353
+ }, 350); // Attesa per vedere se arriva il secondo tap
354
+ }
355
+ };
356
+
357
+ const stopInteraction = () => {
358
+ clearTimeout(pressTimer);
359
+ };
360
+
361
+ // Usiamo PointerEvents: funzionano identici per Mouse e Touch
362
+ el.addEventListener('pointerdown', handleInteraction);
363
+ el.addEventListener('pointerup', stopInteraction);
364
+ el.addEventListener('pointerleave', stopInteraction);
315
365
  });
316
366
 
317
- // Fullscreen via Hotspot
318
- if (ui.hotspot) { ui.hotspot.addEventListener('click', () => { const doc = document.documentElement, isF = document.fullscreenElement || document.webkitFullscreenElement; if (!isF) { if (doc.requestFullscreen) doc.requestFullscreen(); else if (doc.webkitRequestFullscreen) doc.webkitRequestFullscreen(); } else { if (document.exitFullscreen) document.exitFullscreen(); else if (document.webkitExitFullscreen) document.webkitExitFullscreen(); } }); }
367
+ // Fullscreen via Hotspot (Click Singolo)
368
+ if (ui.hotspot) {
369
+ ui.hotspot.addEventListener('click', (e) => {
370
+ e.preventDefault();
371
+ const doc = document.documentElement, isF = document.fullscreenElement || document.webkitFullscreenElement;
372
+ if (!isF) { if (doc.requestFullscreen) doc.requestFullscreen(); else if (doc.webkitRequestFullscreen) doc.webkitRequestFullscreen(); }
373
+ else { if (document.exitFullscreen) document.exitFullscreen(); else if (document.webkitExitFullscreen) document.webkitExitFullscreen(); }
374
+ });
375
+ }
319
376
 
320
377
  // ==========================================================================
321
378
  // 9. INIZIALIZZAZIONE
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sailingrotevista/rotevista-dash",
3
- "version": "2.0.2",
3
+ "version": "2.0.3",
4
4
  "description": "Public Wind Dashboard with navigation and course aids",
5
5
  "main": "index.js",
6
6
  "publishConfig": {
package/style.css CHANGED
@@ -20,9 +20,9 @@ body {
20
20
  width: 100%; height: 100%;
21
21
  padding: 5px; box-sizing: border-box;
22
22
  gap: 8px;
23
- grid-template-columns: 1.7fr 3fr 1.7fr;
23
+ grid-template-columns: 1.7fr 3fr 1.7fr; /* Rapporto scelto dall'utente */
24
24
  grid-template-rows: 100%;
25
- transition: all 0.4s ease;
25
+ transition: all 0.5s cubic-bezier(0.4, 0, 0.2, 1);
26
26
  }
27
27
 
28
28
  /* ==========================================================================
@@ -43,6 +43,11 @@ body {
43
43
  container-type: size;
44
44
  flex: 1;
45
45
  overflow: hidden;
46
+ /* Impedisce ogni azione nativa del browser sul tocco */
47
+ -webkit-touch-callout: none;
48
+ -webkit-user-select: none;
49
+ user-select: none;
50
+ touch-action: none;
46
51
  }
47
52
 
48
53
  /* Altezze Desktop */