Aggiunta stili CSS per Kiosk, struttura HTML per la Mappa e Riferimenti ai Sensori

• Creato un nuovo file CSS per gli stili del chiosco (kiosk) con variabili, stili per le schede (card) e animazioni.
• Aggiunto un file HTML per l'interfaccia della mappa utilizzando Mapbox, inclusi gli stili e il JavaScript per le funzionalità della mappa.
• Introdotto un file JSON per i riferimenti ai sensori, definendo percorsi ed elementi per i dati di temperatura, vento, onde, posizione, batteria, motore e sistema.

Co-authored-by: Copilot <copilot@github.com>
This commit is contained in:
Giuseppe Raffa
2026-04-23 16:19:11 +02:00
parent 41f33ce181
commit bb8d267cd4
85 changed files with 4293 additions and 5083 deletions

View File

@@ -1,141 +1,104 @@
// Mappa globale per salvare gli interval id anche dopo un "hot-reload"
if (!global.__meb_live_trackers) {
global.__meb_live_trackers = new Map();
}
const skFlow = require('../../config/skFlow');
const { startSession } = require('../utility/live');
module.exports = [
{
id: 'live-refresh',
execute: async ({ bot, chatId, msg }) => {
const liveCmd = require('../commands/live.js');
const newText = liveCmd.formatLiveData();
try {
await bot.editMessageText(newText, {
chat_id: chatId,
message_id: msg.message_id,
parse_mode: 'Markdown',
reply_markup: {
inline_keyboard: [
[{ text: 'Avvia Live (2s)', callback_data: 'live-start' }],
[{ text: 'Aggiorna', callback_data: 'live-refresh' }]
]
}
});
} catch (e) {
if (!e.message.includes('message is not modified')) {
console.error('[Telegram Live] Errore refresh:', e.message);
}
}
}
},
{
id: 'live-start',
execute: async ({ bot, chatId, msg }) => {
const liveCmd = require('../commands/live.js');
const messageId = msg.message_id;
const liveKey = `${chatId}_${messageId}`;
// Se gia' attivo per questo messaggio, ignora
if (global.__meb_live_trackers.has(liveKey)) return;
const stopMarkup = {
inline_keyboard: [
[{ text: 'Ferma Live', callback_data: 'live-stop' }]
]
};
await bot.editMessageReplyMarkup(stopMarkup, {
chat_id: chatId,
message_id: messageId
});
// 30 tick da 2 secondi = 60 secondi, poi auto-stop
let count = 30;
const intervalTimer = setInterval(async () => {
count--;
const baseText = liveCmd.formatLiveData();
// Auto-stop quando il tempo scade
if (count <= 0) {
if (global.__meb_live_trackers.has(liveKey)) {
clearInterval(global.__meb_live_trackers.get(liveKey));
global.__meb_live_trackers.delete(liveKey);
}
try {
await bot.editMessageText(
baseText + `\n_Live terminato automaticamente (60s)._`,
{
chat_id: chatId,
message_id: messageId,
parse_mode: 'Markdown',
reply_markup: {
inline_keyboard: [
[{ text: 'Avvia Live (2s)', callback_data: 'live-start' }],
[{ text: 'Aggiorna', callback_data: 'live-refresh' }]
]
}
}
);
} catch (e) { /* ignore */ }
return;
}
// Aggiornamento live con countdown
const newText = baseText + `\n_Live attivo: arresto tra *${count * 2}s*_`;
try {
await bot.editMessageText(newText, {
chat_id: chatId,
message_id: messageId,
parse_mode: 'Markdown',
reply_markup: stopMarkup
});
} catch (e) {
if (e.response && e.response.statusCode === 429) {
console.warn('[Telegram Live] Rate limit raggiunto');
} else if (e.message && e.message.includes('message to edit not found')) {
// Messaggio cancellato dall'utente
clearInterval(intervalTimer);
global.__meb_live_trackers.delete(liveKey);
}
// Ignora "message is not modified"
}
}, 2000);
global.__meb_live_trackers.set(liveKey, intervalTimer);
}
},
{
id: 'live-stop',
execute: async ({ bot, chatId, msg }) => {
const liveCmd = require('../commands/live.js');
const messageId = msg.message_id;
const liveKey = `${chatId}_${messageId}`;
// Pulisci l'interval se esiste
if (global.__meb_live_trackers.has(liveKey)) {
clearInterval(global.__meb_live_trackers.get(liveKey));
global.__meb_live_trackers.delete(liveKey);
}
const newText = liveCmd.formatLiveData();
try {
await bot.editMessageText(newText + '\n_Live fermato._', {
chat_id: chatId,
message_id: messageId,
parse_mode: 'Markdown',
reply_markup: {
inline_keyboard: [
[{ text: 'Avvia Live (2s)', callback_data: 'live-start' }],
[{ text: 'Aggiorna', callback_data: 'live-refresh' }]
]
}
});
} catch (e) { /* ignore */ }
}
}
const logsPaths = [
"navigation.position",
"navigation.headingTrue",
"navigation.speedOverGround",
"propulsion.p1.temperature"
];
// Funzioni per generare il testo aggiornato per ogni tipo di dato
const textGenerators = {
logs: () => {
const data = skFlow.getFrom(logsPaths);
if (!data || Object.keys(data).length === 0) return 'Nessun log disponibile.';
let text = '*Telemetria di Bordo*\n\n';
for (const [path, value] of Object.entries(data)) {
const displayValue = typeof value === 'object' ? JSON.stringify(value) : value;
text += `*${path}*: ${displayValue}\n`;
}
return text;
},
weather: () => {
const data = skFlow.getWithFilter('meb.forecast');
if (!data || Object.keys(data).length === 0) return 'Nessun dato meteo disponibile.';
let text = '*Dati Meteo*\n\n';
for (const [path, value] of Object.entries(data)) {
const displayValue = typeof value === 'object' ? JSON.stringify(value) : value;
text += `*${path}*: ${displayValue}\n`;
}
return text;
},
marine: () => {
const data = skFlow.getWithFilter('meb.marine');
if (!data || Object.keys(data).length === 0) return 'Nessun dato sul mare disponibile.';
let text = '*Dati Meteo del mare*\n\n';
for (const [path, value] of Object.entries(data)) {
const displayValue = typeof value === 'object' ? JSON.stringify(value) : value;
text += `*${path}*: ${displayValue}\n`;
}
return text;
},
data: () => {
let text = '';
const logs = skFlow.getFrom(logsPaths);
text += '*Telemetria di Bordo*\n\n';
if (logs && Object.keys(logs).length > 0) {
for (const [path, value] of Object.entries(logs)) {
const displayValue = typeof value === 'object' ? JSON.stringify(value) : value;
text += `*${path}*: ${displayValue}\n`;
}
} else {
text += 'Nessun dato disponibile.\n';
}
const weather = skFlow.getWithFilter('meb.forecast');
text += '\n*Dati Meteo*\n\n';
if (weather && Object.keys(weather).length > 0) {
for (const [path, value] of Object.entries(weather)) {
const displayValue = typeof value === 'object' ? JSON.stringify(value) : value;
text += `*${path}*: ${displayValue}\n`;
}
} else {
text += 'Nessun dato disponibile.\n';
}
const marine = skFlow.getWithFilter('meb.marine');
text += '\n*Dati Meteo del mare*\n\n';
if (marine && Object.keys(marine).length > 0) {
for (const [path, value] of Object.entries(marine)) {
const displayValue = typeof value === 'object' ? JSON.stringify(value) : value;
text += `*${path}*: ${displayValue}\n`;
}
} else {
text += 'Nessun dato disponibile.\n';
}
return text;
}
};
module.exports = {
prefix: 'live:',
handler: async (bot, query) => {
const chatId = query.message.chat.id;
const botMessageId = query.message.message_id;
// callback_data = live:<dataType>:<userMessageId>
const parts = query.data.split(':');
const dataType = parts[1];
const userMessageId = parts[2];
const getTextFn = textGenerators[dataType];
if (!getTextFn) {
bot.answerCallbackQuery(query.id, { text: 'Tipo non supportato' });
return;
}
startSession(bot, chatId, botMessageId, userMessageId, getTextFn);
bot.answerCallbackQuery(query.id, { text: 'Live avviato' });
},
textGenerators
};