Add interaction and triggered conditions.

This commit is contained in:
Dermot Duffy
2024-01-25 22:06:21 -08:00
parent 06f2151bab
commit 5e72070c96
27 changed files with 379 additions and 90 deletions
@@ -54,6 +54,13 @@ export class CardElementManager {
}
public elementConnected(): void {
// Set initial condition state. Must be done after the element is connected to
// allow callbacks to interact with the card.
this._api.getInteractionManager().initialize();
this._api.getFullscreenManager().initialize();
this._api.getExpandManager().initialize();
this._api.getMediaLoadedInfoManager().initialize();
// Whether or not the card is in panel mode on the dashboard.
setOrRemoveAttribute(this._element, isCardInPanel(this._element), 'panel');
+13 -2
View File
@@ -6,7 +6,7 @@ import {
frigateConditionalSchema,
OverrideConfigurationKey,
RawFrigateCardConfig,
ViewDisplayMode
ViewDisplayMode,
} from '../config/types';
import { CardConditionAPI } from './types';
@@ -18,6 +18,8 @@ interface ConditionState {
state?: HassEntities;
media_loaded?: boolean;
displayMode?: ViewDisplayMode;
triggered?: Set<string>;
interaction?: boolean;
}
export class ConditionEvaluateRequestEvent extends Event {
@@ -133,7 +135,7 @@ export class ConditionsManager {
this._listeners = [
() => this._api.getConfigManager().computeOverrideConfig(),
() => this._api.getAutomationsManager().execute(),
...(listener ? [listener] : [])
...(listener ? [listener] : []),
];
}
@@ -243,6 +245,15 @@ export class ConditionsManager {
if (condition.display_mode) {
result &&= !!state.displayMode && condition.display_mode === state.displayMode;
}
if (condition.triggered?.length) {
result &&= condition.triggered.some((triggeredCameraID) =>
state.triggered?.has(triggeredCameraID),
);
}
if (condition.interaction !== undefined) {
result &&=
state.interaction !== undefined && condition.interaction === state.interaction;
}
return result;
}
+11 -3
View File
@@ -8,6 +8,10 @@ export class ExpandManager {
this._api = api;
}
public initialize(): void {
this._setConditionState();
}
public isExpanded(): boolean {
return this._expanded;
}
@@ -23,9 +27,13 @@ export class ExpandManager {
}
this._expanded = expanded;
this._api.getConditionsManager()?.setState({
expand: expanded,
});
this._setConditionState();
this._api.getCardElementManager().update();
}
protected _setConditionState(): void {
this._api.getConditionsManager()?.setState({
expand: this._expanded,
});
}
}
+11 -3
View File
@@ -8,6 +8,10 @@ export class FullscreenManager {
this._api = api;
}
public initialize(): void {
this._setConditionState();
}
public connect(): void {
if (screenfull.isEnabled) {
screenfull.on('change', this._fullscreenHandler);
@@ -32,12 +36,16 @@ export class FullscreenManager {
screenfull.exit();
}
protected _fullscreenHandler = (): void => {
this._api.getExpandManager().setExpanded(false);
protected _setConditionState(): void {
this._api.getConditionsManager()?.setState({
fullscreen: this.isInFullscreen(),
});
}
protected _fullscreenHandler = (): void => {
this._api.getExpandManager().setExpanded(false);
this._setConditionState();
// Re-render after a change to fullscreen mode to take advantage of
// the expanded screen real-estate (vs staying in aspect-ratio locked
+14 -9
View File
@@ -16,32 +16,37 @@ export class InteractionManager {
this._reportInteraction();
}, 1 * 1000);
public initialize(): void {
this._api.getConditionsManager().setState({ interaction: false });
}
public hasInteraction(): boolean {
return this._timer.isRunning();
}
/**
* Start the user interaction ('screensaver') timer to reset the view to
* default `view.timeout_seconds` after user interaction.
* default `view.interaction_seconds` after user interaction.
*/
protected _reportInteraction(): void {
this._timer.stop();
const timeoutSeconds = this._api.getConfigManager().getConfig()
?.view.timeout_seconds;
?.view.interaction_seconds;
if (timeoutSeconds) {
this._api.getConditionsManager().setState({ interaction: true });
this._timer.start(timeoutSeconds, () => {
if (this._isAutomatedUpdateAllowed()) {
this._api.getViewManager().setViewDefault();
this._api.getConditionsManager().setState({ interaction: false });
if (!this._api.getTriggersManager().isTriggered()) {
if (this._api.getConfigManager().getConfig()?.view.reset_after_interaction) {
this._api.getViewManager().setViewDefault();
}
this._api.getStyleManager().setLightOrDarkMode();
}
});
}
this._api.getStyleManager().setLightOrDarkMode();
}
protected _isAutomatedUpdateAllowed(): boolean {
return !this._api.getTriggersManager().isTriggered();
}
}
@@ -12,6 +12,10 @@ export class MediaLoadedInfoManager {
this._api = api;
}
public initialize(): void {
this.clear();
}
public set(mediaInfo: MediaLoadedInfo): void {
if (!isValidMediaLoadedInfo(mediaInfo)) {
return;
+10
View File
@@ -95,6 +95,7 @@ export class TriggersManager {
cameraID === this._api.getViewManager().getView()?.camera)
) {
this._triggeredCameras.set(cameraID, now);
this._setConditionState();
this._triggerAction(cameraID);
}
}
@@ -132,6 +133,14 @@ export class TriggersManager {
this._api.getCardElementManager().update();
}
protected _setConditionState(): void {
this._api.getConditionsManager().setState({
triggered: this._triggeredCameras.size
? new Set(this._triggeredCameras.keys())
: undefined,
});
}
protected _untriggerAction(cameraID: string): void {
const action = this._api.getConfigManager().getConfig()?.view.scan.actions.untrigger;
@@ -140,6 +149,7 @@ export class TriggersManager {
}
this._triggeredCameras.delete(cameraID);
this._deleteTimer(cameraID);
this._setConditionState();
// Must update master element to remove border pulsing.
this._api.getCardElementManager().update();
+3
View File
@@ -100,6 +100,7 @@ export interface CardDownloadAPI {
export interface CardElementAPI {
getActionsManager(): ActionsManager;
getExpandManager(): ExpandManager;
getFullscreenManager(): FullscreenManager;
getInteractionManager(): InteractionManager;
getMediaLoadedInfoManager(): MediaLoadedInfoManager;
@@ -147,6 +148,7 @@ export interface CardInitializerAPI {
}
export interface CardInteractionAPI {
getConditionsManager(): ConditionsManager;
getConfigManager(): ConfigManager;
getStyleManager(): StyleManager;
getTriggersManager(): TriggersManager;
@@ -198,6 +200,7 @@ export interface CardStyleAPI {
export interface CardTriggersAPI {
getCameraManager(): CameraManager;
getConditionsManager(): ConditionsManager;
getCardElementManager(): CardElementManager;
getConfigManager(): ConfigManager;
getHASSManager(): HASSManager;
+2
View File
@@ -13,6 +13,7 @@ import {
CONF_MEDIA_GALLERY,
CONF_MENU_BUTTONS_CAMERA_UI,
CONF_OVERRIDES,
CONF_VIEW_INTERACTION_SECONDS,
CONF_VIEW_SCAN_ACTIONS_UNTRIGGER,
} from './const';
import { arrayify } from './utils/basic';
@@ -483,4 +484,5 @@ const UPGRADES = [
transform: (val) => (val ? 'default' : null),
},
),
upgradeMoveToWithOverrides('view.timeout_seconds', CONF_VIEW_INTERACTION_SECONDS),
];
+8 -3
View File
@@ -1,4 +1,3 @@
import {
CallServiceActionConfig,
ConfirmationRestrictionConfig,
@@ -490,6 +489,8 @@ export const frigateCardConditionSchema = z.object({
state: stateConditions.optional(),
media_query: z.string().optional(),
display_mode: viewDisplayModeSchema.optional(),
triggered: z.string().array().optional(),
interaction: z.boolean().optional(),
});
export type FrigateCardCondition = z.infer<typeof frigateCardConditionSchema>;
@@ -1114,7 +1115,8 @@ export type CamerasConfig = z.infer<typeof camerasConfigSchema>;
const viewConfigDefault = {
default: FRIGATE_CARD_VIEW_DEFAULT,
camera_select: 'current' as const,
timeout_seconds: 300,
interaction_seconds: 300,
reset_after_interaction: true,
update_seconds: 0,
update_force: false,
update_cycle_camera: false,
@@ -1163,7 +1165,10 @@ const viewConfigSchema = z
camera_select: z
.enum([...FRIGATE_CARD_VIEWS_USER_SPECIFIED, 'current'])
.default(viewConfigDefault.camera_select),
timeout_seconds: z.number().default(viewConfigDefault.timeout_seconds),
interaction_seconds: z.number().default(viewConfigDefault.interaction_seconds),
reset_after_interaction: z
.boolean()
.default(viewConfigDefault.reset_after_interaction),
update_seconds: z.number().default(viewConfigDefault.update_seconds),
update_force: z.boolean().default(viewConfigDefault.update_force),
update_cycle_camera: z.boolean().default(viewConfigDefault.update_cycle_camera),
+2 -1
View File
@@ -71,10 +71,11 @@ const CONF_VIEW = 'view' as const;
export const CONF_VIEW_CAMERA_SELECT = `${CONF_VIEW}.camera_select` as const;
export const CONF_VIEW_DARK_MODE = `${CONF_VIEW}.dark_mode` as const;
export const CONF_VIEW_DEFAULT = `${CONF_VIEW}.default` as const;
export const CONF_VIEW_TIMEOUT_SECONDS = `${CONF_VIEW}.timeout_seconds` as const;
export const CONF_VIEW_INTERACTION_SECONDS = `${CONF_VIEW}.interaction_seconds` as const;
export const CONF_VIEW_UPDATE_CYCLE_CAMERA = `${CONF_VIEW}.update_cycle_camera` as const;
export const CONF_VIEW_UPDATE_FORCE = `${CONF_VIEW}.update_force` as const;
export const CONF_VIEW_UPDATE_SECONDS = `${CONF_VIEW}.update_seconds` as const;
export const CONF_VIEW_RESET_AFTER_INTERACTION = `${CONF_VIEW}.reset_after_interaction` as const;
export const CONF_VIEW_SCAN = `${CONF_VIEW}.scan` as const;
export const CONF_VIEW_SCAN_ENABLED = `${CONF_VIEW_SCAN}.enabled` as const;
export const CONF_VIEW_SCAN_SHOW_TRIGGER_STATUS =
+9 -4
View File
@@ -22,9 +22,9 @@ import {
} from './config-mgmt.js';
import {
BUTTON_SIZE_MIN,
FRIGATE_MENU_PRIORITY_MAX,
FrigateCardConfig,
frigateCardConfigDefaults,
FRIGATE_MENU_PRIORITY_MAX,
RawFrigateCardConfig,
RawFrigateCardConfigArray,
THUMBNAIL_WIDTH_MAX,
@@ -157,8 +157,8 @@ import {
CONF_MEDIA_VIEWER_TRANSITION_EFFECT,
CONF_MEDIA_VIEWER_ZOOMABLE,
CONF_MENU_ALIGNMENT,
CONF_MENU_BUTTON_SIZE,
CONF_MENU_BUTTONS,
CONF_MENU_BUTTON_SIZE,
CONF_MENU_POSITION,
CONF_MENU_STYLE,
CONF_PERFORMANCE_FEATURES_ANIMATED_PROGRESS_INDICATOR,
@@ -180,6 +180,8 @@ import {
CONF_VIEW_CAMERA_SELECT,
CONF_VIEW_DARK_MODE,
CONF_VIEW_DEFAULT,
CONF_VIEW_INTERACTION_SECONDS,
CONF_VIEW_RESET_AFTER_INTERACTION,
CONF_VIEW_SCAN,
CONF_VIEW_SCAN_ACTIONS,
CONF_VIEW_SCAN_ACTIONS_INTERACTION_MODE,
@@ -189,7 +191,6 @@ import {
CONF_VIEW_SCAN_FILTER_SELECTED_CAMERA,
CONF_VIEW_SCAN_SHOW_TRIGGER_STATUS,
CONF_VIEW_SCAN_UNTRIGGER_SECONDS,
CONF_VIEW_TIMEOUT_SECONDS,
CONF_VIEW_UPDATE_CYCLE_CAMERA,
CONF_VIEW_UPDATE_FORCE,
CONF_VIEW_UPDATE_SECONDS,
@@ -1948,7 +1949,11 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
this._cameraSelectViewModes,
)}
${this._renderOptionSelector(CONF_VIEW_DARK_MODE, this._darkModes)}
${this._renderNumberInput(CONF_VIEW_TIMEOUT_SECONDS)}
${this._renderNumberInput(CONF_VIEW_INTERACTION_SECONDS)}
${this._renderSwitch(
CONF_VIEW_RESET_AFTER_INTERACTION,
this._defaults.view.reset_after_interaction,
)}
${this._renderNumberInput(CONF_VIEW_UPDATE_SECONDS)}
${this._renderSwitch(
CONF_VIEW_UPDATE_FORCE,
+2 -1
View File
@@ -410,7 +410,8 @@
"show_trigger_status": "Show pulsing border when triggered",
"untrigger_seconds": "Seconds after inactive state change to untrigger"
},
"timeout_seconds": "Reset to default view X seconds after user action (0=never)",
"interaction_seconds": "Seconds after user action to remain interacted with (0=never)",
"reset_after_interaction": "Reset to the default view after user interaction",
"update_cycle_camera": "Cycle through cameras when default view updates",
"update_force": "Force card updates (ignore user interaction)",
"update_seconds": "Refresh default view every X seconds (0=never)",
+17 -16
View File
@@ -18,8 +18,8 @@
"editor_label": "",
"method": "",
"methods": {
"standard": "",
"dashboard": ""
"dashboard": "",
"standard": ""
}
},
"dependencies": {
@@ -236,27 +236,27 @@
"editor_label": "Controlli dal vivo",
"ptz": {
"editor_label": "",
"hide_home": "",
"hide_pan_tilt": "",
"hide_zoom": "",
"mode": "",
"modes": {
"on": "",
"auto": "",
"off": "",
"auto": ""
"on": ""
},
"orientation": "",
"orientations": {
"vertical": "",
"horizontal": ""
"horizontal": "",
"vertical": ""
},
"position": "",
"positions": {
"top-left": "",
"top-right": "",
"bottom-left": "",
"bottom-right": ""
},
"hide_zoom": "",
"hide_pan_tilt": "",
"hide_home": ""
"bottom-right": "",
"top-left": "",
"top-right": ""
}
}
},
"draggable": "Il Visualizzatore eventi può essere trascinato oppure puoi scorrere",
@@ -380,6 +380,8 @@
"on": "On"
},
"default": "Visualizzazione predefinita",
"interaction_seconds": "",
"reset_after_interaction": "",
"scan": {
"actions": {
"editor_label": "",
@@ -406,7 +408,6 @@
"show_trigger_status": "Mostra bordo pulsante quando attivato",
"untrigger_seconds": "Reimposta la vista ai valori predefiniti dopo aver annullato l'attivazione"
},
"timeout_seconds": "Ripristina la vista predefinita x secondi dopo l'azione dell'utente (0 = mai)",
"update_cycle_camera": "Scorri le telecamere quando si aggiorna la visualizzazione predefinita",
"update_force": "Aggiornamenti della scheda forza (ignora l'interazione dell'utente)",
"update_seconds": "Aggiorna la visualizzazione predefinita ogni x secondi (0 = mai)",
@@ -497,8 +498,8 @@
"no_visible_cameras": "Nessuna telecamera visibile trovata, è necessario configurare almeno una telecamera non nascosta",
"reconnecting": "Riconnessione",
"timeline_no_cameras": "Nessuna telecamera damostrare in Frigate nella timeline",
"troubleshooting": "Controllare la risoluzione dei problemi",
"too_many_automations": "",
"troubleshooting": "Controllare la risoluzione dei problemi",
"unknown": "Errore sconosciuto",
"upgrade_available": "È disponibile un aggiornamento di configurazione della scheda automatizzato, visitare l'editor di schede visive",
"webrtc_card_reported_error": "La scheda WebRTC ha riportato un errore",
@@ -567,4 +568,4 @@
},
"select_date": "Scegli la data"
}
}
}
+16 -15
View File
@@ -18,8 +18,8 @@
"editor_label": "",
"method": "",
"methods": {
"standard": "",
"dashboard": ""
"dashboard": "",
"standard": ""
}
},
"dependencies": {
@@ -236,27 +236,27 @@
"editor_label": "Controles da visualização ao vivo",
"ptz": {
"editor_label": "",
"hide_home": "",
"hide_pan_tilt": "",
"hide_zoom": "",
"mode": "",
"modes": {
"on": "",
"auto": "",
"off": "",
"auto": ""
"on": ""
},
"orientation": "",
"orientations": {
"vertical": "",
"horizontal": ""
"horizontal": "",
"vertical": ""
},
"position": "",
"positions": {
"top-left": "",
"top-right": "",
"bottom-left": "",
"bottom-right": ""
},
"hide_zoom": "",
"hide_pan_tilt": "",
"hide_home": ""
"bottom-right": "",
"top-left": "",
"top-right": ""
}
}
},
"draggable": "A visualização ao vivo das câmeras pode ser arrastada/deslizada",
@@ -383,6 +383,8 @@
"on": "Ligado"
},
"default": "Visualização padrão",
"interaction_seconds": "",
"reset_after_interaction": "",
"scan": {
"actions": {
"editor_label": "",
@@ -409,7 +411,6 @@
"show_trigger_status": "Pulsar borda quando acionado",
"untrigger_seconds": "Segundos após a mudar para o estado inativo para desacionar"
},
"timeout_seconds": "Redefinir para a visualização padrão X segundos após a ação do usuário (0 = nunca)",
"update_cycle_camera": "Percorrer as câmeras quando a visualização padrão for atualizada",
"update_force": "Forçar atualizações do cartão (ignore a interação do usuário)",
"update_seconds": "Atualize a visualização padrão a cada X segundos (0 = nunca)",
@@ -577,4 +578,4 @@
},
"select_date": "Escolha a data"
}
}
}
+16 -15
View File
@@ -18,8 +18,8 @@
"editor_label": "",
"method": "",
"methods": {
"standard": "",
"dashboard": ""
"dashboard": "",
"standard": ""
}
},
"dependencies": {
@@ -229,27 +229,27 @@
"editor_label": "Controles da visualização ao vivo",
"ptz": {
"editor_label": "",
"hide_home": "",
"hide_pan_tilt": "",
"hide_zoom": "",
"mode": "",
"modes": {
"on": "",
"auto": "",
"off": "",
"auto": ""
"on": ""
},
"orientation": "",
"orientations": {
"vertical": "",
"horizontal": ""
"horizontal": "",
"vertical": ""
},
"position": "",
"positions": {
"top-left": "",
"top-right": "",
"bottom-left": "",
"bottom-right": ""
},
"hide_zoom": "",
"hide_pan_tilt": "",
"hide_home": ""
"bottom-right": "",
"top-left": "",
"top-right": ""
}
}
},
"draggable": "A visualização ao vivo das câmeras pode ser arrastada/deslizada",
@@ -373,6 +373,8 @@
"on": "Ligado"
},
"default": "Visualização padrão",
"interaction_seconds": "",
"reset_after_interaction": "",
"scan": {
"actions": {
"editor_label": "",
@@ -399,7 +401,6 @@
"show_trigger_status": "Exibir estado do gatilho",
"untrigger_seconds": "Segundos após a mudar para o estado inativo para desacionar"
},
"timeout_seconds": "Redefinir para a visualização padrão X segundos após a ação do usuário (0 = nunca)",
"update_cycle_camera": "Percorrer as câmeras quando a visualização padrão for atualizada",
"update_force": "Forçar atualizações do cartão (ignore a interação do Utilizador)",
"update_seconds": "Atualize a visualização padrão a cada X segundos (0 = nunca)",
@@ -490,8 +491,8 @@
"no_visible_cameras": "Sem camaras visiveis",
"reconnecting": "A voltar a ligar",
"timeline_no_cameras": "Nenhuma câmera do Frigate para mostrar na linha do tempo",
"troubleshooting": "Verifique a solução de problemas",
"too_many_automations": "",
"troubleshooting": "Verifique a solução de problemas",
"unknown": "Erro desconhecido",
"upgrade_available": "Uma atualização automatizada da configuração do cartão está disponível, visite o editor visual do cartão",
"webrtc_card_reported_error": "O cartão WebRTC relatou um erro",