From dbcbc13102da32fca58a47f1685ed5452616a0dc Mon Sep 17 00:00:00 2001 From: David Enete Date: Mon, 2 Mar 2026 17:22:37 -0500 Subject: [PATCH 1/5] [FEATURE - Map Widgets Alignment] First pass. Added grid snap, removed margin on draggable container --- src/plugins/layers/makeDraggable.js | 43 ++++++++++++++++++++++------- src/plugins/layers/useWSPR.js | 26 ++++++++--------- src/styles/main.css | 6 +++- 3 files changed, 51 insertions(+), 24 deletions(-) diff --git a/src/plugins/layers/makeDraggable.js b/src/plugins/layers/makeDraggable.js index f805bc7f..3efcabfb 100644 --- a/src/plugins/layers/makeDraggable.js +++ b/src/plugins/layers/makeDraggable.js @@ -32,7 +32,22 @@ function clampToViewport(el, margin = 40) { el.style.top = top + 'px'; } -export function makeDraggable(el, storageKey, skipPositionLoad = false) { +/** + * Helper for snapping to a grid + */ +function snapToGrid(value, gridSize) { + if (!gridSize) return value; + return Math.round(value / gridSize) * gridSize; +} + +export function makeDraggable( + el, + storageKey, + { + skipPositionLoad = false, + snap = 0, // pixels; 0 disables snapping + } = {}, +) { if (!el) return; // Cancel any previous listeners for this storageKey (e.g. after layout change) @@ -162,11 +177,16 @@ export function makeDraggable(el, storageKey, skipPositionLoad = false) { 'mousemove', (e) => { if (!isDragging) return; - if (!didDrag && (Math.abs(e.clientX - startX) > 2 || Math.abs(e.clientY - startY) > 2)) { - didDrag = true; - } - el.style.left = startLeft + (e.clientX - startX) + 'px'; - el.style.top = startTop + (e.clientY - startY) + 'px'; + + let nextLeft = startLeft + (e.clientX - startX); + let nextTop = startTop + (e.clientY - startY); + + // Snap to grid if enabled + nextLeft = snapToGrid(nextLeft, snap); + nextTop = snapToGrid(nextTop, snap); + + el.style.left = nextLeft + 'px'; + el.style.top = nextTop + 'px'; }, { signal }, ); @@ -174,15 +194,18 @@ export function makeDraggable(el, storageKey, skipPositionLoad = false) { // --- Mouseup: stop drag, clamp, save --- document.addEventListener( 'mouseup', - () => { + (e) => { if (!isDragging) return; isDragging = false; el.style.opacity = '1'; - el.style.transition = previousTransition; - updateCursor(); + updateCursor(e); suppressClick = didDrag; - // Clamp so element can't be lost off-screen + if (snap) { + el.style.left = snapToGrid(el.offsetLeft, snap) + 'px'; + el.style.top = snapToGrid(el.offsetTop, snap) + 'px'; + } + clampToViewport(el); const topPercent = (el.offsetTop / window.innerHeight) * 100; diff --git a/src/plugins/layers/useWSPR.js b/src/plugins/layers/useWSPR.js index 8800bde6..ba7f834f 100644 --- a/src/plugins/layers/useWSPR.js +++ b/src/plugins/layers/useWSPR.js @@ -402,7 +402,6 @@ export function useLayer({ enabled = false, map = null, callsign, locator, lowMe const container = L.DomUtil.create('div', 'wspr-filter-control'); container.style.cssText = ` background: var(--bg-panel); - padding: 12px; border-radius: 5px; font-family: 'JetBrains Mono', monospace; font-size: 11px; @@ -410,11 +409,11 @@ export function useLayer({ enabled = false, map = null, callsign, locator, lowMe border: 1px solid var(--border-color); box-shadow: 0 2px 8px rgba(0,0,0,0.3); min-width: 180px; + // margin: 0; `; container.innerHTML = ` -
πŸŽ›οΈ Filters
- +
πŸŽ›οΈ Filters
${bandOptions}
- +
- +
- +
- Poor + Poor - Low + Low - Fair + Fair - Good + Good
-
+
${loading ? 'Loading...' : data ? `${data.mode || 'SSB'} ${data.power || 100}W | SFI: ${data.solarData?.sfi} K: ${data.solarData?.kIndex}` : 'Ready'}
-
- `; + L.DomEvent.disableClickPropagation(container); + L.DomEvent.disableScrollPropagation(container); - return container; + return panelWrapper; }, }); - controlRef.current = new VOACAPControl(); - map.addControl(controlRef.current); + const control = new VOACAPControl(); + map.addControl(control); + controlRef.current = control; // Helper to update both plugin state AND global config in localStorage const updateGlobalConfig = (mode, power) => { @@ -311,28 +306,28 @@ export function useLayer({ map, enabled, opacity, locator }) { // Wire up event handlers after DOM is ready setTimeout(() => { - const container = controlRef.current?._container; - if (!container) return; + const container = document.querySelector('.voacap-heatmap-control'); + if (container) { + // Apply saved position + const saved = localStorage.getItem('voacap-heatmap-position'); + if (saved) { + try { + const { top, left } = JSON.parse(saved); + container.style.position = 'fixed'; + container.style.top = top + 'px'; + container.style.left = left + 'px'; + container.style.right = 'auto'; + container.style.bottom = 'auto'; + } catch (e) {} + } - // Apply saved position - const saved = localStorage.getItem('voacap-heatmap-position'); - if (saved) { - try { - const { top, left } = JSON.parse(saved); - container.style.position = 'fixed'; - container.style.top = top + 'px'; - container.style.left = left + 'px'; - container.style.right = 'auto'; - container.style.bottom = 'auto'; - } catch (e) {} + makeDraggable(container, 'voacap-heatmap-position', { snap: 5 }); + addMinimizeToggle(container, 'voacap-heatmap-position', { + contentClassName: 'voacap-panel-content', + buttonClassName: 'voacap-minimize-btn', + }); } - addMinimizeToggle(container, 'voacap-heatmap-position', { - contentClassName: 'voacap-panel-content', - buttonClassName: 'voacap-minimize-btn', - }); - makeDraggable(container, 'voacap-heatmap-position'); - const bandSelect = document.getElementById('voacap-band-select'); const gridSelect = document.getElementById('voacap-grid-select'); const modeSelect = document.getElementById('voacap-mode-select'); diff --git a/src/plugins/layers/useWSPR.js b/src/plugins/layers/useWSPR.js index c1fd923b..a788fe12 100644 --- a/src/plugins/layers/useWSPR.js +++ b/src/plugins/layers/useWSPR.js @@ -409,7 +409,8 @@ export function useLayer({ enabled = false, map = null, callsign, locator, lowMe color: var(--text-primary); border: 1px solid var(--border-color); box-shadow: 0 2px 8px rgba(0,0,0,0.3); - min-width: 180px; + min-width: 200px; + max-width: 280px; `; container.innerHTML = ` @@ -603,7 +604,8 @@ export function useLayer({ enabled = false, map = null, callsign, locator, lowMe color: var(--text-primary); border: 1px solid var(--border-color); box-shadow: 0 2px 8px rgba(0,0,0,0.3); - min-width: 160px; + min-width: 200px; + max-width: 280px; `; div.innerHTML = `
πŸ“Š WSPR Activity
@@ -669,6 +671,8 @@ export function useLayer({ enabled = false, map = null, callsign, locator, lowMe color: var(--text-primary); border: 1px solid var(--border-color); box-shadow: 0 2px 8px rgba(0,0,0,0.3); + min-width: 200px; + max-width: 280px; `; div.innerHTML = `
πŸ“‘ Signal Strength
@@ -724,11 +728,12 @@ export function useLayer({ enabled = false, map = null, callsign, locator, lowMe background: var(--bg-panel); border-radius: 5px; font-family: 'JetBrains Mono', monospace; - font-size: 10px; + font-size: 11px; color: var(--text-primary); border: 1px solid var(--border-color); box-shadow: 0 2px 8px rgba(0,0,0,0.3); - min-width: 160px; + min-width: 200px; + max-width: 280px; `; div.innerHTML = '
πŸ“Š Band Activity
Loading...
'; diff --git a/src/styles/main.css b/src/styles/main.css index e1568452..4389d884 100644 --- a/src/styles/main.css +++ b/src/styles/main.css @@ -704,10 +704,6 @@ body::before { animation: wspr-marker-pulse 2s ease-in-out infinite; } -.wspr-panel-content { - padding: 10px; -} - /* Control panel transitions */ .wspr-filter-control, .wspr-stats, @@ -870,10 +866,6 @@ body::before { /* Static, no animation */ } -.lightning-panel-content { - padding: 10px; -} - .lightning-proximity { } @@ -972,14 +964,15 @@ body::before { z-index: 10000 !important; } -/* Grayline Panel */ -.grayline-panel-content { - padding: 10px; -} - -/* MUF Map Panel */ -.muf-panel-content { - padding: 10px; +/* content spacing in panels */ +.wspr-panel-content, +.lightning-panel-content, +.grayline-panel-content, +.muf-panel-content, +.n3fjp-panel-content, +.rbn-panel-content, +.voacap-panel-content { + padding: 0 10px 10px 10px; } /* ============================================ From 92b168b1e009480343fa12e089bf73510b1bf44b Mon Sep 17 00:00:00 2001 From: David Enete Date: Tue, 3 Mar 2026 01:40:53 -0500 Subject: [PATCH 4/5] [FEATURE - Map Widgets Alignment] updated border-radius --- src/plugins/layers/useLightning.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/layers/useLightning.js b/src/plugins/layers/useLightning.js index 45346cac..da2f2ef5 100644 --- a/src/plugins/layers/useLightning.js +++ b/src/plugins/layers/useLightning.js @@ -448,7 +448,7 @@ export function useLayer({ enabled = false, opacity = 0.9, map = null, lowMemory const div = L.DomUtil.create('div', 'lightning-stats', panelWrapper); div.style.cssText = ` background: var(--bg-panel); - border-radius: 8px; + border-radius: 5px; border: 1px solid var(--border-color); font-family: 'JetBrains Mono', monospace; font-size: 11px; From 12bf98731480c1e86840dd81d6013f86a475da1a Mon Sep 17 00:00:00 2001 From: David Enete Date: Wed, 4 Mar 2026 18:32:52 -0500 Subject: [PATCH 5/5] [FEATURE - Map Widgets Alignment] Updates to address issues from PR #655. Found an issue with the panel header not using the correct cursors due to multiple elements being set as drag handles, fixed issue between addMinimizeToggle.js and makeDraggable.js conflicts, moved styles to main.css as appropriate and replaced hard-coded colors --- src/plugins/layers/addMinimizeToggle.js | 36 +++---------- src/plugins/layers/makeDraggable.js | 8 ++- src/plugins/layers/useGrayLine.js | 13 +---- src/plugins/layers/useLightning.js | 26 ++-------- src/plugins/layers/useMUFMap.js | 15 ++---- src/plugins/layers/useN3FJPLoggedQSOs.js | 14 +----- src/plugins/layers/useRBN.js | 9 +--- src/plugins/layers/useVOACAPHeatmap.js | 14 +----- src/plugins/layers/useWSPR.js | 64 ++++++------------------ src/styles/main.css | 64 +++++++++++++++++------- 10 files changed, 85 insertions(+), 178 deletions(-) diff --git a/src/plugins/layers/addMinimizeToggle.js b/src/plugins/layers/addMinimizeToggle.js index ad4ffd37..19ebbee1 100644 --- a/src/plugins/layers/addMinimizeToggle.js +++ b/src/plugins/layers/addMinimizeToggle.js @@ -6,7 +6,6 @@ export function addMinimizeToggle(element, storageKey, options = {}) { const { contentClassName = 'panel-content', buttonClassName = 'panel-minimize-btn', - titleColor = '#00b4ff', getIsMinimized, onToggle, persist = true, @@ -25,17 +24,9 @@ export function addMinimizeToggle(element, storageKey, options = {}) { const header = element.firstElementChild; if (!header) return; - const existingTitle = header.querySelector('[data-drag-handle="true"]'); const existingButton = header.querySelector(`.${buttonClassName}`); const existingWrapper = element.querySelector(`.${contentClassName}`); - if (existingTitle) { - existingTitle.style.fontFamily = "'JetBrains Mono', monospace"; - existingTitle.style.fontSize = '13px'; - existingTitle.style.fontWeight = '700'; - existingTitle.style.color = titleColor; - } - const readState = () => { if (typeof getIsMinimized === 'function') return !!getIsMinimized(); return localStorage.getItem(minimizeKey) === 'true'; @@ -49,7 +40,8 @@ export function addMinimizeToggle(element, storageKey, options = {}) { const syncState = (button, wrapper) => { const isMinimized = readState(); wrapper.style.display = isMinimized ? 'none' : 'block'; - button.innerHTML = isMinimized ? 'β–Ά' : 'β–Ό'; + button.innerHTML = 'β–Ά'; + button.style.transform = isMinimized ? 'rotate(0deg)' : 'rotate(90deg)'; element.style.cursor = isMinimized ? 'pointer' : 'default'; }; @@ -68,7 +60,8 @@ export function addMinimizeToggle(element, storageKey, options = {}) { e.stopPropagation(); const next = existingWrapper.style.display !== 'none'; existingWrapper.style.display = next ? 'none' : 'block'; - existingButton.innerHTML = next ? 'β–Ά' : 'β–Ό'; + existingButton.innerHTML = 'β–Ά'; + existingButton.style.transform = next ? 'rotate(90deg)' : 'rotate(0deg)'; element.style.cursor = next ? 'pointer' : 'default'; writeState(next); }, @@ -87,23 +80,14 @@ export function addMinimizeToggle(element, storageKey, options = {}) { const minimizeBtn = document.createElement('button'); minimizeBtn.className = buttonClassName; - minimizeBtn.innerHTML = 'β–Ό'; + minimizeBtn.innerHTML = 'β–Ά'; minimizeBtn.style.cssText = ` display: inline-flex; align-items: center; justify-content: center; - width: 16px; - min-width: 16px; - height: 16px; - background: none; - border: none; - color: #888; cursor: pointer; user-select: none; - padding: 2px 4px; - margin: 0; - font-size: 10px; - line-height: 1; + transform: rotate(0deg); `; minimizeBtn.title = 'Minimize/Maximize'; minimizeBtn.addEventListener( @@ -122,12 +106,6 @@ export function addMinimizeToggle(element, storageKey, options = {}) { title.textContent = header.textContent.replace(/[β–Όβ–Ά]/g, '').trim(); title.dataset.dragHandle = 'true'; title.style.flex = '1'; - title.style.cursor = 'grab'; - title.style.userSelect = 'none'; - title.style.fontFamily = "'JetBrains Mono', monospace"; - title.style.fontSize = '13px'; - title.style.fontWeight = '700'; - title.style.color = titleColor; header.textContent = ''; header.appendChild(title); @@ -141,7 +119,7 @@ export function addMinimizeToggle(element, storageKey, options = {}) { e.stopPropagation(); const hidden = contentWrapper.style.display === 'none'; contentWrapper.style.display = hidden ? 'block' : 'none'; - minimizeBtn.innerHTML = hidden ? 'β–Ό' : 'β–Ά'; + minimizeBtn.style.transform = hidden ? 'rotate(90deg)' : 'rotate(0deg)'; element.style.cursor = hidden ? 'default' : 'pointer'; writeState(!hidden); }, diff --git a/src/plugins/layers/makeDraggable.js b/src/plugins/layers/makeDraggable.js index 3efcabfb..a8584d0b 100644 --- a/src/plugins/layers/makeDraggable.js +++ b/src/plugins/layers/makeDraggable.js @@ -157,6 +157,7 @@ export function makeDraggable( (e) => { if (e.button !== 0) return; isDragging = true; + updateCursor(); didDrag = false; startX = e.clientX; startY = e.clientY; @@ -178,6 +179,10 @@ export function makeDraggable( (e) => { if (!isDragging) return; + if (!didDrag && (Math.abs(e.clientX - startX) > 2 || Math.abs(e.clientY - startY) > 2)) { + didDrag = true; + } + let nextLeft = startLeft + (e.clientX - startX); let nextTop = startTop + (e.clientY - startY); @@ -198,7 +203,8 @@ export function makeDraggable( if (!isDragging) return; isDragging = false; el.style.opacity = '1'; - updateCursor(e); + el.style.transition = previousTransition; + updateCursor(); suppressClick = didDrag; if (snap) { diff --git a/src/plugins/layers/useGrayLine.js b/src/plugins/layers/useGrayLine.js index 25488d7d..9c497646 100644 --- a/src/plugins/layers/useGrayLine.js +++ b/src/plugins/layers/useGrayLine.js @@ -288,23 +288,12 @@ export function useLayer({ enabled = false, opacity = 0.5, map = null }) { onAdd: function () { const panelWrapper = L.DomUtil.create('div', 'panel-wrapper'); const container = L.DomUtil.create('div', 'grayline-control', panelWrapper); - container.style.cssText = ` - background: var(--bg-panel); - border-radius: 5px; - font-family: 'JetBrains Mono', monospace; - font-size: 11px; - color: var(--text-primary); - border: 1px solid var(--border-color); - box-shadow: 0 2px 8px rgba(0,0,0,0.3); - min-width: 200px; - max-width: 280px; - `; const now = new Date(); const timeStr = now.toUTCString(); container.innerHTML = ` -
πŸŒ… Gray Line
+
πŸŒ… Gray Line
UTC TIME
diff --git a/src/plugins/layers/useLightning.js b/src/plugins/layers/useLightning.js index da2f2ef5..a751c55a 100644 --- a/src/plugins/layers/useLightning.js +++ b/src/plugins/layers/useLightning.js @@ -446,17 +446,9 @@ export function useLayer({ enabled = false, opacity = 0.9, map = null, lowMemory console.log('[Lightning] StatsControl onAdd called'); const panelWrapper = L.DomUtil.create('div', 'panel-wrapper'); const div = L.DomUtil.create('div', 'lightning-stats', panelWrapper); - div.style.cssText = ` - background: var(--bg-panel); - border-radius: 5px; - border: 1px solid var(--border-color); - font-family: 'JetBrains Mono', monospace; - font-size: 11px; - color: var(--text-primary); - min-width: 180px; - `; + div.innerHTML = ` -
⚑️ Lightning Activity
+
⚑️ Lightning Activity
Connecting...
`; @@ -694,19 +686,9 @@ export function useLayer({ enabled = false, opacity = 0.9, map = null, lowMemory onAdd: function () { const panelWrapper = L.DomUtil.create('div', 'panel-wrapper'); const div = L.DomUtil.create('div', 'lightning-proximity', panelWrapper); - div.style.cssText = ` - background: var(--bg-panel); - border-radius: 5px; - font-family: 'JetBrains Mono', monospace; - font-size: 11px; - color: var(--text-primary); - border: 1px solid var(--border-color); - box-shadow: 0 2px 8px rgba(0,0,0,0.3); - min-width: 200px; - max-width: 280px; - `; + div.innerHTML = - '
πŸ“ Nearby Strikes (30km)
No recent strikes
'; + '
πŸ“ Nearby Strikes (30km)
No recent strikes
'; // Prevent map interaction L.DomEvent.disableClickPropagation(div); diff --git a/src/plugins/layers/useMUFMap.js b/src/plugins/layers/useMUFMap.js index d04f11ef..8dbf041d 100644 --- a/src/plugins/layers/useMUFMap.js +++ b/src/plugins/layers/useMUFMap.js @@ -298,19 +298,10 @@ export function useLayer({ map, enabled, opacity }) { onAdd: function () { const panelWrapper = L.DomUtil.create('div', 'panel-wrapper'); const container = L.DomUtil.create('div', 'muf-map-control', panelWrapper); - container.style.cssText = ` - background: var(--bg-panel); - border-radius: 5px; - font-family: 'JetBrains Mono', monospace; - font-size: 11px; - color: var(--text-primary); - border: 1px solid var(--border-color); - box-shadow: 0 2px 8px rgba(0,0,0,0.3); - min-width: 200px; - backdrop-filter: blur(8px); - `; - container.innerHTML = `
πŸ“‘ MUF Map
+ container.innerHTML = ` +
πŸ“‘ MUF Map
+
πŸ—ΊοΈ N3FJP Logged QSOs
+
πŸ—ΊοΈ N3FJP Logged QSOs
QSOs: ${qsos.length}
diff --git a/src/plugins/layers/useRBN.js b/src/plugins/layers/useRBN.js index d35f9afc..6e28e2ac 100644 --- a/src/plugins/layers/useRBN.js +++ b/src/plugins/layers/useRBN.js @@ -462,18 +462,11 @@ export function useLayer({ const panelWrapper = L.DomUtil.create('div', 'panel-wrapper'); const div = L.DomUtil.create('div', 'rbn-control', panelWrapper); div.style.cssText = ` - background: var(--bg-panel); - border-radius: 5px; - font-family: 'JetBrains Mono', monospace; - font-size: 11px; - color: var(--text-primary); - border: 1px solid var(--border-color); - box-shadow: 0 2px 8px rgba(0,0,0,0.3); min-width: 250px; max-width: 300px; `; div.innerHTML = ` -
πŸ“‘ RBN: ${callsign}
+
πŸ“‘ RBN: ${callsign}
Spots: 0 | Skimmers: 0
diff --git a/src/plugins/layers/useVOACAPHeatmap.js b/src/plugins/layers/useVOACAPHeatmap.js index 8a495905..6c9ef77c 100644 --- a/src/plugins/layers/useVOACAPHeatmap.js +++ b/src/plugins/layers/useVOACAPHeatmap.js @@ -211,20 +211,8 @@ export function useLayer({ map, enabled, opacity, locator }) { .map((p) => ``) .join(''); - container.style.cssText = ` - background: var(--bg-panel); - border-radius: 5px; - font-family: 'JetBrains Mono', monospace; - font-size: 11px; - color: var(--text-primary); - border: 1px solid var(--border-color); - box-shadow: 0 2px 8px rgba(0,0,0,0.3); - min-width: 200px; - max-width: 280px; - `; - container.innerHTML = ` -
🌐 VOACAP Heatmap
+
🌐 VOACAP Heatmap
diff --git a/src/plugins/layers/useWSPR.js b/src/plugins/layers/useWSPR.js index a788fe12..d1afd6e6 100644 --- a/src/plugins/layers/useWSPR.js +++ b/src/plugins/layers/useWSPR.js @@ -401,20 +401,10 @@ export function useLayer({ enabled = false, map = null, callsign, locator, lowMe onAdd: function () { const panelWrapper = L.DomUtil.create('div', 'panel-wrapper'); const container = L.DomUtil.create('div', 'wspr-filter-control', panelWrapper); - container.style.cssText = ` - background: var(--bg-panel); - border-radius: 5px; - font-family: 'JetBrains Mono', monospace; - font-size: 11px; - color: var(--text-primary); - border: 1px solid var(--border-color); - box-shadow: 0 2px 8px rgba(0,0,0,0.3); - min-width: 200px; - max-width: 280px; - `; container.innerHTML = ` -
πŸŽ›οΈ Filters
+
πŸŽ›οΈ Filters
+