v0.8.2
This commit is contained in:
+11
-22
@@ -97,13 +97,6 @@ function groupControlForZone(zone) {
|
||||
return app.groups.find(group => group.id===groupId && group.power_enabled !== false && (group.zone_ids || []).includes(zone.id)) || null;
|
||||
}
|
||||
|
||||
function blockingGroupsForZone(zone) {
|
||||
if (app.settings?.house_power_enabled === false || zone?.local_thermostat_power === true) return [];
|
||||
const zoneId=zone?.id || zone?.zone_id;
|
||||
if(!zoneId) return [];
|
||||
return app.groups.filter(group => group.power_enabled === false && (group.zone_ids || []).includes(zoneId));
|
||||
}
|
||||
|
||||
function zoneLockoutActive(zone) {
|
||||
const until=zone?.lockout_until ? new Date(zone.lockout_until).getTime() : NaN;
|
||||
return Number.isFinite(until) && until>Date.now();
|
||||
@@ -137,8 +130,6 @@ function zoneRuntimeStatusLabel(zone, effectiveMode, device = null) {
|
||||
if (zone.device_manual_override === true) return tr('zones.manualDeviceControl');
|
||||
if (!zone.enabled) return tr('common.disabled');
|
||||
if (zone.local_thermostat_power === false) return tr('zones.localThermostatOff');
|
||||
const blockers=blockingGroupsForZone(zone);
|
||||
if (blockers.length) return tr('zones.disabledByGroup', {groups:blockers.map(group=>group.name).join(', ')});
|
||||
if ((effectiveMode || 'off') === 'off') return tr('zones.waiting');
|
||||
if (device?.enabled === false) return tr('zones.waitingDeviceDisabled');
|
||||
if (device && (!device.online || Number(device.communication_failures || 0) > 0)) return tr('zones.waitingOffline');
|
||||
@@ -166,15 +157,12 @@ function zoneCard(zone, detailed = true) {
|
||||
const hasManualOverride = zone.manual_preset != null || zone.manual_setpoint != null;
|
||||
const deviceManualOverride = zone.device_manual_override === true;
|
||||
const controlGroup = groupControlForZone(zone);
|
||||
const blockingGroups = blockingGroupsForZone(zone);
|
||||
const deviceUnavailable = !!device && (device.enabled === false || !device.online || Number(device.communication_failures || 0) > 0);
|
||||
const lockoutWaiting = zone.demand && zoneLockoutActive(zone);
|
||||
const startWaiting = zone.demand && !!device && !deviceUnavailable && !device.power;
|
||||
const modeWaiting = zone.demand && !!device?.power && !deviceUnavailable && ['heat','cool'].includes(effectiveMode) && device.mode !== effectiveMode;
|
||||
const groupBlockedWaiting = blockingGroups.length > 0;
|
||||
const singleBlockingGroup = blockingGroups.length === 1 ? blockingGroups[0] : null;
|
||||
const visualGroup = controlGroup || singleBlockingGroup;
|
||||
const waitingDemand = lockoutWaiting || startWaiting || modeWaiting || groupBlockedWaiting || (zone.demand && deviceUnavailable);
|
||||
const visualGroup = controlGroup;
|
||||
const waitingDemand = lockoutWaiting || startWaiting || modeWaiting || (zone.demand && deviceUnavailable);
|
||||
const workingDemand = zone.demand && !!device?.power && !deviceUnavailable && !waitingDemand;
|
||||
const localThermostatPower = zone.local_thermostat_power;
|
||||
const localThermostatResumeAt = zone.local_thermostat_resume_at || null;
|
||||
@@ -210,7 +198,7 @@ function zoneCard(zone, detailed = true) {
|
||||
const groupNames = (app.groups || []).filter(group => (group.zone_ids || []).includes(zone.id)).map(group => group.name);
|
||||
const groupText = groupNames.length ? groupNames.join(' · ') : tr('zones.noGroup');
|
||||
const policy = zone.inherit_house_mode ? tr('zones.followHouse') : tr(zone.mode === 'heat' ? 'zones.heatOnly' : 'zones.coolOnly');
|
||||
return `<article class="list-card zone-config-card ${workingDemand ? 'demanding' : ''} ${waitingDemand ? 'lockout-waiting' : ''} ${zone.enabled ? '' : 'zone-disabled'} ${deviceManualOverride ? 'manual-takeover' : ''} ${controlGroup ? 'group-controlled' : ''} ${singleBlockingGroup ? 'group-blocked' : ''}" data-zone-config="${esc(zone.id)}"${groupControlStyle(visualGroup)}>
|
||||
return `<article class="list-card zone-config-card ${workingDemand ? 'demanding' : ''} ${waitingDemand ? 'lockout-waiting' : ''} ${zone.enabled ? '' : 'zone-disabled'} ${deviceManualOverride ? 'manual-takeover' : ''} ${controlGroup ? 'group-controlled' : ''}" data-zone-config="${esc(zone.id)}"${groupControlStyle(visualGroup)}>
|
||||
<div class="list-card-head"><div><span class="eyebrow">${esc(tr('zones.configuration'))}</span><h3>${esc(zone.name)}</h3><p>${esc(device?.name || tr('common.noDevice'))} · ${esc(tr('groups.group'))}: ${esc(groupText)}</p></div><button type="button" class="zone-enable-toggle ${zone.enabled ? 'active' : ''}" data-action="zone-enabled" data-id="${esc(zone.id)}" data-value="${zone.enabled ? 'false' : 'true'}" aria-label="${esc(tr(zone.enabled ? 'zones.disable' : 'zones.enable'))}" title="${esc(tr('zones.automationToggleHint'))}"><span>${zone.enabled ? '✓' : '○'}</span>${esc(state)}</button></div>
|
||||
<div class="zone-config-status">
|
||||
<div class="zone-config-temperature"><small>${esc(tr('zones.currentStatus'))}</small><div><span>${fmtTemp(roomTemperature)}</span><b>→</b><strong>${Number.isFinite(target) ? `${target.toFixed(1)}°C` : '—'}</strong></div></div>
|
||||
@@ -229,14 +217,14 @@ function zoneCard(zone, detailed = true) {
|
||||
}
|
||||
|
||||
const runtimeStatus = zoneRuntimeStatusLabel(zone, effectiveMode, device);
|
||||
return `<article class="list-card zone-thermostat quick-control-card quick-thermostat-control ${workingDemand ? 'demanding' : ''} ${waitingDemand ? 'lockout-waiting' : ''} ${zone.enabled ? '' : 'zone-disabled'} ${deviceManualOverride ? 'manual-takeover' : ''} ${controlGroup ? 'group-controlled' : ''} ${singleBlockingGroup ? 'group-blocked' : ''}" data-zone-card="${esc(zone.id)}"${groupControlStyle(visualGroup)}>
|
||||
return `<article class="list-card zone-thermostat quick-control-card quick-thermostat-control ${workingDemand ? 'demanding' : ''} ${waitingDemand ? 'lockout-waiting' : ''} ${zone.enabled ? '' : 'zone-disabled'} ${deviceManualOverride ? 'manual-takeover' : ''} ${controlGroup ? 'group-controlled' : ''}" data-zone-card="${esc(zone.id)}"${groupControlStyle(visualGroup)}>
|
||||
<div class="list-card-head"><div><h3>${esc(zone.name)}</h3><p>${esc(device?.name || tr('common.noDevice'))} · ${esc(zonePresetLabel(displayPreset))}</p></div><div class="zone-quick-actions">${localThermostatPowerControl}${temporaryThermostatControl}</div></div>
|
||||
<div class="thermostat-main"><div><small>${esc(tr('zones.measurement'))}</small><strong>${fmtTemp(roomTemperature)}</strong></div><div class="temperature-control compact"><button data-action="zone-temperature" data-id="${esc(zone.id)}" data-delta="-0.5">−</button><div class="target-temp compact editable-target" data-temperature-kind="zone" data-id="${esc(zone.id)}" data-value="${Number.isFinite(target) ? target : ''}" tabindex="0" role="button" title="${esc(tr('common.clickTemperatureToEdit'))}">${Number.isFinite(target)?target.toFixed(1):'--'}<small>°C</small></div><button data-action="zone-temperature" data-id="${esc(zone.id)}" data-delta="0.5">+</button></div><div><small>${esc(tr('zones.deviceTarget'))}</small><strong>${fmtTemp(zone.device_setpoint)}</strong></div></div>
|
||||
<div class="preset-row quick-control-row quick-control-row-4">
|
||||
${['auto','comfort','sleep','away'].map(preset=>`<button class="${manual===preset?'active':''}" data-action="zone-preset" data-id="${esc(zone.id)}" data-value="${preset}">${esc(preset==='sleep'?tr('zones.sleepNow'):zonePresetLabel(preset))}</button>`).join('')}
|
||||
</div>
|
||||
<div class="mode-row zone-mode-row quick-control-row quick-control-row-3"><button class="${mode==='house'&&globalModeAvailable?'active':''}" data-action="zone-mode" data-id="${esc(zone.id)}" data-value="house"${globalModeDisabled}>${esc(tr('zones.followHouseShort'))}</button><button class="${mode==='heat'?'active':''}" data-action="zone-mode" data-id="${esc(zone.id)}" data-value="heat">${esc(modeLabel('heat'))}</button><button class="${mode==='cool'?'active':''}" data-action="zone-mode" data-id="${esc(zone.id)}" data-value="cool">${esc(modeLabel('cool'))}</button></div>
|
||||
<div class="zone-state-line"><span class="zone-runtime-status ${groupBlockedWaiting ? 'group-blocked-status' : ''}" title="${esc(runtimeStatus)}">${esc(runtimeStatus)}${controlGroup ? ` <b class="group-control-tag">${esc(controlGroup.name)}</b>` : ''}</span><span>${esc(override)}</span></div>
|
||||
<div class="zone-state-line"><span class="zone-runtime-status" title="${esc(runtimeStatus)}">${esc(runtimeStatus)}${controlGroup ? ` <b class="group-control-tag">${esc(controlGroup.name)}</b>` : ''}</span><span>${esc(override)}</span></div>
|
||||
<div class="zone-state-line control-owner-line"><span><strong>${esc(tr('zones.controlOwner'))}:</strong> ${esc(zoneControlOwnerLabel(zone))}</span><span>${esc(zoneControlOwnerMeta(zone))}</span></div>
|
||||
${manualTakeover}
|
||||
</article>`;
|
||||
@@ -288,18 +276,19 @@ function groupCard(group, detailed = false) {
|
||||
const customDraft = app.groupCustomDrafts?.[group.id];
|
||||
const draftTarget = Number(customDraft?.value);
|
||||
const customTarget = Number.isFinite(draftTarget) ? draftTarget : currentCustomTarget;
|
||||
const customEditorVisible = state.preset === 'custom' || customDraft?.open === true;
|
||||
const customDraftPending = customDraft?.open === true && state.preset !== 'custom';
|
||||
const customEditorVisible = powerEnabled && (state.preset === 'custom' || customDraft?.open === true);
|
||||
const customDraftPending = powerEnabled && customDraft?.open === true && state.preset !== 'custom';
|
||||
const groupIsControlSource = powerEnabled && state.zones.some(zone => String(zone.control_source || '') === `group:${group.id}` && !zone.device_manual_override && zone.local_thermostat_power == null);
|
||||
const climateControlsDisabled = powerEnabled ? '' : ` disabled aria-disabled="true" title="${esc(tr('groups.enableControlFirst'))}"`;
|
||||
const groupStatusBadge = groupIsControlSource
|
||||
? `<span class="group-control-badge">${esc(tr('groups.activeControl'))}</span>`
|
||||
: (!powerEnabled ? `<span class="group-control-badge group-off-badge">${esc(tr('groups.gateOff'))}</span>` : '');
|
||||
return `<article class="list-card group-card group-linked ${powerEnabled ? '' : 'group-off'} ${groupIsControlSource ? 'group-controlled' : ''}"${groupControlStyle(group)}>
|
||||
<div class="list-card-head"><div><span class="eyebrow">${esc(tr('groups.group'))}</span><h3>${esc(group.name)}</h3><p>${esc(memberText)}</p></div>${groupStatusBadge}</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('')}<button class="${state.preset === 'custom' ? 'active' : (customDraft?.open === true ? 'editing' : '')}" data-action="group-custom-open" data-id="${esc(group.id)}" title="${esc(tr('groups.customOpenHint'))}">${esc(tr('preset.custom'))}</button></div>
|
||||
<div class="group-control-block"><small>${esc(tr('groups.controlToggle'))}</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}"${climateControlsDisabled}>${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}"${climateControlsDisabled}>${esc(zonePresetLabel(preset))}</button>`).join('')}<button class="${state.preset === 'custom' ? 'active' : (customDraft?.open === true ? 'editing' : '')}" data-action="group-custom-open" data-id="${esc(group.id)}" title="${esc(tr('groups.customOpenHint'))}"${climateControlsDisabled}>${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><button type="button" class="secondary" data-action="group-custom-cancel" data-id="${esc(group.id)}">${esc(tr('actions.cancel'))}</button></div></label>${customDraftPending ? `<small class="group-custom-draft-note">${esc(tr('groups.customDraftPending'))}</small>` : ''}</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>` : ''}
|
||||
|
||||
+1
-1
@@ -122,7 +122,7 @@ document.addEventListener('click', async event => {
|
||||
catch(error){ toast(error.message,true); }
|
||||
return;
|
||||
}
|
||||
if (action === 'group-power') return sendGroupControl(button.dataset.id,{power:button.dataset.value==='true'});
|
||||
if (action === 'group-power') { const power=button.dataset.value==='true'; if(!power) delete app.groupCustomDrafts[button.dataset.id]; return sendGroupControl(button.dataset.id,{power}); }
|
||||
if (action === 'group-mode') return sendGroupControl(button.dataset.id,{mode:button.dataset.value});
|
||||
if (action === 'group-preset') { delete app.groupCustomDrafts[button.dataset.id]; return sendGroupControl(button.dataset.id,{preset:button.dataset.value}); }
|
||||
if (action === 'group-custom-open') {
|
||||
|
||||
Reference in New Issue
Block a user