Refactor radar system
This commit is contained in:
+131
-25
@@ -8,6 +8,10 @@
|
||||
|
||||
/* ---- DOM handles ------------------------------------------------- */
|
||||
const plotImg = document.getElementById("plot");
|
||||
const sar3dStatusEl = document.getElementById("sar3d-status");
|
||||
const displayComboControls = document.getElementById("display-combo-controls");
|
||||
const displayComboSelect = document.getElementById("display-combo");
|
||||
const btnNextCombo = document.getElementById("btn-next-combo");
|
||||
|
||||
const btnStart = document.getElementById("btn-start");
|
||||
const btnSingle = document.getElementById("btn-single");
|
||||
@@ -89,6 +93,25 @@ bindControl(btnStop, "/api/stop", {});
|
||||
bindControl(btnTmpRef, "/api/tmp_reference", {});
|
||||
bindControl(btnRemoveLast, "/api/remove_last", {});
|
||||
|
||||
let comboOptionsSignature = "";
|
||||
|
||||
async function changeDisplayCombo(path, body) {
|
||||
displayComboSelect.disabled = btnNextCombo.disabled = true;
|
||||
try {
|
||||
applyStatus(await api(path, body));
|
||||
} catch (err) {
|
||||
toast(err.message, true);
|
||||
} finally {
|
||||
displayComboSelect.disabled = btnNextCombo.disabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
displayComboSelect.addEventListener("change", () => {
|
||||
const [input_pos, output_pos] = displayComboSelect.value.split(":").map(Number);
|
||||
changeDisplayCombo("/api/display_combo", { input_pos, output_pos });
|
||||
});
|
||||
btnNextCombo.addEventListener("click", () => changeDisplayCombo("/api/next_combo", {}));
|
||||
|
||||
/* ---- settings panel --------------------------------------------- */
|
||||
settingsToggle.addEventListener("click", () => sidePanel.classList.toggle("collapsed"));
|
||||
|
||||
@@ -98,24 +121,88 @@ settingsToggle.addEventListener("click", () => sidePanel.classList.toggle("colla
|
||||
const fieldInputs = {}; // field name -> { el, kind, dirty }
|
||||
let formSignature = ""; // field names currently in the form (detect mode/structure change)
|
||||
|
||||
function settingsFormSignature(schema) {
|
||||
return JSON.stringify(schema.map((entry) => [entry.name, entry.kind, entry.position_count]));
|
||||
}
|
||||
|
||||
function makeGeometryEditor(entry) {
|
||||
const el = document.createElement("div");
|
||||
el.className = "geometry-editor";
|
||||
const table = document.createElement("table");
|
||||
const head = table.createTHead().insertRow();
|
||||
const keys = [entry.position_field, "x_m", "y_m", "z_m"];
|
||||
for (const title of [entry.position_field === "output_pos" ? "Output" : "Input", "X (m)", "Y (m)", "Z (m)", ""]) {
|
||||
const cell = document.createElement("th");
|
||||
cell.textContent = title;
|
||||
head.appendChild(cell);
|
||||
}
|
||||
const body = table.createTBody();
|
||||
const add = document.createElement("button");
|
||||
const appendRow = (record) => {
|
||||
const row = body.insertRow();
|
||||
for (const [index, key] of keys.entries()) {
|
||||
const input = document.createElement("input");
|
||||
input.type = "number";
|
||||
input.step = index === 0 ? "1" : "any";
|
||||
input.required = true;
|
||||
input.setAttribute("aria-label", `${entry.name} ${key}`);
|
||||
if (index === 0) { input.min = "0"; input.max = entry.position_count - 1; }
|
||||
input.value = record[key] ?? 0;
|
||||
input.disabled = entry.enabled === false;
|
||||
row.insertCell().appendChild(input);
|
||||
}
|
||||
const remove = document.createElement("button");
|
||||
remove.type = "button";
|
||||
remove.textContent = "−";
|
||||
remove.title = "Remove antenna";
|
||||
remove.disabled = entry.enabled === false;
|
||||
remove.addEventListener("click", () => {
|
||||
row.remove();
|
||||
add.disabled = entry.enabled === false;
|
||||
el.dispatchEvent(new Event("input"));
|
||||
});
|
||||
row.insertCell().appendChild(remove);
|
||||
};
|
||||
el.getRecords = () => Array.from(body.rows, (row) => Object.fromEntries(
|
||||
Array.from(row.querySelectorAll("input"), (input, index) => {
|
||||
if (!input.reportValidity()) throw new Error("Enter valid antenna coordinates");
|
||||
return [keys[index], Number(input.value)];
|
||||
})
|
||||
));
|
||||
el.setRecords = (records) => {
|
||||
body.replaceChildren();
|
||||
for (const record of records) appendRow(record);
|
||||
add.disabled = entry.enabled === false || records.length >= entry.position_count;
|
||||
};
|
||||
el.setRecords(entry.value);
|
||||
add.type = "button";
|
||||
add.textContent = "Add antenna";
|
||||
add.addEventListener("click", () => {
|
||||
if (body.rows.length >= entry.position_count) return;
|
||||
const positions = new Set(Array.from(body.rows, (row) => Number(row.querySelector("input").value)));
|
||||
let position = 0;
|
||||
while (positions.has(position)) position++;
|
||||
appendRow({ [entry.position_field]: position });
|
||||
add.disabled = body.rows.length >= entry.position_count;
|
||||
el.dispatchEvent(new Event("input"));
|
||||
});
|
||||
el.append(table, add);
|
||||
return el;
|
||||
}
|
||||
|
||||
function makeFieldRow(entry) {
|
||||
const row = document.createElement("div");
|
||||
row.className = "field";
|
||||
if (entry.kind === "geometry") row.classList.add("geometry-field");
|
||||
const label = document.createElement("label");
|
||||
label.textContent = entry.name;
|
||||
label.textContent = entry.kind === "geometry" ? (entry.name === "tx_geometry" ? "Tx antenna positions" : "Rx antenna positions") : entry.name;
|
||||
label.htmlFor = "f_" + entry.name;
|
||||
if (entry.applies_on_start) {
|
||||
// Stable run_config field: editing it here takes effect on the next pipeline start.
|
||||
const hint = document.createElement("span");
|
||||
hint.className = "on-start-hint";
|
||||
hint.textContent = " (on Start)";
|
||||
hint.title = "Applied when the pipeline next starts";
|
||||
label.appendChild(hint);
|
||||
}
|
||||
row.appendChild(label);
|
||||
|
||||
let el;
|
||||
if (entry.kind === "bool") {
|
||||
if (entry.kind === "geometry") {
|
||||
el = makeGeometryEditor(entry);
|
||||
} else if (entry.kind === "bool") {
|
||||
el = document.createElement("input");
|
||||
el.type = "checkbox";
|
||||
el.checked = !!entry.value;
|
||||
@@ -173,23 +260,23 @@ function buildSettingsForm(schema) {
|
||||
settingsFields.appendChild(heading);
|
||||
}
|
||||
settingsFields.appendChild(makeFieldRow(entry));
|
||||
if (entry.name === "rx_geometry") {
|
||||
const note = document.createElement("div");
|
||||
note.className = "config-active";
|
||||
note.textContent = "To apply Tx/Rx geometry or permittivity changes: Save Config and restart the app";
|
||||
settingsFields.appendChild(note);
|
||||
}
|
||||
}
|
||||
formSignature = schema.map((e) => e.name).join(",");
|
||||
formSignature = settingsFormSignature(schema);
|
||||
}
|
||||
|
||||
function collectFields() {
|
||||
const out = {};
|
||||
for (const key in fieldInputs) {
|
||||
const { el, kind } = fieldInputs[key];
|
||||
if (kind === "bool") out[key] = el.checked;
|
||||
else if (kind === "int") out[key] = parseInt(el.value, 10);
|
||||
else if (kind === "float") out[key] = parseFloat(el.value);
|
||||
if (kind === "geometry") out[key] = el.getRecords();
|
||||
else if (kind === "bool") out[key] = el.checked;
|
||||
else if (kind === "int" || kind === "float") {
|
||||
const value = Number(el.value);
|
||||
if (!el.value.trim() || !Number.isFinite(value) || (kind === "int" && !Number.isSafeInteger(value))) {
|
||||
throw new Error("Enter valid numeric settings");
|
||||
}
|
||||
out[key] = value;
|
||||
}
|
||||
else out[key] = el.value; // select + text (positions are sent as CSV text)
|
||||
}
|
||||
return out;
|
||||
@@ -197,7 +284,8 @@ function collectFields() {
|
||||
|
||||
function setFieldValue(input, value) {
|
||||
const { el, kind } = input;
|
||||
if (kind === "bool") el.checked = !!value;
|
||||
if (kind === "geometry") el.setRecords(value);
|
||||
else if (kind === "bool") el.checked = !!value;
|
||||
else if (kind === "select") el.value = String(value);
|
||||
else el.value = value == null ? "" : value;
|
||||
}
|
||||
@@ -218,13 +306,13 @@ async function applyOne(field, value) {
|
||||
// changed (e.g. the mode switched); otherwise update values in place without
|
||||
// clobbering a field the operator is editing here.
|
||||
function applySettings(schema) {
|
||||
if (schema.map((e) => e.name).join(",") !== formSignature) {
|
||||
if (settingsFormSignature(schema) !== formSignature) {
|
||||
buildSettingsForm(schema);
|
||||
return;
|
||||
}
|
||||
for (const entry of schema) {
|
||||
const input = fieldInputs[entry.name];
|
||||
if (!input || input.el === document.activeElement || input.dirty) continue;
|
||||
if (!input || input.el.contains(document.activeElement) || input.dirty) continue;
|
||||
setFieldValue(input, entry.value);
|
||||
if (entry.enabled !== undefined) input.el.disabled = entry.enabled === false;
|
||||
}
|
||||
@@ -333,8 +421,8 @@ function updateRecordButtonState() {
|
||||
}
|
||||
|
||||
btnStartRecording.addEventListener("click", async () => {
|
||||
const count = parseInt(recordCountInput.value, 10);
|
||||
if (!Number.isFinite(count) || count < 1) {
|
||||
const count = Number(recordCountInput.value);
|
||||
if (!Number.isSafeInteger(count) || count < 1) {
|
||||
toast("Enter how many sweeps to record (>= 1)", true);
|
||||
return;
|
||||
}
|
||||
@@ -361,6 +449,22 @@ function setStat(el, label, value, cls) {
|
||||
}
|
||||
|
||||
function applyStatus(s) {
|
||||
if (s.display_combo) {
|
||||
const combo = s.display_combo;
|
||||
displayComboControls.hidden = !combo.enabled;
|
||||
const options = combo.options || [];
|
||||
const signature = JSON.stringify(options);
|
||||
if (signature !== comboOptionsSignature) {
|
||||
displayComboSelect.replaceChildren(...options.map(([inp, out]) => {
|
||||
const option = document.createElement("option");
|
||||
option.value = `${inp}:${out}`;
|
||||
option.textContent = `Input ${inp} / Output ${out}`;
|
||||
return option;
|
||||
}));
|
||||
comboOptionsSignature = signature;
|
||||
}
|
||||
displayComboSelect.value = `${combo.input}:${combo.output}`;
|
||||
}
|
||||
if ("running" in s) {
|
||||
setStat(runningEl, "running", s.running ? "yes" : "no", s.running ? "ok" : "off");
|
||||
pipelineRunning = !!s.running;
|
||||
@@ -393,6 +497,8 @@ function applyStatus(s) {
|
||||
if ("raw_count" in s)
|
||||
setStat(scansEl, "scans",
|
||||
`raw ${s.raw_count} / preprocessed ${s.preprocessed_count} / results ${s.result_count}`);
|
||||
sar3dStatusEl.textContent = s.sar3d_status || "";
|
||||
sar3dStatusEl.hidden = !s.sar3d_status;
|
||||
}
|
||||
|
||||
/* ---- frame rendering (latest-wins; the frame IS the Qt plot image) - */
|
||||
|
||||
Reference in New Issue
Block a user