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';
/**
* WSPR Propagation Heatmap Plugin v1.3.0
* WSPR Propagation Heatmap Plugin v1.4.1
*
* Advanced Features:
* - 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)
* - Time range slider (15min - 6hr) (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)
* - Propagation score indicator (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)
* - Signal strength legend
*
@ -29,7 +32,7 @@ export const metadata = {
category: 'propagation',
defaultEnabled: false,
defaultOpacity: 0.7,
version: '1.3.0'
version: '1.4.1'
};
// Convert grid square to lat/lon
@ -151,7 +154,7 @@ function calculatePropagationScore(spots) {
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) {
if (!element) return;
@ -176,14 +179,34 @@ function makeDraggable(element, storageKey) {
element.style.bottom = 'auto';
}
// Add drag handle
element.style.cursor = 'move';
element.title = 'Drag to reposition';
// Add drag hint
element.title = 'Hold CTRL and drag to reposition';
let isDragging = false;
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) {
// Only allow dragging with CTRL key
if (!e.ctrlKey) return;
// Only allow dragging from empty areas (not inputs/selects)
if (e.target.tagName === 'SELECT' || e.target.tagName === 'INPUT' || e.target.tagName === 'LABEL') {
return;
@ -195,6 +218,7 @@ function makeDraggable(element, storageKey) {
startLeft = element.offsetLeft;
startTop = element.offsetTop;
element.style.cursor = 'grabbing';
element.style.opacity = '0.8';
e.preventDefault();
});
@ -213,6 +237,7 @@ function makeDraggable(element, storageKey) {
if (isDragging) {
isDragging = false;
element.style.opacity = '1';
updateCursor(e);
// Save position
const position = {
@ -517,12 +542,11 @@ export function useLayer({ enabled = false, opacity = 0.7, map = null }) {
setPathLayers(newPaths);
setMarkerLayers(newMarkers);
// Update statistics control
// Update statistics control - only create once
if (statsControl && map) {
try {
map.removeControl(statsControl);
} catch (e) {}
setStatsControl(null);
}
const StatsControl = L.Control.extend({
@ -559,18 +583,25 @@ export function useLayer({ enabled = false, opacity = 0.7, map = null }) {
}
});
const stats = new StatsControl();
map.addControl(stats);
setStatsControl(stats);
// Only add stats control if enabled
if (enabled) {
const stats = new StatsControl();
map.addControl(stats);
setStatsControl(stats);
}
// Make stats draggable
setTimeout(() => {
const container = document.querySelector('.wspr-stats');
if (container) makeDraggable(container, 'wspr-stats-position');
}, 150);
// Make stats draggable - only if enabled
if (enabled) {
setTimeout(() => {
const container = document.querySelector('.wspr-stats');
if (container) {
makeDraggable(container, 'wspr-stats-position');
}
}, 150);
}
// Add legend
if (!legendControl && map) {
// Add legend - only once and only if enabled
if (!legendControl && map && enabled) {
const LegendControl = L.Control.extend({
options: { position: 'bottomright' },
onAdd: function() {
@ -609,8 +640,8 @@ export function useLayer({ enabled = false, opacity = 0.7, map = null }) {
}, 150);
}
// Add band activity chart
if (!chartControl && map && limitedData.length > 0) {
// Add band activity chart - only once and only if enabled
if (!chartControl && map && limitedData.length > 0 && enabled) {
const bandCounts = {};
limitedData.forEach(spot => {
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]);
// Cleanup controls on disable
// Cleanup controls on disable - FIX: properly remove all controls and layers
useEffect(() => {
if (!enabled && map) {
[filterControl, legendControl, statsControl, chartControl, heatmapLayer].forEach(control => {
if (control) {
try {
map.removeControl(control);
} catch (e) {}
console.log('[WSPR] Plugin disabled - cleaning up all controls and layers');
// Remove filter control
if (filterControl) {
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);
setLegendControl(null);
setStatsControl(null);
setChartControl(null);
setHeatmapLayer(null);
markerLayers.forEach(layer => {
try { map.removeLayer(layer); } catch (e) {}
});
setPathLayers([]);
setMarkerLayers([]);
}
}, [enabled, map, filterControl, legendControl, statsControl, chartControl, heatmapLayer]);
}, [enabled, map, filterControl, legendControl, statsControl, chartControl, heatmapLayer, pathLayers, markerLayers]);
// Update opacity
useEffect(() => {

@ -1,10 +1,10 @@
# WSPR Propagation Heatmap Plugin
**Version:** 1.3.0
**Version:** 1.4.1
**Category:** Propagation
**Icon:** 📡
**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
### ✅ 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)**
- **Band Selector Dropdown**: Filter by specific bands (160m-6m)

Loading…
Cancel
Save

Powered by TurnKey Linux.