mirror of
https://github.com/MeshTender/MeshTender.git
synced 2026-09-02 01:38:17 +00:00
Fix custom radio details
This commit is contained in:
@@ -6,6 +6,7 @@ import (
|
||||
"fmt"
|
||||
"html/template"
|
||||
"image/color"
|
||||
"math"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
@@ -175,15 +176,17 @@ func (s *Handlers) handleAddRepeater(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
// parseRadioForm reads and validates the radio fields from a repeater form.
|
||||
// Frequency and bandwidth are entered in MHz/kHz (matching the region presets)
|
||||
// and stored as Hz.
|
||||
func parseRadioForm(r *http.Request) (freq, bw int64, sf, cr int16, ok bool) {
|
||||
f, e1 := strconv.ParseInt(r.FormValue("radio_freq_hz"), 10, 64)
|
||||
b, e2 := strconv.ParseInt(r.FormValue("radio_bw_hz"), 10, 64)
|
||||
mhz, e1 := strconv.ParseFloat(r.FormValue("radio_freq_mhz"), 64)
|
||||
khz, e2 := strconv.ParseFloat(r.FormValue("radio_bw_khz"), 64)
|
||||
s, e3 := strconv.ParseInt(r.FormValue("radio_sf"), 10, 16)
|
||||
c, e4 := strconv.ParseInt(r.FormValue("radio_cr"), 10, 16)
|
||||
if e1 != nil || e2 != nil || e3 != nil || e4 != nil || f <= 0 || b <= 0 {
|
||||
if e1 != nil || e2 != nil || e3 != nil || e4 != nil || mhz <= 0 || khz <= 0 {
|
||||
return 0, 0, 0, 0, false
|
||||
}
|
||||
return f, b, int16(s), int16(c), true
|
||||
return int64(math.Round(mhz * 1e6)), int64(math.Round(khz * 1e3)), int16(s), int16(c), true
|
||||
}
|
||||
|
||||
// pageEditRepeater shows the edit form for an owned repeater.
|
||||
|
||||
@@ -93,8 +93,8 @@
|
||||
</select>
|
||||
</div>
|
||||
<div class="row g-2">
|
||||
<div class="col-6 col-md-3"><label class="form-label" for="radio_freq_hz">Freq (Hz)</label><input type="number" class="form-control" id="radio_freq_hz" name="radio_freq_hz" value="{{.Defaults.FreqHz}}" required></div>
|
||||
<div class="col-6 col-md-3"><label class="form-label" for="radio_bw_hz">Bandwidth (Hz)</label><input type="number" class="form-control" id="radio_bw_hz" name="radio_bw_hz" value="{{.Defaults.BwHz}}" required></div>
|
||||
<div class="col-6 col-md-3"><label class="form-label" for="radio_freq_mhz">Freq (MHz)</label><input type="number" step="0.001" class="form-control" id="radio_freq_mhz" name="radio_freq_mhz" value="{{mhz .Defaults.FreqHz}}" required></div>
|
||||
<div class="col-6 col-md-3"><label class="form-label" for="radio_bw_khz">Bandwidth (kHz)</label><input type="number" step="0.1" class="form-control" id="radio_bw_khz" name="radio_bw_khz" value="{{khz .Defaults.BwHz}}" required></div>
|
||||
<div class="col-6 col-md-3"><label class="form-label" for="radio_sf">SF</label><input type="number" class="form-control" id="radio_sf" name="radio_sf" value="{{.Defaults.SF}}" min="5" max="12" required></div>
|
||||
<div class="col-6 col-md-3"><label class="form-label" for="radio_cr">CR</label><input type="number" class="form-control" id="radio_cr" name="radio_cr" value="{{.Defaults.CR}}" min="5" max="8" required></div>
|
||||
</div>
|
||||
@@ -112,23 +112,45 @@
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
// The form fields display MHz/kHz; preset data-* attributes are Hz-canonical.
|
||||
var RADIO_FIELDS = ["radio_freq_mhz", "radio_bw_khz", "radio_sf", "radio_cr"];
|
||||
function trim(n) { return String(Math.round(n * 1e6) / 1e6); } // drop float fuzz
|
||||
|
||||
// Seed the radio fields from the chosen preset. Fields stay editable — a
|
||||
// preset is a starting point, not a lock.
|
||||
function applyRegion() {
|
||||
var sel = document.getElementById("region");
|
||||
var opt = sel.options[sel.selectedIndex];
|
||||
var fields = ["radio_freq_hz", "radio_bw_hz", "radio_sf", "radio_cr"].map(function (id) {
|
||||
return document.getElementById(id);
|
||||
});
|
||||
if (opt.dataset.freq) {
|
||||
fields[0].value = opt.dataset.freq;
|
||||
fields[1].value = opt.dataset.bw;
|
||||
fields[2].value = opt.dataset.sf;
|
||||
fields[3].value = opt.dataset.cr;
|
||||
fields.forEach(function (el) { el.readOnly = true; });
|
||||
} else {
|
||||
fields.forEach(function (el) { el.readOnly = false; });
|
||||
}
|
||||
if (!opt.dataset.freq) return; // "Custom" — leave the current values alone
|
||||
document.getElementById("radio_freq_mhz").value = trim(opt.dataset.freq / 1e6);
|
||||
document.getElementById("radio_bw_khz").value = trim(opt.dataset.bw / 1e3);
|
||||
document.getElementById("radio_sf").value = opt.dataset.sf;
|
||||
document.getElementById("radio_cr").value = opt.dataset.cr;
|
||||
}
|
||||
applyRegion();
|
||||
|
||||
// When a field is edited by hand, point the dropdown at whichever preset now
|
||||
// matches (or "Custom" if none do), so it always reflects the actual values.
|
||||
// Compare in Hz so display rounding can't cause a spurious mismatch.
|
||||
function syncPreset() {
|
||||
var sel = document.getElementById("region");
|
||||
var freqHz = Math.round(parseFloat(document.getElementById("radio_freq_mhz").value) * 1e6);
|
||||
var bwHz = Math.round(parseFloat(document.getElementById("radio_bw_khz").value) * 1e3);
|
||||
var sf = document.getElementById("radio_sf").value;
|
||||
var cr = document.getElementById("radio_cr").value;
|
||||
for (var i = 0; i < sel.options.length; i++) {
|
||||
var o = sel.options[i];
|
||||
if (o.dataset.freq && +o.dataset.freq === freqHz && +o.dataset.bw === bwHz &&
|
||||
o.dataset.sf === sf && o.dataset.cr === cr) {
|
||||
sel.value = o.value;
|
||||
return;
|
||||
}
|
||||
}
|
||||
sel.value = "custom";
|
||||
}
|
||||
|
||||
RADIO_FIELDS.forEach(function (id) {
|
||||
document.getElementById(id).addEventListener("input", syncPreset);
|
||||
});
|
||||
</script>
|
||||
{{end}}
|
||||
</div>
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
to verify it has access. This is optional — the repeater works either way.
|
||||
</p>
|
||||
<code class="pk">{{.Repeater.PublicKeyHex}}</code>
|
||||
<div class="text-secondary small mt-2">{{.Repeater.RadioFreqHz}} Hz · BW {{.Repeater.RadioBwHz}} · SF{{.Repeater.RadioSF}} · CR{{.Repeater.RadioCR}}</div>
|
||||
<div class="text-secondary small mt-2">{{mhz .Repeater.RadioFreqHz}} MHz · BW {{khz .Repeater.RadioBwHz}} · SF{{.Repeater.RadioSF}} · CR{{.Repeater.RadioCR}}</div>
|
||||
|
||||
<div id="unsupported" class="alert alert-danger mt-3" role="alert" hidden>
|
||||
This browser does not support WebSerial. Use Chrome, Edge, or another Chromium-based browser over HTTPS or on localhost.
|
||||
|
||||
@@ -29,8 +29,8 @@
|
||||
</select>
|
||||
</div>
|
||||
<div class="row g-2">
|
||||
<div class="col-6 col-md-3"><label class="form-label" for="radio_freq_hz">Freq (Hz)</label><input type="number" class="form-control" id="radio_freq_hz" name="radio_freq_hz" value="{{.Repeater.RadioFreqHz}}" required></div>
|
||||
<div class="col-6 col-md-3"><label class="form-label" for="radio_bw_hz">Bandwidth (Hz)</label><input type="number" class="form-control" id="radio_bw_hz" name="radio_bw_hz" value="{{.Repeater.RadioBwHz}}" required></div>
|
||||
<div class="col-6 col-md-3"><label class="form-label" for="radio_freq_mhz">Freq (MHz)</label><input type="number" step="0.001" class="form-control" id="radio_freq_mhz" name="radio_freq_mhz" value="{{mhz .Repeater.RadioFreqHz}}" required></div>
|
||||
<div class="col-6 col-md-3"><label class="form-label" for="radio_bw_khz">Bandwidth (kHz)</label><input type="number" step="0.1" class="form-control" id="radio_bw_khz" name="radio_bw_khz" value="{{khz .Repeater.RadioBwHz}}" required></div>
|
||||
<div class="col-6 col-md-3"><label class="form-label" for="radio_sf">SF</label><input type="number" class="form-control" id="radio_sf" name="radio_sf" value="{{.Repeater.RadioSF}}" min="5" max="12" required></div>
|
||||
<div class="col-6 col-md-3"><label class="form-label" for="radio_cr">CR</label><input type="number" class="form-control" id="radio_cr" name="radio_cr" value="{{.Repeater.RadioCR}}" min="5" max="8" required></div>
|
||||
</div>
|
||||
@@ -55,22 +55,44 @@
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
// The form fields display MHz/kHz; preset data-* attributes are Hz-canonical.
|
||||
var RADIO_FIELDS = ["radio_freq_mhz", "radio_bw_khz", "radio_sf", "radio_cr"];
|
||||
function trim(n) { return String(Math.round(n * 1e6) / 1e6); } // drop float fuzz
|
||||
|
||||
// Seed the radio fields from the chosen preset. Fields stay editable — a
|
||||
// preset is a starting point, not a lock.
|
||||
function applyRegion() {
|
||||
var sel = document.getElementById("region");
|
||||
var opt = sel.options[sel.selectedIndex];
|
||||
var fields = ["radio_freq_hz", "radio_bw_hz", "radio_sf", "radio_cr"].map(function (id) {
|
||||
return document.getElementById(id);
|
||||
});
|
||||
if (opt.dataset.freq) {
|
||||
fields[0].value = opt.dataset.freq;
|
||||
fields[1].value = opt.dataset.bw;
|
||||
fields[2].value = opt.dataset.sf;
|
||||
fields[3].value = opt.dataset.cr;
|
||||
fields.forEach(function (el) { el.readOnly = true; });
|
||||
} else {
|
||||
fields.forEach(function (el) { el.readOnly = false; });
|
||||
}
|
||||
if (!opt.dataset.freq) return; // "Custom" — leave the current values alone
|
||||
document.getElementById("radio_freq_mhz").value = trim(opt.dataset.freq / 1e6);
|
||||
document.getElementById("radio_bw_khz").value = trim(opt.dataset.bw / 1e3);
|
||||
document.getElementById("radio_sf").value = opt.dataset.sf;
|
||||
document.getElementById("radio_cr").value = opt.dataset.cr;
|
||||
}
|
||||
applyRegion();
|
||||
|
||||
// When a field is edited by hand, point the dropdown at whichever preset now
|
||||
// matches (or "Custom" if none do), so it always reflects the actual values.
|
||||
// Compare in Hz so display rounding can't cause a spurious mismatch.
|
||||
function syncPreset() {
|
||||
var sel = document.getElementById("region");
|
||||
var freqHz = Math.round(parseFloat(document.getElementById("radio_freq_mhz").value) * 1e6);
|
||||
var bwHz = Math.round(parseFloat(document.getElementById("radio_bw_khz").value) * 1e3);
|
||||
var sf = document.getElementById("radio_sf").value;
|
||||
var cr = document.getElementById("radio_cr").value;
|
||||
for (var i = 0; i < sel.options.length; i++) {
|
||||
var o = sel.options[i];
|
||||
if (o.dataset.freq && +o.dataset.freq === freqHz && +o.dataset.bw === bwHz &&
|
||||
o.dataset.sf === sf && o.dataset.cr === cr) {
|
||||
sel.value = o.value;
|
||||
return;
|
||||
}
|
||||
}
|
||||
sel.value = "custom";
|
||||
}
|
||||
|
||||
RADIO_FIELDS.forEach(function (id) {
|
||||
document.getElementById(id).addEventListener("input", syncPreset);
|
||||
});
|
||||
</script>
|
||||
{{end}}
|
||||
|
||||
+10
-1
@@ -12,6 +12,7 @@ import (
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
@@ -117,8 +118,16 @@ func (e *Env) SetDefaultLayout(name string) { e.Renderer.defaultLayout = name }
|
||||
// NewRenderer parses the shared base layout (base.html + icons.html) and composes
|
||||
// each of the surface's own *.html pages onto it. Each page redefines the
|
||||
// content/title/header blocks, so every page gets its own cloned template set.
|
||||
// templateFuncs are helpers available to every page template. mhz/khz present
|
||||
// the Hz-canonical radio values in the human-readable units the region presets
|
||||
// use (MHz for frequency, kHz for bandwidth), formatted without trailing zeros.
|
||||
var templateFuncs = template.FuncMap{
|
||||
"mhz": func(hz int64) string { return strconv.FormatFloat(float64(hz)/1e6, 'f', -1, 64) },
|
||||
"khz": func(hz int64) string { return strconv.FormatFloat(float64(hz)/1e3, 'f', -1, 64) },
|
||||
}
|
||||
|
||||
func NewRenderer(cfg *config.Config, surfaceTemplates fs.FS) (*Renderer, error) {
|
||||
base, err := template.New("").ParseFS(sharedTemplatesFS, "templates/base.html", "templates/icons.html", "templates/org_tabs.html", "templates/repeater_tabs.html")
|
||||
base, err := template.New("").Funcs(templateFuncs).ParseFS(sharedTemplatesFS, "templates/base.html", "templates/icons.html", "templates/org_tabs.html", "templates/repeater_tabs.html")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user