refactor: Significantly refactor how conditions work internally (#1886)

- Much improved API cleanliness and testability to allow further
extensibility in future
 - Simplified code in `live` view

This technically contains a small change in how overrides work in the
`live` view. Since that change is _closer_ to the documentation, and
since this is likely to be rarely used, this is not considered a
breaking change. Previously, overrides for a given live camera would
always render _as if_ that camera was selected, vs was actually
selected. Now, overrides will only apply in the live view when the
camera is _actually_ selected. If this is an issue for you in practice,
lets discuss.
This commit is contained in:
Dermot Duffy
2025-02-11 20:07:55 -08:00
committed by GitHub
parent a92074bc8d
commit 8d3cf07b43
51 changed files with 2443 additions and 2118 deletions
+26 -21
View File
@@ -1,14 +1,15 @@
import { ConditionsManager } from '../conditions/conditions-manager.js';
import { ConditionsEvaluationResult } from '../conditions/types.js';
import { Automation, AutomationActions } from '../config/types.js';
import { localize } from '../localize/localize.js';
import { CardAutomationsAPI, TaggedAutomations } from './types.js';
import { CardAutomationsAPI, TaggedAutomation } from './types.js';
const MAX_NESTED_AUTOMATION_EXECUTIONS = 10;
export class AutomationsManager {
protected _api: CardAutomationsAPI;
protected _automations: TaggedAutomations = [];
protected _priorEvaluations: Map<Automation, boolean> = new Map();
protected _automations = new Map<TaggedAutomation, ConditionsManager>();
// A counter to avoid infinite loops, increases every time actions are run,
// decreases every time actions are complete.
@@ -19,14 +20,28 @@ export class AutomationsManager {
}
public deleteAutomations(tag?: unknown) {
this._automations = this._automations.filter((automation) => automation.tag !== tag);
for (const [automation, conditionManager] of this._automations) {
if (automation.tag === tag) {
this._automations.delete(automation);
conditionManager.destroy();
}
}
}
public addAutomations(automations: TaggedAutomations): void {
this._automations.push(...automations);
public addAutomations(automations: TaggedAutomation[]): void {
for (const automation of automations) {
const conditionManager = new ConditionsManager(
automation.conditions,
this._api.getConditionStateManager(),
);
conditionManager.addListener((result: ConditionsEvaluationResult) =>
this._execute(automation, result),
);
this._automations.set(automation, conditionManager);
}
}
public execute(): void {
protected _execute(automation: Automation, result: ConditionsEvaluationResult): void {
if (
!this._api.getHASSManager().hasHASS() ||
// Never execute automations if the card hasn't finished initializing, as
@@ -40,20 +55,10 @@ export class AutomationsManager {
return;
}
const actionsToRun: AutomationActions = [];
for (const automation of this._automations) {
const shouldExecute = this._api
.getConditionsManager()
.evaluateConditions(automation.conditions);
const actions = shouldExecute ? automation.actions : automation.actions_not;
const priorEvaluation = this._priorEvaluations.get(automation);
this._priorEvaluations.set(automation, shouldExecute);
if (shouldExecute !== priorEvaluation && actions) {
actionsToRun.push(...actions);
}
}
const shouldExecute = result.result;
const actions = shouldExecute ? automation.actions : automation.actions_not;
if (!actionsToRun.length) {
if (!actions?.length) {
return;
}
@@ -70,6 +75,6 @@ export class AutomationsManager {
await this._api.getActionsManager().executeActions(actions);
--this._nestedAutomationExecutions;
};
runActions(actionsToRun);
runActions(actions);
}
}