♻️ refactor : externaliser utilitaires string et time
- Déplacer la fonction `simpleHash` de useCoverImage.ts vers un nouveau fichier utils/string.ts - Déplacer la logique d'analyse des durées HH:MM[:SS] vers un nouveau fichier utils/time.ts - Créer les fonctions utilitaires `addCacheBust`, `normalizeUrl` et `truncate` - Mettre à jour les imports dans useCoverImage.ts,useRenderers.ts -Rendre le code plus réutilisable et maintenable
This commit is contained in:
@@ -9,6 +9,7 @@
|
|||||||
*/
|
*/
|
||||||
import { ref, watch, computed, type Ref } from "vue";
|
import { ref, watch, computed, type Ref } from "vue";
|
||||||
import { imageCache, useImageCache } from "./imageCache";
|
import { imageCache, useImageCache } from "./imageCache";
|
||||||
|
import { simpleHash } from "../utils/string";
|
||||||
|
|
||||||
export interface CoverImageOptions {
|
export interface CoverImageOptions {
|
||||||
maxRetries?: number;
|
maxRetries?: number;
|
||||||
@@ -44,17 +45,6 @@ export function useCoverImage(
|
|||||||
// Computed: synchroniser avec le cache centralisé
|
// Computed: synchroniser avec le cache centralisé
|
||||||
// Note: on garde le controle local du loaded/error pour éviter les effets de bord
|
// Note: on garde le controle local du loaded/error pour éviter les effets de bord
|
||||||
|
|
||||||
// Fonction de hash simple pour le cache-busting
|
|
||||||
function simpleHash(str: string): string {
|
|
||||||
let hash = 0;
|
|
||||||
for (let i = 0; i < str.length; i++) {
|
|
||||||
const char = str.charCodeAt(i);
|
|
||||||
hash = (hash << 5) - hash + char;
|
|
||||||
hash = hash & hash;
|
|
||||||
}
|
|
||||||
return Math.abs(hash).toString(36);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Génère une URL avec cache-busting
|
// Génère une URL avec cache-busting
|
||||||
function getCacheBustedUrl(url: string, retry: number): string {
|
function getCacheBustedUrl(url: string, retry: number): string {
|
||||||
if (!forceReload && retry === 0) {
|
if (!forceReload && retry === 0) {
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import { ref, reactive, computed, toRaw, type Ref } from "vue";
|
|||||||
import { api } from "../services/pmocontrol/api";
|
import { api } from "../services/pmocontrol/api";
|
||||||
import { useSSE } from "./useSSE";
|
import { useSSE } from "./useSSE";
|
||||||
import { apiCache } from "./apiCache";
|
import { apiCache } from "./apiCache";
|
||||||
|
import { parseTimeToMs } from "../utils/time";
|
||||||
import type {
|
import type {
|
||||||
RendererSummary,
|
RendererSummary,
|
||||||
RendererState,
|
RendererState,
|
||||||
@@ -125,35 +126,14 @@ function ensureSSEInitialized() {
|
|||||||
// Le backend envoie TOUJOURS les deux valeurs (même si null)
|
// Le backend envoie TOUJOURS les deux valeurs (même si null)
|
||||||
|
|
||||||
// Convertir rel_time (HH:MM:SS) en millisecondes
|
// Convertir rel_time (HH:MM:SS) en millisecondes
|
||||||
if (event.rel_time) {
|
const positionMs = parseTimeToMs(event.rel_time ?? null);
|
||||||
const parts = event.rel_time.split(":").map(Number);
|
snapshot.state.position_ms = positionMs ?? 0;
|
||||||
if (parts.length === 3) {
|
|
||||||
snapshot.state.position_ms =
|
|
||||||
((parts[0] ?? 0) * 3600 +
|
|
||||||
(parts[1] ?? 0) * 60 +
|
|
||||||
(parts[2] ?? 0)) *
|
|
||||||
1000;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Si rel_time est null/undefined, mettre position à 0
|
|
||||||
snapshot.state.position_ms = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convertir track_duration (HH:MM:SS) en millisecondes
|
// Convertir track_duration (HH:MM:SS) en millisecondes
|
||||||
if (event.track_duration) {
|
const durationMs = parseTimeToMs(event.track_duration ?? null);
|
||||||
const parts = event.track_duration.split(":").map(Number);
|
// Si track_duration est null/undefined (flux continu sans durée),
|
||||||
if (parts.length === 3) {
|
// mettre duration_ms à null pour afficher "--:--"
|
||||||
snapshot.state.duration_ms =
|
snapshot.state.duration_ms = durationMs;
|
||||||
((parts[0] ?? 0) * 3600 +
|
|
||||||
(parts[1] ?? 0) * 60 +
|
|
||||||
(parts[2] ?? 0)) *
|
|
||||||
1000;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Si track_duration est null/undefined (flux continu sans durée),
|
|
||||||
// mettre duration_ms à null pour afficher "--:--"
|
|
||||||
snapshot.state.duration_ms = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Important: Trigger reactivity en réassignant l'objet complet avec deep copy
|
// Important: Trigger reactivity en réassignant l'objet complet avec deep copy
|
||||||
// Le shallow copy ne suffit pas car snapshot.state est partagé entre renderers
|
// Le shallow copy ne suffit pas car snapshot.state est partagé entre renderers
|
||||||
|
|||||||
49
pmoapp/webapp/src/utils/string.ts
Normal file
49
pmoapp/webapp/src/utils/string.ts
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
/**
|
||||||
|
* Utilitaires pour les chaînes de caractères
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Génère un hash simple et rapide pour une chaîne
|
||||||
|
* @param str - Chaîne à hasher
|
||||||
|
* @returns Hash sous forme de chaîne hexadécimale positive
|
||||||
|
*/
|
||||||
|
export function simpleHash(str: string): string {
|
||||||
|
let hash = 0;
|
||||||
|
for (let i = 0; i < str.length; i++) {
|
||||||
|
const char = str.charCodeAt(i);
|
||||||
|
hash = (hash << 5) - hash + char;
|
||||||
|
hash = hash & hash; // Convert to 32bit integer
|
||||||
|
}
|
||||||
|
return Math.abs(hash).toString(36);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ajoute un paramètre cache-busting à une URL
|
||||||
|
* @param url - URL originale
|
||||||
|
* @param cacheKey - Clé de cache (hash ou timestamp)
|
||||||
|
* @returns URL avec le paramètre _cb ajouté
|
||||||
|
*/
|
||||||
|
export function addCacheBust(url: string, cacheKey: string): string {
|
||||||
|
const separator = url.includes('?') ? '&' : '?';
|
||||||
|
return `${url}${separator}_cb=${cacheKey}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Nettoie une URL en supprimant les paramètres de cache-busting
|
||||||
|
* @param url - URL avec possibly _cb params
|
||||||
|
* @returns URL nettoyée
|
||||||
|
*/
|
||||||
|
export function normalizeUrl(url: string): string {
|
||||||
|
return url.replace(/[?&]_cb=[^&]*/, '');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tronque une chaîne à une longueur maximale
|
||||||
|
* @param str - Chaîne à tronquer
|
||||||
|
* @param maxLength - Longueur maximale
|
||||||
|
* @returns Chaîne tronquée avec suffix si nécessaire
|
||||||
|
*/
|
||||||
|
export function truncate(str: string, maxLength: number, suffix = '...'): string {
|
||||||
|
if (str.length <= maxLength) return str;
|
||||||
|
return str.slice(0, maxLength - suffix.length) + suffix;
|
||||||
|
}
|
||||||
60
pmoapp/webapp/src/utils/time.ts
Normal file
60
pmoapp/webapp/src/utils/time.ts
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
/**
|
||||||
|
* Utilitaires pour les dates et durées
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convertit une durée au format HH:MM:SS en millisecondes
|
||||||
|
* @param time - Durée au format "HH:MM:SS" ou "MM:SS"
|
||||||
|
* @returns Durée en millisecondes, ou null si invalide
|
||||||
|
*/
|
||||||
|
export function parseTimeToMs(time: string | null | undefined): number | null {
|
||||||
|
if (!time) return null;
|
||||||
|
|
||||||
|
const parts = time.split(':').map(Number);
|
||||||
|
|
||||||
|
if (parts.length === 3) {
|
||||||
|
// HH:MM:SS
|
||||||
|
const hours = parts[0] ?? 0;
|
||||||
|
const minutes = parts[1] ?? 0;
|
||||||
|
const seconds = parts[2] ?? 0;
|
||||||
|
if (isNaN(hours) || isNaN(minutes) || isNaN(seconds)) return null;
|
||||||
|
return (hours * 3600 + minutes * 60 + seconds) * 1000;
|
||||||
|
} else if (parts.length === 2) {
|
||||||
|
// MM:SS
|
||||||
|
const minutes = parts[0] ?? 0;
|
||||||
|
const seconds = parts[1] ?? 0;
|
||||||
|
if (isNaN(minutes) || isNaN(seconds)) return null;
|
||||||
|
return (minutes * 60 + seconds) * 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convertit des millisecondes en format HH:MM:SS
|
||||||
|
* @param ms - Durée en millisecondes
|
||||||
|
* @returns Durée au format "HH:MM:SS" ou "MM:SS"
|
||||||
|
*/
|
||||||
|
export function formatMsToTime(ms: number | null): string {
|
||||||
|
if (ms === null || ms === undefined || ms < 0) return '--:--';
|
||||||
|
|
||||||
|
const totalSeconds = Math.floor(ms / 1000);
|
||||||
|
const hours = Math.floor(totalSeconds / 3600);
|
||||||
|
const minutes = Math.floor((totalSeconds % 3600) / 60);
|
||||||
|
const seconds = totalSeconds % 60;
|
||||||
|
|
||||||
|
const h = hours > 0 ? `${hours}:` : '';
|
||||||
|
const m = `${minutes.toString().padStart(2, '0')}:`;
|
||||||
|
const s = seconds.toString().padStart(2, '0');
|
||||||
|
|
||||||
|
return `${h}${m}${s}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convertit des millisecondes en format court (pour l'affichage progress)
|
||||||
|
* @param ms - Durée en millisecondes
|
||||||
|
* @returns Durée au format "X:XX" ou "X:XX:XX"
|
||||||
|
*/
|
||||||
|
export function formatMsToShortTime(ms: number | null): string {
|
||||||
|
return formatMsToTime(ms);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user