Improve decoder setup UI and add configurable training timers
This commit is contained in:
142
src/settings.js
142
src/settings.js
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user