From ec248f9c959859478e9a71b478932f1cbc8c3a5d Mon Sep 17 00:00:00 2001 From: Gregory Salaun Date: Mon, 13 Oct 2025 23:29:09 +0530 Subject: [PATCH] sound --- Makefile | 8 +- flex.sqlite-journal | Bin 0 -> 12824 bytes frontend/src/App.svelte | 92 +++++++++--- frontend/src/components/Sidebar.svelte | 21 ++- frontend/src/components/SoundManager.svelte | 158 ++++++++++++++++++++ frontend/src/components/WatchlistTab.svelte | 61 ++++++-- watchlist.json | 2 +- 7 files changed, 301 insertions(+), 41 deletions(-) create mode 100644 flex.sqlite-journal create mode 100644 frontend/src/components/SoundManager.svelte diff --git a/Makefile b/Makefile index ec279e7..6e85b38 100644 --- a/Makefile +++ b/Makefile @@ -25,13 +25,13 @@ help: ## install-deps: Installe les dépendances npm install-deps: - @echo "[1/2] Installation des dépendances npm..." + @echo "[1/2] Installation des dependances npm..." cd $(FRONTEND_DIR) && npm install - @echo "Dépendances installées" + @echo "Dependances installees" @echo "" - @echo "[2/2] Vérification de Go..." + @echo "[2/2] Verification de Go..." @go version - @echo "Go est installé" + @echo "Go est installe" ## frontend: Build le frontend Svelte frontend: diff --git a/flex.sqlite-journal b/flex.sqlite-journal new file mode 100644 index 0000000000000000000000000000000000000000..a5f868f1b25e5ed5e5b3e76c54862e9dbcea9243 GIT binary patch literal 12824 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 F`d=4n5aIv; literal 0 HcmV?d00001 diff --git a/frontend/src/App.svelte b/frontend/src/App.svelte index b603276..2d50616 100644 --- a/frontend/src/App.svelte +++ b/frontend/src/App.svelte @@ -7,9 +7,11 @@ import Sidebar from './components/Sidebar.svelte'; import Toast from './components/Toast.svelte'; import ErrorBanner from './components/ErrorBanner.svelte'; + import SoundManager from './components/SoundManager.svelte'; // State let spots = []; + let previousSpots = []; // Pour détecter les nouveaux spots let filteredSpots = []; let stats = { totalSpots: 0, @@ -23,13 +25,14 @@ filters: { skimmer: false, ft8: false, ft4: false } }; let topSpotters = []; - let watchlist = []; + let watchlist = []; // ✅ Initialisé vide, sera rempli par WebSocket let recentQSOs = []; let logStats = { today: 0, thisWeek: 0, thisMonth: 0, total: 0 }; let dxccProgress = { worked: 0, total: 340, percentage: 0 }; let solarData = { sfi: 'N/A', sunspots: 'N/A', aIndex: 'N/A', kIndex: 'N/A' }; let activeTab = 'stats'; + let showOnlyActive = false; // ✅ État global pour persister entre les onglets let wsStatus = 'disconnected'; let errorMessage = ''; let toastMessage = ''; @@ -58,13 +61,15 @@ band12M: false, band10M: false, band6M: false - }; + }; // WebSocket let ws; let reconnectTimer; let reconnectAttempts = 0; let maxReconnectAttempts = 10; + let soundEnabled = true; + let isShuttingDown = false; // ✅ Flag pour éviter les erreurs pendant le shutdown // Reactive filtered spots $: { @@ -75,6 +80,52 @@ } } + // Détecter les nouveaux spots et jouer les sons appropriés + $: if (spots.length > 0 && soundEnabled) { + checkForNewSpots(spots, previousSpots, watchlist); + previousSpots = [...spots]; + } + + // ✅ SUPPRIMÉ - La watchlist est gérée côté serveur via WebSocket + // Les fonctions addToWatchlist et removeFromWatchlist ne sont plus nécessaires + + function checkForNewSpots(currentSpots, prevSpots, wl) { + // Ne pas jouer de sons au chargement initial + if (prevSpots.length === 0) return; + + // Créer un Set des IDs précédents pour une recherche rapide + const previousIds = new Set(prevSpots.map(s => `${s.DX}-${s.Frequency}-${s.Time}`)); + + // Trouver les nouveaux spots + const newSpots = currentSpots.filter(spot => { + const spotId = `${spot.DX}-${spot.Frequency}-${spot.Time}`; + return !previousIds.has(spotId); + }); + + if (newSpots.length === 0) return; + + // Vérifier s'il y a un nouveau DXCC (priorité maximale) + const hasNewDXCC = newSpots.some(spot => spot.NewDXCC === true); + if (hasNewDXCC) { + playSound('newDXCC'); + return; // Ne jouer qu'un seul son + } + + // Vérifier s'il y a un spot de la watchlist + const hasWatchlistSpot = newSpots.some(spot => + wl.some(pattern => spot.DX === pattern || spot.DX.startsWith(pattern)) + ); + if (hasWatchlistSpot) { + playSound('watchlist'); + } + } + + function playSound(type) { + window.dispatchEvent(new CustomEvent('playSound', { + detail: { type } + })); + } + function applyFilters(allSpots, filters, wl) { const bandFiltersActive = filters.band160M || filters.band80M || filters.band60M || filters.band40M || filters.band30M || filters.band20M || filters.band17M || @@ -255,6 +306,7 @@ topSpotters = message.data || []; break; case 'watchlist': + // ✅ La watchlist est mise à jour par WebSocket watchlist = message.data || []; break; case 'log': @@ -344,13 +396,11 @@ if (data.success) { showToast('FlexDXCluster shutting down...', 'info'); - // Fermer le WebSocket et arrêter les tentatives de reconnexion if (ws) ws.close(); if (reconnectTimer) clearTimeout(reconnectTimer); wsStatus = 'disconnected'; - maxReconnectAttempts = 0; // Empêcher les reconnexions + maxReconnectAttempts = 0; - // Afficher la page de shutdown après 1 seconde setTimeout(() => { document.body.innerHTML = `
@@ -378,10 +428,8 @@ connectWebSocket(); fetchSolarData(); - // Update solar data every 15 minutes const solarInterval = setInterval(fetchSolarData, 15 * 60 * 1000); - // Listen for sendSpot events from watchlist const handleSendSpot = (e) => { sendCallsign(e.detail.callsign, e.detail.frequency, e.detail.mode); }; @@ -397,6 +445,9 @@
+ + + {#if errorMessage} errorMessage = ''} /> {/if} @@ -424,8 +475,8 @@ on:toggleFilter={(e) => toggleFilter(e.detail)} /> -
-
+
+
- showToast(e.detail.message, e.detail.type)} - /> +
+ showToast(e.detail.message, e.detail.type)} + /> +
\ No newline at end of file diff --git a/frontend/src/components/Sidebar.svelte b/frontend/src/components/Sidebar.svelte index 0408f1a..49eeb86 100644 --- a/frontend/src/components/Sidebar.svelte +++ b/frontend/src/components/Sidebar.svelte @@ -11,11 +11,17 @@ export let recentQSOs; export let logStats; export let dxccProgress; + export let showOnlyActive = false; // ✅ Export pour persister l'état const dispatch = createEventDispatcher(); + + // ✅ Propagation des évènements vers le parent + function handleToast(event) { + dispatch('toast', event.detail); + } -
+
+ +
+ + + + updateVolume(e.target.value / 100)} + class="w-20 h-1 bg-slate-700 rounded-lg appearance-none cursor-pointer accent-blue-500" + title="Volume: {Math.round(volume * 100)}%" + /> + {Math.round(volume * 100)}% +
+
+{/if} + + \ No newline at end of file diff --git a/frontend/src/components/WatchlistTab.svelte b/frontend/src/components/WatchlistTab.svelte index 601b417..c3b1d2a 100644 --- a/frontend/src/components/WatchlistTab.svelte +++ b/frontend/src/components/WatchlistTab.svelte @@ -1,25 +1,61 @@ -
+

Watchlist

-
+
{#if displayList.length === 0}
@@ -186,7 +227,7 @@
{#if count > 0} -
+
{#each matchingSpots.slice(0, 10) as spot}