From 2a818bd2d095a0167b2ead01809b6119c034fb29 Mon Sep 17 00:00:00 2001 From: trancen Date: Tue, 3 Feb 2026 16:31:44 +0000 Subject: [PATCH] feat: WSPR v1.5.0 - Add minimize/maximize toggle to all panels MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ✨ New Feature - Panel Minimization: - All 4 control panels can now be minimized/maximized - Click panel header or toggle button (▼/▶) to collapse/expand - Minimized panels show only header with ▶ icon - Expanded panels show full content with ▼ icon - State persisted to localStorage per panel 🎯 Panels with Minimize: 1. Filter Panel (top-right) - Most useful! Reduces large panel to header 2. Statistics Panel (top-left) - Clean up activity display 3. Legend Panel (bottom-right) - Hide when not needed 4. Band Activity Chart (bottom-left) - Minimize chart view 🎨 UI/UX Improvements: - Minimize button in header (right-aligned) - Visual feedback: button opacity changes on hover - Click header anywhere to toggle (except on controls) - Click ▼/▶ button to toggle - Header cursor changes to pointer when minimized - Smooth transitions - Icons: ▼ = expanded, ▶ = minimized 🔧 Technical Implementation: - New addMinimizeToggle() function - Wraps panel content in .wspr-panel-content div - Toggle button appended to header - Persists state to localStorage with '-minimized' suffix - Loads saved state on panel creation - CTRL+drag still works when minimized - Panel positions preserved 💾 State Persistence: - wspr-filter-position-minimized - wspr-stats-position-minimized - wspr-legend-position-minimized - wspr-chart-position-minimized 🎉 User Benefits: - Minimize large Filter panel to reduce screen clutter - Temporarily hide panels without disabling plugin - Quick access - just click header to restore - Positions and minimize states both saved - Clean, unobstructed map view when needed Version: 1.4.3 → 1.5.0 --- src/plugins/layers/useWSPR.js | 110 ++++++++++++++++++++++++++++++++-- 1 file changed, 105 insertions(+), 5 deletions(-) diff --git a/src/plugins/layers/useWSPR.js b/src/plugins/layers/useWSPR.js index 2c675aa..3cea4a1 100644 --- a/src/plugins/layers/useWSPR.js +++ b/src/plugins/layers/useWSPR.js @@ -1,7 +1,7 @@ import { useState, useEffect, useRef } from 'react'; /** - * WSPR Propagation Heatmap Plugin v1.4.3 + * WSPR Propagation Heatmap Plugin v1.5.0 * * Advanced Features: * - Great circle curved path lines between transmitters and receivers @@ -20,6 +20,7 @@ import { useState, useEffect, useRef } from 'react'; * - Fixed duplicate control creation (v1.4.2) * - Performance optimizations (v1.4.2) * - Separate opacity controls for paths and heatmap (v1.4.3) + * - Minimize/maximize toggle for all panels (v1.5.0) * - Statistics display (total stations, spots) * - Signal strength legend * @@ -35,7 +36,7 @@ export const metadata = { category: 'propagation', defaultEnabled: false, defaultOpacity: 0.7, - version: '1.4.3' + version: '1.5.0' }; // Convert grid square to lat/lon @@ -252,6 +253,95 @@ function makeDraggable(element, storageKey) { }); } +// Add minimize/maximize functionality to control panels +function addMinimizeToggle(element, storageKey) { + if (!element) return; + + const minimizeKey = storageKey + '-minimized'; + + // Create minimize button + const header = element.querySelector('div:first-child'); + if (!header) return; + + // Wrap content (everything except header) + const content = Array.from(element.children).slice(1); + const contentWrapper = document.createElement('div'); + contentWrapper.className = 'wspr-panel-content'; + content.forEach(child => contentWrapper.appendChild(child)); + element.appendChild(contentWrapper); + + // Add minimize button to header + const minimizeBtn = document.createElement('span'); + minimizeBtn.className = 'wspr-minimize-btn'; + minimizeBtn.innerHTML = '▼'; + minimizeBtn.style.cssText = ` + float: right; + cursor: pointer; + user-select: none; + padding: 0 4px; + margin: -2px -4px 0 0; + font-size: 10px; + opacity: 0.7; + transition: opacity 0.2s; + `; + minimizeBtn.title = 'Minimize/Maximize'; + + minimizeBtn.addEventListener('mouseenter', () => { + minimizeBtn.style.opacity = '1'; + }); + minimizeBtn.addEventListener('mouseleave', () => { + minimizeBtn.style.opacity = '0.7'; + }); + + header.style.display = 'flex'; + header.style.justifyContent = 'space-between'; + header.style.alignItems = 'center'; + header.appendChild(minimizeBtn); + + // Load saved state + const isMinimized = localStorage.getItem(minimizeKey) === 'true'; + if (isMinimized) { + contentWrapper.style.display = 'none'; + minimizeBtn.innerHTML = '▶'; + element.style.cursor = 'pointer'; + } + + // Toggle function + const toggle = (e) => { + // Don't toggle if CTRL is held (for dragging) + if (e && e.ctrlKey) return; + + const isCurrentlyMinimized = contentWrapper.style.display === 'none'; + + if (isCurrentlyMinimized) { + // Expand + contentWrapper.style.display = 'block'; + minimizeBtn.innerHTML = '▼'; + element.style.cursor = 'default'; + localStorage.setItem(minimizeKey, 'false'); + } else { + // Minimize + contentWrapper.style.display = 'none'; + minimizeBtn.innerHTML = '▶'; + element.style.cursor = 'pointer'; + localStorage.setItem(minimizeKey, 'true'); + } + }; + + // Click header to toggle (except on button itself) + header.addEventListener('click', (e) => { + if (e.target === header || e.target.tagName === 'DIV') { + toggle(e); + } + }); + + // Click button to toggle + minimizeBtn.addEventListener('click', (e) => { + e.stopPropagation(); + toggle(e); + }); +} + export function useLayer({ enabled = false, opacity = 0.7, map = null }) { const [pathLayers, setPathLayers] = useState([]); const [markerLayers, setMarkerLayers] = useState([]); @@ -408,6 +498,7 @@ export function useLayer({ enabled = false, opacity = 0.7, map = null }) { const container = document.querySelector('.wspr-filter-control'); if (container) { makeDraggable(container, 'wspr-filter-position'); + addMinimizeToggle(container, 'wspr-filter-position'); } }, 150); @@ -490,7 +581,10 @@ export function useLayer({ enabled = false, opacity = 0.7, map = null }) { setTimeout(() => { const container = document.querySelector('.wspr-stats'); - if (container) makeDraggable(container, 'wspr-stats-position'); + if (container) { + makeDraggable(container, 'wspr-stats-position'); + addMinimizeToggle(container, 'wspr-stats-position'); + } }, 150); // Create legend control @@ -528,7 +622,10 @@ export function useLayer({ enabled = false, opacity = 0.7, map = null }) { setTimeout(() => { const container = document.querySelector('.wspr-legend'); - if (container) makeDraggable(container, 'wspr-legend-position'); + if (container) { + makeDraggable(container, 'wspr-legend-position'); + addMinimizeToggle(container, 'wspr-legend-position'); + } }, 150); // Create band chart control @@ -558,7 +655,10 @@ export function useLayer({ enabled = false, opacity = 0.7, map = null }) { setTimeout(() => { const container = document.querySelector('.wspr-chart'); - if (container) makeDraggable(container, 'wspr-chart-position'); + if (container) { + makeDraggable(container, 'wspr-chart-position'); + addMinimizeToggle(container, 'wspr-chart-position'); + } }, 150); console.log('[WSPR] All controls created once');