v0.10.0
This commit is contained in:
@@ -175,11 +175,11 @@ d6df434f3fbd33ba38dd244830c980e54433c6dff94dd39c716b377255bd2ebf ./web/index.ht
|
||||
9ba3f6fa05b72aa989139b2a909982571b2a02055052e4c40104f1e81a9aa7eb ./web/js/README.md
|
||||
21b662914590e720dafefe4dc3dccc91dbeb963cce9ae91f007c835d2d0c1fe5 ./web/js/bootstrap.js
|
||||
72fa280996ba331a05bdb29a1f49b4e61bdd8964953c8ce1db952156620f08fe ./web/js/charts.js
|
||||
7dd04683781137deedc5dfccafcb5a4d15954041f2e5207312816de216904513 ./web/js/core.js
|
||||
f07600277c60c771c04b1d43c5ed08de6bf518a72660011767b71cde8ec5bde8 ./web/js/core.js
|
||||
dfc123885a9b6b5c1d07c12d2d2625ff15aa0844e5cd3736092ccd0a360da39c ./web/js/dashboard.js
|
||||
2887856e35e086bb575e9fc0d8b0ed1160b6912297a2957535d5416bda68b4b7 ./web/js/entities.js
|
||||
9a0e87911f78cd8a969f4c57ce74774f5fa829fdecd79494d9a7f9d5804f9792 ./web/js/events.js
|
||||
92674d277d12aa2bca0fbd7dffcd6e76f182a2601ae60dbdd33210e906a5fee2 ./web/js/flows.js
|
||||
707614dc2c48c09b483b39d67af8ec4c5c0423f4f1d59e17402bbef521fedb9d ./web/js/flows.js
|
||||
f06557c6d338259059b990f8f79c91d68ac8ec1f245c302468f08929b5506901 ./web/js/forms.js
|
||||
dbf36223863ba882c03eccea4516ba8db7acf0c2e72c6c64490cb994f1e20f96 ./web/js/history.js
|
||||
7eaa04992ea828dc89f5eaebf674cbbfd160f53eb8d0d6ef0d4e47bd8c28c435 ./web/js/main.js
|
||||
@@ -189,6 +189,6 @@ c8c82e88c3715b1bfabf155e36266a4c94f5e2fc03eb39dcdd9d08a1985a997c ./web/js/route
|
||||
34513d9ec2e45c60443dffe028eea74f415b8a29016df6af3377a80a4ac0df5b ./web/js/settings-ui.js
|
||||
aa6a2ba22648de44547efd09f41c4335ebe38ed564f4684e942d7827df40f1a2 ./web/js/settings.js
|
||||
6b653e4fe2d4db4ffae6ff8f39b8ce57a10a990dc0000ee4aae51d949afadd52 ./web/manifest.webmanifest
|
||||
cad5c59275ab306b0de88b76dcc7b667a3a83585bd9c64ba0221bde7744dd1a1 ./web/styles.css
|
||||
91cc0f52bd276a39d11bad6b76674f41c8e88b70f699d02ca71a1c473d1ca557 ./web/styles.css
|
||||
ae1b03f30b494f474a781a5d32072f1e73eba2b7c768109fc1cc8028cb6662a4 ./web/sw.js
|
||||
d505d793ce7cc9485b45b78bba1c0d51887adc7451ab59a42702946e5b991382 ./web/theme-init.js
|
||||
|
||||
+1
-1
@@ -32,7 +32,7 @@ const app = {
|
||||
historyTab: 'overview', historyData: { zones: [], devices: [], sensors: [] }, historyCounts: {},
|
||||
historyZone: 'all', historyDevice: 'all', historySensor: 'all', historyLoading: false,
|
||||
customChartSeries: [], savedCharts: [], chartZooms: {}, chartHiddenSeries: {}, zoneControlSeq: {}, groupControlSeq: {}, zoneControlQueue: {}, groupControlQueue: {}, deviceControlQueue: {}, houseControlQueue: null, climateControlQueue: null, groupCustomDrafts: {}, zoneTemperatureTimers: {},
|
||||
controlPlan: null, controlPlanTimer: null, debugLines: [], debugBacklogLoaded: false, debugFilter: 'all', sensorAliases: {}, flowSharedInputs: [],
|
||||
controlPlan: null, controlPlanTimer: null, debugLines: [], debugBacklogLoaded: false, debugFilter: 'all', sensorAliases: {}, flowSharedInputs: [], flowSharedInputValueCache: {}, flowSharedInputValueRequests: {}, flowSharedInputValueTimer: null,
|
||||
simulationScope: 'units', simulationTarget: 'all', standaloneSimulation: false, dashboardTab: 'main', settingsTab: 'app', systemSnapshotAt: Date.now(),
|
||||
flowDraft: null, flowSelectedNodeId: null, flowSelectedNodeIds: [], flowConnectFrom: null, flowDirty: false,
|
||||
};
|
||||
|
||||
+129
-2
@@ -103,17 +103,144 @@ function openFlowEditor(id = '', { push = true } = {}) {
|
||||
$('#flowEditor').hidden = false;
|
||||
document.body.classList.add('flow-editor-open');
|
||||
renderFlowEditor();
|
||||
startFlowSharedInputValueRefresh();
|
||||
if (push) updateBrowserUrl(`/flows/${flow?.id || 'new'}`);
|
||||
}
|
||||
|
||||
function closeFlowEditor({ push = true, force = false } = {}) {
|
||||
if (!force && app.flowDirty && !confirm(tr('confirm.discardChanges'))) return false;
|
||||
stopFlowSharedInputValueRefresh();
|
||||
$('#flowEditor').hidden = true; document.body.classList.remove('flow-editor-open');
|
||||
app.flowDraft = null; app.flowSelectedNodeId = null; app.flowSelectedNodeIds = []; app.flowConnectFrom = null; app.flowDirty = false;
|
||||
if (push) updateBrowserUrl('/flows');
|
||||
return true;
|
||||
}
|
||||
|
||||
function flowSharedInputValueSignature(item) {
|
||||
return `${item?.kind || ''}:${JSON.stringify(item?.config || {})}`;
|
||||
}
|
||||
|
||||
function flowSharedInputLocalObservation(item) {
|
||||
if (!item) return { hasValue:false, value:null };
|
||||
const c = item.config || {};
|
||||
if (item.kind === 'outdoor_temperature') return { hasValue:app.outdoorTemperature !== null && app.outdoorTemperature !== undefined && Number.isFinite(Number(app.outdoorTemperature)), value:app.outdoorTemperature };
|
||||
if (item.kind === 'device_temperature') {
|
||||
const value = app.devices.find(device => device.id === c.device_id)?.current_temperature;
|
||||
return { hasValue:value !== null && value !== undefined && Number.isFinite(Number(value)), value };
|
||||
}
|
||||
if (item.kind === 'zone_temperature') {
|
||||
const value = app.zones.find(zone => zone.id === c.zone_id)?.current_temperature;
|
||||
return { hasValue:value !== null && value !== undefined && Number.isFinite(Number(value)), value };
|
||||
}
|
||||
if (item.kind === 'house_mode') {
|
||||
const value = app.settings?.house_mode;
|
||||
return { hasValue:value !== null && value !== undefined && value !== '', value };
|
||||
}
|
||||
if (item.kind === 'device_state') {
|
||||
const device = app.devices.find(value => value.id === c.device_id), value = device?.[c.field];
|
||||
return { hasValue:value !== undefined && value !== null, value };
|
||||
}
|
||||
if (item.kind === 'zone_state') {
|
||||
const zone = app.zones.find(value => value.id === c.zone_id), value = zone?.[c.field];
|
||||
return { hasValue:value !== undefined && value !== null, value };
|
||||
}
|
||||
if (item.kind === 'group_state') {
|
||||
const group = app.groups.find(value => value.id === c.group_id), value = group?.[c.field || 'power_enabled'];
|
||||
return { hasValue:value !== undefined && value !== null, value };
|
||||
}
|
||||
if (item.kind === 'night_mode') {
|
||||
const value = app.controlPlan?.night_mode_active;
|
||||
return { hasValue:typeof value === 'boolean', value };
|
||||
}
|
||||
if (item.kind === 'constant') return { hasValue:true, value:c.value };
|
||||
return null;
|
||||
}
|
||||
|
||||
function flowSharedInputObservation(item) {
|
||||
const local = flowSharedInputLocalObservation(item);
|
||||
if (local) return local;
|
||||
const cached = app.flowSharedInputValueCache?.[item?.id];
|
||||
return cached?.signature === flowSharedInputValueSignature(item) ? cached : { hasValue:false, value:null };
|
||||
}
|
||||
|
||||
function flowSharedInputCurrentText(item, observation = flowSharedInputObservation(item)) {
|
||||
if (!observation?.hasValue) return '—';
|
||||
const value = observation.value;
|
||||
if (['outdoor_temperature','device_temperature','zone_temperature'].includes(item.kind)) return fmtTemp(value);
|
||||
if (item.kind === 'ha_numeric') {
|
||||
const numeric = Number(value);
|
||||
if (!Number.isFinite(numeric)) return '—';
|
||||
const unit = String(observation.unit || '').trim();
|
||||
return `${Number.isInteger(numeric) ? numeric : Number(numeric.toFixed(2))}${unit ? ` ${unit}` : ''}`;
|
||||
}
|
||||
if (item.kind === 'house_mode') return houseModeLabel(String(value));
|
||||
if (typeof value === 'boolean') return value ? tr('common.yes') : tr('common.no');
|
||||
if (value === null || value === undefined || value === '') return '—';
|
||||
return String(value);
|
||||
}
|
||||
|
||||
function renderFlowSharedInputCurrentValues() {
|
||||
if (!app.flowDraft) return;
|
||||
$$('[data-flow-shared-current]').forEach(host => {
|
||||
const item = (app.flowSharedInputs || []).find(value => value.id === host.dataset.flowSharedCurrent);
|
||||
const text = item ? flowSharedInputCurrentText(item) : '—';
|
||||
host.title = text;
|
||||
host.innerHTML = `<span>${esc(tr('flow.sharedInputTestCurrent'))}</span><strong>${esc(text)}</strong>`;
|
||||
});
|
||||
}
|
||||
|
||||
async function loadFlowSharedInputHaValue(item) {
|
||||
if (!item || !['ha_state','ha_numeric','ha_attribute','ha_available'].includes(item.kind)) return;
|
||||
const signature = flowSharedInputValueSignature(item), cached = app.flowSharedInputValueCache?.[item.id];
|
||||
if (cached?.signature === signature && Date.now() - Number(cached.fetchedAt || 0) < 15000) return;
|
||||
if (app.flowSharedInputValueRequests?.[item.id] === signature) return;
|
||||
app.flowSharedInputValueRequests[item.id] = signature;
|
||||
try {
|
||||
const entityId = String(item.config?.entity_id || '').trim();
|
||||
if (!entityId) throw new Error('missing entity_id');
|
||||
const result = await api('/api/integrations/home-assistant/entity', { method:'POST', body:{ entity_id:entityId } });
|
||||
let value = result.state, hasValue = true;
|
||||
if (item.kind === 'ha_available') value = result.available === true;
|
||||
else if (item.kind === 'ha_attribute') {
|
||||
value = result.attributes?.[item.config?.attribute];
|
||||
hasValue = value !== undefined;
|
||||
} else if (item.kind === 'ha_numeric') {
|
||||
value = Number(result.state);
|
||||
hasValue = Number.isFinite(value);
|
||||
}
|
||||
app.flowSharedInputValueCache[item.id] = {
|
||||
signature, fetchedAt:Date.now(), hasValue, value,
|
||||
unit:item.kind === 'ha_numeric' ? result.attributes?.unit_of_measurement : '',
|
||||
};
|
||||
} catch (_) {
|
||||
app.flowSharedInputValueCache[item.id] = { signature, fetchedAt:Date.now(), hasValue:false, value:null };
|
||||
} finally {
|
||||
if (app.flowSharedInputValueRequests?.[item.id] === signature) delete app.flowSharedInputValueRequests[item.id];
|
||||
renderFlowSharedInputCurrentValues();
|
||||
}
|
||||
}
|
||||
|
||||
function refreshFlowSharedInputCurrentValues() {
|
||||
if (!app.flowDraft) return;
|
||||
renderFlowSharedInputCurrentValues();
|
||||
const ids = new Set((app.flowDraft.nodes || []).filter(node => node.kind === 'shared_input').map(node => node.config?.input_id).filter(Boolean));
|
||||
ids.forEach(id => {
|
||||
const item = (app.flowSharedInputs || []).find(value => value.id === id);
|
||||
if (item) loadFlowSharedInputHaValue(item);
|
||||
});
|
||||
}
|
||||
|
||||
function startFlowSharedInputValueRefresh() {
|
||||
stopFlowSharedInputValueRefresh();
|
||||
refreshFlowSharedInputCurrentValues();
|
||||
app.flowSharedInputValueTimer = setInterval(refreshFlowSharedInputCurrentValues, 5000);
|
||||
}
|
||||
|
||||
function stopFlowSharedInputValueRefresh() {
|
||||
if (app.flowSharedInputValueTimer) clearInterval(app.flowSharedInputValueTimer);
|
||||
app.flowSharedInputValueTimer = null;
|
||||
}
|
||||
|
||||
function flowNodeSummary(node) {
|
||||
const c = node.config || {};
|
||||
if (node.kind === 'weekday') return (c.days || []).map(day => tr(`day.${day}`)).join(', ') || '—';
|
||||
@@ -163,13 +290,13 @@ function renderFlowEditor() {
|
||||
return `<article class="flow-node flow-node-${esc(meta.category)} ${(app.flowSelectedNodeIds || []).includes(node.id) ? 'selected' : ''}" data-flow-node="${esc(node.id)}" style="left:${Number(node.x || 0)}px;top:${Number(node.y || 0)}px">
|
||||
<button class="flow-port flow-port-in" type="button" data-flow-input="${esc(node.id)}" title="${esc(tr('flow.connectHere'))}"></button>
|
||||
<div class="flow-node-head"><span>${esc(flowNodeTitle(meta))}</span><button type="button" data-flow-remove="${esc(node.id)}" aria-label="${esc(tr('actions.delete'))}">×</button></div>
|
||||
<div class="flow-node-body">${esc(flowNodeSummary(node))}</div>
|
||||
<div class="flow-node-body">${esc(flowNodeSummary(node))}${node.kind === 'shared_input' && node.config?.input_id ? `<div class="flow-node-current" data-flow-shared-current="${esc(node.config.input_id)}"><span>${esc(tr('flow.sharedInputTestCurrent'))}</span><strong>—</strong></div>` : ''}</div>
|
||||
<button class="flow-port flow-port-out ${app.flowConnectFrom === node.id ? 'armed' : ''}" type="button" data-flow-output="${esc(node.id)}" title="${esc(tr('flow.startConnection'))}"></button>
|
||||
</article>`;
|
||||
}).join('');
|
||||
$('#flowEmptyHint').hidden = draft.nodes.length > 0;
|
||||
const selectionCount = $('#flowSelectionCount'); if (selectionCount) selectionCount.textContent = (app.flowSelectedNodeIds || []).length ? tr('flow.selectedCount', { count:(app.flowSelectedNodeIds || []).length }) : '';
|
||||
renderFlowEdges(); renderFlowInspector(); renderFlowInterpretation();
|
||||
renderFlowEdges(); renderFlowInspector(); renderFlowInterpretation(); refreshFlowSharedInputCurrentValues();
|
||||
const status = $('#flowCompileStatus');
|
||||
status.textContent = tr('flow.compileCount', { schedules: draft.compiled_schedule_ids?.length || 0, automations: draft.compiled_automation_ids?.length || 0 });
|
||||
}
|
||||
|
||||
@@ -6379,3 +6379,6 @@ body.flow-editor-open { overflow:hidden; }
|
||||
|
||||
/* Keep zone hysteresis/source controls aligned without stretching selects. */
|
||||
.zone-hysteresis-row { align-items:start; }
|
||||
.flow-editor .flow-node-current { margin-top:6px; padding-top:6px; border-top:1px solid color-mix(in srgb, var(--border) 72%, transparent); display:flex; align-items:baseline; justify-content:space-between; gap:8px; }
|
||||
.flow-editor .flow-node-current span { color:var(--muted); font-size:.68rem; white-space:nowrap; }
|
||||
.flow-editor .flow-node-current strong { min-width:0; max-width:72px; color:var(--text); font-size:.76rem; font-weight:700; text-align:right; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
|
||||
|
||||
Reference in New Issue
Block a user