v0.10.0
This commit is contained in:
+70
-3
@@ -29,6 +29,29 @@ function newFlowId(prefix = 'node') {
|
||||
return `${prefix}-${Date.now()}-${Math.random().toString(16).slice(2)}`;
|
||||
}
|
||||
|
||||
function sharedFlowInputRequiresComparison(kind) {
|
||||
return ['outdoor_temperature','device_temperature','zone_temperature','ha_state','ha_numeric','ha_attribute','house_mode','device_state','zone_state','group_state'].includes(kind);
|
||||
}
|
||||
|
||||
function sharedFlowReferenceComparisonDefaults(item) {
|
||||
const kind = item?.kind || '';
|
||||
if (['outdoor_temperature','device_temperature','zone_temperature'].includes(kind)) return { operator:'lt', value:20 };
|
||||
if (kind === 'ha_numeric') return { operator:'lt', value:0 };
|
||||
if (kind === 'ha_state') return { operator:'eq', value:'on' };
|
||||
if (kind === 'ha_attribute') return { operator:'eq', value:'' };
|
||||
if (kind === 'house_mode') return { operator:'eq', value:'cool' };
|
||||
if (kind === 'device_state') return { operator:'eq', value:'true' };
|
||||
if (kind === 'zone_state') return { operator:'eq', value:'true' };
|
||||
if (kind === 'group_state') return { operator:'eq', value:'true' };
|
||||
return {};
|
||||
}
|
||||
|
||||
function sharedFlowReferenceDefaultConfig(item) {
|
||||
const config = { input_id:item?.id || '' };
|
||||
if (item && sharedFlowInputRequiresComparison(item.kind)) Object.assign(config, sharedFlowReferenceComparisonDefaults(item));
|
||||
return config;
|
||||
}
|
||||
|
||||
function flowDefaultConfig(kind) {
|
||||
if (kind === 'weekday') return { days: [1, 2, 3, 4, 5] };
|
||||
if (kind === 'time_range') return { start: '06:00', end: '08:00' };
|
||||
@@ -46,7 +69,7 @@ function flowDefaultConfig(kind) {
|
||||
if (kind === 'group_state') return { group_id: app.groups[0]?.id || '', field: 'power_enabled', operator: 'eq', value: 'true' };
|
||||
if (kind === 'night_mode') return {};
|
||||
if (kind === 'constant') return { value: true };
|
||||
if (kind === 'shared_input') return { input_id: app.flowSharedInputs?.[0]?.id || '' };
|
||||
if (kind === 'shared_input') return sharedFlowReferenceDefaultConfig(app.flowSharedInputs?.[0]);
|
||||
if (kind === 'zone_thermostat') return { zone_id: app.zones[0]?.id || '', preset: 'comfort', setpoint: 21, mode: 'auto', cooldown_seconds: 60 };
|
||||
if (kind === 'device_action') return { device_id: app.devices[0]?.id || '', power: true, mode: '', target_temperature: null, fan_speed: null, swing_vertical: null, swing_horizontal: null, quiet: null, turbo: null, light: null, air: null, xfan: null, health: null, sleep: null, cooldown_seconds: 60 };
|
||||
if (kind === 'group_action') return { group_id: app.groups[0]?.id || '', power: true, mode: '', preset: '', setpoint: 21, cooldown_seconds: 60 };
|
||||
@@ -109,7 +132,16 @@ function flowNodeSummary(node) {
|
||||
if (node.kind === 'group_state') return `${app.groups.find(g => g.id === c.group_id)?.name || tr('groups.group')} · ${c.field || 'power_enabled'} ${flowOperatorLabel(c.operator)} ${c.value ?? '—'}`;
|
||||
if (node.kind === 'night_mode') return tr('flow.nightModeActive');
|
||||
if (node.kind === 'constant') return c.value === false ? tr('common.no') : tr('common.yes');
|
||||
if (node.kind === 'shared_input') { const item = (app.flowSharedInputs || []).find(value => value.id === c.input_id); return item ? `${item.name} · ${sharedFlowInputSummary(item)}` : tr('flow.sharedInputMissing'); }
|
||||
if (node.kind === 'shared_input') {
|
||||
const item = (app.flowSharedInputs || []).find(value => value.id === c.input_id);
|
||||
if (!item) return tr('flow.sharedInputMissing');
|
||||
if (sharedFlowInputRequiresComparison(item.kind)) {
|
||||
return c.operator
|
||||
? `${item.name} · ${sharedFlowInputSourceSummary(item)} · ${flowOperatorLabel(c.operator)} ${c.value ?? '—'}`
|
||||
: `${item.name} · ${tr('flow.selectOperator')}`;
|
||||
}
|
||||
return `${item.name} · ${sharedFlowInputSourceSummary(item)}`;
|
||||
}
|
||||
if (node.kind === 'logic_and') return tr('flow.allConditions');
|
||||
if (node.kind === 'logic_or') return tr('flow.anyCondition');
|
||||
if (node.kind === 'logic_not') return tr('flow.invertCondition');
|
||||
@@ -161,6 +193,20 @@ function flowSelectOptions(items, selected, nameFn = item => item.name) {
|
||||
}
|
||||
function flowOperatorOptions(selected) { return [['lt','<'],['lte','≤'],['gt','>'],['gte','≥'],['eq','='],['neq','≠']].map(([v,l]) => `<option value="${v}" ${v === selected ? 'selected' : ''}>${l}</option>`).join(''); }
|
||||
|
||||
function sharedFlowReferenceComparisonFields(item, config) {
|
||||
if (!item || !sharedFlowInputRequiresComparison(item.kind)) return '';
|
||||
const defaults = sharedFlowReferenceComparisonDefaults(item);
|
||||
const selected = config.operator || '';
|
||||
const numeric = ['outdoor_temperature','device_temperature','zone_temperature','ha_numeric'].includes(item.kind);
|
||||
const fullOperators = numeric || item.kind === 'ha_attribute';
|
||||
const operatorOptions = `${!selected ? `<option value="" selected disabled>${esc(tr('flow.selectOperator'))}</option>` : ''}${fullOperators ? flowOperatorOptions(selected) : [['eq','='],['neq','≠']].map(([value,label]) => `<option value="${value}" ${value === selected ? 'selected' : ''}>${label}</option>`).join('')}`;
|
||||
const value = config.value ?? defaults.value ?? '';
|
||||
let valueField = `<input data-flow-config="value" value="${esc(value)}">`;
|
||||
if (numeric) valueField = `<input type="number" step="0.1" data-flow-config="value" value="${Number(value ?? 0)}">`;
|
||||
else if (item.kind === 'house_mode') valueField = `<select data-flow-config="value">${['cool','heat','off'].map(v => `<option value="${v}" ${String(value) === v ? 'selected' : ''}>${esc(v === 'off' ? tr('common.off') : tr(`mode.${v}`))}</option>`).join('')}</select>`;
|
||||
return `<p class="field-note">${esc(tr('flow.sharedInputFlowComparisonHint'))}</p><div class="two"><label><span>${esc(tr('flow.operator'))}</span><select data-flow-config="operator">${operatorOptions}</select></label><label><span>${esc(tr('flow.value'))}</span>${valueField}</label></div>`;
|
||||
}
|
||||
|
||||
function renderFlowInspector() {
|
||||
const host = $('#flowInspector'), node = flowNodeById(app.flowSelectedNodeId); if (!host) return;
|
||||
if (!node) { host.innerHTML = `<div class="empty compact"><strong>${esc(tr('flow.selectBlock'))}</strong><span>${esc(tr('flow.selectBlockHint'))}</span></div>`; return; }
|
||||
@@ -182,7 +228,12 @@ function renderFlowInspector() {
|
||||
else if (node.kind === 'group_state') fields = `<label><span>${esc(tr('groups.group'))}</span><select data-flow-config="group_id">${flowSelectOptions(app.groups, c.group_id)}</select></label><label><span>${esc(tr('flow.field'))}</span><select data-flow-config="field"><option value="power_enabled" selected>power_enabled</option></select></label>${flowTextComparisonFields(c)}`;
|
||||
else if (node.kind === 'night_mode') fields = `<p class="field-note">${esc(tr('flow.nightModeHint'))}</p>`;
|
||||
else if (node.kind === 'constant') fields = `<label><span>${esc(tr('flow.value'))}</span><select data-flow-config="value"><option value="true" ${c.value !== false ? 'selected' : ''}>${esc(tr('common.yes'))}</option><option value="false" ${c.value === false ? 'selected' : ''}>${esc(tr('common.no'))}</option></select></label>`;
|
||||
else if (node.kind === 'shared_input') fields = (app.flowSharedInputs || []).length ? `<label><span>${esc(tr('flow.sharedInputTitle'))}</span><select data-flow-config="input_id">${flowSelectOptions(app.flowSharedInputs || [], c.input_id)}</select></label><p class="field-note">${esc(tr('flow.sharedInputNodeHint'))}</p>` : `<div class="empty compact"><strong>${esc(tr('flow.sharedInputsEmpty'))}</strong><span>${esc(tr('flow.sharedInputNodeEmptyHint'))}</span></div>`;
|
||||
else if (node.kind === 'shared_input') {
|
||||
const item = (app.flowSharedInputs || []).find(value => value.id === c.input_id) || app.flowSharedInputs?.[0];
|
||||
fields = (app.flowSharedInputs || []).length
|
||||
? `<label><span>${esc(tr('flow.sharedInputTitle'))}</span><select data-flow-config="input_id">${flowSelectOptions(app.flowSharedInputs || [], c.input_id)}</select></label><p class="field-note">${esc(tr('flow.sharedInputNodeHint'))}</p>${sharedFlowReferenceComparisonFields(item, c)}`
|
||||
: `<div class="empty compact"><strong>${esc(tr('flow.sharedInputsEmpty'))}</strong><span>${esc(tr('flow.sharedInputNodeEmptyHint'))}</span></div>`;
|
||||
}
|
||||
else if (node.kind === 'logic_and') fields = `<p class="field-note">${esc(tr('flow.andHint'))}</p>`;
|
||||
else if (node.kind === 'logic_or') fields = `<p class="field-note">${esc(tr('flow.orHint'))}</p>`;
|
||||
else if (node.kind === 'logic_not') fields = `<p class="field-note">${esc(tr('flow.notHint'))}</p>`;
|
||||
@@ -698,6 +749,22 @@ function updateFlowConfig(input) {
|
||||
const node = flowNodeById(app.flowSelectedNodeId); if (!node) return;
|
||||
const key = input.dataset.flowConfig; if (!key) return;
|
||||
if (key === 'days') node.config.days = $$('[data-flow-config="days"]', $('#flowInspector')).filter(el => el.checked).map(el => Number(el.value));
|
||||
else if (node.kind === 'shared_input' && key === 'input_id') {
|
||||
const item = (app.flowSharedInputs || []).find(value => value.id === input.value);
|
||||
node.config = sharedFlowReferenceDefaultConfig(item);
|
||||
}
|
||||
else if (node.kind === 'shared_input' && key === 'operator') {
|
||||
node.config.operator = input.value;
|
||||
if (node.config.value == null) {
|
||||
const item = (app.flowSharedInputs || []).find(value => value.id === node.config.input_id);
|
||||
node.config.value = sharedFlowReferenceComparisonDefaults(item).value ?? '';
|
||||
}
|
||||
}
|
||||
else if (node.kind === 'shared_input' && key === 'value') {
|
||||
const item = (app.flowSharedInputs || []).find(value => value.id === node.config.input_id);
|
||||
const numeric = ['outdoor_temperature','device_temperature','zone_temperature','ha_numeric'].includes(item?.kind);
|
||||
node.config.value = numeric ? (input.value === '' ? null : Number(input.value)) : input.value;
|
||||
}
|
||||
else if (['power','swing_vertical','swing_horizontal','quiet','turbo','light','air','xfan','health','sleep'].includes(key)) node.config[key] = input.value === '' ? null : input.value === 'true';
|
||||
else if (key === 'value' && node.kind === 'constant') node.config[key] = input.value === 'true';
|
||||
else if (['value','setpoint','target_temperature','cooldown_seconds','fan_speed'].includes(key) && ['outdoor_temperature','device_temperature','zone_temperature','ha_numeric','zone_thermostat','device_action','group_action'].includes(node.kind)) node.config[key] = input.value === '' ? null : Number(input.value);
|
||||
|
||||
+36
-21
@@ -36,9 +36,31 @@ function flowSharedInputKinds() {
|
||||
];
|
||||
}
|
||||
|
||||
function sharedFlowInputSummary(item) {
|
||||
function flowSharedInputDefaultConfig(kind) {
|
||||
if (kind === 'device_temperature') return { device_id: app.devices[0]?.id || '' };
|
||||
if (kind === 'zone_temperature') return { zone_id: app.zones[0]?.id || '' };
|
||||
if (kind === 'ha_state' || kind === 'ha_numeric' || kind === 'ha_available') return { entity_id: '' };
|
||||
if (kind === 'ha_attribute') return { entity_id: '', attribute: '' };
|
||||
if (kind === 'device_state') return { device_id: app.devices[0]?.id || '', field: 'online' };
|
||||
if (kind === 'zone_state') return { zone_id: app.zones[0]?.id || '', field: 'demand' };
|
||||
if (kind === 'group_state') return { group_id: app.groups[0]?.id || '', field: 'power_enabled' };
|
||||
if (kind === 'constant') return { value: true };
|
||||
return {};
|
||||
}
|
||||
|
||||
function sharedFlowInputSourceSummary(item) {
|
||||
if (!item) return '—';
|
||||
try { return flowNodeSummary({ kind: item.kind, config: item.config || {} }); } catch (_) { return item.kind || '—'; }
|
||||
const c = item.config || {};
|
||||
if (item.kind === 'outdoor_temperature') return tr('flow.node.outdoorTemperature');
|
||||
if (item.kind === 'device_temperature') return app.devices.find(value => value.id === c.device_id)?.name || tr('common.noDevice');
|
||||
if (item.kind === 'zone_temperature') return app.zones.find(value => value.id === c.zone_id)?.name || tr('common.noZone');
|
||||
if (item.kind === 'ha_state' || item.kind === 'ha_numeric' || item.kind === 'ha_available') return c.entity_id || 'entity_id';
|
||||
if (item.kind === 'ha_attribute') return `${c.entity_id || 'entity_id'}.${c.attribute || 'attribute'}`;
|
||||
if (item.kind === 'house_mode') return tr('flow.houseMode');
|
||||
if (item.kind === 'device_state') return `${app.devices.find(value => value.id === c.device_id)?.name || tr('common.noDevice')} · ${c.field || 'state'}`;
|
||||
if (item.kind === 'zone_state') return `${app.zones.find(value => value.id === c.zone_id)?.name || tr('common.noZone')} · ${c.field || 'state'}`;
|
||||
if (item.kind === 'group_state') return `${app.groups.find(value => value.id === c.group_id)?.name || tr('groups.group')} · ${c.field || 'power_enabled'}`;
|
||||
return flowNodeSummary({ kind:item.kind, config:c });
|
||||
}
|
||||
|
||||
function isHaSharedInputKind(kind) { return ['ha_state','ha_numeric','ha_attribute','ha_available'].includes(kind); }
|
||||
@@ -56,7 +78,7 @@ function renderFlowSharedInputs() {
|
||||
? `<div class="flow-shared-usage"><small>${esc(tr('flow.sharedInputUsedBy', { count:usages.length }))}</small><div>${usages.slice(0, 4).map(flow => `<button type="button" class="link-button" data-open-shared-flow="${esc(flow.id)}" title="${esc(tr('flow.openReferencedFlow', { name:flow.name }))}">${esc(flow.name)}</button>`).join('')}${usages.length > 4 ? `<span class="muted">+${usages.length - 4}</span>` : ''}</div></div>`
|
||||
: `<small class="flow-shared-unused">${esc(tr('flow.sharedInputUnused'))}</small>`;
|
||||
return `<div class="flow-shared-input-row">
|
||||
<div><strong>${esc(item.name)}</strong><small>${esc(flowNodeTitle(FLOW_NODE_META[item.kind] || { title:item.kind }))} · ${esc(sharedFlowInputSummary(item))}</small><code>${esc(item.id)}</code>${usageMarkup}</div>
|
||||
<div><strong>${esc(item.name)}</strong><small>${esc(flowNodeTitle(FLOW_NODE_META[item.kind] || { title:item.kind }))} · ${esc(sharedFlowInputSourceSummary(item))}</small><code>${esc(item.id)}</code>${usageMarkup}</div>
|
||||
<div class="flow-shared-input-actions"><button type="button" class="secondary" data-flow-shared-edit="${esc(item.id)}">${esc(tr('actions.edit'))}</button><button type="button" class="danger" data-flow-shared-delete="${esc(item.id)}">${esc(tr('actions.delete'))}</button></div>
|
||||
</div>`;
|
||||
}).join('') : `<div class="empty compact"><strong>${esc(tr('flow.sharedInputsEmpty'))}</strong><span>${esc(tr('flow.sharedInputsEmptyHint'))}</span></div>`;
|
||||
@@ -68,28 +90,22 @@ function sharedFlowOptions(items, selected, label = item => item.name) {
|
||||
return items.map(item => `<option value="${esc(item.id)}" ${item.id === selected ? 'selected' : ''}>${esc(label(item))}</option>`).join('');
|
||||
}
|
||||
|
||||
function sharedFlowOperatorOptions(selected = 'eq') {
|
||||
return [['lt','<'],['lte','≤'],['gt','>'],['gte','≥'],['eq','='],['neq','≠']].map(([value,label]) => `<option value="${value}" ${value === selected ? 'selected' : ''}>${label}</option>`).join('');
|
||||
}
|
||||
|
||||
function renderFlowSharedInputFields(kind, config = {}) {
|
||||
const host = $('#flowSharedInputFields'); if (!host) return;
|
||||
const c = config || {};
|
||||
const comparison = (temperature = false) => `<div class="two"><label><span>${esc(tr('flow.operator'))}</span><select data-shared-config="operator">${sharedFlowOperatorOptions(c.operator || 'lt')}</select></label><label><span>${esc(temperature ? tr('automations.thresholdC') : tr('flow.value'))}</span><input type="number" step="0.1" data-shared-config="value" value="${Number(c.value ?? 0)}"></label></div>`;
|
||||
const textComparison = () => `<div class="two"><label><span>${esc(tr('flow.operator'))}</span><select data-shared-config="operator"><option value="eq" ${c.operator !== 'neq' ? 'selected' : ''}>=</option><option value="neq" ${c.operator === 'neq' ? 'selected' : ''}>≠</option></select></label><label><span>${esc(tr('flow.value'))}</span><input data-shared-config="value" value="${esc(c.value ?? '')}"></label></div>`;
|
||||
let fields = '';
|
||||
if (kind === 'constant') fields = `<label><span>${esc(tr('flow.value'))}</span><select data-shared-config="value"><option value="true" ${c.value !== false ? 'selected' : ''}>${esc(tr('common.yes'))}</option><option value="false" ${c.value === false ? 'selected' : ''}>${esc(tr('common.no'))}</option></select></label>`;
|
||||
else if (kind === 'ha_state') fields = `<label><span>entity_id</span><input data-shared-config="entity_id" value="${esc(c.entity_id || '')}" placeholder="binary_sensor.window"></label>${textComparison()}`;
|
||||
else if (kind === 'ha_numeric') fields = `<label><span>entity_id</span><input data-shared-config="entity_id" value="${esc(c.entity_id || '')}" placeholder="sensor.energy_price"></label>${comparison(false)}`;
|
||||
else if (kind === 'ha_attribute') fields = `<label><span>entity_id</span><input data-shared-config="entity_id" value="${esc(c.entity_id || '')}" placeholder="climate.living_room"></label><label><span>${esc(tr('flow.attribute'))}</span><input data-shared-config="attribute" value="${esc(c.attribute || '')}" placeholder="hvac_action"></label><div class="two"><label><span>${esc(tr('flow.operator'))}</span><select data-shared-config="operator">${sharedFlowOperatorOptions(c.operator || 'eq')}</select></label><label><span>${esc(tr('flow.value'))}</span><input data-shared-config="value" value="${esc(c.value ?? '')}"></label></div>`;
|
||||
else if (kind === 'ha_state') fields = `<label><span>entity_id</span><input data-shared-config="entity_id" value="${esc(c.entity_id || '')}" placeholder="binary_sensor.window"></label>`;
|
||||
else if (kind === 'ha_numeric') fields = `<label><span>entity_id</span><input data-shared-config="entity_id" value="${esc(c.entity_id || '')}" placeholder="sensor.energy_price"></label>`;
|
||||
else if (kind === 'ha_attribute') fields = `<label><span>entity_id</span><input data-shared-config="entity_id" value="${esc(c.entity_id || '')}" placeholder="climate.living_room"></label><label><span>${esc(tr('flow.attribute'))}</span><input data-shared-config="attribute" value="${esc(c.attribute || '')}" placeholder="hvac_action"></label>`;
|
||||
else if (kind === 'ha_available') fields = `<label><span>entity_id</span><input data-shared-config="entity_id" value="${esc(c.entity_id || '')}" placeholder="binary_sensor.window"></label><p class="field-note">${esc(tr('flow.haAvailableHint'))}</p>`;
|
||||
else if (kind === 'outdoor_temperature') fields = comparison(true);
|
||||
else if (kind === 'device_temperature') fields = `<label><span>${esc(tr('common.device'))}</span><select data-shared-config="device_id">${sharedFlowOptions(app.devices, c.device_id)}</select></label>${comparison(true)}`;
|
||||
else if (kind === 'zone_temperature') fields = `<label><span>${esc(tr('common.zone'))}</span><select data-shared-config="zone_id">${sharedFlowOptions(app.zones, c.zone_id)}</select></label>${comparison(true)}`;
|
||||
else if (kind === 'house_mode') fields = `<div class="two"><label><span>${esc(tr('flow.operator'))}</span><select data-shared-config="operator"><option value="eq" ${c.operator !== 'neq' ? 'selected' : ''}>=</option><option value="neq" ${c.operator === 'neq' ? 'selected' : ''}>≠</option></select></label><label><span>${esc(tr('flow.houseMode'))}</span><select data-shared-config="value">${['cool','heat','off'].map(v => `<option value="${v}" ${c.value === v ? 'selected' : ''}>${esc(v === 'off' ? tr('common.off') : tr(`mode.${v}`))}</option>`).join('')}</select></label></div>`;
|
||||
else if (kind === 'device_state') fields = `<label><span>${esc(tr('common.device'))}</span><select data-shared-config="device_id">${sharedFlowOptions(app.devices, c.device_id)}</select></label><label><span>${esc(tr('flow.field'))}</span><select data-shared-config="field">${['enabled','online','power','mode','fan_speed','swing_vertical','swing_horizontal','quiet','turbo','light','air','xfan','health','sleep'].map(v => `<option value="${v}" ${c.field === v ? 'selected' : ''}>${esc(v)}</option>`).join('')}</select></label>${textComparison()}`;
|
||||
else if (kind === 'zone_state') fields = `<label><span>${esc(tr('common.zone'))}</span><select data-shared-config="zone_id">${sharedFlowOptions(app.zones, c.zone_id)}</select></label><label><span>${esc(tr('flow.field'))}</span><select data-shared-config="field">${['enabled','mode','active_preset','demand','control_owner','device_manual_override','local_thermostat_power'].map(v => `<option value="${v}" ${c.field === v ? 'selected' : ''}>${esc(v)}</option>`).join('')}</select></label>${textComparison()}`;
|
||||
else if (kind === 'group_state') fields = `<label><span>${esc(tr('groups.group'))}</span><select data-shared-config="group_id">${sharedFlowOptions(app.groups, c.group_id)}</select></label><input type="hidden" data-shared-config="field" value="power_enabled">${textComparison()}`;
|
||||
else if (kind === 'outdoor_temperature') fields = `<p class="field-note">${esc(tr('flow.sharedInputSourceOnlyHint'))}</p>`;
|
||||
else if (kind === 'device_temperature') fields = `<label><span>${esc(tr('common.device'))}</span><select data-shared-config="device_id">${sharedFlowOptions(app.devices, c.device_id)}</select></label>`;
|
||||
else if (kind === 'zone_temperature') fields = `<label><span>${esc(tr('common.zone'))}</span><select data-shared-config="zone_id">${sharedFlowOptions(app.zones, c.zone_id)}</select></label>`;
|
||||
else if (kind === 'house_mode') fields = `<p class="field-note">${esc(tr('flow.sharedInputSourceOnlyHint'))}</p>`;
|
||||
else if (kind === 'device_state') fields = `<label><span>${esc(tr('common.device'))}</span><select data-shared-config="device_id">${sharedFlowOptions(app.devices, c.device_id)}</select></label><label><span>${esc(tr('flow.field'))}</span><select data-shared-config="field">${['enabled','online','power','mode','fan_speed','swing_vertical','swing_horizontal','quiet','turbo','light','air','xfan','health','sleep'].map(v => `<option value="${v}" ${c.field === v ? 'selected' : ''}>${esc(v)}</option>`).join('')}</select></label>`;
|
||||
else if (kind === 'zone_state') fields = `<label><span>${esc(tr('common.zone'))}</span><select data-shared-config="zone_id">${sharedFlowOptions(app.zones, c.zone_id)}</select></label><label><span>${esc(tr('flow.field'))}</span><select data-shared-config="field">${['enabled','mode','active_preset','demand','control_owner','device_manual_override','local_thermostat_power'].map(v => `<option value="${v}" ${c.field === v ? 'selected' : ''}>${esc(v)}</option>`).join('')}</select></label>`;
|
||||
else if (kind === 'group_state') fields = `<label><span>${esc(tr('groups.group'))}</span><select data-shared-config="group_id">${sharedFlowOptions(app.groups, c.group_id)}</select></label><input type="hidden" data-shared-config="field" value="power_enabled">`;
|
||||
else if (kind === 'night_mode') fields = `<p class="field-note">${esc(tr('flow.nightModeHint'))}</p>`;
|
||||
host.innerHTML = fields;
|
||||
const testPanel = $('#flowSharedInputTestPanel'), testResult = $('#flowSharedInputTestResult');
|
||||
@@ -106,7 +122,7 @@ function openFlowSharedInputEditor(id = '') {
|
||||
form.name.value = item?.name || '';
|
||||
form.kind.value = item?.kind || 'constant';
|
||||
form.dataset.editingId = item?.id || '';
|
||||
renderFlowSharedInputFields(form.kind.value, item?.config || flowDefaultConfig(form.kind.value));
|
||||
renderFlowSharedInputFields(form.kind.value, item?.config || flowSharedInputDefaultConfig(form.kind.value));
|
||||
dialog.showModal();
|
||||
setTimeout(() => form.name.focus(), 0);
|
||||
}
|
||||
@@ -117,7 +133,6 @@ function collectFlowSharedInputConfig(kind) {
|
||||
const key = field.dataset.sharedConfig;
|
||||
let value = field.value;
|
||||
if (kind === 'constant' && key === 'value') value = value === 'true';
|
||||
else if (field.type === 'number') value = Number(value);
|
||||
config[key] = value;
|
||||
});
|
||||
return config;
|
||||
|
||||
+15
-24
@@ -293,28 +293,19 @@ document.addEventListener('change', event => {
|
||||
|
||||
$('#addFlowSharedInput')?.addEventListener('click', () => openFlowSharedInputEditor());
|
||||
$('#flowSharedInputKind')?.addEventListener('change', event => {
|
||||
renderFlowSharedInputFields(event.target.value, flowDefaultConfig(event.target.value));
|
||||
renderFlowSharedInputFields(event.target.value, flowSharedInputDefaultConfig(event.target.value));
|
||||
});
|
||||
|
||||
function compareFlowSharedHaValue(actual, operator, expected) {
|
||||
if (['lt','lte','gt','gte'].includes(operator)) {
|
||||
const a = Number(actual), b = Number(expected);
|
||||
if (!Number.isFinite(a) || !Number.isFinite(b)) return false;
|
||||
if (operator === 'lt') return a < b;
|
||||
if (operator === 'lte') return a <= b;
|
||||
if (operator === 'gt') return a > b;
|
||||
return a >= b;
|
||||
}
|
||||
const left = actual == null ? '' : String(actual);
|
||||
const right = expected == null ? '' : String(expected);
|
||||
return operator === 'neq' ? left !== right : left === right;
|
||||
}
|
||||
|
||||
function evaluateFlowSharedHaTest(kind, config, result) {
|
||||
if (kind === 'ha_available') return { actual:result.state, matched:result.available === true };
|
||||
let actual = result.state;
|
||||
if (kind === 'ha_attribute') actual = result.attributes?.[config.attribute];
|
||||
return { actual, matched:compareFlowSharedHaValue(actual, config.operator || 'eq', config.value) };
|
||||
if (kind === 'ha_available') return { actual:result.available === true, valid:true };
|
||||
if (kind === 'ha_attribute') {
|
||||
const actual = result.attributes?.[config.attribute];
|
||||
return { actual, valid:actual !== undefined };
|
||||
}
|
||||
if (kind === 'ha_numeric') {
|
||||
const actual = Number(result.state);
|
||||
return { actual, valid:Number.isFinite(actual) };
|
||||
}
|
||||
return { actual:result.state, valid:true };
|
||||
}
|
||||
|
||||
$('#flowSharedInputTest')?.addEventListener('click', async event => {
|
||||
@@ -336,10 +327,10 @@ $('#flowSharedInputTest')?.addEventListener('click', async event => {
|
||||
const result = await api('/api/integrations/home-assistant/entity', { method:'POST', body:{ entity_id:entityId } });
|
||||
const evaluation = evaluateFlowSharedHaTest(kind, config, result);
|
||||
const actual = evaluation.actual == null ? 'null' : (typeof evaluation.actual === 'object' ? JSON.stringify(evaluation.actual) : String(evaluation.actual));
|
||||
const expectation = kind === 'ha_available' ? tr('flow.sharedInputTestAvailable') : `${config.operator || 'eq'} ${config.value ?? ''}`;
|
||||
resultHost.classList.toggle('pass', evaluation.matched);
|
||||
resultHost.classList.toggle('fail', !evaluation.matched);
|
||||
resultHost.innerHTML = `<div><strong>${esc(evaluation.matched ? tr('flow.sharedInputTestMatched') : tr('flow.sharedInputTestNotMatched'))}</strong><span class="badge ${evaluation.matched ? 'active' : ''}">${esc(result.available ? tr('flow.sharedInputTestAvailable') : tr('flow.sharedInputTestUnavailable'))}</span></div><dl><div><dt>${esc(tr('flow.sharedInputTestCurrent'))}</dt><dd><code>${esc(actual)}</code></dd></div><div><dt>${esc(tr('flow.sharedInputTestExpected'))}</dt><dd><code>${esc(expectation)}</code></dd></div></dl><small>${esc(result.entity_id)}${result.last_updated ? ` · ${esc(dateTime(result.last_updated))}` : ''}</small>`;
|
||||
const success = evaluation.valid;
|
||||
resultHost.classList.toggle('pass', success);
|
||||
resultHost.classList.toggle('fail', !success);
|
||||
resultHost.innerHTML = `<div><strong>${esc(success ? tr('flow.sharedInputTestValueRead') : tr('flow.sharedInputTestUnavailable'))}</strong><span class="badge ${success ? 'active' : ''}">${esc(result.available ? tr('flow.sharedInputTestAvailable') : tr('flow.sharedInputTestUnavailable'))}</span></div><dl><div><dt>${esc(tr('flow.sharedInputTestCurrent'))}</dt><dd><code>${esc(actual)}</code></dd></div></dl><small>${esc(result.entity_id)}${result.last_updated ? ` · ${esc(dateTime(result.last_updated))}` : ''}</small>`;
|
||||
} catch (error) {
|
||||
resultHost.classList.remove('pass'); resultHost.classList.add('fail');
|
||||
resultHost.innerHTML = `<strong>${esc(tr('flow.sharedInputTestUnavailable'))}</strong><span>${esc(error.message)}</span>`;
|
||||
|
||||
Reference in New Issue
Block a user