Initial version of status bar.
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user