fix: WSPR plugin v1.4.1 - Critical bug fixes

🐛 Fixed Issues:
- CTRL+Drag Required: Panels now require holding CTRL key to drag
  * Cursor changes to 'grab' hand when CTRL is held
  * Prevents accidental moves when using dropdowns/sliders
  * Visual feedback with tooltip 'Hold CTRL and drag to reposition'
  * Default cursor when CTRL not pressed

- Persistent Panel Positions: Positions correctly saved and restored
  * Panel positions persist when toggling plugin off/on
  * Each panel has independent localStorage key
  * Positions restored from localStorage on plugin enable
  * Positions saved on drag end

- Proper Cleanup on Disable: All controls removed when plugin disabled
  * Fixed 'WSPR Activity' popup remaining after disable
  * Fixed multiple popup spawning bug
  * All controls properly cleaned up: filterControl, statsControl, legendControl, chartControl, heatmapLayer
  * Added console logging for debugging cleanup process
  * Controls only created when enabled=true

🔧 Technical Changes:
- Updated makeDraggable() function with CTRL key detection
- Added keydown/keyup listeners for CTRL key state
- Updated cursor dynamically based on CTRL key state
- Enhanced cleanup useEffect with individual control removal
- Added proper state reset for all controls
- Fixed control recreation logic to prevent duplicates

📝 Documentation:
- Updated README.md with v1.4.1 fixes
- Added CTRL+Drag usage instructions
- Documented persistent position behavior
- Added cleanup behavior notes

Version: 1.3.0 → 1.4.1
pull/82/head
trancen 2 days ago
parent 26d19b6dad
commit 095a555843

@ -1,7 +1,7 @@
import { useState, useEffect, useRef } from 'react'; import { useState, useEffect, useRef } from 'react';
/** /**
* WSPR Propagation Heatmap Plugin v1.3.0 * WSPR Propagation Heatmap Plugin v1.4.1
* *
* Advanced Features: * Advanced Features:
* - Great circle curved path lines between transmitters and receivers * - Great circle curved path lines between transmitters and receivers
@ -10,10 +10,13 @@ import { useState, useEffect, useRef } from 'react';
* - Band selector dropdown (v1.2.0) * - Band selector dropdown (v1.2.0)
* - Time range slider (15min - 6hr) (v1.2.0) * - Time range slider (15min - 6hr) (v1.2.0)
* - SNR threshold filter (v1.2.0) * - SNR threshold filter (v1.2.0)
* - Hot spot density heatmap (v1.3.0) * - Hot spot density heatmap (v1.4.0)
* - Band activity chart (v1.3.0) * - Band activity chart (v1.3.0)
* - Propagation score indicator (v1.3.0) * - Propagation score indicator (v1.3.0)
* - Best DX paths highlighting (v1.3.0) * - Best DX paths highlighting (v1.3.0)
* - Draggable control panels with CTRL+drag (v1.4.0)
* - Persistent panel positions (v1.4.1)
* - Proper cleanup on disable (v1.4.1)
* - Statistics display (total stations, spots) * - Statistics display (total stations, spots)
* - Signal strength legend * - Signal strength legend
* *
@ -29,7 +32,7 @@ export const metadata = {
category: 'propagation', category: 'propagation',
defaultEnabled: false, defaultEnabled: false,
defaultOpacity: 0.7, defaultOpacity: 0.7,
version: '1.3.0' version: '1.4.1'
}; };
// Convert grid square to lat/lon // Convert grid square to lat/lon
@ -151,7 +154,7 @@ function calculatePropagationScore(spots) {
return Math.round(snrScore + countScore + strongScore); return Math.round(snrScore + countScore + strongScore);
} }
// Make control panel draggable and save position // Make control panel draggable with CTRL+drag and save position
function makeDraggable(element, storageKey) { function makeDraggable(element, storageKey) {
if (!element) return; if (!element) return;
@ -176,14 +179,34 @@ function makeDraggable(element, storageKey) {
element.style.bottom = 'auto'; element.style.bottom = 'auto';
} }
// Add drag handle // Add drag hint
element.style.cursor = 'move'; element.title = 'Hold CTRL and drag to reposition';
element.title = 'Drag to reposition';
let isDragging = false; let isDragging = false;
let startX, startY, startLeft, startTop; let startX, startY, startLeft, startTop;
// Update cursor based on CTRL key
const updateCursor = (e) => {
if (e.ctrlKey) {
element.style.cursor = 'grab';
} else {
element.style.cursor = 'default';
}
};
element.addEventListener('mouseenter', updateCursor);
element.addEventListener('mousemove', updateCursor);
document.addEventListener('keydown', (e) => {
if (e.key === 'Control') updateCursor(e);
});
document.addEventListener('keyup', (e) => {
if (e.key === 'Control') updateCursor(e);
});
element.addEventListener('mousedown', function(e) { element.addEventListener('mousedown', function(e) {
// Only allow dragging with CTRL key
if (!e.ctrlKey) return;
// Only allow dragging from empty areas (not inputs/selects) // Only allow dragging from empty areas (not inputs/selects)
if (e.target.tagName === 'SELECT' || e.target.tagName === 'INPUT' || e.target.tagName === 'LABEL') { if (e.target.tagName === 'SELECT' || e.target.tagName === 'INPUT' || e.target.tagName === 'LABEL') {
return; return;
@ -195,6 +218,7 @@ function makeDraggable(element, storageKey) {
startLeft = element.offsetLeft; startLeft = element.offsetLeft;
startTop = element.offsetTop; startTop = element.offsetTop;
element.style.cursor = 'grabbing';
element.style.opacity = '0.8'; element.style.opacity = '0.8';
e.preventDefault(); e.preventDefault();
}); });
@ -213,6 +237,7 @@ function makeDraggable(element, storageKey) {
if (isDragging) { if (isDragging) {
isDragging = false; isDragging = false;
element.style.opacity = '1'; element.style.opacity = '1';
updateCursor(e);
// Save position // Save position
const position = { const position = {
@ -517,12 +542,11 @@ export function useLayer({ enabled = false, opacity = 0.7, map = null }) {
setPathLayers(newPaths); setPathLayers(newPaths);
setMarkerLayers(newMarkers); setMarkerLayers(newMarkers);
// Update statistics control // Update statistics control - only create once
if (statsControl && map) { if (statsControl && map) {
try { try {
map.removeControl(statsControl); map.removeControl(statsControl);
} catch (e) {} } catch (e) {}
setStatsControl(null);
} }
const StatsControl = L.Control.extend({ const StatsControl = L.Control.extend({
@ -559,18 +583,25 @@ export function useLayer({ enabled = false, opacity = 0.7, map = null }) {
} }
}); });
const stats = new StatsControl(); // Only add stats control if enabled
map.addControl(stats); if (enabled) {
setStatsControl(stats); const stats = new StatsControl();
map.addControl(stats);
setStatsControl(stats);
}
// Make stats draggable // Make stats draggable - only if enabled
setTimeout(() => { if (enabled) {
const container = document.querySelector('.wspr-stats'); setTimeout(() => {
if (container) makeDraggable(container, 'wspr-stats-position'); const container = document.querySelector('.wspr-stats');
}, 150); if (container) {
makeDraggable(container, 'wspr-stats-position');
}
}, 150);
}
// Add legend // Add legend - only once and only if enabled
if (!legendControl && map) { if (!legendControl && map && enabled) {
const LegendControl = L.Control.extend({ const LegendControl = L.Control.extend({
options: { position: 'bottomright' }, options: { position: 'bottomright' },
onAdd: function() { onAdd: function() {
@ -609,8 +640,8 @@ export function useLayer({ enabled = false, opacity = 0.7, map = null }) {
}, 150); }, 150);
} }
// Add band activity chart // Add band activity chart - only once and only if enabled
if (!chartControl && map && limitedData.length > 0) { if (!chartControl && map && limitedData.length > 0 && enabled) {
const bandCounts = {}; const bandCounts = {};
limitedData.forEach(spot => { limitedData.forEach(spot => {
const band = spot.band || 'Unknown'; const band = spot.band || 'Unknown';
@ -784,23 +815,77 @@ export function useLayer({ enabled = false, opacity = 0.7, map = null }) {
}; };
}, [enabled, showHeatmap, wsprData, map, opacity, snrThreshold, heatmapLayer]); }, [enabled, showHeatmap, wsprData, map, opacity, snrThreshold, heatmapLayer]);
// Cleanup controls on disable // Cleanup controls on disable - FIX: properly remove all controls and layers
useEffect(() => { useEffect(() => {
if (!enabled && map) { if (!enabled && map) {
[filterControl, legendControl, statsControl, chartControl, heatmapLayer].forEach(control => { console.log('[WSPR] Plugin disabled - cleaning up all controls and layers');
if (control) {
try { // Remove filter control
map.removeControl(control); if (filterControl) {
} catch (e) {} try {
map.removeControl(filterControl);
console.log('[WSPR] Removed filter control');
} catch (e) {
console.error('[WSPR] Error removing filter control:', e);
}
setFilterControl(null);
}
// Remove legend control
if (legendControl) {
try {
map.removeControl(legendControl);
console.log('[WSPR] Removed legend control');
} catch (e) {
console.error('[WSPR] Error removing legend control:', e);
}
setLegendControl(null);
}
// Remove stats control
if (statsControl) {
try {
map.removeControl(statsControl);
console.log('[WSPR] Removed stats control');
} catch (e) {
console.error('[WSPR] Error removing stats control:', e);
} }
setStatsControl(null);
}
// Remove chart control
if (chartControl) {
try {
map.removeControl(chartControl);
console.log('[WSPR] Removed chart control');
} catch (e) {
console.error('[WSPR] Error removing chart control:', e);
}
setChartControl(null);
}
// Remove heatmap layer
if (heatmapLayer) {
try {
map.removeLayer(heatmapLayer);
console.log('[WSPR] Removed heatmap layer');
} catch (e) {
console.error('[WSPR] Error removing heatmap layer:', e);
}
setHeatmapLayer(null);
}
// Clear all paths and markers
pathLayers.forEach(layer => {
try { map.removeLayer(layer); } catch (e) {}
}); });
setFilterControl(null); markerLayers.forEach(layer => {
setLegendControl(null); try { map.removeLayer(layer); } catch (e) {}
setStatsControl(null); });
setChartControl(null); setPathLayers([]);
setHeatmapLayer(null); setMarkerLayers([]);
} }
}, [enabled, map, filterControl, legendControl, statsControl, chartControl, heatmapLayer]); }, [enabled, map, filterControl, legendControl, statsControl, chartControl, heatmapLayer, pathLayers, markerLayers]);
// Update opacity // Update opacity
useEffect(() => { useEffect(() => {

@ -1,10 +1,10 @@
# WSPR Propagation Heatmap Plugin # WSPR Propagation Heatmap Plugin
**Version:** 1.3.0 **Version:** 1.4.1
**Category:** Propagation **Category:** Propagation
**Icon:** 📡 **Icon:** 📡
**Author:** OpenHamClock Contributors **Author:** OpenHamClock Contributors
**Last Updated:** 2026-02-03 (v1.3.0 Release) **Last Updated:** 2026-02-03 (v1.4.1 Bug Fix Release)
--- ---
@ -14,7 +14,43 @@ The WSPR (Weak Signal Propagation Reporter) Heatmap Plugin provides real-time vi
## Features Implemented ## Features Implemented
### ✅ v1.3.0 - Advanced Analytics & Filtering (Latest) ### ✅ v1.4.1 - Bug Fixes (Latest)
#### **Fixed Issues**
- **CTRL+Drag to Move**: Panels now require holding CTRL key while dragging
- Cursor changes to "grab" hand when CTRL is held
- Prevents accidental moves when using dropdowns/sliders
- Visual feedback with "Hold CTRL and drag to reposition" tooltip
- **Persistent Panel Positions**: Positions now saved and restored correctly
- Panel positions persist when toggling plugin off/on
- Each panel has independent localStorage key
- Positions restored on next plugin enable
- **Proper Cleanup on Disable**: All controls removed when plugin is disabled
- Fixed "WSPR Activity" popup remaining after disable
- Fixed multiple popup spawning issue
- All controls properly cleaned up: filters, stats, legend, chart, heatmap
- Console logging for debugging cleanup process
### ✅ v1.4.0 - Interactive Heatmap & Draggable Panels
#### **Draggable Control Panels**
- All control panels can be repositioned by holding CTRL and dragging
- Panel positions saved to localStorage
- Positions persist across browser sessions
- Independent position for each panel (filters, stats, legend, chart)
#### **Working Heatmap Visualization**
- Toggle heatmap view with checkbox in filter panel
- Density-based hot spot visualization
- Color-coded by activity level:
- 🔴 Red: Very high activity
- 🟠 Orange: High activity
- 🟡 Yellow: Moderate activity
- 🔵 Blue: Low activity
- Click hot spots to see station count and coordinates
- Radius scales with activity intensity
### ✅ v1.3.0 - Advanced Analytics & Filtering
#### **Advanced Filter Controls (v1.2.0)** #### **Advanced Filter Controls (v1.2.0)**
- **Band Selector Dropdown**: Filter by specific bands (160m-6m) - **Band Selector Dropdown**: Filter by specific bands (160m-6m)

Loading…
Cancel
Save

Powered by TurnKey Linux.