Add initial keyboard shortcut support.

This commit is contained in:
Dermot Duffy
2024-06-05 21:44:17 -07:00
parent 904f6d8142
commit 7ab545738d
186 changed files with 9564 additions and 3658 deletions
@@ -0,0 +1,147 @@
import isEqual from 'lodash-es/isEqual';
import { isConfigUpgradeable } from '../../config/management.js';
import {
CardWideConfig,
FrigateCardConfig,
frigateCardConfigSchema,
RawFrigateCardConfig,
} from '../../config/types.js';
import { localize } from '../../localize/localize.js';
import { setProfiles } from '../../config/profiles/index.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 { setAutomationsFromConfig } from './load-automations.js';
import { setKeyboardShortcutsFromConfig } from './load-keyboard-shortcuts.js';
export class ConfigManager {
protected _api: CardConfigAPI;
// The main base configuration object. For most usecases use getConfig() to
// get the correct configuration (which will return overrides as appropriate).
// This variable must be called `_config` or `config` to be compatible with
// card-mod.
protected _config: FrigateCardConfig | null = null;
protected _overriddenConfig: FrigateCardConfig | null = null;
protected _rawConfig: RawFrigateCardConfig | null = null;
protected _cardWideConfig: CardWideConfig | null = null;
constructor(api: CardConfigAPI) {
this._api = api;
}
public hasConfig(): boolean {
return !!this.getConfig();
}
public getConfig(): FrigateCardConfig | null {
return this._overriddenConfig ?? this._config;
}
public getCardWideConfig(): CardWideConfig | null {
return this._cardWideConfig;
}
public getNonOverriddenConfig(): FrigateCardConfig | null {
return this._config;
}
public getRawConfig(): RawFrigateCardConfig | null {
return this._rawConfig;
}
public setConfig(inputConfig?: RawFrigateCardConfig): void {
if (!inputConfig) {
throw new Error(localize('error.invalid_configuration'));
}
const parseResult = frigateCardConfigSchema.safeParse(inputConfig);
if (!parseResult.success) {
const configUpgradeable = isConfigUpgradeable(inputConfig);
const hint = getParseErrorPaths(parseResult.error);
let upgradeMessage = '';
if (configUpgradeable) {
upgradeMessage = `${localize('error.upgrade_available')}. `;
}
throw new Error(
upgradeMessage +
`${localize('error.invalid_configuration')}: ` +
(hint && hint.size
? JSON.stringify([...hint], null, ' ')
: localize('error.invalid_configuration_no_hint')),
);
}
const config = setProfiles(inputConfig, parseResult.data, parseResult.data.profiles);
this._rawConfig = inputConfig;
if (isEqual(this._config, config)) {
return;
}
this._config = config;
this._cardWideConfig = {
performance: config.performance,
debug: config.debug,
};
this._api.getConditionsManager().setConditionsFromConfig();
this._api.getConditionsManager().setState({
view: undefined,
displayMode: undefined,
camera: undefined,
});
this._api.getMediaLoadedInfoManager().clear();
this._api.getViewManager().reset();
this._api.getMessageManager().reset();
this._api.getStyleManager().setPerformance();
this._api.getCardElementManager().update();
setKeyboardShortcutsFromConfig(this._api, this);
setAutomationsFromConfig(this._api);
this.computeOverrideConfig();
}
public computeOverrideConfig(): void {
const conditionsManager = this._api.getConditionsManager();
if (!this._config) {
return;
}
const overriddenConfig = getOverriddenConfig(conditionsManager, this._config, {
configOverrides: this._config.overrides,
schema: frigateCardConfigSchema,
logOnParseError: !!this.getCardWideConfig()?.debug?.logging,
}) as FrigateCardConfig;
// Save on Lit re-rendering costs by only updating the configuration if it
// actually changes.
if (isEqual(overriddenConfig, this._overriddenConfig)) {
return;
}
const previousConfig = this._overriddenConfig;
this._overriddenConfig = overriddenConfig;
this._api.getStyleManager().setMinMaxHeight();
if (
previousConfig &&
(!isEqual(previousConfig?.cameras, this._overriddenConfig?.cameras) ||
!isEqual(previousConfig?.cameras_global, this._overriddenConfig?.cameras_global))
) {
this._api.getInitializationManager().uninitialize(InitializationAspect.CAMERAS);
}
if (
previousConfig &&
previousConfig?.live.microphone.always_connected !==
this._overriddenConfig?.live.microphone.always_connected
) {
this._api
.getInitializationManager()
.uninitialize(InitializationAspect.MICROPHONE_CONNECT);
}
this._api.getCardElementManager().update();
}
}
@@ -0,0 +1,8 @@
import { CardConfigLoaderAPI } from '../types';
export const setAutomationsFromConfig = (api: CardConfigLoaderAPI) => {
api.getAutomationsManager().deleteAutomations();
api
.getAutomationsManager()
.addAutomations(api.getConfigManager().getNonOverriddenConfig()?.automations ?? []);
};
@@ -0,0 +1,130 @@
import {
KeyboardShortcuts,
PTZKeyboardShortcutName,
} from '../../config/keyboard-shortcuts';
import { PTZAction } from '../../config/ptz';
import { CardConfigLoaderAPI, TaggedAutomations } from '../types';
import { createPTZMultiAction } from '../../utils/action';
export const setKeyboardShortcutsFromConfig = (
api: CardConfigLoaderAPI,
tag: unknown,
) => {
api.getAutomationsManager().deleteAutomations(tag);
const shortcuts = api.getConfigManager().getConfig()?.view.keyboard_shortcuts;
if (!shortcuts) {
return;
}
const automations = convertKeyboardShortcutsToAutomations(tag, shortcuts);
if (automations.length) {
api.getAutomationsManager().addAutomations(automations);
}
};
const ptzKeyboardShortcutToPTZAction = (
ptzKbs: PTZKeyboardShortcutName,
): PTZAction | null => {
switch (ptzKbs) {
case 'ptz_left':
return 'left';
case 'ptz_right':
return 'right';
case 'ptz_up':
return 'up';
case 'ptz_down':
return 'down';
case 'ptz_zoom_in':
return 'zoom_in';
case 'ptz_zoom_out':
return 'zoom_out';
}
/* istanbul ignore next: No (current) way to reach this code -- @preserve */
return null;
};
const convertKeyboardShortcutsToAutomations = (
tag: unknown,
shortcuts: KeyboardShortcuts,
): TaggedAutomations => {
if (!shortcuts.enabled) {
return [];
}
const automations: TaggedAutomations = [];
for (const name of [
'ptz_down',
'ptz_left',
'ptz_right',
'ptz_up',
'ptz_zoom_in',
'ptz_zoom_out',
] as const) {
const shortcut = shortcuts[name];
const ptzAction = ptzKeyboardShortcutToPTZAction(name);
if (!shortcut || !ptzAction) {
continue;
}
automations.push({
conditions: [
{
condition: 'key' as const,
key: shortcut.key,
state: 'down',
shift: shortcut.shift,
ctrl: shortcut.ctrl,
alt: shortcut.alt,
meta: shortcut.meta,
},
],
actions: [
createPTZMultiAction({
ptzAction: ptzAction,
ptzPhase: 'start',
}),
],
tag: tag,
});
automations.push({
conditions: [
{
condition: 'key' as const,
key: shortcut.key,
state: 'up',
},
],
actions: [
createPTZMultiAction({
ptzAction: ptzAction,
ptzPhase: 'stop',
}),
],
tag: tag,
});
}
const homeShortcut = shortcuts.ptz_home;
if (homeShortcut) {
automations.push({
conditions: [
{
condition: 'key' as const,
key: homeShortcut.key,
state: 'down',
shift: homeShortcut.shift,
ctrl: homeShortcut.ctrl,
alt: homeShortcut.alt,
meta: homeShortcut.meta,
},
],
actions: [createPTZMultiAction()],
tag: tag,
});
}
return automations;
};