Initial version of status bar.

This commit is contained in:
Dermot Duffy
2024-08-16 20:08:21 -07:00
parent db9303c60d
commit e503f0e429
77 changed files with 3072 additions and 1061 deletions
@@ -0,0 +1,24 @@
import { StatusBarActionConfig } from '../../../config/types';
import { CardActionsAPI } from '../../types';
import { FrigateCardAction } from './base';
export class StatusBarAction extends FrigateCardAction<StatusBarActionConfig> {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
public async execute(api: CardActionsAPI): Promise<void> {
switch (this._action.status_bar_action) {
case 'reset':
api.getStatusBarItemManager().removeAllDynamicStatusBarItems();
break;
case 'add':
this._action.items?.forEach((item) =>
api.getStatusBarItemManager().addDynamicStatusBarItem(item),
);
break;
case 'remove':
this._action.items?.forEach((item) =>
api.getStatusBarItemManager().removeDynamicStatusBarItem(item),
);
break;
}
}
}
+4 -1
View File
@@ -19,11 +19,12 @@ import { MuteAction } from './actions/mute';
import { PauseAction } from './actions/pause';
import { PlayAction } from './actions/play';
import { PTZAction } from './actions/ptz';
import { PTZControlsAction } from './actions/ptz-controls';
import { PTZDigitalAction } from './actions/ptz-digital';
import { PTZMultiAction } from './actions/ptz-multi';
import { ScreenshotAction } from './actions/screenshot';
import { PTZControlsAction } from './actions/ptz-controls';
import { SleepAction } from './actions/sleep';
import { StatusBarAction } from './actions/status-bar';
import { SubstreamOffAction } from './actions/substream-off';
import { SubstreamOnAction } from './actions/substream-on';
import { SubstreamSelectAction } from './actions/substream-select';
@@ -121,6 +122,8 @@ export class ActionFactory {
return new PTZControlsAction(context, frigateCardAction, options?.config);
case 'log':
return new LogAction(context, frigateCardAction, options?.config);
case 'status_bar':
return new StatusBarAction(context, frigateCardAction, options?.config);
}
/* istanbul ignore next: this path cannot be reached -- @preserve */
@@ -96,6 +96,8 @@ export class ConfigManager {
this._api.getMessageManager().reset();
this._api.getStyleManager().setPerformance();
this._api.getCardElementManager().update();
this._api.getStatusBarItemManager().removeAllDynamicStatusBarItems();
setKeyboardShortcutsFromConfig(this._api, this);
setAutomationsFromConfig(this._api);
+6
View File
@@ -28,6 +28,7 @@ import { MediaPlayerManager } from './media-player-manager';
import { MessageManager } from './message-manager';
import { MicrophoneManager } from './microphone-manager';
import { QueryStringManager } from './query-string-manager';
import { StatusBarItemManager } from './status-bar-item-manager';
import { StyleManager } from './style-manager';
import { TriggersManager } from './triggers-manager';
import {
@@ -110,6 +111,7 @@ export class CardController
protected _messageManager = new MessageManager(this);
protected _microphoneManager = new MicrophoneManager(this);
protected _queryStringManager = new QueryStringManager(this);
protected _statusBarItemManager = new StatusBarItemManager(this);
protected _styleManager = new StyleManager(this);
protected _triggersManager = new TriggersManager(this);
protected _viewManager = new ViewManager(this);
@@ -228,6 +230,10 @@ export class CardController
return this._resolvedMediaCache;
}
public getStatusBarItemManager(): StatusBarItemManager {
return this._statusBarItemManager;
}
public static getStubConfig(entities: string[]): FrigateCardConfig {
const cameraEntity = entities.find((element) => element.startsWith('camera.'));
return {
@@ -0,0 +1,160 @@
import { isEqual } from 'lodash-es';
import { CameraManager } from '../camera-manager/manager';
import { StatusBarConfig, StatusBarItem } from '../config/types';
import { MediaLoadedInfo } from '../types';
import { View } from '../view/view';
import { CardStatusBarAPI } from './types';
const RESOLUTION_TOLERANCE_PCT = 0.01;
export class StatusBarItemManager {
protected _api: CardStatusBarAPI;
constructor(api: CardStatusBarAPI) {
this._api = api;
}
protected _items: StatusBarItem[] = [];
protected _dynamicItems: StatusBarItem[] = [];
public addDynamicStatusBarItem(item: StatusBarItem): void {
if (!this._dynamicItems.includes(item)) {
this._dynamicItems.push(item);
}
this._api.getCardElementManager().update();
}
public removeDynamicStatusBarItem(item: StatusBarItem): void {
this._dynamicItems = this._dynamicItems.filter(
(existingItem) => !isEqual(existingItem, item),
);
this._api.getCardElementManager().update();
}
public removeAllDynamicStatusBarItems(): void {
this._dynamicItems = [];
this._api.getCardElementManager().update();
}
public calculateItems(options?: {
statusConfig?: StatusBarConfig | null;
cameraManager?: CameraManager | null;
view?: View | null;
mediaLoadedInfo?: MediaLoadedInfo | null;
}): StatusBarItem[] {
const cameraMetadata = options?.view
? options?.cameraManager?.getCameraMetadata(options?.view?.camera)
: null;
const engineLogoIcon = cameraMetadata?.engineLogo ?? null;
const title = options?.view?.is('live')
? cameraMetadata?.title ?? null
: options?.view?.isAnyMediaView()
? options?.view.queryResults?.getSelectedResult()?.getTitle() ?? null
: null;
const resolution = options?.mediaLoadedInfo
? this._calculateResolution(options?.mediaLoadedInfo)
: null;
const technology = options?.mediaLoadedInfo?.technology?.length
? options?.mediaLoadedInfo.technology[0]
: null;
return [
...(title
? [
{
type: 'custom:frigate-card-status-bar-string' as const,
string: title,
expand: true,
sufficient: true,
...options?.statusConfig?.items.title,
},
]
: []),
...(resolution
? [
{
type: 'custom:frigate-card-status-bar-string' as const,
string: resolution,
...options?.statusConfig?.items.resolution,
},
]
: []),
...(technology && technology === 'webrtc'
? [
{
type: 'custom:frigate-card-status-bar-icon' as const,
icon: 'mdi:webrtc',
...options?.statusConfig?.items.technology,
},
]
: !!technology
? [
{
type: 'custom:frigate-card-status-bar-string' as const,
string: technology.toUpperCase(),
...options?.statusConfig?.items.technology,
},
]
: []),
...(engineLogoIcon
? [
{
type: 'custom:frigate-card-status-bar-image' as const,
image: engineLogoIcon,
...options?.statusConfig?.items.engine,
},
]
: []),
...this._dynamicItems,
];
}
protected _matchesWidthHeight(
mediaLoadedInfo: MediaLoadedInfo | null,
width: number,
height: number,
): boolean {
const widthMin = width * (1 - RESOLUTION_TOLERANCE_PCT);
const widthMax = width * (1 + RESOLUTION_TOLERANCE_PCT);
const heightMin = height * (1 - RESOLUTION_TOLERANCE_PCT);
const heightMax = height * (1 + RESOLUTION_TOLERANCE_PCT);
const matchesDimension = (val: number, min: number, max: number): boolean => {
return val >= min && val <= max;
};
// Allows matching the resolution width and height in either orientation,
// and within RESOLUTION_TOLERANCE_PCT of the resolution.
return (
!!mediaLoadedInfo &&
((matchesDimension(mediaLoadedInfo.width, widthMin, widthMax) &&
matchesDimension(mediaLoadedInfo.height, heightMin, heightMax)) ||
(matchesDimension(mediaLoadedInfo.height, widthMin, widthMax) &&
matchesDimension(mediaLoadedInfo.width, heightMin, heightMax)))
);
}
protected _calculateResolution(mediaLoadedInfo: MediaLoadedInfo): string {
// Ordered roughly by a guess at most common towards the top.
if (this._matchesWidthHeight(mediaLoadedInfo, 1920, 1080)) {
return '1080p';
} else if (this._matchesWidthHeight(mediaLoadedInfo, 1280, 720)) {
return '720p';
} else if (this._matchesWidthHeight(mediaLoadedInfo, 640, 480)) {
return 'VGA';
} else if (this._matchesWidthHeight(mediaLoadedInfo, 3840, 2160)) {
return '4K';
} else if (this._matchesWidthHeight(mediaLoadedInfo, 720, 480)) {
return '480p';
} else if (this._matchesWidthHeight(mediaLoadedInfo, 720, 576)) {
return '576p';
} else if (this._matchesWidthHeight(mediaLoadedInfo, 7680, 4320)) {
return '8K';
} else {
return `${mediaLoadedInfo.width}x${mediaLoadedInfo.height}`;
}
}
}
+12 -5
View File
@@ -1,29 +1,30 @@
import type { CameraManager } from '../camera-manager/manager';
import type { ConditionsManager } from './conditions-manager';
import type { Automation } from '../config/types';
import type { EntityRegistryManager } from '../utils/ha/entity-registry';
import type { ResolvedMediaCache } from '../utils/ha/resolved-media';
import type { ActionsManager } from './actions/actions-manager';
import type { DefaultManager } from './default-manager';
import type { AutomationsManager } from './automations-manager';
import type { CameraURLManager } from './camera-url-manager';
import type { CardElementManager } from './card-element-manager';
import type { ConditionsManager } from './conditions-manager';
import type { ConfigManager } from './config/config-manager';
import type { DefaultManager } from './default-manager';
import type { DownloadManager } from './download-manager';
import type { ExpandManager } from './expand-manager';
import type { FullscreenManager } from './fullscreen-manager';
import type { HASSManager } from './hass-manager';
import type { InitializationManager } from './initialization-manager';
import type { InteractionManager } from './interaction-manager';
import type { KeyboardStateManager } from './keyboard-state-manager';
import type { MediaLoadedInfoManager } from './media-info-manager';
import type { MediaPlayerManager } from './media-player-manager';
import type { MessageManager } from './message-manager';
import type { MicrophoneManager } from './microphone-manager';
import type { QueryStringManager } from './query-string-manager';
import type { StatusBarItemManager } from './status-bar-item-manager';
import type { StyleManager } from './style-manager';
import type { TriggersManager } from './triggers-manager';
import type { ViewManager } from './view/view-manager';
import type { QueryStringManager } from './query-string-manager';
import { KeyboardStateManager } from './keyboard-state-manager';
import { Automation } from '../config/types';
// *************************************************************************
// Manager APIs
@@ -47,6 +48,7 @@ export interface CardActionsAPI {
getMediaPlayerManager(): MediaPlayerManager;
getMessageManager(): MessageManager;
getMicrophoneManager(): MicrophoneManager;
getStatusBarItemManager(): StatusBarItemManager;
getTriggersManager(): TriggersManager;
getViewManager(): ViewManager;
}
@@ -89,6 +91,7 @@ export interface CardConfigAPI {
getInitializationManager(): InitializationManager;
getMediaLoadedInfoManager(): MediaLoadedInfoManager;
getMessageManager(): MessageManager;
getStatusBarItemManager(): StatusBarItemManager;
getStyleManager(): StyleManager;
getViewManager(): ViewManager;
}
@@ -218,6 +221,10 @@ export interface CardQueryStringAPI {
getViewManager(): ViewManager;
}
export interface CardStatusBarAPI {
getCardElementManager(): CardElementManager;
}
export interface CardStyleAPI {
getCardElementManager(): CardElementManager;
getConfigManager(): ConfigManager;