Improve decoder setup UI and add configurable training timers

This commit is contained in:
larssand
2026-08-27 17:29:44 +02:00
parent 32acc0e9c7
commit a87c64a47c
8 changed files with 493 additions and 57 deletions

View File

@@ -56,23 +56,99 @@ export function renderSettings(deps) {
dom.view.innerHTML = `
<section class="panel">
<div class="panel-header"><h3>${t("settings.decoder")}</h3></div>
<form id="settingsForm" class="panel-body form-grid cols-3 settings-grid">
<p class="hint form-grid-full">${t("settings.decoder_roles_hint")}</p>
<label><span>${t("settings.main_ws_url")}</span><input name="wsUrl" value="${escapeHtml(state.settings.wsUrl)}" placeholder="ws://127.0.0.1:9000" /></label>
<label class="toggle"><input type="checkbox" name="splitDecoderEnabled" ${state.settings.splitDecoderEnabled ? "checked" : ""} /><span>${t("settings.enable_split_decoder")}</span></label>
<label><span>${t("settings.split_ws_url")}</span><input name="splitWsUrl" value="${escapeHtml(state.settings.splitWsUrl || "")}" placeholder="ws://127.0.0.1:9001" /></label>
<label class="toggle"><input type="checkbox" name="pitDecoderEnabled" ${state.settings.pitDecoderEnabled ? "checked" : ""} /><span>${t("settings.enable_pit_decoders")}</span></label>
<label><span>${t("settings.pit_entry_ws_url")}</span><input name="pitEntryWsUrl" value="${escapeHtml(state.settings.pitEntryWsUrl || "")}" placeholder="ws://127.0.0.1:9002" /></label>
<label><span>${t("settings.pit_exit_ws_url")}</span><input name="pitExitWsUrl" value="${escapeHtml(state.settings.pitExitWsUrl || "")}" placeholder="ws://127.0.0.1:9003" /></label>
<input name="backendUrl" value="${escapeHtml(
state.settings.backendUrl || getDefaultBackendUrl()
)}" placeholder="http://127.0.0.1:8081" />
<label class="toggle">
<input type="checkbox" name="autoReconnect" ${state.settings.autoReconnect ? "checked" : ""} />
<span>${t("settings.auto_reconnect")}</span>
</label>
<button class="btn btn-primary" type="submit">${t("settings.save")}</button>
<button id="settingsConnect" class="btn" type="button">${t("settings.connect_now")}</button>
<form id="settingsForm" class="panel-body decoder-settings-form">
<div class="decoder-layout-picker">
<div>
<strong>${t("settings.decoder_setup_title")}</strong>
<p class="hint">${t("settings.decoder_setup_hint")}</p>
</div>
<div class="decoder-layout-options" role="group" aria-label="${t("settings.decoder_setup_title")}">
<button class="btn decoder-layout-button" type="button" data-decoder-layout="main">${t("settings.decoder_layout_main")}</button>
<button class="btn decoder-layout-button" type="button" data-decoder-layout="split">${t("settings.decoder_layout_split")}</button>
<button class="btn decoder-layout-button" type="button" data-decoder-layout="pit">${t("settings.decoder_layout_pit")}</button>
<button class="btn decoder-layout-button" type="button" data-decoder-layout="full">${t("settings.decoder_layout_full")}</button>
</div>
</div>
<p class="hint">${t("settings.decoder_roles_hint")}</p>
<div class="decoder-role-grid">
<section class="decoder-role-card decoder-role-card-main">
<div class="decoder-role-header">
<div>
<strong>${t("timing.main_decoder")}</strong>
<span class="decoder-role-badge is-required">${t("settings.role_required")}</span>
</div>
<p>${t("settings.main_role_hint")}</p>
</div>
<label class="decoder-role-body">
<span>${t("settings.main_ws_url")}</span>
<input name="wsUrl" value="${escapeHtml(state.settings.wsUrl)}" placeholder="ws://127.0.0.1:9000" />
</label>
</section>
<section id="settingsSplitCard" class="decoder-role-card">
<div class="decoder-role-header">
<div>
<strong>${t("timing.split_decoder")}</strong>
<span class="decoder-role-badge" data-decoder-state="split"></span>
</div>
<label class="toggle decoder-role-toggle">
<input id="settingsSplitEnabled" type="checkbox" name="splitDecoderEnabled" ${state.settings.splitDecoderEnabled ? "checked" : ""} />
<span>${t("settings.enable_split_decoder")}</span>
</label>
<p>${t("settings.split_role_hint")}</p>
</div>
<label class="decoder-role-body">
<span>${t("settings.split_ws_url")}</span>
<input name="splitWsUrl" value="${escapeHtml(state.settings.splitWsUrl || "")}" placeholder="ws://127.0.0.1:9001" />
</label>
</section>
<section id="settingsPitCard" class="decoder-role-card">
<div class="decoder-role-header">
<div>
<strong>${t("timing.pit_decoders")}</strong>
<span class="decoder-role-badge" data-decoder-state="pit"></span>
</div>
<label class="toggle decoder-role-toggle">
<input id="settingsPitEnabled" type="checkbox" name="pitDecoderEnabled" ${state.settings.pitDecoderEnabled ? "checked" : ""} />
<span>${t("settings.enable_pit_decoders")}</span>
</label>
<p>${t("settings.pit_role_hint")}</p>
</div>
<div class="decoder-role-body decoder-pit-fields">
<label>
<span>${t("settings.pit_entry_ws_url")}</span>
<input name="pitEntryWsUrl" value="${escapeHtml(state.settings.pitEntryWsUrl || "")}" placeholder="ws://127.0.0.1:9002" />
</label>
<label>
<span>${t("settings.pit_exit_ws_url")}</span>
<input name="pitExitWsUrl" value="${escapeHtml(state.settings.pitExitWsUrl || "")}" placeholder="ws://127.0.0.1:9003" />
</label>
</div>
</section>
</div>
<section class="decoder-connection-card">
<div>
<strong>${t("settings.connection_title")}</strong>
<p class="hint">${t("settings.backend_url_hint")}</p>
</div>
<label>
<span>${t("settings.backend_url")}</span>
<input name="backendUrl" value="${escapeHtml(
state.settings.backendUrl || getDefaultBackendUrl()
)}" placeholder="http://127.0.0.1:8081" />
</label>
<label class="toggle decoder-reconnect-toggle">
<input type="checkbox" name="autoReconnect" ${state.settings.autoReconnect ? "checked" : ""} />
<span>${t("settings.auto_reconnect")}</span>
</label>
<div class="decoder-settings-actions">
<button class="btn btn-primary" type="submit">${t("settings.save")}</button>
<button id="settingsConnect" class="btn" type="button">${t("settings.connect_now")}</button>
</div>
</section>
</form>
<div class="panel-body">
<p>${t("settings.expected_json")}: <code>{"msg":"PASSING", "transponder":232323, "rtc_time":"..."}</code></p>
@@ -346,6 +422,38 @@ export function renderSettings(deps) {
state.settings.speechPitch = Math.max(0.7, Math.min(1.3, Number(form.get("speechPitch") || 1) || 1));
};
const splitDecoderToggle = document.getElementById("settingsSplitEnabled");
const pitDecoderToggle = document.getElementById("settingsPitEnabled");
const updateDecoderLayoutUi = () => {
const splitEnabled = Boolean(splitDecoderToggle?.checked);
const pitEnabled = Boolean(pitDecoderToggle?.checked);
document.getElementById("settingsSplitCard")?.classList.toggle("is-disabled", !splitEnabled);
document.getElementById("settingsPitCard")?.classList.toggle("is-disabled", !pitEnabled);
document.querySelector('[data-decoder-state="split"]')?.replaceChildren(
document.createTextNode(t(splitEnabled ? "settings.role_enabled" : "settings.role_disabled"))
);
document.querySelector('[data-decoder-state="pit"]')?.replaceChildren(
document.createTextNode(t(pitEnabled ? "settings.role_enabled" : "settings.role_disabled"))
);
const activeLayout = splitEnabled && pitEnabled ? "full" : splitEnabled ? "split" : pitEnabled ? "pit" : "main";
document.querySelectorAll("[data-decoder-layout]").forEach((button) => {
button.classList.toggle("is-active", button.dataset.decoderLayout === activeLayout);
button.setAttribute("aria-pressed", String(button.dataset.decoderLayout === activeLayout));
});
};
splitDecoderToggle?.addEventListener("change", updateDecoderLayoutUi);
pitDecoderToggle?.addEventListener("change", updateDecoderLayoutUi);
document.querySelectorAll("[data-decoder-layout]").forEach((button) => {
button.addEventListener("click", () => {
const layout = button.dataset.decoderLayout;
if (splitDecoderToggle) splitDecoderToggle.checked = layout === "split" || layout === "full";
if (pitDecoderToggle) pitDecoderToggle.checked = layout === "pit" || layout === "full";
updateDecoderLayoutUi();
});
});
updateDecoderLayoutUi();
document.getElementById("settingsForm")?.addEventListener("submit", (e) => {
e.preventDefault();
const form = new FormData(e.currentTarget);