diff --git a/README.md b/README.md index 6691f9f..2289506 100644 --- a/README.md +++ b/README.md @@ -115,6 +115,11 @@ La carte `sncf-train-card` est **automatiquement disponible** dans le sélecteur Dans un tableau de bord, cliquer sur **+ Ajouter une carte** → chercher **SNCF Train Card**. +La configuration peut ensuite se faire : + +- via l'éditeur visuel Lovelace +- ou via YAML + Ou en YAML : ```yaml @@ -124,6 +129,8 @@ device_id: VOTRE_DEVICE_ID ### 🔍 Trouver le `device_id` +_S'obtient dynamiquement via la configuration visuelle._ + Le `device_id` correspond à l'appareil créé lors de la configuration du trajet. 1. Aller dans **Paramètres → Appareils & services → SNCF Trains** @@ -139,11 +146,14 @@ Le `device_id` correspond à l'appareil créé lors de la configuration du traje | `device_id` | `string` | **obligatoire** | Identifiant de l'appareil SNCF (voir ci-dessus) | | `title` | `string` | `'Trains SNCF'` | Titre affiché en haut de la carte | | `train_lines` | `number` | `3` | Nombre de trains affichés simultanément | -| `train_emoji` | `string` | `'🚅'` | Emoji du train animé sur la barre de progression | -| `train_emoji_axial_symmetry` | `boolean` | `true` | Retourne l'emoji (à utiliser selon son sens) | -| `train_station_emoji` | `string` | `'🚉'` | Emoji affiché à côté des gares | | `animation_duration` | `number` | `30` | Nombre de minutes avant l'arrivée en gare à partir duquel l'animation du train se déclenche (ex : `30` = animation active dans les 30 dernières minutes, `60` = dans la dernière heure) | | `update_interval` | `number` | `30000` | Intervalle de rafraîchissement de la carte en **millisecondes** | +| `train_emoji_axial_symmetry` | `boolean` | `true` | Retourne l'emoji du train horizontalement | +| `train_emoji` | `string` | `'🚅'` | Emoji du train animé sur la barre | +| `show_departure_station` | `boolean` | `true` | Affiche ou masque les informations de départ | +| `departure_station_emoji` | `string` | `''` | Emoji de la station de départ | +| `show_arrival_station` | `boolean` | `true` | Affiche ou masque les informations d'arrivée | +| `arrival_station_emoji` | `string` | `'🚉'` | Emoji de la station d'arrivée | ### Exemple complet @@ -154,13 +164,17 @@ title: "Paris → Lyon" train_lines: 4 train_emoji: "🚆" train_emoji_axial_symmetry: true -train_station_emoji: "🏙️" -animation_duration: 45 +show_departure_station: true +departure_station_emoji: "🚉" +show_arrival_station: true +arrival_station_emoji: "🏙️" +animation_duration: 0 update_interval: 60000 ``` ### Exemple d'affichage +![Exemple d'affichage](./assets/card_example.png) ![Exemple d'affichage](./assets/card_example.png) --- diff --git a/custom_components/sncf_trains/www/sncf-train-card.js b/custom_components/sncf_trains/www/sncf-train-card.js index 5e27af0..4e40394 100644 --- a/custom_components/sncf_trains/www/sncf-train-card.js +++ b/custom_components/sncf_trains/www/sncf-train-card.js @@ -1,53 +1,201 @@ // Ajouter au registre des cartes personnalisées -(window.customCards = window.customCards || []).push({ +globalThis.customCards ||= [] +globalThis.customCards.push({ type: 'sncf-train-card', name: 'SNCF Train Card', - description: 'Carte personnalisée animée pour afficher les trains SNCF en temps réel' + description: 'Carte personnalisée animée pour afficher les trains SNCF en temps réel', + preview: true, + configurable: true }); class SncfTrainCard extends HTMLElement { constructor() { super(); - this.attachShadow({ mode: 'open' }); + this.attachShadow({mode: 'open'}); this.updateInterval = null; this.lastTrainSignature = null; this._lastRenderTime = 0; } + /** + * Méthode héritée
+ * Permet de définir la configuration de la carte, avec validation et gestion des changements de device_id pour forcer une mise à jour immédiate + * @param {Object} config - La configuration de la carte, qui doit inclure au minimum un device_id valide pour fonctionner correctement, et peut inclure d'autres paramètres pour personnaliser l'affichage + * @throws {Error} Si le device_id n'est pas défini, une erreur est levée pour informer l'utilisateur de la nécessité de fournir cette information essentielle + */ setConfig(config) { if (!config.device_id) { throw new Error('You need to define device_id'); } - + const previousDeviceId = this.config ? this.config.device_id : null; const deviceIdChanged = previousDeviceId && previousDeviceId !== config.device_id; - - this.config = { - device_id: config.device_id, - train_lines: config.train_lines || 3, - title: config.title || 'Trains SNCF', - train_emoji: config.train_emoji || '🚅', - train_emoji_axial_symmetry: config.train_emoji_axial_symmetry || true, - train_station_emoji: config.train_station_emoji || '🚉', - animation_duration: config.animation_duration || 30, - update_interval: config.update_interval || 30000, - ...config - }; - + + this.config = config; + // Forcer la mise à jour immédiate si device_id a changé if (deviceIdChanged) { this.stopUpdateTimer(); this.startUpdateTimer(); } - + // Toujours forcer un nouveau rendu this.render(); } + /** + * Méthode héritée
+ * Fournit la configuration du formulaire pour l'éditeur de Lovelace, avec des labels et des aides personnalisés + */ + static getConfigForm() { + return { + schema: [ + { + name: "device_id", + required: true, + selector: { + device: { + filter: { + integration: "sncf_trains" + } + } + } + }, + { + name: "title", + selector: {text: {}}, + }, + { + name: "train_lines", + selector: { + number: { + min: 1, + max: 10, + step: 1, + }, + }, + }, + { + name: "animation_duration", + selector: { + number: { + min: 0, + max: 100, + step: 1, + }, + }, + }, + { + name: "update_interval", + selector: { + number: { + min: 5000, + step: 1000, + }, + }, + }, + { + type: "grid", + name: "", + column_min_width: "150px", + schema: [ + { + name: "train_emoji_axial_symmetry", + selector: {boolean: {}}, + }, + { + name: "train_emoji", + selector: { + icon: {}, + }, + }, + { + name: "show_departure_station", + selector: {boolean: {}}, + }, + { + name: "departure_station_emoji", + selector: { + icon: {}, + }, + }, + { + name: "show_arrival_station", + selector: {boolean: {}}, + }, + { + name: "arrival_station_emoji", + selector: { + icon: {}, + }, + }, + ] + }, + ], + computeLabel: (schema) => { + const labels = { + device_id: "ID du Device (obligatoire)", + title: "Titre de la carte", + train_emoji: "Emoji du train", + train_lines: "Nombre de trains à afficher", + animation_duration: "Durée d'animation (minutes)", + update_interval: "Intervalle de mise à jour (ms)", + departure_station_emoji: "Emoji de la gare de départ", + arrival_station_emoji: "Emoji de la gare d'arrivée", + show_departure_station: "Afficher les informations de départ", + show_arrival_station: "Afficher les informations d'arrivée", + train_emoji_axial_symmetry: "Symétrie axiale du train", + }; + return labels[schema.name] || undefined; + }, + computeHelper: (schema) => { + const helpers = { + device_id: "L'identifiant unique du device SNCF à afficher", + title: "Le titre affiché en haut de la carte", + train_emoji: "L'emoji représentant le train", + train_lines: "Le nombre de trains à afficher (1-10)", + animation_duration: "Nombre de minutes avant le départ pour que le train apparaisse", + update_interval: "Fréquence de rafraîchissement en millisecondes (ex: 30000 pour 30s)", + departure_station_emoji: "L'emoji pour la gare de départ", + arrival_station_emoji: "L'emoji pour la gare d'arrivée", + show_departure_station: "Affiche ou masque la gare de départ", + show_arrival_station: "Affiche ou masque la gare d'arrivée", + train_emoji_axial_symmetry: "Retourner l'emoji du train horizontalement", + }; + return helpers[schema.name] || undefined; + }, + }; + } + + /** + * Méthode héritée
+ * Fournit une configuration par défaut pour le mode aperçu dans l'éditeur de Lovelace + */ + static getStubConfig() { + return { + device_id: '', + title: 'Trains SNCF', + train_lines: 3, + animation_duration: 30, + update_interval: 30000, + train_emoji_axial_symmetry: true, + train_emoji: '🚅', + show_departure_station: true, + departure_station_emoji: '', + show_arrival_station: true, + arrival_station_emoji: '🚉', + }; + } + + /** + * Méthode héritée
+ * Permet de recevoir l'objet Home Assistant et de déclencher une vérification des mises à jour des trains pour éviter les rendus inutiles + * @param {Object} hass - L'objet Home Assistant fourni par le système, utilisé pour accéder aux états et aux services, et pour déclencher des mises à jour de la carte lorsque les données des trains changent + */ set hass(hass) { const previousHass = this._hass; this._hass = hass; - + // Vérifier si les données des trains ont changé if (this.config && previousHass) { this.checkForTrainUpdates(previousHass, hass); @@ -56,14 +204,43 @@ class SncfTrainCard extends HTMLElement { } } + /** + * Méthode héritée
+ * Démarre un timer pour forcer des mises à jour régulières, ce qui est nécessaire pour capturer les changements de données en temps réel + */ + connectedCallback() { + this.startUpdateTimer(); + } + + /** + * Méthode héritée
+ * Arrête le timer de mise à jour pour éviter les fuites de mémoire lorsque la carte est retirée du DOM + */ + disconnectedCallback() { + this.stopUpdateTimer(); + } + + /** + * Méthode héritée
+ * Calcule la taille de la carte en fonction du nombre de lignes de train à afficher, avec une taille minimale pour éviter les problèmes d'affichage + */ + getCardSize() { + return Math.max(3, this.config.train_lines + 1); + } + + /** + * Vérifie si les données des trains ont changé en comparant une signature des données actuelles avec la dernière signature connue, et ne fait un rendu que si nécessaire pour optimiser les performances + * @param {Object} previousHass - L'objet Home Assistant précédent pour comparer les données + * @param {Object} currentHass - L'objet Home Assistant actuel pour récupérer les données fraîches + */ async checkForTrainUpdates(previousHass, currentHass) { try { // Récupérer les entités actuelles const currentTrains = await this.getTrainEntities(); - + // Créer une signature des données actuelles const currentSignature = this.createTrainSignature(currentTrains); - + // Comparer avec la signature précédente if (currentSignature !== this.lastTrainSignature) { this.lastTrainSignature = currentSignature; @@ -71,24 +248,25 @@ class SncfTrainCard extends HTMLElement { } } catch (error) { // En cas d'erreur, faire un rendu quand même + console.error(error); this.render(); } } + /** + * Crée une signature unique pour les données des trains en concaténant les informations clés de chaque train, ce qui permet de détecter facilement les changements sans faire un rendu complet à chaque fois + * @param {Array} trains - Un tableau d'entités de train + * @returns {string} Une chaîne de caractères représentant la signature des données des trains + */ createTrainSignature(trains) { - return trains.map(train => + return trains.map(train => `${train.entity_id}:${train.attributes.departure_time}:${train.attributes.delay_minutes || 0}:${train.attributes.has_delay || false}` ).join('|'); } - connectedCallback() { - this.startUpdateTimer(); - } - - disconnectedCallback() { - this.stopUpdateTimer(); - } - + /** + * Démarre un timer qui force un rendu de la carte à intervalles réguliers, ce qui est nécessaire pour capturer les changements de données en temps réel, surtout pour les données de train qui peuvent changer fréquemment + */ startUpdateTimer() { this.stopUpdateTimer(); this.updateInterval = setInterval(async () => { @@ -100,6 +278,9 @@ class SncfTrainCard extends HTMLElement { }, this.config.update_interval); } + /** + * Arrête le timer de mise à jour pour éviter les fuites de mémoire lorsque la carte est retirée du DOM ou lorsque le device_id change, ce qui est important pour maintenir les performances et éviter les rendus inutiles + */ stopUpdateTimer() { if (this.updateInterval) { clearInterval(this.updateInterval); @@ -107,158 +288,202 @@ class SncfTrainCard extends HTMLElement { } } + /** + * Récupère les entités de train associées au device_id configuré en utilisant l'API WebSocket de Home Assistant pour obtenir des données fraîches, filtre les trains qui ne sont pas encore passés, et trie les résultats par heure de départ pour n'afficher que les trains à venir, ce qui garantit que les informations affichées sont toujours à jour et pertinentes pour l'utilisateur + * @returns {Promise} Un tableau d'entités de train avec des données fraîches + */ async getTrainEntities() { if (!this._hass) return []; - + try { // Utiliser l'API Home Assistant pour récupérer toutes les entités const allEntityRegistry = await this._hass.callWS({ type: 'config/entity_registry/list' }); - + // Filtrer les entités par device_id - const deviceEntities = allEntityRegistry.filter(entityInfo => + const deviceEntities = allEntityRegistry.filter(entityInfo => entityInfo.device_id === this.config.device_id ); - + if (!deviceEntities || deviceEntities.length === 0) { console.warn('⚠️ Aucune entité trouvée pour ce device_id dans le registre'); return []; } - + // Récupérer les états des entités train trouvées avec données fraîches const trainEntities = deviceEntities .filter(entityInfo => entityInfo.entity_id.includes('train')) .map(entityInfo => { // Forcer la récupération de l'état frais - const freshState = this._hass.states[entityInfo.entity_id]; - return freshState; + return this._hass.states[entityInfo.entity_id]; }) - .filter(entity => entity && entity.attributes && entity.attributes.departure_time); - + .filter(entity => entity?.attributes?.departure_time); + + // Source - https://stackoverflow.com/a/1214753 + // Posted by Kip, modified by community. See post 'Timeline' for change history + // Retrieved 2026-05-15, License - CC BY-SA 4.0 + const addMinutes = (date, minutes) => { + return new Date(date.getTime() + minutes*60000); + } + // Filtrer les trains qui ne sont pas encore passés const currentTime = new Date(); const upcomingTrains = trainEntities.filter(entity => { - const departureTime = this.parseTime(entity.attributes.departure_time); - return departureTime >= currentTime; + // TODO : paramétrer le temps d'affichage max d'un train arrivé en gare + const arrivalTime = addMinutes(this.parseTime(entity.attributes.arrival_time), 30); + return arrivalTime >= currentTime; }); - - const sortedEntities = upcomingTrains + + return upcomingTrains .sort((a, b) => { - const aTime = this.parseTime(a.attributes.departure_time); - const bTime = this.parseTime(b.attributes.departure_time); + const aTime = this.parseTime(a.attributes.arrival_time); + const bTime = this.parseTime(b.attributes.arrival_time); return aTime - bTime; }) .slice(0, this.config.train_lines); - - return sortedEntities; - + } catch (error) { console.error('❌ Erreur lors de la récupération via API:', error); return []; } } - // Méthode pour parser correctement le format SNCF + /** + * Parse une chaîne de temps au format spécifique de la SNCF (ex: "19/11/2025 - 08:20") et retourne un objet Date, ou une date par défaut si le format est invalide ou si la chaîne est vide, ce qui permet de gérer correctement les données de temps fournies par les entités de train et d'éviter les erreurs d'affichage + * @param {string} departureTime - La chaîne de temps à parser, qui peut être au format SNCF ou un format standard reconnu par JavaScript + * @returns {Date} Un objet Date représentant le temps de départ, ou une date par défaut si le parsing échoue + */ parseTime(departureTime) { if (!departureTime) { return new Date(0); } - + // Format SNCF: "19/11/2025 - 08:20" if (departureTime.includes('/') && departureTime.includes(' - ')) { const parts = departureTime.split(' - '); if (parts.length === 2) { const datePart = parts[0]; // "19/11/2025" const timePart = parts[1]; // "08:20" - + const dateComponents = datePart.split('/'); if (dateComponents.length === 3) { - const day = parseInt(dateComponents[0]); - const month = parseInt(dateComponents[1]) - 1; // Mois 0-indexé - const year = parseInt(dateComponents[2]); - + const day = Number.parseInt(dateComponents[0]); + const month = Number.parseInt(dateComponents[1]) - 1; // Mois 0-indexé + const year = Number.parseInt(dateComponents[2]); + const timeComponents = timePart.split(':'); if (timeComponents.length === 2) { - const hour = parseInt(timeComponents[0]); - const minute = parseInt(timeComponents[1]); - + const hour = Number.parseInt(timeComponents[0]); + const minute = Number.parseInt(timeComponents[1]); + return new Date(year, month, day, hour, minute); } } } } - + // Fallback vers Date classique return new Date(departureTime); } - calculateTrainPosition(departureTime, currentTime) { - if (!departureTime) { + /** + * Calcule la position du train sur la barre de progression en fonction de l'heure actuelle et de l'heure de départ, en affichant le train 30 minutes avant le départ et en le faisant avancer vers la droite à mesure que l'heure de départ approche, ce qui crée une animation visuelle intuitive pour les utilisateurs afin de suivre l'approche du train vers la gare, et retourne une position en pourcentage (0% = train à gauche, 100% = train arrivé) ou une valeur négative pour indiquer que le train n'est pas encore visible, ce qui permet de gérer l'affichage du train de manière dynamique en fonction du temps restant avant le départ + * @param {object} trainAttributes - Les attributs du train, qui doivent inclure au minimum une heure de départ valide pour que le calcul fonctionne correctement, et peuvent inclure d'autres informations pour personnaliser l'affichage + * @returns {number} Un nombre représentant la position du train en pourcentage (0-100) ou une valeur négative si le train n'est pas encore visible + */ + calculateTrainPosition(trainAttributes) { + if (!trainAttributes.departure_time || !trainAttributes.arrival_time) { return -10; } - - const departure = this.parseTime(departureTime); - - if (isNaN(departure.getTime())) { + + const departure = this.parseTime(trainAttributes.departure_time); + const arrival = this.parseTime(trainAttributes.arrival_time); + const travelTime = (arrival - departure) / (1000 * 60); + + if (Number.isNaN(departure.getTime()) || Number.isNaN(arrival.getTime()) || travelTime < 0) { return -10; } - - const now = currentTime || new Date(); - const diffMinutes = (departure - now) / (1000 * 60); - - // Train apparaît 30 minutes avant l'heure - const maxMinutes = this.config.animation_duration; - - if (diffMinutes > maxMinutes) { - return -10; // Hors de la barre + + const now = new Date(); + const diffMinutes = (arrival - now) / (1000 * 60); + + if (diffMinutes > travelTime) { + // TODO : tester et s'assurer de la véracité / nom du param animation_duration + if (this.config.animation_duration === 0 || this.config.animation_duration > diffMinutes - travelTime) { + // Train apparaît X minutes avant l'heure + return 0; + } + // Hors de la barre + return -10; } if (diffMinutes <= 0) { - return 100; // Arrivé à la gare + // Arrivé à la gare + return 100; } - + // Position sur la barre (0% = gauche, 100% = droite) - return ((maxMinutes - diffMinutes) / maxMinutes) * 100; + return ((travelTime - diffMinutes) / travelTime) * 100; } + /** + * Formate une chaîne de temps en une heure lisible au format français (ex: "08:20"), ou retourne "N/A" si la chaîne est vide, ou "Format invalide" si le parsing échoue, ce qui permet d'afficher les heures de départ et d'arrivée de manière claire et compréhensible pour les utilisateurs, tout en gérant les cas où les données de temps peuvent être manquantes ou mal formatées + * @param {string} timeString - La chaîne de temps à formater, qui doit être au format reconnu par la méthode parseTime + * @returns {string} Une chaîne représentant l'heure formatée ou un message d'erreur si le format est invalide + */ formatTime(timeString) { if (!timeString) { return 'N/A'; } - + const time = this.parseTime(timeString); - - if (isNaN(time.getTime())) { + + if (Number.isNaN(time.getTime())) { return 'Format invalide'; } - - const result = time.toLocaleTimeString('fr-FR', { - hour: '2-digit', - minute: '2-digit' + + return time.toLocaleTimeString('fr-FR', { + hour: '2-digit', + minute: '2-digit' }); - - return result; } + /** + * Calcule l'heure d'arrivée réelle en ajoutant les minutes de retard à l'heure de départ prévue, et retourne une chaîne formatée de l'heure d'arrivée réelle, ou null si les données nécessaires sont manquantes ou si le train n'a pas de retard. + * @param departureTime - L'heure de départ + * @param delayMinutes - Le temps de retard en minutes + * @returns {string} Une chaîne représentant l'heure avec retard formatée ou null + */ + // TODO : tester si encore utile ? calculateRealArrivalTime(departureTime, delayMinutes) { if (!departureTime || !delayMinutes || delayMinutes === 0) { return null; } - + const originalTime = this.parseTime(departureTime); const realTime = new Date(originalTime.getTime() + (delayMinutes * 60000)); // Ajouter les minutes de retard - - return realTime.toLocaleTimeString('fr-FR', { - hour: '2-digit', - minute: '2-digit' + + return realTime.toLocaleTimeString('fr-FR', { + hour: '2-digit', + minute: '2-digit' }); } + /** + * Calcule la couleur du train en fonction du retard + * @param {number} delayMinutes - Le nombre de minutes de retard + * @param {boolean} hasDelay - Indique si le train a du retard ou non + * @returns {string} La couleur correspondante + */ getTrainColor(delayMinutes, hasDelay) { if (!hasDelay || delayMinutes === 0) return '#4caf50'; // Vert à l'heure return '#f44336'; // Rouge en retard (peu importe le nombre de minutes) } + /** + * Méthode héritée
+ * Génération du rendu de l'ensemble de la carte, incluant le css et l'html + */ async render() { if (!this._hass || !this.config) { return; @@ -272,7 +497,7 @@ class SncfTrainCard extends HTMLElement { this._lastRenderTime = now; const trains = await this.getTrainEntities(); - + if (trains.length === 0) { this.shadowRoot.innerHTML = ` @@ -284,10 +509,139 @@ class SncfTrainCard extends HTMLElement { return; } - const currentTime = new Date(); - - const trainLinesHTML = this.renderTrainLines(trains, currentTime); this.shadowRoot.innerHTML = ` + ${this.renderCss()} + + +
+
+
${this.config.title}
+
+ + ${this.renderTrainLines(trains)} + +
+
+ `; + } + + /** + * Rendu des icônes en fonction de la configuration, en vérifiant si l'icône est un emoji simple ou une icône HA (mdi:, fa:, ic:, ...), et en retournant le HTML approprié pour chaque cas. + * @param icone - La chaîne de caractères représentant l'icône configurée, qui peut être un emoji simple ou une icône HA avec un préfixe spécifique, et qui doit être traitée différemment pour s'assurer qu'elle s'affiche correctement dans la carte + * @return {string} Une chaîne HTML représentant l'icône à afficher, soit en utilisant la balise pour les icônes HA, soit en affichant directement l'emoji pour les emojis simples, ce qui permet de gérer une grande variété d'icônes de manière flexible et personnalisable + */ + renderIcone(icone) { + if (icone?.includes(':')) { + return ``; + } + return icone; + } + + /** + * Rendu des lignes de train en fonction des données fournies, en calculant la position de chaque train sur la barre de progression, en affichant les informations de départ et d'arrivée selon la configuration, et en appliquant des styles différents pour les trains en retards. + * @param {Array} trains - Un tableau d'entités de train à afficher, avec leurs attributs contenant les informations nécessaires pour le rendu + * @returns {string} Une chaîne HTML représentant la section complète du train + */ + renderTrainLines(trains) { + return trains.map((train, index) => { + const TA = train.attributes; + const position = this.calculateTrainPosition(TA); + const delayMinutes = TA.delay_minutes || 0; + const hasDelay = TA.has_delay || false; + const isRunning = this.parseTime(TA.departure_time) < new Date() && new Date() < this.parseTime(TA.arrival_time) + const isArrived = new Date() > this.parseTime(TA.arrival_time) + const trainColor = this.getTrainColor(delayMinutes, hasDelay); + + const theme = isArrived ? 'arrived' : hasDelay ? 'delayed' : isRunning ? 'running' : ''; + return ` +
+ ${this.config.show_departure_station ? this.renderDeparture(TA) : ''} + +
+ ${ position >= 0 ? + `
+ ${this.renderIcone(this.config.train_emoji)} +
` : '' + } +
+ + ${this.config.show_arrival_station ? this.renderArrival(TA) : ''} +
+ `; + }).join(''); + } + + /** + * Rendu de la section de départ pour un train donné, en affichant l'heure de départ prévue, l'heure de départ réelle si le train a du retard. + * @param {object} trainAttributes - Les attributs du train + * @returns {string} Une chaîne HTML représentant la section de départ du train + */ + renderDeparture(trainAttributes) { + const hasDelay = trainAttributes.has_delay || false; + const isGone = new Date() > this.parseTime(trainAttributes.departure_time) + const delayMinutes = trainAttributes.delay_minutes || 0; + const departureTime = this.formatTime(trainAttributes.base_departure_time); + const realDepartureTime = this.formatTime(trainAttributes.departure_time); + + return ` +
+
+
+ ${hasDelay && realDepartureTime ? ` +
${departureTime}
+
${realDepartureTime}
+ ` : ` +
${departureTime}
+ `} +
+
+ ${hasDelay ? `+${delayMinutes}min` : isGone ? 'Parti' : 'À l\'heure'} +
+
+
${this.renderIcone(this.config.departure_station_emoji)}
+
+ ` + } + + /** + * Rendu de la section d'arrivée pour un train donné, en affichant l'heure d'arrivée prévue, l'heure d'arrivée réelle si le train a du retard. + * @param {object} trainAttributes - Les attributs du train + * @returns {string} Une chaîne HTML représentant la section d'arrivée du train + */ + renderArrival(trainAttributes) { + const hasDelay = trainAttributes.has_delay || false; + const isArrived = new Date() > this.parseTime(trainAttributes.arrival_time) + const delayMinutes = trainAttributes.delay_minutes || 0; + const arrivalTime = this.formatTime(trainAttributes.base_arrival_time); + const realArrivalTime = this.formatTime(trainAttributes.arrival_time); + + return ` +
+
${this.renderIcone(this.config.arrival_station_emoji)}
+
+
+ ${hasDelay && realArrivalTime ? ` +
${arrivalTime}
+
${realArrivalTime}
+ ` : ` +
${arrivalTime}
+ `} +
+
+ ${hasDelay ? `+${delayMinutes}min` : isArrived ? 'Arrivé' : 'À l\'heure'} +
+
+
+ `; + } + + /** + * Rendu du CSS pour la carte, en définissant les styles de base pour la carte, les lignes de train, les barres de progression, les emojis, et les informations de station + * @return {string} Une chaîne HTML contenant les styles CSS pour la carte. + */ + renderCss() { + return ` - - -
-
-
${this.config.title}
-
- - ${trainLinesHTML} - -
-
`; } - renderTrainLines(trains, currentTime) { - return trains.map((train, index) => { - const position = this.calculateTrainPosition(train.attributes.departure_time, currentTime); - const delayMinutes = train.attributes.delay_minutes || 0; - const hasDelay = train.attributes.has_delay || false; - const trainColor = this.getTrainColor(delayMinutes, hasDelay); - const formattedTime = this.formatTime(train.attributes.departure_time); - const realArrivalTime = this.calculateRealArrivalTime(train.attributes.departure_time, delayMinutes); - - const html = ` -
-
- ${position >= 0 && position <= 100 ? ` -
- ${this.config.train_emoji} -
- ` : ` - - `} -
- -
-
${this.config.train_station_emoji}
-
-
- ${hasDelay && realArrivalTime ? ` -
${formattedTime}
-
${realArrivalTime}
- ` : ` -
${formattedTime}
- `} -
-
- ${hasDelay ? `+${delayMinutes}min` : 'À l\'heure'} -
-
-
-
- `; - - return html; - }).join(''); - } - - getCardSize() { - return Math.max(3, this.config.train_lines + 1); - } } // Définir l'élément custom