legend added

pull/27/head
accius 4 days ago
parent 5c1f7c2e62
commit b92169c0f2

@ -1496,7 +1496,7 @@
// ============================================
// LEAFLET MAP COMPONENT
// ============================================
const WorldMap = ({ deLocation, dxLocation, onDXChange, potaSpots, mySpots, dxPaths, satellites }) => {
const WorldMap = ({ deLocation, dxLocation, onDXChange, potaSpots, mySpots, dxPaths, satellites, showDXPaths, showPOTA, showSatellites }) => {
const mapRef = useRef(null);
const mapInstanceRef = useRef(null);
const tileLayerRef = useRef(null);
@ -1513,7 +1513,7 @@
const satMarkersRef = useRef([]);
const satTracksRef = useRef([]);
// Load map view settings from localStorage
// Load map style from localStorage
const getStoredMapSettings = () => {
try {
const stored = localStorage.getItem('openhamclock_mapSettings');
@ -1523,19 +1523,15 @@
const storedSettings = getStoredMapSettings();
const [mapStyle, setMapStyle] = useState(storedSettings.mapStyle || 'dark');
const [showSatellites, setShowSatellites] = useState(storedSettings.showSatellites !== false);
const [showDXPaths, setShowDXPaths] = useState(storedSettings.showDXPaths !== false);
// Save map view settings to localStorage when they change
// Save map style to localStorage when changed
useEffect(() => {
try {
localStorage.setItem('openhamclock_mapSettings', JSON.stringify({
mapStyle,
showSatellites,
showDXPaths
mapStyle
}));
} catch (e) { console.error('Failed to save map settings:', e); }
}, [mapStyle, showSatellites, showDXPaths]);
}, [mapStyle]);
// Initialize map
useEffect(() => {
@ -1836,6 +1832,7 @@
}
}, [dxPaths, showDXPaths]);
// Update POTA markers
useEffect(() => {
if (!mapInstanceRef.current) return;
const map = mapInstanceRef.current;
@ -1844,21 +1841,23 @@
potaMarkersRef.current.forEach(m => map.removeLayer(m));
potaMarkersRef.current = [];
// Add new POTA markers
potaSpots.forEach(spot => {
if (spot.lat && spot.lon) {
const icon = L.divIcon({
className: '',
html: `<div style="background: #aa66ff; color: white; padding: 2px 6px; border-radius: 4px; font-size: 12px; font-family: JetBrains Mono; white-space: nowrap; border: 1px solid white;">${spot.call}</div>`,
iconAnchor: [20, 10]
});
const marker = L.marker([spot.lat, spot.lon], { icon })
.bindPopup(`<b>${spot.call}</b><br>${spot.ref}<br>${spot.freq} ${spot.mode}`)
.addTo(map);
potaMarkersRef.current.push(marker);
}
});
}, [potaSpots]);
// Add new POTA markers if enabled
if (showPOTA && potaSpots) {
potaSpots.forEach(spot => {
if (spot.lat && spot.lon) {
const icon = L.divIcon({
className: '',
html: `<div style="background: #aa66ff; color: white; padding: 2px 6px; border-radius: 4px; font-size: 12px; font-family: JetBrains Mono; white-space: nowrap; border: 1px solid white;">${spot.call}</div>`,
iconAnchor: [20, 10]
});
const marker = L.marker([spot.lat, spot.lon], { icon })
.bindPopup(`<b>${spot.call}</b><br>${spot.ref}<br>${spot.freq} ${spot.mode}`)
.addTo(map);
potaMarkersRef.current.push(marker);
}
});
}
}, [potaSpots, showPOTA]);
// Update satellite markers and tracks
useEffect(() => {
@ -1976,53 +1975,65 @@
))}
</select>
{/* Satellite toggle */}
<button
onClick={() => setShowSatellites(!showSatellites)}
style={{
position: 'absolute',
top: '10px',
left: '50px',
background: showSatellites ? 'rgba(0, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.7)',
border: `1px solid ${showSatellites ? '#00ffff' : '#666'}`,
color: showSatellites ? '#00ffff' : '#888',
padding: '6px 10px',
borderRadius: '4px',
fontSize: '11px',
fontFamily: 'JetBrains Mono',
cursor: 'pointer',
zIndex: 1000,
display: 'flex',
alignItems: 'center',
gap: '4px'
}}
>
🛰 {showSatellites ? 'SAT' : 'SAT'}
</button>
{/* DX Paths toggle */}
<button
onClick={() => setShowDXPaths(!showDXPaths)}
style={{
position: 'absolute',
top: '10px',
left: '118px',
background: showDXPaths ? 'rgba(68, 136, 255, 0.2)' : 'rgba(0, 0, 0, 0.7)',
border: `1px solid ${showDXPaths ? '#4488ff' : '#666'}`,
color: showDXPaths ? '#4488ff' : '#888',
padding: '6px 10px',
borderRadius: '4px',
fontSize: '11px',
fontFamily: 'JetBrains Mono',
cursor: 'pointer',
zIndex: 1000,
display: 'flex',
alignItems: 'center',
gap: '4px'
}}
>
📡 {showDXPaths ? 'DX' : 'DX'}
</button>
{/* Map Legend - Bottom of map */}
<div style={{
position: 'absolute',
bottom: '8px',
left: '50%',
transform: 'translateX(-50%)',
background: 'rgba(0, 0, 0, 0.85)',
border: '1px solid #444',
borderRadius: '6px',
padding: '6px 12px',
zIndex: 1000,
display: 'flex',
gap: '12px',
alignItems: 'center',
fontSize: '10px',
fontFamily: 'JetBrains Mono, monospace'
}}>
{/* DX Paths Band Colors */}
{showDXPaths && (
<div style={{ display: 'flex', gap: '6px', alignItems: 'center' }}>
<span style={{ color: '#888' }}>DX:</span>
<span style={{ color: '#ff6666' }}>160m</span>
<span style={{ color: '#ff9966' }}>80m</span>
<span style={{ color: '#ffcc66' }}>40m</span>
<span style={{ color: '#99ff66' }}>30m</span>
<span style={{ color: '#66ff99' }}>20m</span>
<span style={{ color: '#66ffcc' }}>17m</span>
<span style={{ color: '#66ccff' }}>15m</span>
<span style={{ color: '#6699ff' }}>12m</span>
<span style={{ color: '#9966ff' }}>10m</span>
<span style={{ color: '#ff66ff' }}>6m</span>
</div>
)}
{showDXPaths && (showPOTA || showSatellites) && <span style={{ color: '#444' }}>|</span>}
{/* POTA */}
{showPOTA && (
<div style={{ display: 'flex', gap: '4px', alignItems: 'center' }}>
<div style={{ width: '10px', height: '10px', background: '#aa66ff', borderRadius: '2px' }} />
<span style={{ color: '#aa66ff' }}>POTA</span>
</div>
)}
{/* Satellites */}
{showSatellites && (
<div style={{ display: 'flex', gap: '4px', alignItems: 'center' }}>
<span style={{ color: '#00ffff' }}>🛰</span>
<span style={{ color: '#00ffff' }}>SAT</span>
</div>
)}
{/* DE/DX markers */}
<span style={{ color: '#444' }}>|</span>
<div style={{ display: 'flex', gap: '6px', alignItems: 'center' }}>
<span style={{ color: '#00ff00' }}>● DE</span>
<span style={{ color: '#ff4444' }}>● DX</span>
<span style={{ color: '#ffff00' }}>☀ Sun</span>
</div>
</div>
</div>
);
};
@ -2201,13 +2212,32 @@
);
};
const DXClusterPanel = ({ spots, loading, activeSource }) => (
const DXClusterPanel = ({ spots, loading, activeSource, showOnMap, onToggleMap }) => (
<div style={{ background: 'var(--bg-panel)', border: '1px solid var(--border-color)', borderRadius: '8px', padding: '20px', backdropFilter: 'blur(10px)', maxHeight: '280px', overflow: 'hidden' }}>
<div style={{ fontSize: '12px', fontWeight: '600', color: 'var(--accent-cyan)', letterSpacing: '2px', marginBottom: '16px', display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
<span>🌐 DX CLUSTER</span>
<div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
{loading && <div className="loading-spinner" />}
{activeSource && <span style={{ fontSize: '10px', color: 'var(--text-muted)' }}>{activeSource}</span>}
<button
onClick={onToggleMap}
style={{
background: showOnMap ? 'rgba(68, 136, 255, 0.3)' : 'rgba(100, 100, 100, 0.3)',
border: `1px solid ${showOnMap ? '#4488ff' : '#666'}`,
color: showOnMap ? '#4488ff' : '#888',
padding: '2px 8px',
borderRadius: '4px',
fontSize: '10px',
fontFamily: 'JetBrains Mono',
cursor: 'pointer',
display: 'flex',
alignItems: 'center',
gap: '4px'
}}
title={showOnMap ? 'Hide DX paths on map' : 'Show DX paths on map'}
>
🗺️ {showOnMap ? 'ON' : 'OFF'}
</button>
<span style={{ fontSize: '12px', color: 'var(--accent-green)' }}>● LIVE</span>
</div>
</div>
@ -2224,12 +2254,31 @@
</div>
);
const POTAPanel = ({ activities, loading }) => (
const POTAPanel = ({ activities, loading, showOnMap, onToggleMap }) => (
<div style={{ background: 'var(--bg-panel)', border: '1px solid var(--border-color)', borderRadius: '8px', padding: '20px', backdropFilter: 'blur(10px)' }}>
<div style={{ fontSize: '12px', fontWeight: '600', color: 'var(--accent-cyan)', letterSpacing: '2px', marginBottom: '16px', display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
<span>🏕 POTA ACTIVITY</span>
<div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
{loading && <div className="loading-spinner" />}
<button
onClick={onToggleMap}
style={{
background: showOnMap ? 'rgba(170, 102, 255, 0.3)' : 'rgba(100, 100, 100, 0.3)',
border: `1px solid ${showOnMap ? '#aa66ff' : '#666'}`,
color: showOnMap ? '#aa66ff' : '#888',
padding: '2px 8px',
borderRadius: '4px',
fontSize: '10px',
fontFamily: 'JetBrains Mono',
cursor: 'pointer',
display: 'flex',
alignItems: 'center',
gap: '4px'
}}
title={showOnMap ? 'Hide POTA markers on map' : 'Show POTA markers on map'}
>
🗺️ {showOnMap ? 'ON' : 'OFF'}
</button>
<span style={{ fontSize: '12px', color: 'var(--accent-green)' }}>● LIVE</span>
</div>
</div>
@ -2345,7 +2394,8 @@
deGrid, dxGrid, deSunTimes, dxSunTimes, dxLocation, onDXChange,
spaceWeather, bandConditions, potaSpots, dxCluster, dxPaths, contests, propagation, mySpots, satellites,
localWeather, use12Hour, onTimeFormatToggle,
onSettingsClick, onFullscreenToggle, isFullscreen
onSettingsClick, onFullscreenToggle, isFullscreen,
mapLayers, toggleDXPaths, togglePOTA, toggleSatellites
}) => {
const bearing = calculateBearing(config.location.lat, config.location.lon, dxLocation.lat, dxLocation.lon);
const distance = calculateDistance(config.location.lat, config.location.lon, dxLocation.lat, dxLocation.lon);
@ -2517,6 +2567,9 @@
mySpots={mySpots.data}
dxPaths={dxPaths.data}
satellites={satellites.positions}
showDXPaths={mapLayers.showDXPaths}
showPOTA={mapLayers.showPOTA}
showSatellites={mapLayers.showSatellites}
/>
</div>
@ -2525,8 +2578,26 @@
{/* DX Cluster */}
<div>
<div style={{ ...labelStyle, marginBottom: '8px', display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<span>🌐 DX CLUSTER <span style={{ color: 'var(--accent-green)', fontSize: '12px' }}>● LIVE</span></span>
{dxCluster.activeSource && <span style={{ fontSize: '9px', color: 'var(--text-muted)', fontWeight: '400' }}>{dxCluster.activeSource}</span>}
<span>🌐 DX CLUSTER</span>
<div style={{ display: 'flex', alignItems: 'center', gap: '6px' }}>
<button
onClick={toggleDXPaths}
style={{
background: mapLayers.showDXPaths ? 'rgba(68, 136, 255, 0.3)' : 'rgba(100, 100, 100, 0.3)',
border: `1px solid ${mapLayers.showDXPaths ? '#4488ff' : '#666'}`,
color: mapLayers.showDXPaths ? '#4488ff' : '#888',
padding: '1px 6px',
borderRadius: '3px',
fontSize: '9px',
fontFamily: 'JetBrains Mono',
cursor: 'pointer'
}}
title={mapLayers.showDXPaths ? 'Hide on map' : 'Show on map'}
>
🗺️ {mapLayers.showDXPaths ? 'ON' : 'OFF'}
</button>
<span style={{ color: 'var(--accent-green)', fontSize: '10px' }}>● LIVE</span>
</div>
</div>
<div style={{ maxHeight: '180px', overflowY: 'auto' }}>
{dxCluster.data.slice(0, 8).map((s, i) => (
@ -2565,7 +2636,25 @@
{/* POTA */}
<div style={{ marginTop: '8px' }}>
<div style={{ ...labelStyle, marginBottom: '8px' }}>🏕 POTA</div>
<div style={{ ...labelStyle, marginBottom: '8px', display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<span>🏕 POTA</span>
<button
onClick={togglePOTA}
style={{
background: mapLayers.showPOTA ? 'rgba(170, 102, 255, 0.3)' : 'rgba(100, 100, 100, 0.3)',
border: `1px solid ${mapLayers.showPOTA ? '#aa66ff' : '#666'}`,
color: mapLayers.showPOTA ? '#aa66ff' : '#888',
padding: '1px 6px',
borderRadius: '3px',
fontSize: '9px',
fontFamily: 'JetBrains Mono',
cursor: 'pointer'
}}
title={mapLayers.showPOTA ? 'Hide on map' : 'Show on map'}
>
🗺️ {mapLayers.showPOTA ? 'ON' : 'OFF'}
</button>
</div>
<div style={{ maxHeight: '100px', overflowY: 'auto' }}>
{potaSpots.data.slice(0, 4).map((a, i) => (
<div key={i} style={{ padding: '3px 0', fontSize: '12px' }}>
@ -2990,6 +3079,27 @@
const [showSettings, setShowSettings] = useState(false);
const [isFullscreen, setIsFullscreen] = useState(false);
// Map layer visibility state with localStorage persistence
const [mapLayers, setMapLayers] = useState(() => {
try {
const stored = localStorage.getItem('openhamclock_mapLayers');
const defaults = { showDXPaths: true, showPOTA: true, showSatellites: true };
return stored ? { ...defaults, ...JSON.parse(stored) } : defaults;
} catch (e) { return { showDXPaths: true, showPOTA: true, showSatellites: true }; }
});
// Save map layer preferences when changed
useEffect(() => {
try {
localStorage.setItem('openhamclock_mapLayers', JSON.stringify(mapLayers));
} catch (e) { console.error('Failed to save map layers:', e); }
}, [mapLayers]);
// Toggle handlers for map layers
const toggleDXPaths = useCallback(() => setMapLayers(prev => ({ ...prev, showDXPaths: !prev.showDXPaths })), []);
const togglePOTA = useCallback(() => setMapLayers(prev => ({ ...prev, showPOTA: !prev.showPOTA })), []);
const toggleSatellites = useCallback(() => setMapLayers(prev => ({ ...prev, showSatellites: !prev.showSatellites })), []);
// 12/24 hour format preference with localStorage persistence
const [use12Hour, setUse12Hour] = useState(() => {
try {
@ -3125,6 +3235,10 @@
onSettingsClick={() => setShowSettings(true)}
onFullscreenToggle={handleFullscreenToggle}
isFullscreen={isFullscreen}
mapLayers={mapLayers}
toggleDXPaths={toggleDXPaths}
togglePOTA={togglePOTA}
toggleSatellites={toggleSatellites}
/>
<SettingsPanel
isOpen={showSettings}
@ -3329,7 +3443,18 @@
{/* CENTER - MAP */}
<div style={{ position: 'relative', borderRadius: '6px', overflow: 'hidden' }}>
<WorldMap deLocation={config.location} dxLocation={dxLocation} onDXChange={handleDXChange} potaSpots={potaSpots.data} mySpots={mySpots.data} dxPaths={dxPaths.data} satellites={satellites.positions} />
<WorldMap
deLocation={config.location}
dxLocation={dxLocation}
onDXChange={handleDXChange}
potaSpots={potaSpots.data}
mySpots={mySpots.data}
dxPaths={dxPaths.data}
satellites={satellites.positions}
showDXPaths={mapLayers.showDXPaths}
showPOTA={mapLayers.showPOTA}
showSatellites={mapLayers.showSatellites}
/>
<div style={{ position: 'absolute', bottom: '8px', left: '50%', transform: 'translateX(-50%)', fontSize: '13px', color: 'var(--text-muted)', background: 'rgba(0,0,0,0.7)', padding: '2px 8px', borderRadius: '4px' }}>
Click map to set DX • 73 de {config.callsign}
</div>
@ -3337,11 +3462,50 @@
{/* RIGHT SIDEBAR */}
<div style={{ display: 'flex', flexDirection: 'column', gap: '8px', overflow: 'hidden' }}>
{/* Satellite Toggle at top */}
<div className="panel" style={{ padding: '6px 10px', display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<span style={{ fontSize: '12px', color: '#00ffff', fontWeight: '700' }}>🛰 SATELLITES</span>
<button
onClick={toggleSatellites}
style={{
background: mapLayers.showSatellites ? 'rgba(0, 255, 255, 0.3)' : 'rgba(100, 100, 100, 0.3)',
border: `1px solid ${mapLayers.showSatellites ? '#00ffff' : '#666'}`,
color: mapLayers.showSatellites ? '#00ffff' : '#888',
padding: '2px 8px',
borderRadius: '4px',
fontSize: '10px',
fontFamily: 'JetBrains Mono',
cursor: 'pointer'
}}
title={mapLayers.showSatellites ? 'Hide satellites on map' : 'Show satellites on map'}
>
🗺️ {mapLayers.showSatellites ? 'ON' : 'OFF'}
</button>
</div>
{/* DX Cluster - Compact */}
<div className="panel" style={{ padding: '10px', flex: '1 1 auto', overflow: 'hidden', minHeight: 0 }}>
<div style={{ fontSize: '12px', color: 'var(--accent-green)', fontWeight: '700', marginBottom: '6px', display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<span>🌐 DX CLUSTER <span style={{ color: 'var(--accent-green)', fontSize: '12px' }}>● LIVE</span></span>
{dxCluster.activeSource && <span style={{ fontSize: '9px', color: 'var(--text-muted)', fontWeight: '400' }}>{dxCluster.activeSource}</span>}
<span>🌐 DX CLUSTER <span style={{ color: 'var(--accent-green)', fontSize: '10px' }}>● LIVE</span></span>
<div style={{ display: 'flex', alignItems: 'center', gap: '6px' }}>
<button
onClick={toggleDXPaths}
style={{
background: mapLayers.showDXPaths ? 'rgba(68, 136, 255, 0.3)' : 'rgba(100, 100, 100, 0.3)',
border: `1px solid ${mapLayers.showDXPaths ? '#4488ff' : '#666'}`,
color: mapLayers.showDXPaths ? '#4488ff' : '#888',
padding: '2px 8px',
borderRadius: '4px',
fontSize: '10px',
fontFamily: 'JetBrains Mono',
cursor: 'pointer'
}}
title={mapLayers.showDXPaths ? 'Hide on map' : 'Show on map'}
>
🗺️ {mapLayers.showDXPaths ? 'ON' : 'OFF'}
</button>
{dxCluster.activeSource && <span style={{ fontSize: '9px', color: 'var(--text-muted)', fontWeight: '400' }}>{dxCluster.activeSource}</span>}
</div>
</div>
<div style={{ overflow: 'auto', maxHeight: 'calc(100% - 20px)' }}>
{dxCluster.data.slice(0, 8).map((s, i) => (
@ -3359,7 +3523,25 @@
{/* POTA - Compact */}
<div className="panel" style={{ padding: '10px', flex: '0 0 auto' }}>
<div style={{ fontSize: '12px', color: 'var(--accent-amber)', fontWeight: '700', marginBottom: '6px' }}>🏕 POTA ACTIVATORS</div>
<div style={{ fontSize: '12px', color: 'var(--accent-amber)', fontWeight: '700', marginBottom: '6px', display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<span>🏕 POTA ACTIVATORS</span>
<button
onClick={togglePOTA}
style={{
background: mapLayers.showPOTA ? 'rgba(170, 102, 255, 0.3)' : 'rgba(100, 100, 100, 0.3)',
border: `1px solid ${mapLayers.showPOTA ? '#aa66ff' : '#666'}`,
color: mapLayers.showPOTA ? '#aa66ff' : '#888',
padding: '2px 8px',
borderRadius: '4px',
fontSize: '10px',
fontFamily: 'JetBrains Mono',
cursor: 'pointer'
}}
title={mapLayers.showPOTA ? 'Hide on map' : 'Show on map'}
>
🗺️ {mapLayers.showPOTA ? 'ON' : 'OFF'}
</button>
</div>
<div style={{ fontSize: '12px', fontFamily: 'JetBrains Mono' }}>
{potaSpots.data.slice(0, 5).map((a, i) => (
<div key={i} style={{ padding: '2px 0', display: 'flex', justifyContent: 'space-between' }}>

Loading…
Cancel
Save

Powered by TurnKey Linux.