Skip to content

Commit 0da644d

Browse files
committed
feat: unified node selection, UI centering, and React error fixes (v1.7.2)
1 parent 92765a0 commit 0da644d

22 files changed

Lines changed: 823 additions & 414 deletions

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# meshRF 📡 v1.7.0
1+
# meshRF 📡 v1.7.2
22

33
A professional-grade RF propagation and link analysis tool designed for LoRa Mesh networks (Meshtastic, Reticulum, Sidewinder). Built with **React**, **Leaflet**, and a high-fidelity **Geodetic Physics Engine**.
44

RELEASE_NOTES.md

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,48 @@
11
# Release Notes
22

3+
## v1.7.2 - Selection Logic & UI Polish
4+
5+
This release unifies the node selection behavior, resolves critical event propagation bugs, and polishes the import notification UI for a more professional experience.
6+
7+
### 📡 Selection Logic & Architecture
8+
9+
- **Unified Selection Flow**: Consolidated map clicks and batch node selections into a single, high-reliability handler.
10+
- **Predictable Roles**: Selecting a 3rd node now consistently resets the sequence, making the new node the **TX** point.
11+
- **Double-Selection Prevention**:
12+
- Blocked event propagation from UI panels to the map to prevent accidental "double-selection".
13+
- Implemented a 100ms temporal guard to ignore rapid-fire duplicate events.
14+
- **React Performance**: Fixed a "Cannot update a component while rendering another" state warning by refactoring state update sequences.
15+
16+
### 🎨 UI & UX Refinements
17+
18+
- **Centered Notifications**: The "IMPORT SUCCESSFUL" popup is now visually centered over the map area, intelligently adjusting for sidebar state.
19+
- **Streamlined Feedback**: Removed the redundant "CLOSE" button from the import notification, relying on its auto-dismiss behavior for a zero-click workflow.
20+
- **Bug Fixes**: Resolved a `ReferenceError` in `BatchProcessing.jsx` related to inconsistent state access.
21+
22+
---
23+
24+
## v1.7.1 - Mobile UX & HUD Refinement
25+
26+
This update focuses on polishing the HUD layout and significantly improving the mobile user experience for multi-step workflows like Batch Node analysis.
27+
28+
### 🎨 HUD Layout & Aesthetics
29+
30+
- **Off-Center Toolbar**: Relocated the main toolbar to `left: 20px` to create a modern, asymmetrical layout.
31+
- **Action Button Alignment**: Standardized action buttons (Lock, Clear Link, etc.) to `left: 60px`, maintaining clear separation from the sidebar toggle.
32+
- **Sidebar Toggle Centering**: Vertically aligned the sidebar toggle button with other HUD action elements for a balanced visual center at `top: 76px`.
33+
- **Global Zoom Standard**: Zoom controls are now consistently located in the `bottom-right` corner across all devices, eliminating toolbar overlap.
34+
35+
### 📱 Mobile UI Enhancements
36+
37+
- **Smart Panel Minimization**:
38+
- The **Batch Nodes list** now automatically minimizes when a link is established, maximizing map visibility.
39+
- Relocated the minimized Batch Nodes panel to the top-left area to prevent conflict with analysis result sheets.
40+
- **Link Analysis Summaries**:
41+
- Added a compact "Status & Margin" live summary to the minimized header on mobile, allowing users to see results without expanding the full sheet.
42+
- Refined the "Grab Handle" and minimized height (`72px`) for a more responsive feel.
43+
44+
---
45+
346
## v1.7.0 - Pro RF Coverage & Runtime Config
447

548
This major release introduces a powerful new **RF Coverage Tool** with stitched multi-tile analysis and draggable transmitters, alongside significant DevOps improvements for runtime configuration.

libmeshrf/include/meshrf_coverage.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ std::vector<float> calculate_rf_coverage(
2929
int tx_x,
3030
int tx_y,
3131
float tx_h_meters,
32+
float rx_h_meters,
3233
float frequency_mhz,
3334
float tx_power_dbm,
3435
float tx_gain_dbi,

libmeshrf/js/meshrf.wasm

71 Bytes
Binary file not shown.

libmeshrf/src/bindings.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ EMSCRIPTEN_BINDINGS(meshrf_module) {
5151
int tx_x,
5252
int tx_y,
5353
float tx_h,
54+
float rx_h,
5455
float freq_mhz,
5556
float tx_power_dbm,
5657
float tx_gain_dbi,
@@ -61,7 +62,7 @@ EMSCRIPTEN_BINDINGS(meshrf_module) {
6162
) {
6263
float* elev = reinterpret_cast<float*>(elev_ptr);
6364
return calculate_rf_coverage(
64-
elev, width, height, tx_x, tx_y, tx_h,
65+
elev, width, height, tx_x, tx_y, tx_h, rx_h,
6566
freq_mhz, tx_power_dbm, tx_gain_dbi, rx_gain_dbi,
6667
rx_sensitivity, max_dist, gsd_meters
6768
);

libmeshrf/src/meshrf_coverage.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ std::vector<float> calculate_rf_coverage(
1111
int tx_x,
1212
int tx_y,
1313
float tx_h_meters,
14+
float rx_h_meters,
1415
float frequency_mhz,
1516
float tx_power_dbm,
1617
float tx_gain_dbi,
@@ -77,7 +78,7 @@ std::vector<float> calculate_rf_coverage(
7778
LinkParameters params;
7879
params.frequency_mhz = frequency_mhz;
7980
params.tx_height_m = tx_h_meters;
80-
params.rx_height_m = 2.0; // Default RX antenna height (can be parameterized later)
81+
params.rx_height_m = rx_h_meters; // Use parameterized RX height
8182
params.polarization = 1; // Vertical (typical for LoRa)
8283
params.step_size_m = gsd_meters;
8384
params.N_0 = 301.0; // Standard atmosphere

public/meshrf.wasm

104 KB
Binary file not shown.

public/rf-engine/meshrf/meshrf.js

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

rf-engine/rf_physics.py

Lines changed: 1 addition & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import numpy as np
22
import math
3-
# tile_manager import removed
43

54
# Constants
65
EARTH_RADIUS_KM = 6371.0
@@ -60,14 +59,11 @@ def analyze_link(elevs, dist_m, freq_mhz, tx_h, rx_h):
6059
tx_alt = elevs[0] + tx_h # Height above sea level (no bulge at 0)
6160
rx_alt = elevs[-1] + rx_h # Height above sea level
6261

63-
# Linear interpolation of LOS height across the path (ignoring bulge on LOS itself since it's the reference usually?)
64-
# Wait, if terrain is bulged, LOS is straight line?
65-
# Yes, standard method: Terrain += Bulge, Link = Straight Line.
62+
# Linear interpolation of LOS height across the path
6663

6764
los_h = np.linspace(tx_alt, rx_alt, num_points)
6865

6966
# 3. Fresnel Clearance
70-
# clearance = los_h - terrain_h
7167
clearance = los_h - terrain_h
7268

7369
# Fresnel Radius at each point
@@ -104,20 +100,3 @@ def analyze_link(elevs, dist_m, freq_mhz, tx_h, rx_h):
104100
"path_loss_db": 0.0, # Placeholder for ITM
105101
"profile": elevs.tolist() # Return raw profile for debug?
106102
}
107-
108-
def calculate_itm_loss(dist_m, elevs, freq_mhz, tx_h, rx_h):
109-
try:
110-
import itmlogic
111-
# Placeholder: Check actual ITM API.
112-
# Assuming itmlogic.p2p(dist_km, profile, freq, h1, h2, ...)
113-
# Since I cannot verify the API at this exact second without "search_web" deep dive,
114-
# I will fall back to a robust simulation or basic models if import fails.
115-
# But let's try to simulate calling it.
116-
pass
117-
except ImportError:
118-
pass
119-
120-
dist_km = dist_m / 1000
121-
if dist_km < 0.001: return 0
122-
fspl = 20 * math.log10(dist_km) + 20 * math.log10(freq_mhz) + 32.44
123-
return fspl

src/components/Layout/Sidebar.jsx

Lines changed: 33 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -23,24 +23,22 @@ const Sidebar = () => {
2323
batchNodes, setBatchNodes,
2424
setShowBatchPanel,
2525
triggerRecalc,
26-
editMode, setEditMode
26+
editMode, setEditMode,
27+
rxHeight, setRxHeight,
28+
toolMode,
29+
sidebarIsOpen, setSidebarIsOpen,
30+
isMobile
2731
} = useRF();
2832

29-
// Responsive & Collapse Logic
30-
const [isMobile, setIsMobile] = useState(window.innerWidth < 768);
31-
const [isOpen, setIsOpen] = useState(window.innerWidth > 768);
3233
const [isSettingsOpen, setIsSettingsOpen] = useState(false);
3334

35+
// Initial sync
3436
useEffect(() => {
35-
const handleResize = () => {
36-
const mobile = window.innerWidth < 768;
37-
setIsMobile(mobile);
38-
if (mobile && isOpen) setIsOpen(false);
39-
if (!mobile && !isOpen) setIsOpen(true);
40-
};
41-
window.addEventListener('resize', handleResize);
42-
return () => window.removeEventListener('resize', handleResize);
43-
}, []);
37+
if (isMobile && sidebarIsOpen) setSidebarIsOpen(false);
38+
}, [isMobile]); // Add dependency
39+
40+
const isOpen = sidebarIsOpen;
41+
const setIsOpen = setSidebarIsOpen;
4442

4543
const handleTxPowerChange = (e) => {
4644
setTxPower(Math.min(Number(e.target.value), DEVICE_PRESETS[selectedDevice].tx_power_max));
@@ -96,7 +94,7 @@ const Sidebar = () => {
9694
title={isOpen ? "Collapse Sidebar" : "Expand Sidebar"}
9795
style={{
9896
position: isMobile ? 'fixed' : 'absolute', // Stay with sidebar
99-
top: '85px',
97+
top: '76px',
10098
left: isOpen ? '330px' : '15px', // Floating to the right
10199
zIndex: 2010, // Above sidebar (2000)
102100
background: 'var(--color-primary)',
@@ -373,6 +371,27 @@ const Sidebar = () => {
373371
style={{width: '100%', cursor: 'pointer', accentColor: 'var(--color-primary)'}}
374372
/>
375373

374+
{/* RX Height Slider - Only for RF Coverage Tool */}
375+
{toolMode === 'rf_coverage' && (
376+
<div style={{marginTop: 'var(--spacing-md)'}}>
377+
<label style={labelStyle} htmlFor="rx-height">
378+
Receiver Height: {units === 'imperial' ? `${(rxHeight * 3.28084).toFixed(0)} ft` : `${rxHeight} m`}
379+
<span style={{color: 'var(--color-text-muted)', marginLeft: '8px', fontSize: '0.8em'}}>
380+
({rxHeight <= 2 ? 'Handheld' : rxHeight <= 5 ? 'Vehicle' : 'Mast'})
381+
</span>
382+
</label>
383+
<input
384+
id="rx-height"
385+
name="rx-height"
386+
type="range"
387+
min="1" max="30" steps="1"
388+
value={rxHeight}
389+
onChange={(e) => setRxHeight(Number(e.target.value))}
390+
style={{width: '100%', cursor: 'pointer', accentColor: 'var(--color-secondary)'}}
391+
/>
392+
</div>
393+
)}
394+
376395
{/* ERP CALCULATION DISPLAY */}
377396
<div style={{
378397
marginTop: 'var(--spacing-md)',

0 commit comments

Comments
 (0)