Web: rework MQTT IATA field
Replace the hardcoded 24-city <select id="mqttIata"> with an <input list="mqttIataList"> + <datalist id="mqttIataList"> pair. The datalist is populated at runtime from the same conf.json endpoint used for radio presets, so the city list stays in sync with the API without requiring firmware updates. The <input> allows the user to type any IATA code directly, including codes not present in the preset list. onfocus="this.select()" selects all text on focus so the user can immediately type to replace the current value without manually clearing it first. Disable the Save button in HTML and enable it only when the field is non-empty. The <input> event handler updates the button state as the user types so Save is disabled immediately when the field is cleared. refreshMqttIataWarning() also updates the button state after a Refresh or Save, ensuring the button is re-enabled when the device value is loaded. The warning banner and inline warning are not updated while the user is typing — the <input> event handler does not call refreshMqttIataWarning(). The warning only reflects the device's saved state, updated on Refresh and after a successful Save. Switch the event listener from "change" to "input" so the Save button responds to every keystroke rather than only on blur.
이 커밋은 다음에 포함됨:
@@ -866,39 +866,10 @@ const char kWebPanelAppHtml[] PROGMEM = R"HTML(
|
||||
<div class="field-card">
|
||||
<label class="label" for="mqttIata">MQTT IATA</label>
|
||||
<div class="inline-actions">
|
||||
<select id="mqttIata">
|
||||
<optgroup label="Configuration">
|
||||
<option value="UNSET">UNSET - To be configured</option>
|
||||
</optgroup>
|
||||
<optgroup label="Russia">
|
||||
<option value="BSK">BSK - Biysk</option>
|
||||
<option value="CEE">CEE - Cherepovets</option>
|
||||
<option value="CEK">CEK - Chelyabinsk</option>
|
||||
<option value="GOJ">GOJ - Nizhny Novgorod</option>
|
||||
<option value="IKT">IKT - Irkutsk</option>
|
||||
<option value="IWA">IWA - Ivanovo</option>
|
||||
<option value="KHV">KHV - Khabarovsk</option>
|
||||
<option value="KLD">KLD - Tver</option>
|
||||
<option value="KLF">KLF - Kaluga</option>
|
||||
<option value="KVX">KVX - Kirov</option>
|
||||
<option value="KZN">KZN - Kazan</option>
|
||||
<option value="LPK">LPK - Lipetsk</option>
|
||||
<option value="MOW">MOW - Moscow</option>
|
||||
<option value="OVB">OVB - Novosibirsk</option>
|
||||
<option value="ROV">ROV - Rostov-on-Don</option>
|
||||
<option value="RZN">RZN - Ryazan</option>
|
||||
<option value="SVX">SVX - Yekaterinburg</option>
|
||||
<option value="TBW">TBW - Tambov</option>
|
||||
<option value="TYA">TYA - Tula</option>
|
||||
<option value="UFA">UFA - Ufa</option>
|
||||
<option value="VOG">VOG - Volgograd</option>
|
||||
<option value="VOZ">VOZ - Voronezh</option>
|
||||
<option value="VSG">VSG - Lugansk</option>
|
||||
<option value="VVO">VVO - Vladivostok</option>
|
||||
</optgroup>
|
||||
</select>
|
||||
<input id="mqttIata" list="mqttIataList" placeholder="Specify IATA code (e.g. MOW)" onfocus="this.select()">
|
||||
<datalist id="mqttIataList"></datalist>
|
||||
<button class="iconbtn" data-load-cmd="get mqtt.iata" data-load-input="mqttIata" title="Refresh MQTT IATA">↻</button>
|
||||
<button class="savebtn" data-prefix="set mqtt.iata " data-input="mqttIata">Save</button>
|
||||
<button class="savebtn" data-prefix="set mqtt.iata " data-input="mqttIata" disabled>Save</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field-card">
|
||||
@@ -2150,6 +2121,8 @@ const char kWebPanelAppHtml[] PROGMEM = R"HTML(
|
||||
}
|
||||
function refreshMqttIataWarning() {
|
||||
const input = document.getElementById("mqttIata");
|
||||
const saveBtn = document.querySelector('[data-input="mqttIata"][data-prefix]');
|
||||
if (saveBtn) saveBtn.disabled = !input || !input.value.trim();
|
||||
const banner = document.getElementById("mqttIataBanner");
|
||||
const inlineWarning = document.getElementById("mqttBrokerWarning");
|
||||
const showWarning = !!(input && isUnsetMqttIata(input.value));
|
||||
@@ -2402,6 +2375,13 @@ const char kWebPanelAppHtml[] PROGMEM = R"HTML(
|
||||
}
|
||||
selectEl.innerHTML = options.join("");
|
||||
syncRadioPresetUi();
|
||||
const datalist = document.getElementById("mqttIataList");
|
||||
const iataOptions = ['<option value="UNSET">UNSET - To be configured</option>'];
|
||||
for (const entry of entries) {
|
||||
if (!entry.code || !entry.name) continue;
|
||||
iataOptions.push(`<option value="${escapeHtml(entry.code)}">${escapeHtml(entry.name)}</option>`);
|
||||
}
|
||||
datalist.innerHTML = iataOptions.join("");
|
||||
} catch (error) {
|
||||
radioPresetEntries = [];
|
||||
selectEl.innerHTML = '<option value="">Community presets unavailable</option>';
|
||||
@@ -2453,9 +2433,12 @@ const char kWebPanelAppHtml[] PROGMEM = R"HTML(
|
||||
document.querySelectorAll("[data-cmd]").forEach((btn) => btn.onclick = () => runCommand(btn.dataset.cmd));
|
||||
document.querySelectorAll("[data-prefix]").forEach((btn) => btn.onclick = () => runPrefixed(btn.dataset.prefix, btn.dataset.input));
|
||||
document.querySelectorAll("[data-load-cmd]").forEach((btn) => btn.onclick = () => loadField(btn.dataset.loadCmd, btn.dataset.loadInput, btn.dataset.loadFormat));
|
||||
const mqttIataSelect = document.getElementById("mqttIata");
|
||||
if (mqttIataSelect) {
|
||||
mqttIataSelect.addEventListener("change", refreshMqttIataWarning);
|
||||
const mqttIataInput = document.getElementById("mqttIata");
|
||||
if (mqttIataInput) {
|
||||
mqttIataInput.addEventListener("input", () => {
|
||||
const saveBtn = document.querySelector('[data-input="mqttIata"][data-prefix]');
|
||||
if (saveBtn) saveBtn.disabled = !mqttIataInput.value.trim();
|
||||
});
|
||||
}
|
||||
async function setMeshcoretelMode(enabled) {
|
||||
if (enabled && getLetsmeshMode() === "both") {
|
||||
|
||||
새 이슈에서 참조
사용자 차단