up
This commit is contained in:
@@ -79,15 +79,17 @@
|
||||
filteredSpots = applyFilters(spots, spotFilters, watchlist);
|
||||
}
|
||||
}
|
||||
|
||||
$: if (typeof localStorage !== 'undefined') {
|
||||
localStorage.setItem('soundEnabled', soundEnabled.toString());
|
||||
}
|
||||
|
||||
// Détecter les nouveaux spots et jouer les sons appropriés
|
||||
$: if (spots.length > 0 && soundEnabled) {
|
||||
checkForNewSpots(spots, previousSpots, watchlist);
|
||||
previousSpots = [...spots];
|
||||
// ✅ Ne garder que les 100 derniers spots pour la comparaison
|
||||
previousSpots = spots.slice(0, 100);
|
||||
}
|
||||
|
||||
// ✅ 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
|
||||
@@ -318,6 +320,14 @@
|
||||
case 'dxccProgress':
|
||||
dxccProgress = message.data || { worked: 0, total: 340, percentage: 0 };
|
||||
break;
|
||||
case 'milestone': // ✅ AJOUTER
|
||||
const milestoneData = message.data;
|
||||
const toastType = milestoneData.type === 'qso' ? 'milestone' : 'band';
|
||||
showToast(milestoneData.message, toastType);
|
||||
if (soundEnabled) {
|
||||
playSound('milestone');
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -425,11 +435,22 @@
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
const savedSoundEnabled = localStorage.getItem('soundEnabled');
|
||||
if (savedSoundEnabled !== null) {
|
||||
soundEnabled = savedSoundEnabled === 'true';
|
||||
}
|
||||
connectWebSocket();
|
||||
fetchSolarData();
|
||||
|
||||
|
||||
const solarInterval = setInterval(fetchSolarData, 15 * 60 * 1000);
|
||||
|
||||
|
||||
const cleanupInterval = setInterval(() => {
|
||||
// Nettoyer previousSpots
|
||||
if (previousSpots.length > 100) {
|
||||
previousSpots = previousSpots.slice(0, 100);
|
||||
}
|
||||
}, 60000);
|
||||
|
||||
const handleSendSpot = (e) => {
|
||||
sendCallsign(e.detail.callsign, e.detail.frequency, e.detail.mode);
|
||||
};
|
||||
@@ -444,7 +465,7 @@
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="bg-gradient-to-br from-slate-900 via-slate-800 to-slate-900 text-white min-h-screen p-4">
|
||||
<div class="bg-gradient-to-br from-slate-900 via-slate-800 to-slate-900 text-white min-h-screen p-2">
|
||||
<!-- Gestionnaire de sons -->
|
||||
<SoundManager bind:enabled={soundEnabled} />
|
||||
|
||||
@@ -460,7 +481,9 @@
|
||||
{stats}
|
||||
{solarData}
|
||||
{wsStatus}
|
||||
on:shutdown={shutdownApp}
|
||||
{soundEnabled}
|
||||
on:shutdown={shutdownApp}
|
||||
on:toggleSound={() => soundEnabled = !soundEnabled}
|
||||
/>
|
||||
|
||||
<StatsCards
|
||||
@@ -475,9 +498,9 @@
|
||||
on:toggleFilter={(e) => toggleFilter(e.detail)}
|
||||
/>
|
||||
|
||||
<div class="grid grid-cols-4 gap-3 overflow-hidden" style="height: calc(100vh - 360px); min-height: 500px;">
|
||||
<div class="col-span-3 overflow-hidden">
|
||||
<SpotsTable
|
||||
<div class="grid grid-cols-[2.8fr_1.2fr] gap-3 overflow-hidden" style="height: calc(100vh - 280px); min-height: 500px;">
|
||||
<div class="overflow-hidden">
|
||||
<SpotsTable
|
||||
spots={filteredSpots}
|
||||
{watchlist}
|
||||
myCallsign={stats.myCallsign}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<script>
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
|
||||
export let soundEnabled = true;
|
||||
export let stats;
|
||||
export let solarData;
|
||||
export let wsStatus;
|
||||
@@ -50,7 +51,7 @@
|
||||
FlexDXCluster
|
||||
</h1>
|
||||
<div class="flex items-center gap-3 text-xs text-slate-400">
|
||||
<span>F4BPO • <span>{stats.totalContacts}</span> Contacts</span>
|
||||
<span>{stats.myCallsign || 'N/A'} • <span>{stats.totalContacts}</span> Contacts</span>
|
||||
<span class="text-slate-600">|</span>
|
||||
<span class="flex items-center gap-1">
|
||||
<span class="font-semibold text-amber-400">SFI:</span>
|
||||
@@ -100,6 +101,25 @@
|
||||
Flex
|
||||
</span>
|
||||
|
||||
<!-- Bouton Son -->
|
||||
<button
|
||||
on:click={() => dispatch('toggleSound')}
|
||||
class="px-3 py-1.5 rounded-lg transition-colors flex items-center gap-2 text-sm {soundEnabled ? 'bg-blue-600 hover:bg-blue-700 text-white' : 'bg-slate-700/50 hover:bg-slate-700 text-slate-300'}"
|
||||
title={soundEnabled ? 'Mute sounds' : 'Enable sounds'}>
|
||||
{#if soundEnabled}
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15.536 8.464a5 5 0 010 7.072m2.828-9.9a9 9 0 010 12.728M5.586 15H4a1 1 0 01-1-1v-4a1 1 0 011-1h1.586l4.707-4.707C10.923 3.663 12 4.109 12 5v14c0 .891-1.077 1.337-1.707.707L5.586 15z"></path>
|
||||
</svg>
|
||||
<span>Sound On</span>
|
||||
{:else}
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5.586 15H4a1 1 0 01-1-1v-4a1 1 0 011-1h1.586l4.707-4.707C10.923 3.663 12 4.109 12 5v14c0 .891-1.077 1.337-1.707.707L5.586 15z" clip-rule="evenodd"></path>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2"></path>
|
||||
</svg>
|
||||
<span>Sound Off</span>
|
||||
{/if}
|
||||
</button>
|
||||
|
||||
<button
|
||||
on:click={() => dispatch('shutdown')}
|
||||
class="px-3 py-1.5 text-xs bg-red-600 hover:bg-red-700 rounded transition-colors flex items-center gap-1">
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
});
|
||||
|
||||
function handlePlaySound(event) {
|
||||
if (!enabled || isMuted) return;
|
||||
if (!enabled) return; // ✅ Utiliser 'enabled' (qui vient de App.svelte) au lieu de 'isMuted'
|
||||
|
||||
const { type } = event.detail;
|
||||
|
||||
@@ -89,10 +89,20 @@
|
||||
oscillator.start(startTime);
|
||||
oscillator.stop(startTime + duration);
|
||||
}
|
||||
|
||||
function playMilestoneSound() {
|
||||
if (!audioContext) return;
|
||||
|
||||
const now = audioContext.currentTime;
|
||||
// Mélodie festive : E5 -> G5 -> A5 -> C6
|
||||
playBeep(now, 659.25, 0.15);
|
||||
playBeep(now + 0.15, 783.99, 0.15);
|
||||
playBeep(now + 0.3, 880.00, 0.15);
|
||||
playBeep(now + 0.45, 1046.50, 0.2);
|
||||
}
|
||||
|
||||
function toggleMute() {
|
||||
isMuted = !isMuted;
|
||||
localStorage.setItem('soundMuted', isMuted.toString());
|
||||
enabled = !enabled; // ✅ Modifier 'enabled' au lieu de 'isMuted'
|
||||
}
|
||||
|
||||
function updateVolume(newVolume) {
|
||||
@@ -105,9 +115,9 @@
|
||||
<div class="fixed bottom-4 right-4 flex items-center gap-2 bg-slate-800/90 backdrop-blur rounded-lg border border-slate-700/50 p-2 shadow-lg z-50">
|
||||
<button
|
||||
on:click={toggleMute}
|
||||
class="p-2 rounded transition-colors {isMuted ? 'bg-red-600/20 text-red-400 hover:bg-red-600/30' : 'bg-slate-700/50 text-slate-300 hover:bg-slate-700'}"
|
||||
title={isMuted ? 'Unmute sounds' : 'Mute sounds'}>
|
||||
{#if isMuted}
|
||||
class="p-2 rounded transition-colors {!enabled ? 'bg-red-600/20 text-red-400 hover:bg-red-600/30' : 'bg-slate-700/50 text-slate-300 hover:bg-slate-700'}"
|
||||
title={!enabled ? 'Unmute sounds' : 'Mute sounds'}>
|
||||
{#if !enabled}
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5.586 15H4a1 1 0 01-1-1v-4a1 1 0 011-1h1.586l4.707-4.707C10.923 3.663 12 4.109 12 5v14c0 .891-1.077 1.337-1.707.707L5.586 15z" clip-rule="evenodd"></path>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2"></path>
|
||||
|
||||
@@ -1,37 +1,41 @@
|
||||
<script>
|
||||
export let message;
|
||||
export let type = 'info'; // 'success', 'error', 'warning', 'info'
|
||||
export let type = 'info'; // 'success', 'error', 'warning', 'info', 'milestone', 'band'
|
||||
|
||||
const icons = {
|
||||
success: `<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path>`,
|
||||
error: `<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>`,
|
||||
warning: `<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"></path>`,
|
||||
info: ''
|
||||
info: `<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path>`,
|
||||
milestone: `<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 3v4M3 5h4M6 17v4m-2-2h4m5-16l2.286 6.857L21 12l-5.714 2.143L13 21l-2.286-6.857L5 12l5.714-2.143L13 3z"></path>`,
|
||||
band: `<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9.663 17h4.673M12 3v1m6.364 1.636l-.707.707M21 12h-1M4 12H3m3.343-5.657l-.707-.707m2.828 9.9a5 5 0 117.072 0l-.548.547A3.374 3.374 0 0014 18.469V19a2 2 0 11-4 0v-.531c0-.895-.356-1.754-.988-2.386l-.548-.547z"></path>`
|
||||
};
|
||||
|
||||
const colors = {
|
||||
success: 'bg-green-500',
|
||||
error: 'bg-red-500',
|
||||
warning: 'bg-orange-500',
|
||||
info: 'bg-blue-500'
|
||||
info: 'bg-blue-500',
|
||||
milestone: 'bg-gradient-to-r from-purple-500 to-pink-500',
|
||||
band: 'bg-gradient-to-r from-orange-500 to-amber-500'
|
||||
};
|
||||
</script>
|
||||
|
||||
<div class="fixed bottom-5 right-5 {colors[type]} text-white px-5 py-3 rounded-lg shadow-lg z-50 animate-in slide-in-from-bottom-5 duration-300">
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="fixed bottom-5 right-5 {colors[type]} text-white px-5 py-3 rounded-lg shadow-lg z-50 animate-in slide-in-from-bottom-5 duration-300 min-w-[300px] backdrop-blur-sm">
|
||||
<div class="flex items-center gap-3">
|
||||
{#if icons[type]}
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<svg class="w-6 h-6 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
{@html icons[type]}
|
||||
</svg>
|
||||
{/if}
|
||||
<span>{message}</span>
|
||||
<span class="font-medium text-sm">{message}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
@keyframes slide-in-from-bottom {
|
||||
from {
|
||||
transform: translateY(400px);
|
||||
transform: translateY(100px);
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
|
||||
@@ -75,17 +75,24 @@
|
||||
).length;
|
||||
}
|
||||
|
||||
function getMatchingSpotsForCallsign(callsign) {
|
||||
const spots = watchlistSpots.filter(s => s.dx === callsign || s.dx.startsWith(callsign));
|
||||
function getMatchingSpotsForCallsign(callsign) {
|
||||
const spots = watchlistSpots.filter(s => s.dx === callsign || s.dx.startsWith(callsign));
|
||||
|
||||
// ✅ Trier par bande d'abord, puis par heure
|
||||
const bandOrder = { '160M': 0, '80M': 1, '60M': 2, '40M': 3, '30M': 4, '20M': 5, '17M': 6, '15M': 7, '12M': 8, '10M': 9, '6M': 10 };
|
||||
|
||||
return spots.sort((a, b) => {
|
||||
// Trier par bande en premier
|
||||
const bandA = bandOrder[a.band] ?? 99;
|
||||
const bandB = bandOrder[b.band] ?? 99;
|
||||
if (bandA !== bandB) return bandA - bandB;
|
||||
|
||||
// ✅ Trier les spots par heure décroissante (plus récent en premier)
|
||||
return spots.sort((a, b) => {
|
||||
// Comparer les heures UTC (format "HH:MM")
|
||||
const timeA = a.utcTime || "00:00";
|
||||
const timeB = b.utcTime || "00:00";
|
||||
return timeB.localeCompare(timeA);
|
||||
});
|
||||
}
|
||||
// Si même bande, trier par heure (plus récent en premier)
|
||||
const timeA = a.utcTime || "00:00";
|
||||
const timeB = b.utcTime || "00:00";
|
||||
return timeB.localeCompare(timeA);
|
||||
});
|
||||
}
|
||||
|
||||
async function addToWatchlist() {
|
||||
const callsign = newCallsign.trim().toUpperCase();
|
||||
|
||||
Reference in New Issue
Block a user