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:
@@ -1,4 +1,5 @@
|
||||
import isEqual from 'lodash-es/isEqual';
|
||||
import { ConditionsManager } from '../../conditions/conditions-manager.js';
|
||||
import { isConfigUpgradeable } from '../../config/management.js';
|
||||
import { setProfiles } from '../../config/profiles/index.js';
|
||||
import {
|
||||
@@ -9,9 +10,9 @@ import {
|
||||
} from '../../config/types.js';
|
||||
import { localize } from '../../localize/localize.js';
|
||||
import { getParseErrorPaths } from '../../utils/zod.js';
|
||||
import { getOverriddenConfig } from '../conditions-manager.js';
|
||||
import { InitializationAspect } from '../initialization-manager.js';
|
||||
import { CardConfigAPI } from '../types.js';
|
||||
import { getOverriddenConfig } from './get-overridden-config.js';
|
||||
import { setAutomationsFromConfig } from './load-automations.js';
|
||||
import { setKeyboardShortcutsFromConfig } from './load-keyboard-shortcuts.js';
|
||||
|
||||
@@ -26,6 +27,7 @@ export class ConfigManager {
|
||||
protected _overriddenConfig: AdvancedCameraCardConfig | null = null;
|
||||
protected _rawConfig: RawAdvancedCameraCardConfig | null = null;
|
||||
protected _cardWideConfig: CardWideConfig | null = null;
|
||||
protected _overridesConditionsManager: ConditionsManager | null = null;
|
||||
|
||||
constructor(api: CardConfigAPI) {
|
||||
this._api = api;
|
||||
@@ -85,8 +87,16 @@ export class ConfigManager {
|
||||
debug: config.debug,
|
||||
};
|
||||
|
||||
this._api.getConditionsManager().setConditionsFromConfig();
|
||||
this._api.getConditionsManager().setState({
|
||||
this._overridesConditionsManager?.destroy();
|
||||
this._overridesConditionsManager = this._config.overrides?.length
|
||||
? new ConditionsManager(
|
||||
this._config.overrides.map((override) => override.conditions).flat(),
|
||||
this._api.getConditionStateManager(),
|
||||
)
|
||||
: null;
|
||||
this._overridesConditionsManager?.addListener(() => this._processOverrideConfig());
|
||||
|
||||
this._api.getConditionStateManager().setState({
|
||||
view: undefined,
|
||||
displayMode: undefined,
|
||||
camera: undefined,
|
||||
@@ -102,31 +112,22 @@ export class ConfigManager {
|
||||
setKeyboardShortcutsFromConfig(this._api, this);
|
||||
setAutomationsFromConfig(this._api);
|
||||
|
||||
this.computeOverrideConfig();
|
||||
this._processOverrideConfig();
|
||||
|
||||
this._api.getCardElementManager().update();
|
||||
}
|
||||
|
||||
public computeOverrideConfig(): void {
|
||||
const conditionsManager = this._api.getConditionsManager();
|
||||
protected _processOverrideConfig(): void {
|
||||
/* istanbul ignore if: No (current) way to reach this code -- @preserve */
|
||||
if (!this._config) {
|
||||
return;
|
||||
}
|
||||
|
||||
let overriddenConfig: AdvancedCameraCardConfig | null = null;
|
||||
try {
|
||||
overriddenConfig = getOverriddenConfig(conditionsManager, this._config, {
|
||||
configOverrides: this._config.overrides,
|
||||
schema: advancedCameraCardConfigSchema,
|
||||
});
|
||||
} catch (ev) {
|
||||
this._api.getMessageManager().setErrorIfHigherPriority(ev);
|
||||
return;
|
||||
}
|
||||
const overriddenConfig = this._getOverriddenConfig();
|
||||
|
||||
// Save on Lit re-rendering costs by only updating the configuration if it
|
||||
// actually changes.
|
||||
if (isEqual(overriddenConfig, this._overriddenConfig)) {
|
||||
if (!overriddenConfig || isEqual(overriddenConfig, this._overriddenConfig)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -154,14 +155,30 @@ export class ConfigManager {
|
||||
.uninitialize(InitializationAspect.MICROPHONE_CONNECT);
|
||||
}
|
||||
|
||||
/* async */ this._initializeBackground(previousConfig);
|
||||
/* async */ this._initializeBackgroundAndUpdate(previousConfig);
|
||||
}
|
||||
|
||||
protected _getOverriddenConfig(): AdvancedCameraCardConfig | null {
|
||||
if (!this._overridesConditionsManager || !this._config) {
|
||||
return this._config;
|
||||
}
|
||||
|
||||
try {
|
||||
return getOverriddenConfig(this._overridesConditionsManager, this._config, {
|
||||
configOverrides: this._config.overrides,
|
||||
schema: advancedCameraCardConfigSchema,
|
||||
});
|
||||
} catch (ev) {
|
||||
this._api.getMessageManager().setErrorIfHigherPriority(ev);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize config dependent items in the background. For items that the
|
||||
* card hard requires, use InitializationManager instead.
|
||||
*/
|
||||
protected async _initializeBackground(
|
||||
protected async _initializeBackgroundAndUpdate(
|
||||
previousConfig: AdvancedCameraCardConfig | null,
|
||||
): Promise<void> {
|
||||
await this._api.getDefaultManager().initializeIfNecessary(previousConfig);
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
import { merge } from 'lodash-es';
|
||||
import { ZodType as ZodSchema } from 'zod';
|
||||
import { ConditionsManagerReadonlyInterface } from '../../conditions/types';
|
||||
import {
|
||||
copyConfig,
|
||||
deleteConfigValue,
|
||||
getConfigValue,
|
||||
setConfigValue,
|
||||
} from '../../config/management';
|
||||
import { Overrides, RawAdvancedCameraCardConfig } from '../../config/types';
|
||||
import { localize } from '../../localize/localize';
|
||||
import { AdvancedCameraCardError } from '../../types';
|
||||
import { desparsifyArrays } from '../../utils/basic';
|
||||
|
||||
class OverrideConfigurationError extends AdvancedCameraCardError {}
|
||||
|
||||
export function getOverriddenConfig<RT extends RawAdvancedCameraCardConfig>(
|
||||
manager: ConditionsManagerReadonlyInterface,
|
||||
config: Readonly<RT>,
|
||||
options?: {
|
||||
configOverrides?: Readonly<Overrides>;
|
||||
schema?: ZodSchema;
|
||||
},
|
||||
): RT {
|
||||
if (!options?.configOverrides) {
|
||||
return config;
|
||||
}
|
||||
|
||||
let output = copyConfig(config);
|
||||
let overridden = false;
|
||||
for (const override of options.configOverrides) {
|
||||
if (manager.getEvaluation()?.result) {
|
||||
override.delete?.forEach((deletionKey) => {
|
||||
deleteConfigValue(output, deletionKey);
|
||||
});
|
||||
|
||||
Object.keys(override.set ?? {}).forEach((setKey) => {
|
||||
setConfigValue(output, setKey, override.set?.[setKey]);
|
||||
});
|
||||
|
||||
Object.keys(override.merge ?? {}).forEach((mergeKey) => {
|
||||
setConfigValue(
|
||||
output,
|
||||
mergeKey,
|
||||
merge({}, getConfigValue(output, mergeKey), override.merge?.[mergeKey]),
|
||||
);
|
||||
});
|
||||
|
||||
overridden = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!overridden) {
|
||||
// Return the same configuration object if it has not been overridden (to
|
||||
// reduce re-renders for a configuration that has not changed).
|
||||
return config;
|
||||
}
|
||||
|
||||
if (options?.configOverrides?.some((override) => override.delete?.length)) {
|
||||
// If anything was deleted during this override, empty undefined slots may
|
||||
// be left in arrays where values were unset. Desparsify them.
|
||||
output = desparsifyArrays(output);
|
||||
}
|
||||
|
||||
if (options?.schema) {
|
||||
const parseResult = options.schema.safeParse(output);
|
||||
if (!parseResult.success) {
|
||||
throw new OverrideConfigurationError(
|
||||
localize('error.invalid_configuration_override'),
|
||||
[parseResult.error.errors, output],
|
||||
);
|
||||
}
|
||||
return parseResult.data;
|
||||
}
|
||||
return output;
|
||||
}
|
||||
@@ -3,8 +3,8 @@ import {
|
||||
PTZKeyboardShortcutName,
|
||||
} from '../../config/keyboard-shortcuts';
|
||||
import { PTZAction } from '../../config/ptz';
|
||||
import { CardConfigLoaderAPI, TaggedAutomations } from '../types';
|
||||
import { createPTZMultiAction } from '../../utils/action';
|
||||
import { CardConfigLoaderAPI, TaggedAutomation } from '../types';
|
||||
|
||||
export const setKeyboardShortcutsFromConfig = (
|
||||
api: CardConfigLoaderAPI,
|
||||
@@ -47,12 +47,12 @@ const ptzKeyboardShortcutToPTZAction = (
|
||||
const convertKeyboardShortcutsToAutomations = (
|
||||
tag: unknown,
|
||||
shortcuts: KeyboardShortcuts,
|
||||
): TaggedAutomations => {
|
||||
): TaggedAutomation[] => {
|
||||
if (!shortcuts.enabled) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const automations: TaggedAutomations = [];
|
||||
const automations: TaggedAutomation[] = [];
|
||||
|
||||
for (const name of [
|
||||
'ptz_down',
|
||||
|
||||
Reference in New Issue
Block a user