@sailingrotevista/rotevista-dash 6.0.9 → 6.0.12

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 +182 -61
  2. package/package.json +1 -1
  3. package/style.css +1 -3
package/app.js CHANGED
@@ -115,6 +115,17 @@ function getShortestRotation(curr, target) {
115
115
  return curr + diff;
116
116
  }
117
117
 
118
+ /**
119
+ * Scrive il testo nel DOM/SVG solo se il valore è realmente cambiato.
120
+ * Utilizza innerHTML perché è l'unico metodo sicuro al 100% per forzare il ridisegno dei testi negli SVG.
121
+ */
122
+ function safeSetText(el, text) {
123
+ if (!el) return;
124
+ if (el.innerHTML !== text) {
125
+ el.innerHTML = text;
126
+ }
127
+ }
128
+
118
129
  /**
119
130
  * Inserimento sicuro nel buffer con trigonometria precaricata e pruning automatico
120
131
  */
@@ -600,21 +611,22 @@ function startDisplayLoop() {
600
611
  ui.status.className = (socket && socket.readyState === WebSocket.OPEN) ? "online" : "offline";
601
612
 
602
613
  // --- WATCHDOG: CONTROLLO TIMEOUT ---
603
- const watch = {
604
- "navigation.speedThroughWater": ui.stw, "navigation.speedOverGround": ui.sog,
605
- "navigation.headingTrue": ui.hdg, "navigation.courseOverGroundTrue": ui.cog,
606
- "environment.wind.speedApparent": ui.awsSvg, "environment.depth.belowTransducer": ui.depth,
607
- "environment.wind.speedTrue": ui.tws
608
- };
609
- for (let p in watch) {
610
- if (!store.timestamps[p] || (now - store.timestamps[p] > TIMEOUT_MS)) {
611
- watch[p].innerText = "---"; delete store.raw[p];
614
+ const watch = {
615
+ "navigation.speedThroughWater": ui.stw, "navigation.speedOverGround": ui.sog,
616
+ "navigation.headingTrue": ui.hdg, "navigation.courseOverGroundTrue": ui.cog,
617
+ "environment.wind.speedApparent": ui.awsSvg, "environment.depth.belowTransducer": ui.depth,
618
+ "environment.wind.speedTrue": ui.tws
619
+ };
620
+ for (let p in watch) {
621
+ if (!store.timestamps[p] || (now - store.timestamps[p] > TIMEOUT_MS)) {
622
+ safeSetText(watch[p], "---"); // Sostituito innerText con la funzione protetta!
623
+ delete store.raw[p];
624
+ }
612
625
  }
613
- }
614
626
 
615
627
  // --- AGGIORNAMENTO VELOCITÀ SULL'ACQUA (STW) ---
616
628
  if (store.raw["navigation.speedThroughWater"] !== undefined) {
617
- ui.stw.innerText = stwKts.toFixed(1);
629
+ safeSetText(ui.stw, stwKts.toFixed(1));
618
630
  ui.stw.style.color = ""; // Neutro
619
631
  manageHistory('stw', stwKts);
620
632
  }
@@ -627,11 +639,11 @@ function startDisplayLoop() {
627
639
 
628
640
  const labelSogVmg = document.getElementById('sog-vmg-label');
629
641
  if (displayModeSog === 'VMG') {
630
- ui.sog.innerText = vmgVal.toFixed(1);
642
+ safeSetText(ui.sog, vmgVal.toFixed(1));
631
643
  ui.sog.style.setProperty('color', '#00b8d4', 'important'); // Cyan
632
644
  if (labelSogVmg) labelSogVmg.textContent = 'VMG';
633
645
  } else {
634
- ui.sog.innerText = sogKts.toFixed(1);
646
+ safeSetText(ui.sog, sogKts.toFixed(1));
635
647
  if (labelSogVmg) labelSogVmg.textContent = 'SOG';
636
648
 
637
649
  if (isNavigating) {
@@ -650,23 +662,29 @@ function startDisplayLoop() {
650
662
 
651
663
  // --- AGGIORNAMENTO PROFONDITÀ (DEPTH) ---
652
664
  if (store.raw["environment.depth.belowTransducer"] !== undefined) {
653
- ui.depth.innerText = store.raw["environment.depth.belowTransducer"].toFixed(1);
665
+ safeSetText(ui.depth, store.raw["environment.depth.belowTransducer"].toFixed(1));
654
666
  checkDepthAlarm(store.raw["environment.depth.belowTransducer"]);
655
667
  manageHistory('depth', store.raw["environment.depth.belowTransducer"]);
656
668
  }
657
669
 
658
- // --- GESTIONE VENTO (TWS / AWS SWITCH) ---
659
- const twsVal = store.raw["environment.wind.speedTrue"] ? msToKts(store.raw["environment.wind.speedTrue"]) : 0;
660
- const awsVal = store.raw["environment.wind.speedApparent"] ? msToKts(store.raw["environment.wind.speedApparent"]) : 0;
670
+ // --- GESTIONE VENTO (TWS / AWS SWITCH & BUSSOLA) ---
671
+
672
+ // Estrazione dati sicura: controlliamo esplicitamente se il dato esiste, altrimenti 0
673
+ const rawTws = store.raw["environment.wind.speedTrue"];
674
+ const rawAws = store.raw["environment.wind.speedApparent"];
675
+
676
+ const twsVal = (rawTws !== undefined && rawTws !== null) ? msToKts(rawTws) : 0;
677
+ const awsVal = (rawAws !== undefined && rawAws !== null) ? msToKts(rawAws) : 0;
661
678
 
662
- if (store.raw["environment.wind.speedTrue"] !== undefined) manageHistory('tws', twsVal);
663
- if (store.raw["environment.wind.speedApparent"] !== undefined) manageHistory('aws', awsVal);
679
+ if (rawTws !== undefined && rawTws !== null) manageHistory('tws', twsVal);
680
+ if (rawAws !== undefined && rawAws !== null) manageHistory('aws', awsVal);
664
681
 
665
- if (store.raw["environment.wind.speedTrue"] !== undefined || store.raw["environment.wind.speedApparent"] !== undefined) {
682
+ // Disegno testo casella destra (TWS o AWS)
683
+ if (rawTws !== undefined || rawAws !== undefined) {
666
684
  const labelWind = document.getElementById('tws-aws-label');
667
685
  const currentWind = (displayModeTws === 'AWS') ? awsVal : twsVal;
668
686
 
669
- ui.tws.innerText = currentWind.toFixed(1);
687
+ safeSetText(ui.tws, currentWind.toFixed(1));
670
688
  if (labelWind) labelWind.textContent = displayModeTws;
671
689
 
672
690
  if (currentWind >= CONFIG.graphs.reef2) {
@@ -683,8 +701,21 @@ function startDisplayLoop() {
683
701
  }
684
702
  }
685
703
 
686
- if (store.raw["environment.wind.speedApparent"] !== undefined) {
687
- ui.awsSvg.textContent = awsVal.toFixed(1);
704
+ // --- SBLOCCO FORZATO TESTO BUSSOLA (AWS) ---
705
+ if (rawAws !== undefined && rawAws !== null && !isNaN(awsVal)) {
706
+ const strVal = awsVal.toFixed(1);
707
+ // Recupero robusto dell'elemento
708
+ let awsSvgEl = document.getElementById('aws-val-svg');
709
+
710
+ if (awsSvgEl) {
711
+ // Trucco anti-congelamento SVG: se il testo non combacia, lo forziamo usando
712
+ // textContent (standard SVG) invece di innerHTML (standard HTML).
713
+ if (awsSvgEl.textContent !== strVal) {
714
+ awsSvgEl.textContent = strVal;
715
+ }
716
+ } else {
717
+ console.error("ERRORE: Elemento SVG bussola ('aws-val-svg') non trovato nel documento!");
718
+ }
688
719
  }
689
720
 
690
721
  // --- PUNTATORI ANALOGICI ---
@@ -702,9 +733,8 @@ function startDisplayLoop() {
702
733
  updateLeewayDisplay(Math.max(-20, Math.min(20, smoothedLeeway)));
703
734
  }
704
735
 
705
- // --- HEAVY TIER (2s) E SLOW TIER (3s) ---
706
- if (lastAvgUIUpdate++ % 2 === 0) {
707
- ['stw', 'sog', 'depth', 'tws'].forEach(refreshGraph);
736
+ // --- SLOW TIER (Salvataggio stato ogni 10 secondi) ---
737
+ if (lastAvgUIUpdate++ % 10 === 0) {
708
738
  saveDashboardState();
709
739
  }
710
740
 
@@ -949,6 +979,10 @@ function manageHistory(type, value) {
949
979
 
950
980
  // --- 7. STORAGE STORICO ---
951
981
  store.histories[type].push({ val: finalValue, time: now });
982
+
983
+ // --- TRIGGER STRATEGIA 1 (EVENT-DRIVEN) ---
984
+ // Ridisegnamo lo strumento SOLO nell'esatto istante in cui viene generato un nuovo punto storico!
985
+ refreshGraph(type);
952
986
 
953
987
  // --- 8. PRUNING DINAMICO ---
954
988
  const maxViewportMinutes = historyMinutes * 2;
@@ -958,9 +992,8 @@ function manageHistory(type, value) {
958
992
  store.histories[type].shift();
959
993
  }
960
994
 
961
- // Reset per il prossimo bucket
995
+ // Reset per il prossimo bucket (sincronizzato)
962
996
  store.graphTempBuf[type] = [];
963
- // Spostiamo il timer esattamente al confine del secchiello assoluto appena concluso
964
997
  store.lastUpdates[type] = Math.floor(now / bucketIntervalMs) * bucketIntervalMs;
965
998
  }
966
999
 
@@ -1018,30 +1051,36 @@ function calculateScale(type, data, mode) {
1018
1051
  return { min: 0, max: Math.max(s.stdMax, Math.ceil(maxHistorico / s.step) * s.step) };
1019
1052
  }
1020
1053
 
1021
- // --- 1.2 HERCULES PROFONDITÀ (0 IN BASSO, SNAP SUL MAX SENZA PADDING) ---
1054
+ // --- 1.2 HERCULES PROFONDITÀ (BASE 0 IN ACQUE BASSE, FLUTTUANTE AL LARGO) ---
1022
1055
  if (mode === 'hercules') {
1023
1056
  const roundStep = s.hercSpan; // Passo di griglia selezionato dall'utente
1057
+ const shallowThreshold = Math.max(s.stdMax, 10); // Es. 20m
1024
1058
 
1025
1059
  let targetMin = 0;
1060
+ // Se siamo in acque profonde, lasciamo che il minimo fluttui per mostrare i micro-dettagli
1061
+ if (localMin > shallowThreshold) {
1062
+ targetMin = Math.max(0, Math.floor(localMin / roundStep) * roundStep);
1063
+ }
1064
+
1026
1065
  let targetMax = Math.ceil(localMax / roundStep) * roundStep;
1027
1066
 
1028
- // Impediamo una scala inferiore a 4 metri per sicurezza visiva
1029
- const absoluteMinSpan = 4;
1030
- if (targetMax < absoluteMinSpan) {
1031
- targetMax = absoluteMinSpan;
1067
+ // Impediamo uno span inferiore a 4 metri per evitare grafici piatti
1068
+ if (targetMax - targetMin < 4) {
1069
+ targetMax = targetMin + 4;
1032
1070
  }
1033
1071
 
1034
- // Regola asimmetrica per il MAX (Espansione istantanea, contrazione a 2 minuti)
1035
- if (currentVal > currentScale.max) {
1036
- currentScale.max = targetMax;
1072
+ // Regola asimmetrica per il MIN e il MAX (Espansione istantanea, contrazione a 2 minuti)
1073
+ if (currentVal < currentScale.min || currentVal > currentScale.max) {
1074
+ currentScale.min = Math.min(currentScale.min, targetMin);
1075
+ currentScale.max = Math.max(currentScale.max, targetMax);
1037
1076
  } else {
1038
- const allStableInTarget = recentVals.every(val => val <= targetMax);
1077
+ const allStableInTarget = recentVals.every(val => val >= targetMin && val <= targetMax);
1039
1078
  if (allStableInTarget) {
1079
+ currentScale.min = targetMin;
1040
1080
  currentScale.max = targetMax;
1041
1081
  }
1042
1082
  }
1043
1083
 
1044
- currentScale.min = 0;
1045
1084
  return { min: currentScale.min, max: currentScale.max };
1046
1085
  }
1047
1086
  }
@@ -1100,10 +1139,21 @@ function updateScaleLabels(t, min, max) {
1100
1139
  }
1101
1140
 
1102
1141
  /**
1103
- * refreshGraph: Recupero dati, switch AWS/TWS e passaggio a motore grafico.
1142
+ * refreshGraph: Recupero dati, switch AWS/TWS/VMG e passaggio a motore grafico.
1143
+ * Redirige e protegge i canali secondari (AWS/VMG) evitando ridisegni inutili.
1104
1144
  */
1105
1145
  function refreshGraph(t) {
1106
- const boxType = (t === 'vmg') ? 'sog' : t;
1146
+ // Redirezione di sicurezza dei canali secondari (AWS / VMG) verso i contenitori principali
1147
+ if (t === 'aws') {
1148
+ if (displayModeTws !== 'AWS') return; // Se non stiamo visualizzando AWS a schermo, ignora il rinfresco
1149
+ t = 'tws';
1150
+ }
1151
+ if (t === 'vmg') {
1152
+ if (displayModeSog !== 'VMG') return; // Se non stiamo visualizzando VMG a schermo, ignora il rinfresco
1153
+ t = 'sog';
1154
+ }
1155
+
1156
+ const boxType = t;
1107
1157
  let rawData;
1108
1158
 
1109
1159
  if (t === 'tws' && displayModeTws === 'AWS') {
@@ -1128,6 +1178,7 @@ function refreshGraph(t) {
1128
1178
  /**
1129
1179
  * drawGraph: Motore SVG con Timeline Reale e Gestione GAP
1130
1180
  * Risolve i conflitti di orologio (Clock Drift) tra Cerbo GX e Tablet.
1181
+ * Integra la reattività dello spessore della curva in modalità Dual Screen (Focus).
1131
1182
  */
1132
1183
  function drawGraph(d, id, min, max, isTws, isHercules) {
1133
1184
  const svg = document.getElementById(id);
@@ -1142,6 +1193,10 @@ function drawGraph(d, id, min, max, isTws, isHercules) {
1142
1193
  const latestPoint = d[d.length - 1];
1143
1194
  const now = latestPoint ? latestPoint.time : Date.now();
1144
1195
 
1196
+ // --- RILEVAMENTO DINAMICO DUAL SCREEN BLINDATO ---
1197
+ const box = svg.closest('.data-box');
1198
+ const isFocused = isFocusActive && box && box.classList.contains('is-focused');
1199
+
1145
1200
  const visibleMinutes = CONFIG.graphs.historyMinutes * (isNavigating ? 1 : 2);
1146
1201
  const viewportMs = visibleMinutes * 60000;
1147
1202
  const viewportStart = now - viewportMs;
@@ -1153,15 +1208,20 @@ function drawGraph(d, id, min, max, isTws, isHercules) {
1153
1208
  const colDepth = "#0088cc", colStw = "#00C851", colSog = "#ffbb33", colVmg = "#00b8d4";
1154
1209
 
1155
1210
  const getColorProps = (val) => {
1156
- let color = colTws, opacity = "0.15", stroke = "1";
1211
+ // Se siamo in Dual Screen (focus), aumentiamo lo spessore base della curva di un filino (da 1.6 a 2.2)
1212
+ const baseStroke = isFocused ? "4.2" : "1.6";
1213
+ const alertStroke = isFocused ? "4.8" : "2.2";
1214
+ const warnStroke = isFocused ? "4.4" : "1.8";
1215
+
1216
+ let color = colTws, opacity = "0.15", stroke = baseStroke;
1157
1217
  if (isTws) {
1158
1218
  const baseWind = (displayModeTws === 'AWS') ? colAws : colTws;
1159
- if (val >= CONFIG.graphs.reef2) { color = colDanger; opacity = "0.55"; stroke = "1.2"; }
1160
- else if (val >= CONFIG.graphs.reef1) { color = colWarning; opacity = "0.45"; stroke = "1"; }
1219
+ if (val >= CONFIG.graphs.reef2) { color = colDanger; opacity = "0.55"; stroke = alertStroke; }
1220
+ else if (val >= CONFIG.graphs.reef1) { color = colWarning; opacity = "0.45"; stroke = warnStroke; }
1161
1221
  else color = baseWind;
1162
1222
  } else if (isDepth) {
1163
- if (val < CONFIG.alarms.depthDanger) { color = colDanger; opacity = "0.55"; stroke = "1.2"; }
1164
- else if (val < CONFIG.alarms.depthWarning) { color = colWarning; opacity = "0.45"; stroke = "1"; }
1223
+ if (val < CONFIG.alarms.depthDanger) { color = colDanger; opacity = "0.55"; stroke = alertStroke; }
1224
+ else if (val < CONFIG.alarms.depthWarning) { color = colWarning; opacity = "0.45"; stroke = warnStroke; }
1165
1225
  else color = colDepth;
1166
1226
  } else {
1167
1227
  if (id === 'stw-graph') color = colStw;
@@ -1171,14 +1231,37 @@ function drawGraph(d, id, min, max, isTws, isHercules) {
1171
1231
  };
1172
1232
 
1173
1233
  let grids = "";
1174
- // Tracciamo 3 linee simmetriche al 25%, 50% (centrale) e 75%.
1175
- // La linea al 50% passerà sempre in modo matematicamente esatto dietro il valore medio stampato.
1176
- [0.25, 0.5, 0.75].forEach(p => grids += `<line x1="0" y1="${h-(p*h)}" x2="${w}" y2="${h-(p*h)}" stroke="rgba(0,0,0,0.12)" stroke-width="0.5" />`);
1234
+ // --- GRIGLIE ORIZZONTALI (Spessore fisso a 0.5px, non deformabile) ---
1235
+ [0.25, 0.5, 0.75].forEach(p => grids += `<line x1="0" y1="${h-(p*h)}" x2="${w}" y2="${h-(p*h)}" stroke="rgba(0,0,0,0.12)" stroke-width="0.5" vector-effect="non-scaling-stroke" />`);
1177
1236
 
1237
+ // --- GRIGLIE VERTICALI (Spessore fisso a 0.4px, non deformabile) ---
1178
1238
  const gridInterval = (visibleMinutes <= 15) ? 1 : 5;
1179
1239
  for (let m = gridInterval; m < visibleMinutes; m += gridInterval) {
1180
1240
  const x = w - ((m / visibleMinutes) * w);
1181
- grids += `<line x1="${x}" y1="0" x2="${x}" y2="${h}" stroke="rgba(0,0,0,0.08)" stroke-width="0.5" />`;
1241
+ grids += `<line x1="${x}" y1="0" x2="${x}" y2="${h}" stroke="rgba(0,0,0,0.08)" stroke-width="0.4" vector-effect="non-scaling-stroke" />`;
1242
+ }
1243
+
1244
+ // --- DISEGNO LINEE DI SICUREZZA ALLARME PROFONDITÀ (DANGER & WARNING) ---
1245
+ if (isDepth) {
1246
+ const dangerVal = CONFIG.alarms.depthDanger; // Es. 2.5m
1247
+ const warningVal = CONFIG.alarms.depthWarning; // Es. 3.5m
1248
+ const marginX = 4; // Margine per non toccare i bordi esterni
1249
+
1250
+ // Spessore dinamico per gli allarmi: 4.8px in Dual Screen, 1.8px nella griglia normale
1251
+ const alarmStrokeWidth = isFocused ? "4.8" : "1.8";
1252
+
1253
+ // Linea Rossa Viva (Spessore dinamico, con il tuo tratteggio alternato personalizzato)
1254
+ if (dangerVal >= min && dangerVal <= max) {
1255
+ const p = (dangerVal - min) / range;
1256
+ const y = h - (p * h);
1257
+ grids += `<line x1="${marginX}" y1="${y}" x2="${w - marginX}" y2="${y}" stroke="rgba(255, 59, 48, 0.95)" stroke-width="${alarmStrokeWidth}" stroke-dasharray="12, 6, 2, 6" vector-effect="non-scaling-stroke" />`;
1258
+ }
1259
+ // Linea Giallo Oro Viva (Spessore dinamico, con il tuo tratteggio alternato personalizzato)
1260
+ if (warningVal >= min && warningVal <= max) {
1261
+ const p = (warningVal - min) / range;
1262
+ const y = h - (p * h);
1263
+ grids += `<line x1="${marginX}" y1="${y}" x2="${w - marginX}" y2="${y}" stroke="rgba(255, 204, 0, 0.95)" stroke-width="${alarmStrokeWidth}" stroke-dasharray="6 , 2, 6, 12" vector-effect="non-scaling-stroke" />`;
1264
+ }
1182
1265
  }
1183
1266
 
1184
1267
  let gradientStops = "", lines = "", areaPath = "";
@@ -1208,7 +1291,8 @@ function drawGraph(d, id, min, max, isTws, isHercules) {
1208
1291
  started = false;
1209
1292
  }
1210
1293
  } else {
1211
- lines += `<line x1="${x1}" y1="${y1}" x2="${x2}" y2="${y2}" style="stroke:${props.color}; stroke-width:${props.stroke}; stroke-linecap:round; shape-rendering:geometricPrecision;" />`;
1294
+ // Curva del grafico: spessore reattivo al focus e protezione da deformazione (non-scaling)
1295
+ lines += `<line x1="${x1}" y1="${y1}" x2="${x2}" y2="${y2}" style="stroke:${props.color}; stroke-width:${props.stroke}; stroke-linecap:round; shape-rendering:geometricPrecision;" vector-effect="non-scaling-stroke" />`;
1212
1296
  if (!started) {
1213
1297
  areaPath += `M ${Math.max(0, x1)} ${h} L ${Math.max(0, x1)} ${y1} `;
1214
1298
  started = true;
@@ -1235,8 +1319,20 @@ function toggleFocusMode(type, element) {
1235
1319
  const container = document.querySelector('.main-container');
1236
1320
  const isLeft = ['stw', 'sog', 'hdg', 'cog', 'tack'].includes(type);
1237
1321
  isFocusActive = !isFocusActive;
1238
- if (isFocusActive) { container.classList.add('focus-active', isLeft ? 'focus-side-left' : 'focus-side-right'); element.classList.add('is-focused'); }
1239
- else { container.classList.remove('focus-active', 'focus-side-left', 'focus-side-right'); document.querySelectorAll('.data-box').forEach(b => b.classList.remove('is-focused')); }
1322
+ if (isFocusActive) {
1323
+ container.classList.add('focus-active', isLeft ? 'focus-side-left' : 'focus-side-right');
1324
+ element.classList.add('is-focused');
1325
+ } else {
1326
+ container.classList.remove('focus-active', 'focus-side-left', 'focus-side-right');
1327
+ document.querySelectorAll('.data-box').forEach(b => b.classList.remove('is-focused'));
1328
+ }
1329
+
1330
+ // --- FORZATURA REDRAW IMMEDIATO DUAL SCREEN ---
1331
+ // Quando entriamo o usciamo dal Focus, ridisegnamo subito lo strumento
1332
+ // interessato per applicare istantaneamente il cambio di spessore della linea!
1333
+ if (['stw', 'sog', 'tws', 'depth'].includes(type)) {
1334
+ refreshGraph(type);
1335
+ }
1240
1336
  }
1241
1337
 
1242
1338
  ['stw', 'sog', 'tws', 'depth'].forEach(type => {
@@ -1275,8 +1371,10 @@ function toggleFocusMode(type, element) {
1275
1371
  } else if (!isFocusActive) {
1276
1372
  if (type === 'sog') {
1277
1373
  displayModeSog = (displayModeSog === 'SOG') ? 'VMG' : 'SOG';
1374
+ refreshGraph('sog'); // --- FORZA RINFRESCO ISTANTANEO AL CAMBIO SOG/VMG ---
1278
1375
  } else if (type === 'tws') {
1279
1376
  displayModeTws = (displayModeTws === 'TWS') ? 'AWS' : 'TWS';
1377
+ refreshGraph('tws'); // --- FORZA RINFRESCO ISTANTANEO AL CAMBIO TWS/AWS ---
1280
1378
  }
1281
1379
  el.style.backgroundColor = "rgba(0, 0, 0, 0.05)";
1282
1380
  setTimeout(() => el.style.backgroundColor = "", 150);
@@ -1404,22 +1502,45 @@ async function init() {
1404
1502
  // RICONNESSIONE RAPIDA AL RISVEGLIO (VISIBILITY WATCHDOG)
1405
1503
  // ==========================================================================
1406
1504
 
1407
- // Quando sblocchi l'iPad o riapri il browser, forziamo la chiusura del vecchio
1408
- // WebSocket orfano. Questo farà scattare immediatamente connect() e la sincronizzazione.
1505
+ // Quando sblocchi l'iPad o riapri la scheda, sincronizziamo in modo intelligente
1409
1506
  document.addEventListener('visibilitychange', () => {
1410
1507
  if (document.visibilityState === 'visible') {
1411
- console.log("⏰ Schermo sbloccato: forzatura riconnessione rapida.");
1412
- if (socket) {
1413
- try {
1414
- socket.close(); // Fa scattare onclose -> connect() -> onopen -> fetchServerHistory()
1415
- } catch (e) {
1508
+ // 1. Se la connessione è già attiva e sana, scarichiamo solo lo storico senza disconnetterci
1509
+ if (socket && socket.readyState === WebSocket.OPEN) {
1510
+ console.log("⏰ Schermo sbloccato: connessione attiva, sincronizzazione dello storico.");
1511
+ fetchServerHistory().then(() => {
1512
+ ['stw', 'sog', 'depth', 'tws'].forEach(refreshGraph);
1513
+ }).catch(err => {});
1514
+ }
1515
+ // 2. Se la connessione è persa o morta, forzatura riconnessione rapida
1516
+ else {
1517
+ console.log("⏰ Schermo sbloccato: connessione assente, forzatura riconnessione rapida.");
1518
+ if (socket) {
1519
+ try {
1520
+ socket.close();
1521
+ } catch (e) {
1522
+ connect();
1523
+ }
1524
+ } else {
1416
1525
  connect();
1417
1526
  }
1418
- } else {
1419
- connect();
1420
1527
  }
1421
1528
  }
1422
1529
  });
1530
+
1531
+ // Rileva se il dispositivo è andato in sospensione misurando il ritardo dei secondi
1532
+ let lastHeartbeat = Date.now();
1533
+ setInterval(() => {
1534
+ const now = Date.now();
1535
+ const diff = now - lastHeartbeat;
1536
+ lastHeartbeat = now;
1537
+
1538
+ // Se passano più di 6 secondi tra un ciclo e l'altro (invece di 1 secondo),
1539
+ // significa che il PC era in sospensione. Avviamo il risveglio.
1540
+ if (diff > 6000) {
1541
+ handleWakeUp();
1542
+ }
1543
+ }, 1000);
1423
1544
  }
1424
1545
 
1425
1546
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sailingrotevista/rotevista-dash",
3
- "version": "6.0.9",
3
+ "version": "6.0.12",
4
4
  "description": "Wind Dashboard with navigation and course aids",
5
5
  "main": "index.js",
6
6
  "publishConfig": {
package/style.css CHANGED
@@ -175,8 +175,7 @@ body.night-mode .alarm-danger {
175
175
 
176
176
  .focus-active .is-focused .sparkline path,
177
177
  .focus-active .is-focused .sparkline line {
178
- stroke-width: 1.5px !important; /* Un briciolo più spessa per la stabilità visiva su schermi grandi */
179
- }
178
+ }
180
179
 
181
180
  .focus-active.focus-side-left {
182
181
  grid-template-columns: 3fr 2fr !important;
@@ -415,7 +414,6 @@ body.night-mode .alarm-danger {
415
414
  rect[fill="#222"] { fill: #eee !important; }
416
415
  #leeway-val { color: #000 !important; }
417
416
 
418
- #awa-pointer, #twa-pointer, #track-pointer, #twd-arrow, #twd-boat-wrap { transition: all 0.8s cubic-bezier(0.4, 0, 0.2, 1); }
419
417
  #wind-gauge { width: 100%; height: 100%; max-height: 100%; object-fit: contain; }
420
418
 
421
419
  /* ==========================================================================