v0.8.19
This commit is contained in:
+27
-3
@@ -208,10 +208,18 @@ function groupState(group) {
|
||||
const zones = groupZones(group);
|
||||
const modes = [...new Set(zones.map(zone => zone.inherit_house_mode ? 'house' : (zone.mode || 'cool')))];
|
||||
const presets = [...new Set(zones.map(zone => zone.manual_preset || 'auto'))];
|
||||
const customTargets = zones
|
||||
.filter(zone => (zone.manual_preset || 'auto') === 'custom')
|
||||
.map(zone => Number(zone.manual_setpoint ?? zone.effective_setpoint ?? zone.setpoint))
|
||||
.filter(Number.isFinite);
|
||||
const customTemperature = customTargets.length === zones.length && customTargets.length && customTargets.every(value => Math.abs(value - customTargets[0]) < 0.05)
|
||||
? customTargets[0]
|
||||
: null;
|
||||
return {
|
||||
zones,
|
||||
mode: modes.length === 1 ? modes[0] : (modes.length ? 'mixed' : 'house'),
|
||||
preset: presets.length === 1 ? presets[0] : (presets.length ? 'mixed' : 'auto'),
|
||||
customTemperature,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -223,12 +231,16 @@ function groupCard(group, detailed = false) {
|
||||
const masterOff = app.settings?.house_power_enabled === false;
|
||||
const modeLabelForGroup = value => value === 'house' ? tr('groups.followHouse') : (value === 'mixed' ? tr('groups.mixed') : modeLabel(value));
|
||||
const presetLabelForGroup = value => value === 'mixed' ? tr('groups.mixed') : zonePresetLabel(value);
|
||||
const fallbackTarget = Number(state.zones[0]?.manual_setpoint ?? state.zones[0]?.effective_setpoint ?? state.zones[0]?.setpoint ?? 23);
|
||||
const customTarget = Number.isFinite(state.customTemperature) ? state.customTemperature : (Number.isFinite(fallbackTarget) ? fallbackTarget : 23);
|
||||
const customEditorVisible = state.preset === 'custom';
|
||||
return `<article class="list-card group-card ${powerEnabled ? '' : 'group-off'}">
|
||||
<div class="list-card-head"><div><span class="eyebrow">${esc(tr('groups.group'))}</span><h3>${esc(group.name)}</h3><p>${esc(memberText)}</p></div></div>
|
||||
${detailed ? `<div class="group-state-summary"><div><small>${esc(tr('groups.mode'))}</small><strong>${esc(modeLabelForGroup(state.mode))}</strong></div><div><small>${esc(tr('groups.profile'))}</small><strong>${esc(presetLabelForGroup(state.preset))}</strong></div><div><small>${esc(tr('groups.members'))}</small><strong>${state.zones.length}</strong></div></div>` : ''}
|
||||
<div class="group-control-block"><small>${esc(tr('common.power'))}</small><div class="group-button-row"><button class="${powerEnabled ? 'active' : ''}" data-action="group-power" data-id="${esc(group.id)}" data-value="true">${esc(tr('common.on'))}</button><button class="${!powerEnabled ? 'active' : ''}" data-action="group-power" data-id="${esc(group.id)}" data-value="false">${esc(tr('common.off'))}</button></div></div>
|
||||
<div class="group-control-block"><small>${esc(tr('groups.mode'))}</small><div class="mode-row group-mode-row">${['house','heat','cool'].map(mode => `<button class="${state.mode === mode ? 'active' : ''}" data-action="group-mode" data-id="${esc(group.id)}" data-value="${mode}">${esc(mode === 'house' ? tr('groups.followHouse') : modeLabel(mode))}</button>`).join('')}</div></div>
|
||||
<div class="group-control-block"><small>${esc(tr('groups.profile'))}</small><div class="preset-row group-preset-row">${['auto','comfort','sleep','away'].map(preset => `<button class="${state.preset === preset ? 'active' : ''}" data-action="group-preset" data-id="${esc(group.id)}" data-value="${preset}">${esc(zonePresetLabel(preset))}</button>`).join('')}</div></div>
|
||||
<div class="group-control-block"><small>${esc(tr('groups.profile'))}</small><div class="preset-row group-preset-row">${['auto','comfort','sleep','away'].map(preset => `<button class="${state.preset === preset ? 'active' : ''}" data-action="group-preset" data-id="${esc(group.id)}" data-value="${preset}">${esc(zonePresetLabel(preset))}</button>`).join('')}<button class="${state.preset === 'custom' ? 'active' : ''}" data-action="group-custom-open" data-id="${esc(group.id)}">${esc(tr('preset.custom'))}</button></div>
|
||||
<div class="group-custom-temperature" data-group-custom-editor="${esc(group.id)}" ${customEditorVisible ? '' : 'hidden'}><label><span>${esc(tr('groups.customTemperature'))}</span><div class="group-custom-temperature-row"><input type="text" inputmode="decimal" min="8" max="30" step="0.1" value="${customTarget.toFixed(1)}" data-group-custom-temperature="${esc(group.id)}" aria-label="${esc(tr('groups.customTemperature'))}" title="${esc(tr('groups.customTemperatureHint'))}"><span>°C</span><button type="button" class="primary" data-action="group-custom-temperature" data-id="${esc(group.id)}" title="${esc(tr('groups.applyCustomTemperatureHint'))}">${esc(tr('actions.apply'))}</button></div></label></div></div>
|
||||
${masterOff ? `<p class="field-note warning-note">${esc(tr('groups.masterOff'))}</p>` : ''}
|
||||
${detailed ? `<div class="card-footer"><small>${esc(tr('groups.memberCount', {count: state.zones.length}))}</small><div class="card-menu"><button data-action="edit-group" data-id="${esc(group.id)}">${esc(tr('actions.edit'))}</button><button class="danger" data-action="delete-group" data-id="${esc(group.id)}">${esc(tr('actions.delete'))}</button></div></div>` : ''}
|
||||
</article>`;
|
||||
@@ -245,8 +257,16 @@ function renderGroups() {
|
||||
}
|
||||
|
||||
async function sendGroupControl(id, patch) {
|
||||
const sequence = (app.groupControlSeq[id] || 0) + 1;
|
||||
app.groupControlSeq[id] = sequence;
|
||||
const previous = app.groupControlQueue[id] || Promise.resolve();
|
||||
const request = previous.catch(() => {}).then(() =>
|
||||
api(`/api/groups/${encodeURIComponent(id)}/control`, {method:'POST', body:patch})
|
||||
);
|
||||
app.groupControlQueue[id] = request;
|
||||
try {
|
||||
const result = await api(`/api/groups/${encodeURIComponent(id)}/control`, {method:'POST', body:patch});
|
||||
const result = await request;
|
||||
if (app.groupControlSeq[id] !== sequence) return;
|
||||
if (result.group) {
|
||||
const index = app.groups.findIndex(group => group.id === result.group.id);
|
||||
if (index >= 0) app.groups[index] = result.group; else app.groups.push(result.group);
|
||||
@@ -261,7 +281,11 @@ async function sendGroupControl(id, patch) {
|
||||
const failed = Array.isArray(result.failed) ? result.failed.length : 0;
|
||||
if (failed) toast(tr('groups.partial', {count: failed}), true);
|
||||
else toast(tr('groups.controlUpdated'));
|
||||
} catch (error) { toast(error.message, true); }
|
||||
} catch (error) {
|
||||
if (app.groupControlSeq[id] === sequence) { await loadBootstrap(); toast(error.message, true); }
|
||||
} finally {
|
||||
if (app.groupControlQueue[id] === request) delete app.groupControlQueue[id];
|
||||
}
|
||||
}
|
||||
|
||||
function renderGroupZoneChoices(selectedIds = null) {
|
||||
|
||||
Reference in New Issue
Block a user