Initial configuration reorganization.
This commit is contained in:
+52
-56
@@ -192,72 +192,72 @@ export class FrigateCard extends LitElement {
|
||||
protected _getMenuButtons(): MenuButton[] {
|
||||
const buttons: MenuButton[] = [];
|
||||
|
||||
if (this.config.menu_buttons?.frigate ?? true) {
|
||||
if (this.config.menu.buttons.frigate) {
|
||||
buttons.push({
|
||||
type: 'internal-menu-icon',
|
||||
tap_action: 'frigate',
|
||||
title: localize('menu.frigate'),
|
||||
title: localize('config.menu.buttons.frigate'),
|
||||
});
|
||||
}
|
||||
if (this.config.menu_buttons?.live ?? true) {
|
||||
if (this.config.menu.buttons.live) {
|
||||
buttons.push({
|
||||
type: 'internal-menu-icon',
|
||||
tap_action: 'live',
|
||||
title: localize('menu.live'),
|
||||
title: localize('config.view.views.live'),
|
||||
icon: 'mdi:cctv',
|
||||
emphasize: this._view.is('live'),
|
||||
});
|
||||
}
|
||||
if (this.config.menu_buttons?.clips ?? true) {
|
||||
if (this.config.menu.buttons.clips) {
|
||||
buttons.push({
|
||||
type: 'internal-menu-icon',
|
||||
tap_action: 'clips',
|
||||
hold_action: 'clip',
|
||||
title: localize('menu.clips'),
|
||||
title: localize('config.view.views.clips'),
|
||||
icon: 'mdi:filmstrip',
|
||||
emphasize: this._view.is('clips'),
|
||||
});
|
||||
}
|
||||
if (this.config.menu_buttons?.snapshots ?? true) {
|
||||
if (this.config.menu.buttons.snapshots) {
|
||||
buttons.push({
|
||||
type: 'internal-menu-icon',
|
||||
tap_action: 'snapshots',
|
||||
hold_action: 'snapshot',
|
||||
title: localize('menu.snapshots'),
|
||||
title: localize('config.view.views.snapshots'),
|
||||
icon: 'mdi:camera',
|
||||
emphasize: this._view.is('snapshots'),
|
||||
});
|
||||
}
|
||||
if (this.config.menu_buttons?.image ?? false) {
|
||||
if (this.config.menu.buttons.image) {
|
||||
buttons.push({
|
||||
type: 'internal-menu-icon',
|
||||
tap_action: 'image',
|
||||
title: localize('menu.image'),
|
||||
title: localize('config.view.views.image'),
|
||||
icon: 'mdi:image',
|
||||
emphasize: this._view.is('image'),
|
||||
});
|
||||
}
|
||||
if (this._view.isViewerView() && (this.config.menu_buttons?.download ?? true)) {
|
||||
if (this.config.menu.buttons.download && this._view.isViewerView()) {
|
||||
buttons.push({
|
||||
type: 'internal-menu-icon',
|
||||
tap_action: 'download',
|
||||
title: localize('menu.download'),
|
||||
title: localize('config.menu.buttons.download'),
|
||||
icon: 'mdi:download',
|
||||
});
|
||||
}
|
||||
if ((this.config.menu_buttons?.frigate_ui ?? true) && this.config.frigate_url) {
|
||||
if (this.config.menu.buttons.frigate_ui && this.config.frigate.url) {
|
||||
buttons.push({
|
||||
type: 'internal-menu-icon',
|
||||
tap_action: 'frigate_ui',
|
||||
title: localize('menu.frigate_ui'),
|
||||
title: localize('config.menu.buttons.frigate_ui'),
|
||||
icon: 'mdi:web',
|
||||
});
|
||||
}
|
||||
if ((this.config.menu_buttons?.fullscreen ?? true) && screenfull.isEnabled) {
|
||||
if (this.config.menu.buttons.fullscreen && screenfull.isEnabled) {
|
||||
buttons.push({
|
||||
type: 'internal-menu-icon',
|
||||
tap_action: 'fullscreen',
|
||||
title: localize('menu.fullscreen'),
|
||||
title: localize('config.menu.buttons.fullscreen'),
|
||||
icon: screenfull.isFullscreen ? 'mdi:fullscreen-exit' : 'mdi:fullscreen',
|
||||
});
|
||||
}
|
||||
@@ -300,8 +300,8 @@ export class FrigateCard extends LitElement {
|
||||
}
|
||||
|
||||
// Option 1: Name specified in config -> done!
|
||||
if (this.config.frigate_camera_name) {
|
||||
return this.config.frigate_camera_name;
|
||||
if (this.config.frigate.camera_name) {
|
||||
return this.config.frigate.camera_name;
|
||||
}
|
||||
|
||||
if (this.config.camera_entity) {
|
||||
@@ -436,7 +436,7 @@ export class FrigateCard extends LitElement {
|
||||
this._message = null;
|
||||
|
||||
if (view === undefined) {
|
||||
this._view = new View({ view: this.config.view_default });
|
||||
this._view = new View({ view: this.config.view.default });
|
||||
} else {
|
||||
this._view = view;
|
||||
}
|
||||
@@ -505,7 +505,7 @@ export class FrigateCard extends LitElement {
|
||||
}
|
||||
|
||||
const path =
|
||||
`/api/frigate/${this.config.frigate_client_id}` +
|
||||
`/api/frigate/${this.config.frigate.client_id}` +
|
||||
`/notifications/${event_id}/` +
|
||||
`${this._view.isClipRelatedView() ? 'clip.mp4' : 'snapshot.jpg'}` +
|
||||
`?download=true`;
|
||||
@@ -595,22 +595,22 @@ export class FrigateCard extends LitElement {
|
||||
* @returns The URL or null if unavailable.
|
||||
*/
|
||||
protected _getFrigateURLFromContext(): string | null {
|
||||
if (!this.config.frigate_url) {
|
||||
if (!this.config.frigate.url) {
|
||||
return null;
|
||||
}
|
||||
if (!this._frigateCameraName) {
|
||||
return this.config.frigate_url;
|
||||
return this.config.frigate.url;
|
||||
} else if (this._view.is('live')) {
|
||||
return `${this.config.frigate_url}/cameras/${this._frigateCameraName}`;
|
||||
return `${this.config.frigate.url}/cameras/${this._frigateCameraName}`;
|
||||
}
|
||||
return `${this.config.frigate_url}/events?camera=${this._frigateCameraName}`;
|
||||
return `${this.config.frigate.url}/events?camera=${this._frigateCameraName}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle interaction with the card.
|
||||
*/
|
||||
protected _interactionHandler(): void {
|
||||
if (!this.config.view_timeout) {
|
||||
if (!this.config.view.timeout) {
|
||||
return;
|
||||
}
|
||||
if (this._interactionTimerID) {
|
||||
@@ -619,7 +619,7 @@ export class FrigateCard extends LitElement {
|
||||
this._interactionTimerID = window.setTimeout(() => {
|
||||
this._interactionTimerID = null;
|
||||
this._changeView();
|
||||
}, this.config.view_timeout * 1000);
|
||||
}, this.config.view.timeout * 1000);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -628,15 +628,14 @@ export class FrigateCard extends LitElement {
|
||||
*/
|
||||
protected _renderMenu(): TemplateResult | void {
|
||||
const classes = {
|
||||
'hover-menu': this.config.menu_mode.startsWith('hover-'),
|
||||
'hover-menu': this.config.menu.mode.startsWith('hover-'),
|
||||
};
|
||||
return html`
|
||||
<frigate-card-menu
|
||||
class="${classMap(classes)}"
|
||||
.hass=${this._hass}
|
||||
.menuMode=${this.config.menu_mode}
|
||||
.menuConfig=${this.config.menu}
|
||||
.buttons=${this._getMenuButtons()}
|
||||
.buttonSize=${this.config.menu_button_size}
|
||||
class="${classMap(classes)}"
|
||||
@frigate-card:menu-interaction=${this._menuInteractionHandler.bind(this)}
|
||||
></frigate-card-menu>
|
||||
`;
|
||||
@@ -655,10 +654,10 @@ export class FrigateCard extends LitElement {
|
||||
}
|
||||
return {
|
||||
mediaType: this._view.isClipRelatedView() ? 'clips' : 'snapshots',
|
||||
clientId: this.config.frigate_client_id,
|
||||
clientId: this.config.frigate.client_id,
|
||||
cameraName: this._frigateCameraName,
|
||||
label: this.config.label,
|
||||
zone: this.config.zone,
|
||||
label: this.config.frigate.label,
|
||||
zone: this.config.frigate.zone,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -760,7 +759,7 @@ export class FrigateCard extends LitElement {
|
||||
* context.
|
||||
*/
|
||||
protected _isAspectRatioEnforced(): boolean {
|
||||
const aspect_ratio_mode = this.config.dimensions?.aspect_ratio_mode ?? 'dynamic';
|
||||
const aspectRatioMode = this.config.dimensions.aspect_ratio_mode;
|
||||
|
||||
// Do not artifically constrain aspect ratio if:
|
||||
// - It's fullscreen.
|
||||
@@ -769,8 +768,8 @@ export class FrigateCard extends LitElement {
|
||||
|
||||
return !(
|
||||
(screenfull.isEnabled && screenfull.isFullscreen) ||
|
||||
aspect_ratio_mode == 'unconstrained' ||
|
||||
(aspect_ratio_mode == 'dynamic' && this._view.isMediaView())
|
||||
aspectRatioMode == 'unconstrained' ||
|
||||
(aspectRatioMode == 'dynamic' && this._view.isMediaView())
|
||||
);
|
||||
}
|
||||
|
||||
@@ -784,14 +783,14 @@ export class FrigateCard extends LitElement {
|
||||
return null;
|
||||
}
|
||||
|
||||
const aspect_ratio_mode = this.config.dimensions?.aspect_ratio_mode ?? 'dynamic';
|
||||
if (aspect_ratio_mode == 'dynamic' && this._mediaShowInfo) {
|
||||
const aspectRatioMode = this.config.dimensions.aspect_ratio_mode;
|
||||
if (aspectRatioMode == 'dynamic' && this._mediaShowInfo) {
|
||||
return (this._mediaShowInfo.height / this._mediaShowInfo.width) * 100;
|
||||
}
|
||||
|
||||
const default_aspect_ratio = this.config.dimensions?.aspect_ratio;
|
||||
if (default_aspect_ratio) {
|
||||
return (default_aspect_ratio[1] / default_aspect_ratio[0]) * 100;
|
||||
const defaultAspectRatio = this.config.dimensions.aspect_ratio;
|
||||
if (defaultAspectRatio) {
|
||||
return (defaultAspectRatio[1] / defaultAspectRatio[0]) * 100;
|
||||
} else {
|
||||
return (9 / 16) * 100;
|
||||
}
|
||||
@@ -836,7 +835,7 @@ export class FrigateCard extends LitElement {
|
||||
window.innerWidth / window.innerHeight
|
||||
) {
|
||||
// If the menu is outside the media (i.e. above/below) allow space for it.
|
||||
const allowance = ['above', 'below'].includes(this.config.menu_mode)
|
||||
const allowance = ['above', 'below'].includes(this.config.menu.mode)
|
||||
? MENU_HEIGHT
|
||||
: 0;
|
||||
innerStyle['max-width'] = `calc(${
|
||||
@@ -850,7 +849,7 @@ export class FrigateCard extends LitElement {
|
||||
};
|
||||
|
||||
return html` <ha-card @click=${this._interactionHandler}>
|
||||
${this.config.menu_mode == 'above' ? this._renderMenu() : ''}
|
||||
${this.config.menu.mode == 'above' ? this._renderMenu() : ''}
|
||||
<div class="container outer" style="${styleMap(outerStyle)}">
|
||||
<div class="${classMap(contentClasses)}" style="${styleMap(innerStyle)}">
|
||||
${this._frigateCameraName == undefined
|
||||
@@ -864,7 +863,7 @@ export class FrigateCard extends LitElement {
|
||||
: this._render()}
|
||||
</div>
|
||||
</div>
|
||||
${this.config.menu_mode != 'above' ? this._renderMenu() : ''}
|
||||
${this.config.menu.mode != 'above' ? this._renderMenu() : ''}
|
||||
</ha-card>`;
|
||||
}
|
||||
|
||||
@@ -891,16 +890,16 @@ export class FrigateCard extends LitElement {
|
||||
gallery: this._view.isGalleryView(),
|
||||
};
|
||||
const galleryClasses = {
|
||||
hidden: this.config.live_preload && !this._view.isGalleryView(),
|
||||
hidden: this.config.live.preload && !this._view.isGalleryView(),
|
||||
};
|
||||
const viewerClasses = {
|
||||
hidden: this.config.live_preload && !this._view.isViewerView(),
|
||||
hidden: this.config.live.preload && !this._view.isViewerView(),
|
||||
};
|
||||
const liveClasses = {
|
||||
hidden: this.config.live_preload && this._view.view != 'live',
|
||||
hidden: this.config.live.preload && this._view.view != 'live',
|
||||
};
|
||||
const imageClasses = {
|
||||
hidden: this.config.live_preload && this._view.view != 'image',
|
||||
hidden: this.config.live.preload && this._view.view != 'image',
|
||||
};
|
||||
|
||||
return html`
|
||||
@@ -908,7 +907,7 @@ export class FrigateCard extends LitElement {
|
||||
${this._message ? renderMessage(this._message) : ``}
|
||||
${!this._message && this._view.is('image')
|
||||
? html` <frigate-card-image
|
||||
.image=${this.config.image}
|
||||
.imageConfig=${this.config.image}
|
||||
class="${classMap(imageClasses)}"
|
||||
@frigate-card:media-show=${this._mediaShowHandler}
|
||||
@frigate-card:message=${this._messageHandler}
|
||||
@@ -931,11 +930,8 @@ export class FrigateCard extends LitElement {
|
||||
.hass=${this._hass}
|
||||
.view=${this._view}
|
||||
.browseMediaQueryParameters=${mediaQueryParameters}
|
||||
.nextPreviousControlStyle=${this.config.controls?.nextprev ?? 'thumbnails'}
|
||||
.nextPreviousControlSize=${this.config.controls?.nextprev_size}
|
||||
.autoplayClip=${this.config.autoplay_clip}
|
||||
.viewerConfig=${this.config.event_viewer}
|
||||
.resolvedMediaCache=${this._resolvedMediaCache}
|
||||
.lazyLoad=${this.config.event_viewer?.lazy_load ?? true}
|
||||
class="${classMap(viewerClasses)}"
|
||||
@frigate-card:change-view=${this._changeViewHandler}
|
||||
@frigate-card:media-show=${this._mediaShowHandler}
|
||||
@@ -947,14 +943,14 @@ export class FrigateCard extends LitElement {
|
||||
: ``}
|
||||
${
|
||||
// Note the subtle difference in condition below vs the other views in order
|
||||
// to always render the live view for live_preload mode.
|
||||
(!this._message && this._view.is('live')) || this.config.live_preload
|
||||
// to always render the live view for live.preload mode.
|
||||
(!this._message && this._view.is('live')) || this.config.live.preload
|
||||
? html`
|
||||
<frigate-card-live
|
||||
.hass=${this._hass}
|
||||
.config=${this.config}
|
||||
.frigateCameraName=${this._frigateCameraName}
|
||||
.preload=${this.config.live_preload && !this._view.is('live')}
|
||||
.preload=${this.config.live.preload && !this._view.is('live')}
|
||||
class="${classMap(liveClasses)}"
|
||||
@frigate-card:media-show=${this._mediaShowHandler}
|
||||
@frigate-card:pause=${this._pauseHandler}
|
||||
|
||||
+2
-2
@@ -214,8 +214,8 @@ export function dispatchErrorMessageEvent(element: HTMLElement, message: string)
|
||||
* @returns A boolean indicating whether or not to allow an update.
|
||||
*/
|
||||
export function shouldUpdateBasedOnHass(
|
||||
newHass: HomeAssistant | null,
|
||||
oldHass: HomeAssistant | undefined,
|
||||
newHass: HomeAssistant | undefined | null,
|
||||
oldHass: HomeAssistant | undefined | null,
|
||||
entities: string[] | null,
|
||||
): boolean {
|
||||
if (!newHass || !entities) {
|
||||
|
||||
@@ -3,17 +3,19 @@ import { customElement, property } from 'lit/decorators.js';
|
||||
|
||||
import { dispatchMediaShowEvent } from '../common.js';
|
||||
|
||||
import imageStyle from '../scss/image.scss';
|
||||
import type { ImageViewConfig } from '../types.js';
|
||||
import defaultImage from '../images/frigate-bird-in-sky.jpg'
|
||||
|
||||
import imageStyle from '../scss/image.scss';
|
||||
|
||||
@customElement('frigate-card-image')
|
||||
export class FrigateCardImage extends LitElement {
|
||||
@property({ attribute: false })
|
||||
protected image?: string;
|
||||
protected imageConfig?: ImageViewConfig;
|
||||
|
||||
protected render(): TemplateResult | void {
|
||||
return html` <img
|
||||
src=${this.image || defaultImage}
|
||||
src=${this.imageConfig?.src || defaultImage}
|
||||
@load=${(e) => {
|
||||
dispatchMediaShowEvent(this, e);
|
||||
}}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { CSSResultGroup, LitElement, TemplateResult, html, unsafeCSS } from 'lit';
|
||||
import type { ExtendedHomeAssistant, FrigateCardConfig, MediaShowInfo } from '../types.js';
|
||||
import type { ExtendedHomeAssistant, FrigateCardConfig, MediaShowInfo, WebRTCConfig } from '../types.js';
|
||||
import { HomeAssistant } from 'custom-card-helpers';
|
||||
import { customElement, property } from 'lit/decorators.js';
|
||||
import { until } from 'lit/directives/until.js';
|
||||
@@ -75,24 +75,24 @@ export class FrigateCardLive extends LitElement {
|
||||
return;
|
||||
}
|
||||
|
||||
return html` ${this.config.live_provider == 'frigate'
|
||||
return html` ${this.config.live.provider == 'frigate'
|
||||
? html` <frigate-card-live-frigate
|
||||
.hass=${this.hass}
|
||||
.cameraEntity=${this.config.camera_entity}
|
||||
@frigate-card:media-show=${this._mediaShowHandler}
|
||||
>
|
||||
</frigate-card-live-frigate>`
|
||||
: this.config.live_provider == 'webrtc'
|
||||
: this.config.live.provider == 'webrtc'
|
||||
? html`<frigate-card-live-webrtc
|
||||
.hass=${this.hass}
|
||||
.webRTCConfig=${this.config.webrtc || {}}
|
||||
.webRTCConfig=${this.config.live.webrtc || {}}
|
||||
@frigate-card:media-show=${this._mediaShowHandler}
|
||||
>
|
||||
</frigate-card-live-webrtc>`
|
||||
: html` <frigate-card-live-jsmpeg
|
||||
.hass=${this.hass}
|
||||
.cameraName=${this.frigateCameraName}
|
||||
.clientId=${this.config.frigate_client_id}
|
||||
.clientId=${this.config.frigate.client_id}
|
||||
@frigate-card:media-show=${this._mediaShowHandler}
|
||||
>
|
||||
</frigate-card-live-jsmpeg>`}`;
|
||||
@@ -152,7 +152,7 @@ export class FrigateCardLiveFrigate extends LitElement {
|
||||
@customElement('frigate-card-live-webrtc')
|
||||
export class FrigateCardLiveWebRTC extends LitElement {
|
||||
@property({ attribute: false })
|
||||
protected webRTCConfig?: Record<string, unknown>;
|
||||
protected webRTCConfig?: WebRTCConfig;
|
||||
|
||||
protected hass?: HomeAssistant & ExtendedHomeAssistant;
|
||||
protected _webRTCElement: HTMLElement | null = null;
|
||||
|
||||
+37
-32
@@ -16,8 +16,8 @@ import { actionHandler } from '../action-handler-directive.js';
|
||||
|
||||
import type {
|
||||
ExtendedHomeAssistant,
|
||||
FrigateMenuMode,
|
||||
MenuButton,
|
||||
MenuConfig,
|
||||
MenuInteraction,
|
||||
} from '../types.js';
|
||||
import { dispatchFrigateCardEvent, shouldUpdateBasedOnHass } from '../common.js';
|
||||
@@ -30,10 +30,16 @@ export const MENU_HEIGHT = 46;
|
||||
@customElement('frigate-card-menu')
|
||||
export class FrigateCardMenu extends LitElement {
|
||||
@property({ attribute: false })
|
||||
public hass!: HomeAssistant & ExtendedHomeAssistant;
|
||||
public hass?: HomeAssistant & ExtendedHomeAssistant;
|
||||
|
||||
@property({ attribute: false })
|
||||
protected menuMode: FrigateMenuMode = 'hidden-top';
|
||||
set menuConfig(menuConfig: MenuConfig) {
|
||||
this._menuConfig = menuConfig;
|
||||
if (menuConfig) {
|
||||
this.style.setProperty('--frigate-card-menu-button-size', menuConfig.button_size);
|
||||
}
|
||||
}
|
||||
public _menuConfig?: MenuConfig;
|
||||
|
||||
@property({ attribute: false })
|
||||
protected expand = false;
|
||||
@@ -41,13 +47,8 @@ export class FrigateCardMenu extends LitElement {
|
||||
@property({ attribute: false })
|
||||
public buttons: MenuButton[] = [];
|
||||
|
||||
@property({ attribute: false })
|
||||
set buttonSize(buttonSize: string) {
|
||||
this.style.setProperty('--frigate-card-menu-button-size', buttonSize);
|
||||
}
|
||||
|
||||
protected _interactionHandler(ev: CustomEvent, button: MenuButton): void {
|
||||
if (this.menuMode.startsWith('hidden-')) {
|
||||
if (this._menuConfig?.mode.startsWith('hidden-')) {
|
||||
if (button.type == 'internal-menu-icon' && button.tap_action === 'frigate') {
|
||||
this.expand = !this.expand;
|
||||
return;
|
||||
@@ -82,7 +83,7 @@ export class FrigateCardMenu extends LitElement {
|
||||
}
|
||||
|
||||
// Render a menu button.
|
||||
protected _renderButton(button: MenuButton): TemplateResult {
|
||||
protected _renderButton(button: MenuButton): TemplateResult | void {
|
||||
let state: HassEntity | null = null;
|
||||
let emphasize = false;
|
||||
let title = button.title;
|
||||
@@ -90,6 +91,9 @@ export class FrigateCardMenu extends LitElement {
|
||||
const style = ('style' in button ? button.style : {}) || {};
|
||||
|
||||
if (button.type === 'custom:frigate-card-menu-state-icon') {
|
||||
if (!this.hass) {
|
||||
return;
|
||||
}
|
||||
state = this.hass.states[button.entity];
|
||||
emphasize =
|
||||
!!state && button.state_color && ['on', 'active', 'home'].includes(state.state);
|
||||
@@ -133,9 +137,9 @@ export class FrigateCardMenu extends LitElement {
|
||||
}
|
||||
|
||||
// Render the Frigate menu button.
|
||||
protected _renderFrigateButton(button: MenuButton): TemplateResult {
|
||||
protected _renderFrigateButton(button: MenuButton): TemplateResult | void {
|
||||
const icon =
|
||||
this.menuMode.startsWith('hidden-') && !this.expand
|
||||
this._menuConfig?.mode.startsWith('hidden-') && !this.expand
|
||||
? 'mdi:alpha-f-box-outline'
|
||||
: 'mdi:alpha-f-box';
|
||||
|
||||
@@ -143,7 +147,12 @@ export class FrigateCardMenu extends LitElement {
|
||||
}
|
||||
|
||||
// Render the menu.
|
||||
protected render(): TemplateResult {
|
||||
protected render(): TemplateResult | void {
|
||||
if (!this._menuConfig) {
|
||||
return;
|
||||
}
|
||||
const mode = this._menuConfig.mode;
|
||||
|
||||
const isFrigateButton = function (button: MenuButton): boolean {
|
||||
return button.type === 'internal-menu-icon' && button.tap_action === 'frigate';
|
||||
};
|
||||
@@ -151,33 +160,29 @@ export class FrigateCardMenu extends LitElement {
|
||||
// If the menu is off, or if it's in hidden mode but there's no button to
|
||||
// unhide it, just show nothing.
|
||||
if (
|
||||
this.menuMode == 'none' ||
|
||||
(this.menuMode.startsWith('hidden-') && !this.buttons.find(isFrigateButton))
|
||||
mode == 'none' ||
|
||||
(mode.startsWith('hidden-') && !this.buttons.find(isFrigateButton))
|
||||
) {
|
||||
return html``;
|
||||
return;
|
||||
}
|
||||
|
||||
const classes = {
|
||||
'frigate-card-menu': true,
|
||||
'overlay-hidden':
|
||||
this.menuMode.startsWith('hidden-') ||
|
||||
this.menuMode.startsWith('overlay-') ||
|
||||
this.menuMode.startsWith('hover-'),
|
||||
mode.startsWith('hidden-') ||
|
||||
mode.startsWith('overlay-') ||
|
||||
mode.startsWith('hover-'),
|
||||
'expanded-horizontal':
|
||||
(this.menuMode.startsWith('overlay-') ||
|
||||
this.menuMode.startsWith('hover-') ||
|
||||
this.expand) &&
|
||||
(this.menuMode.endsWith('-top') || this.menuMode.endsWith('-bottom')),
|
||||
(mode.startsWith('overlay-') || mode.startsWith('hover-') || this.expand) &&
|
||||
(mode.endsWith('-top') || mode.endsWith('-bottom')),
|
||||
'expanded-vertical':
|
||||
(this.menuMode.startsWith('overlay-') ||
|
||||
this.menuMode.startsWith('hover-') ||
|
||||
this.expand) &&
|
||||
(this.menuMode.endsWith('-left') || this.menuMode.endsWith('-right')),
|
||||
full: ['above', 'below'].includes(this.menuMode),
|
||||
left: this.menuMode.endsWith('-left'),
|
||||
right: this.menuMode.endsWith('-right'),
|
||||
top: this.menuMode.endsWith('-top'),
|
||||
bottom: this.menuMode.endsWith('-bottom'),
|
||||
(mode.startsWith('overlay-') || mode.startsWith('hover-') || this.expand) &&
|
||||
(mode.endsWith('-left') || mode.endsWith('-right')),
|
||||
full: mode == 'above' || mode == 'below',
|
||||
left: mode.endsWith('-left'),
|
||||
right: mode.endsWith('-right'),
|
||||
top: mode.endsWith('-top'),
|
||||
bottom: mode.endsWith('-bottom'),
|
||||
};
|
||||
|
||||
return html`
|
||||
|
||||
@@ -2,30 +2,29 @@ import { CSSResultGroup, LitElement, TemplateResult, html, unsafeCSS } from 'lit
|
||||
import { customElement, property } from 'lit/decorators.js';
|
||||
import { classMap } from 'lit/directives/class-map.js';
|
||||
|
||||
import { NextPreviousControlStyle } from '../types.js';
|
||||
import { NextPreviousControlConfig } from '../types.js';
|
||||
|
||||
import controlStyle from '../scss/next-previous-control.scss';
|
||||
|
||||
@customElement('frigate-card-next-previous-control')
|
||||
export class FrigateCardMessage extends LitElement {
|
||||
export class FrigateCardNextPreviousControl extends LitElement {
|
||||
@property({ attribute: false })
|
||||
protected direction?: 'next' | 'previous';
|
||||
|
||||
@property({ attribute: false })
|
||||
protected controlStyle?: NextPreviousControlStyle;
|
||||
set controlConfig(controlConfig: NextPreviousControlConfig | undefined) {
|
||||
if (controlConfig?.size) {
|
||||
this.style.setProperty('--frigate-card-next-prev-size', controlConfig.size);
|
||||
}
|
||||
this._controlConfig = controlConfig;
|
||||
}
|
||||
protected _controlConfig?: NextPreviousControlConfig;
|
||||
|
||||
@property({ attribute: false })
|
||||
protected thumbnail?: string;
|
||||
|
||||
@property({ attribute: false })
|
||||
set controlSize(controlSize: string | undefined) {
|
||||
if (controlSize) {
|
||||
this.style.setProperty('--frigate-card-next-prev-size', controlSize);
|
||||
}
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this.controlStyle || this.controlStyle == 'none') {
|
||||
if (!this._controlConfig || this._controlConfig.style == 'none') {
|
||||
return html``;
|
||||
}
|
||||
|
||||
@@ -33,12 +32,12 @@ export class FrigateCardMessage extends LitElement {
|
||||
controls: true,
|
||||
previous: this.direction == 'previous',
|
||||
next: this.direction == 'next',
|
||||
thumbnails: this.controlStyle == 'thumbnails',
|
||||
chevrons: this.controlStyle == 'chevrons',
|
||||
button: this.controlStyle == 'chevrons',
|
||||
thumbnails: this._controlConfig.style == 'thumbnails',
|
||||
chevrons: this._controlConfig.style == 'chevrons',
|
||||
button: this._controlConfig.style == 'chevrons',
|
||||
};
|
||||
|
||||
if (this.controlStyle == 'chevrons') {
|
||||
if (this._controlConfig.style == 'chevrons') {
|
||||
const icon = this.direction == 'previous' ? 'mdi:chevron-left' : 'mdi:chevron-right';
|
||||
|
||||
// TODO: Upon a safe distance from the release of HA 2021.11 these
|
||||
|
||||
+39
-55
@@ -19,7 +19,7 @@ import type {
|
||||
BrowseMediaSource,
|
||||
ExtendedHomeAssistant,
|
||||
MediaShowInfo,
|
||||
NextPreviousControlStyle,
|
||||
ViewerConfig,
|
||||
} from '../types.js';
|
||||
import { ResolvedMediaCache, ResolvedMediaUtil } from '../resolved-media.js';
|
||||
import { View } from '../view.js';
|
||||
@@ -49,24 +49,15 @@ export class FrigateCardViewer extends LitElement {
|
||||
@property({ attribute: false })
|
||||
protected view?: View;
|
||||
|
||||
@property({ attribute: false })
|
||||
protected viewerConfig?: ViewerConfig;
|
||||
|
||||
@property({ attribute: false })
|
||||
protected browseMediaQueryParameters?: BrowseMediaQueryParameters;
|
||||
|
||||
@property({ attribute: false })
|
||||
protected nextPreviousControlStyle?: NextPreviousControlStyle;
|
||||
|
||||
@property({ attribute: false })
|
||||
protected nextPreviousControlSize?: string;
|
||||
|
||||
@property({ attribute: false })
|
||||
protected autoplayClip?: boolean;
|
||||
|
||||
@property({ attribute: false })
|
||||
protected resolvedMediaCache?: ResolvedMediaCache;
|
||||
|
||||
@property({ attribute: false })
|
||||
protected lazyLoad?: boolean;
|
||||
|
||||
/**
|
||||
* Resolve all the given media for a target.
|
||||
* @param target The target to resolve media from.
|
||||
@@ -92,21 +83,23 @@ export class FrigateCardViewer extends LitElement {
|
||||
return errorFree;
|
||||
}
|
||||
|
||||
/**
|
||||
* Master render method.
|
||||
* @returns A rendered template.
|
||||
*/
|
||||
protected render(): TemplateResult | void {
|
||||
return html`${until(this._render(), renderProgressIndicator())}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Asyncronously render the element.
|
||||
* @returns A template to render.
|
||||
* @returns A rendered template.
|
||||
*/
|
||||
protected async _render(): Promise<TemplateResult | void> {
|
||||
if (!this.hass || !this.view || !this.browseMediaQueryParameters) {
|
||||
return html``;
|
||||
}
|
||||
|
||||
let autoplay = true;
|
||||
|
||||
if (this.view.is('clip') || this.view.is('snapshot')) {
|
||||
let parent: BrowseMediaSource | null = null;
|
||||
try {
|
||||
@@ -129,13 +122,6 @@ export class FrigateCardViewer extends LitElement {
|
||||
}
|
||||
this.view.target = parent;
|
||||
this.view.childIndex = childIndex;
|
||||
|
||||
// In this block, no clip has been manually selected, so this is loading
|
||||
// the most recent clip on card load. In this mode, autoplay of the clip
|
||||
// may be disabled by configuration. If does not make sense to disable
|
||||
// autoplay when the user has explicitly picked an event to play in the
|
||||
// gallery.
|
||||
autoplay = this.autoplayClip ?? true;
|
||||
}
|
||||
|
||||
if (this.view.target && !(await this._resolveAllMediaForTarget(this.view.target))) {
|
||||
@@ -144,13 +130,10 @@ export class FrigateCardViewer extends LitElement {
|
||||
|
||||
return html` <frigate-card-viewer-core
|
||||
.view=${this.view}
|
||||
.nextPreviousControlStyle=${this.nextPreviousControlStyle}
|
||||
.nextPreviousControlSize=${this.nextPreviousControlSize}
|
||||
.viewerConfig=${this.viewerConfig}
|
||||
.resolvedMediaCache=${this.resolvedMediaCache}
|
||||
.autoplayClip=${autoplay}
|
||||
.hass=${this.hass}
|
||||
.browseMediaQueryParameters=${this.browseMediaQueryParameters}
|
||||
.lazyLoad=${this.lazyLoad}
|
||||
>
|
||||
</frigate-card-viewer-core>`;
|
||||
}
|
||||
@@ -165,29 +148,20 @@ export class FrigateCardViewer extends LitElement {
|
||||
|
||||
@customElement('frigate-card-viewer-core')
|
||||
export class FrigateCardViewerCore extends LitElement {
|
||||
@property({ attribute: false })
|
||||
protected hass?: HomeAssistant & ExtendedHomeAssistant;
|
||||
|
||||
@property({ attribute: false })
|
||||
protected view?: View;
|
||||
|
||||
@property({ attribute: false })
|
||||
protected nextPreviousControlStyle?: NextPreviousControlStyle;
|
||||
|
||||
@property({ attribute: false })
|
||||
protected nextPreviousControlSize?: string;
|
||||
|
||||
@property({ attribute: false })
|
||||
protected resolvedMediaCache?: ResolvedMediaCache;
|
||||
|
||||
@property({ attribute: false })
|
||||
protected autoplayClip?: boolean;
|
||||
|
||||
@property({ attribute: false })
|
||||
protected hass?: HomeAssistant & ExtendedHomeAssistant;
|
||||
protected viewerConfig?: ViewerConfig;
|
||||
|
||||
@property({ attribute: false })
|
||||
protected browseMediaQueryParameters?: BrowseMediaQueryParameters;
|
||||
|
||||
@property({ attribute: false })
|
||||
protected lazyLoad?: boolean;
|
||||
protected resolvedMediaCache?: ResolvedMediaCache;
|
||||
|
||||
// Media carousel object.
|
||||
protected _carousel?: EmblaCarouselType;
|
||||
@@ -418,7 +392,7 @@ export class FrigateCardViewerCore extends LitElement {
|
||||
* Lazily load media in the carousel.
|
||||
*/
|
||||
protected _lazyLoadMediaHandler(): void {
|
||||
if (!this.lazyLoad || !this._carousel) {
|
||||
if (!this.viewerConfig?.lazy_load || !this._carousel) {
|
||||
return;
|
||||
}
|
||||
const slides = this._carousel.slideNodes();
|
||||
@@ -504,8 +478,7 @@ export class FrigateCardViewerCore extends LitElement {
|
||||
${neighbors && neighbors.previous
|
||||
? html`<frigate-card-next-previous-control
|
||||
.direction=${'previous'}
|
||||
.controlStyle=${this.nextPreviousControlStyle}
|
||||
.controlSize=${this.nextPreviousControlSize}
|
||||
.controlConfig=${this.viewerConfig?.controls.next_previous}
|
||||
.thumbnail=${neighbors.previous.thumbnail}
|
||||
.title=${neighbors.previous.title}
|
||||
@click=${() => this._nextPreviousHandler('previous')}
|
||||
@@ -519,8 +492,7 @@ export class FrigateCardViewerCore extends LitElement {
|
||||
${neighbors && neighbors.next
|
||||
? html`<frigate-card-next-previous-control
|
||||
.direction=${'next'}
|
||||
.controlStyle=${this.nextPreviousControlStyle}
|
||||
.controlSize=${this.nextPreviousControlSize}
|
||||
.controlConfig=${this.viewerConfig?.controls.next_previous}
|
||||
.thumbnail=${neighbors.next.thumbnail}
|
||||
.title=${neighbors.next.title}
|
||||
@click=${() => this._nextPreviousHandler('next')}
|
||||
@@ -594,7 +566,7 @@ export class FrigateCardViewerCore extends LitElement {
|
||||
): TemplateResult | void {
|
||||
// media that can be expanded (folders) cannot be resolved to a single media
|
||||
// item, skip them.
|
||||
if (!this.view || !BrowseMediaUtil.isTrueMedia(mediaToRender)) {
|
||||
if (!this.view || !this.viewerConfig || !BrowseMediaUtil.isTrueMedia(mediaToRender)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -603,20 +575,32 @@ export class FrigateCardViewerCore extends LitElement {
|
||||
return;
|
||||
}
|
||||
|
||||
// In this block, no clip has been manually selected, so this is loading
|
||||
// the most recent clip on card load. In this mode, autoplay of the clip
|
||||
// may be disabled by configuration. If does not make sense to disable
|
||||
// autoplay when the user has explicitly picked an event to play in the
|
||||
// gallery.
|
||||
let autoplay = true;
|
||||
if (this.view.is('clip') || this.view.is('snapshot')) {
|
||||
autoplay = this.viewerConfig.autoplay_clip;
|
||||
}
|
||||
|
||||
const lazyLoad = this.viewerConfig.lazy_load;
|
||||
|
||||
return html`
|
||||
<div class="embla__slide">
|
||||
${this.view.isClipRelatedView()
|
||||
? resolvedMedia?.mime_type.toLowerCase() == 'application/x-mpegurl'
|
||||
? html`<frigate-card-ha-hls-player
|
||||
.hass=${this.hass}
|
||||
url=${ifDefined(this.lazyLoad ? undefined : resolvedMedia.url)}
|
||||
data-url=${ifDefined(this.lazyLoad ? resolvedMedia.url : undefined)}
|
||||
url=${ifDefined(lazyLoad ? undefined : resolvedMedia.url)}
|
||||
data-url=${ifDefined(lazyLoad ? resolvedMedia.url : undefined)}
|
||||
title="${mediaToRender.title}"
|
||||
muted
|
||||
controls
|
||||
playsinline
|
||||
allow-exoplayer
|
||||
?autoplay="${this.autoplayClip}"
|
||||
?autoplay="${autoplay}"
|
||||
@frigate-card:media-show=${(e: CustomEvent<MediaShowInfo>) =>
|
||||
this._mediaShowEventHandler(slideIndex, e)}
|
||||
>
|
||||
@@ -626,7 +610,7 @@ export class FrigateCardViewerCore extends LitElement {
|
||||
muted
|
||||
controls
|
||||
playsinline
|
||||
?autoplay="${this.autoplayClip}"
|
||||
?autoplay="${autoplay}"
|
||||
@loadedmetadata="${(e: Event) => {
|
||||
this._mediaShowInfoHandler(slideIndex, createMediaShowInfo(e));
|
||||
}}"
|
||||
@@ -634,14 +618,14 @@ export class FrigateCardViewerCore extends LitElement {
|
||||
@pause=${() => dispatchPauseEvent(this)}
|
||||
>
|
||||
<source
|
||||
src=${ifDefined(this.lazyLoad ? undefined : resolvedMedia.url)}
|
||||
data-src=${ifDefined(this.lazyLoad ? resolvedMedia.url : undefined)}
|
||||
src=${ifDefined(lazyLoad ? undefined : resolvedMedia.url)}
|
||||
data-src=${ifDefined(lazyLoad ? resolvedMedia.url : undefined)}
|
||||
type="${resolvedMedia.mime_type}"
|
||||
/>
|
||||
</video>`
|
||||
: html`<img
|
||||
src=${ifDefined(this.lazyLoad ? IMG_EMPTY : resolvedMedia.url)}
|
||||
data-src=${ifDefined(this.lazyLoad ? resolvedMedia.url : undefined)}
|
||||
src=${ifDefined(lazyLoad ? IMG_EMPTY : resolvedMedia.url)}
|
||||
data-src=${ifDefined(lazyLoad ? resolvedMedia.url : undefined)}
|
||||
title="${mediaToRender.title}"
|
||||
@click=${() => {
|
||||
if (this._carousel?.clickAllowed()) {
|
||||
|
||||
+182
-153
@@ -1,10 +1,18 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
|
||||
// TODO Add editor for control sizes
|
||||
// TODO reorganizse editor by section
|
||||
|
||||
import { CSSResultGroup, LitElement, TemplateResult, html, unsafeCSS } from 'lit';
|
||||
import { customElement, property, state } from 'lit/decorators.js';
|
||||
|
||||
import { HomeAssistant, LovelaceCardEditor, fireEvent } from 'custom-card-helpers';
|
||||
import { localize } from './localize/localize.js';
|
||||
import type { FrigateCardConfig } from './types.js';
|
||||
import {
|
||||
FrigateCardConfig,
|
||||
frigateCardConfigDefaults,
|
||||
frigateCardConfigSchema,
|
||||
} from './types.js';
|
||||
|
||||
import frigate_card_editor_style from './scss/editor.scss';
|
||||
|
||||
@@ -84,66 +92,70 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
|
||||
return html``;
|
||||
}
|
||||
|
||||
// The climate more-info has ha-switch and paper-dropdown-menu elements that are lazy loaded unless explicitly done here
|
||||
// The climate more-info has ha-switch and paper-dropdown-menu elements that
|
||||
// are lazy loaded unless explicitly loaded via climate here.
|
||||
this._helpers.importMoreInfoControl('climate');
|
||||
|
||||
// You can restrict on domain type
|
||||
const cameraEntities = this._getEntities('camera');
|
||||
|
||||
const webrtcCameraEntity =
|
||||
this._config?.webrtc && (this._config?.webrtc as any).entity
|
||||
? (this._config?.webrtc as any).entity
|
||||
this._config?.live?.webrtc && (this._config?.live.webrtc as any).entity
|
||||
? (this._config?.live.webrtc as any).entity
|
||||
: '';
|
||||
|
||||
const viewModes = {
|
||||
'': '',
|
||||
live: localize('menu.live'),
|
||||
clips: localize('menu.clips'),
|
||||
snapshots: localize('menu.snapshots'),
|
||||
clip: localize('menu.clip'),
|
||||
snapshot: localize('menu.snapshot'),
|
||||
image: localize('menu.image'),
|
||||
live: localize('config.view.views.live'),
|
||||
clips: localize('config.view.views.clips'),
|
||||
snapshots: localize('config.view.views.snapshots'),
|
||||
clip: localize('config.view.views.clip'),
|
||||
snapshot: localize('config.view.views.snapshot'),
|
||||
image: localize('config.view.views.image'),
|
||||
};
|
||||
|
||||
const menuModes = {
|
||||
'': '',
|
||||
none: localize('menu_mode.none'),
|
||||
'hidden-top': localize('menu_mode.hidden-top'),
|
||||
'hidden-left': localize('menu_mode.hidden-left'),
|
||||
'hidden-bottom': localize('menu_mode.hidden-bottom'),
|
||||
'hidden-right': localize('menu_mode.hidden-right'),
|
||||
'overlay-top': localize('menu_mode.overlay-top'),
|
||||
'overlay-left': localize('menu_mode.overlay-left'),
|
||||
'overlay-bottom': localize('menu_mode.overlay-bottom'),
|
||||
'overlay-right': localize('menu_mode.overlay-right'),
|
||||
'hover-top': localize('menu_mode.hover-top'),
|
||||
'hover-left': localize('menu_mode.hover-left'),
|
||||
'hover-bottom': localize('menu_mode.hover-bottom'),
|
||||
'hover-right': localize('menu_mode.hover-right'),
|
||||
above: localize('menu_mode.above'),
|
||||
below: localize('menu_mode.below'),
|
||||
none: localize('config.menu.modes.none'),
|
||||
'hidden-top': localize('config.menu.modes.hidden-top'),
|
||||
'hidden-left': localize('config.menu.modes.hidden-left'),
|
||||
'hidden-bottom': localize('config.menu.modes.hidden-bottom'),
|
||||
'hidden-right': localize('config.menu.modes.hidden-right'),
|
||||
'overlay-top': localize('config.menu.modes.overlay-top'),
|
||||
'overlay-left': localize('config.menu.modes.overlay-left'),
|
||||
'overlay-bottom': localize('config.menu.modes.overlay-bottom'),
|
||||
'overlay-right': localize('config.menu.modes.overlay-right'),
|
||||
'hover-top': localize('config.menu.modes.hover-top'),
|
||||
'hover-left': localize('config.menu.modes.hover-left'),
|
||||
'hover-bottom': localize('config.menu.modes.hover-bottom'),
|
||||
'hover-right': localize('config.menu.modes.hover-right'),
|
||||
above: localize('config.menu.modes.above'),
|
||||
below: localize('config.menu.modes.below'),
|
||||
};
|
||||
|
||||
const liveProvider = {
|
||||
const liveProviders = {
|
||||
'': '',
|
||||
frigate: localize('live_provider.frigate'),
|
||||
'frigate-jsmpeg': localize('live_provider.frigate-jsmpeg'),
|
||||
webrtc: localize('live_provider.webrtc'),
|
||||
frigate: localize('config.live.providers.frigate'),
|
||||
'frigate-jsmpeg': localize('config.live.providers.frigate-jsmpeg'),
|
||||
webrtc: localize('config.live.providers.webrtc'),
|
||||
};
|
||||
|
||||
const controlsNextPrev = {
|
||||
const eventViewerNextPreviousControlStyles = {
|
||||
'': '',
|
||||
thumbnails: localize('control.thumbnails'),
|
||||
chevrons: localize('control.chevrons'),
|
||||
none: localize('control.none'),
|
||||
thumbnails: localize(
|
||||
'config.event_viewer.controls.next_previous.styles.thumbnails',
|
||||
),
|
||||
chevrons: localize('config.event_viewer.controls.next_previous.styles.chevrons'),
|
||||
none: localize('config.event_viewer.controls.next_previous.styles.none'),
|
||||
};
|
||||
|
||||
const aspectRatioModes = {
|
||||
'': '',
|
||||
dynamic: localize('aspect_ratio_mode.dynamic'),
|
||||
static: localize('aspect_ratio_mode.static'),
|
||||
unconstrained: localize('aspect_ratio_mode.unconstrained'),
|
||||
}
|
||||
dynamic: localize('config.dimensions.aspect_ratio_modes.dynamic'),
|
||||
static: localize('config.dimensions.aspect_ratio_modes.static'),
|
||||
unconstrained: localize('config.dimensions.aspect_ratio_modes.unconstrained'),
|
||||
};
|
||||
|
||||
const defaults = frigateCardConfigDefaults;
|
||||
|
||||
return html`
|
||||
<div class="card-config">
|
||||
@@ -158,7 +170,7 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
|
||||
? html`
|
||||
<div class="values">
|
||||
<paper-dropdown-menu
|
||||
label=${localize('editor.camera_entity')}
|
||||
label=${localize('config.camera_entity')}
|
||||
@value-changed=${this._valueChanged}
|
||||
.configValue=${'camera_entity'}
|
||||
>
|
||||
@@ -174,29 +186,29 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
|
||||
</paper-listbox>
|
||||
</paper-dropdown-menu>
|
||||
<paper-input
|
||||
label=${localize('editor.frigate_camera_name')}
|
||||
.value=${this._config?.frigate_camera_name || ''}
|
||||
.configValue=${'frigate_camera_name'}
|
||||
label=${localize('config.frigate.camera_name')}
|
||||
.value=${this._config?.frigate?.camera_name || ''}
|
||||
.configValue=${'frigate.camera_name'}
|
||||
@value-changed=${this._valueChanged}
|
||||
></paper-input>
|
||||
<paper-dropdown-menu
|
||||
label=${localize('editor.default_view')}
|
||||
label=${localize('config.view.default')}
|
||||
@value-changed=${this._valueChanged}
|
||||
.configValue=${'view_default'}
|
||||
.configValue=${'view.default'}
|
||||
>
|
||||
<paper-listbox
|
||||
slot="dropdown-content"
|
||||
.selected=${Object.keys(viewModes).indexOf(
|
||||
this._config?.view_default || '',
|
||||
)}
|
||||
>
|
||||
${Object.keys(viewModes).map((key) => {
|
||||
return html`
|
||||
<paper-item .label="${key}"> ${viewModes[key]} </paper-item>
|
||||
`;
|
||||
})}
|
||||
</paper-listbox>
|
||||
</paper-dropdown-menu>
|
||||
<paper-listbox
|
||||
slot="dropdown-content"
|
||||
.selected=${Object.keys(viewModes).indexOf(
|
||||
this._config?.view?.default || '',
|
||||
)}
|
||||
>
|
||||
${Object.keys(viewModes).map((key) => {
|
||||
return html`
|
||||
<paper-item .label="${key}"> ${viewModes[key]} </paper-item>
|
||||
`;
|
||||
})}
|
||||
</paper-listbox>
|
||||
</paper-dropdown-menu>
|
||||
</div>
|
||||
`
|
||||
: ''}
|
||||
@@ -210,62 +222,64 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
|
||||
${options.optional.show
|
||||
? html` <div class="values">
|
||||
<paper-dropdown-menu
|
||||
.label=${localize('editor.live_provider')}
|
||||
.label=${localize('config.live.provider')}
|
||||
@value-changed=${this._valueChanged}
|
||||
.configValue=${'live_provider'}
|
||||
.configValue=${'live.provider'}
|
||||
>
|
||||
<paper-listbox
|
||||
slot="dropdown-content"
|
||||
.selected=${Object.keys(liveProvider).indexOf(
|
||||
this._config?.live_provider || '',
|
||||
.selected=${Object.keys(liveProviders).indexOf(
|
||||
this._config?.live?.provider || '',
|
||||
)}
|
||||
>
|
||||
${Object.keys(liveProvider).map((key) => {
|
||||
${Object.keys(liveProviders).map((key) => {
|
||||
return html`
|
||||
<paper-item .label="${key}">${liveProvider[key]} </paper-item>
|
||||
<paper-item .label="${key}">${liveProviders[key]} </paper-item>
|
||||
`;
|
||||
})}
|
||||
</paper-listbox>
|
||||
</paper-dropdown-menu>
|
||||
<paper-input
|
||||
label=${localize('editor.frigate_client_id')}
|
||||
.value=${this._config?.frigate_client_id || ''}
|
||||
.configValue=${'frigate_client_id'}
|
||||
label=${localize('config.frigate.client_id')}
|
||||
.value=${this._config?.frigate?.client_id || ''}
|
||||
.configValue=${'frigate.client_id'}
|
||||
@value-changed=${this._valueChanged}
|
||||
></paper-input>
|
||||
<paper-input
|
||||
label=${localize('editor.view_timeout')}
|
||||
label=${localize('config.view.timeout')}
|
||||
prevent-invalid-input
|
||||
allowed-pattern="[0-9]"
|
||||
.value=${this._config?.view_timeout
|
||||
? String(this._config.view_timeout)
|
||||
.value=${this._config?.view?.timeout
|
||||
? String(this._config?.view?.timeout)
|
||||
: ''}
|
||||
.configValue=${'view_timeout'}
|
||||
.configValue=${'view.timeout'}
|
||||
@value-changed=${this._valueChanged}
|
||||
></paper-input>
|
||||
<paper-input
|
||||
label=${localize('editor.frigate_url')}
|
||||
.value=${this._config?.frigate_url || ''}
|
||||
.configValue=${'frigate_url'}
|
||||
label=${localize('config.frigate.url')}
|
||||
.value=${this._config?.frigate?.url || ''}
|
||||
.configValue=${'frigate.url'}
|
||||
@value-changed=${this._valueChanged}
|
||||
></paper-input>
|
||||
<ha-formfield .label=${localize('editor.autoplay_clip')}>
|
||||
<ha-formfield .label=${localize('config.event_viewer.autoplay_clip')}>
|
||||
<ha-switch
|
||||
.checked=${this._config?.autoplay_clip === true}
|
||||
.configValue=${'autoplay_clip'}
|
||||
.checked=${this._config?.event_viewer?.autoplay_clip ??
|
||||
defaults.event_viewer.autoplay_clip}
|
||||
.configValue=${'event_viewer.autoplay_clip'}
|
||||
@change=${this._valueChanged}
|
||||
></ha-switch>
|
||||
</ha-formfield>
|
||||
<ha-formfield .label=${localize('editor.live_preload')}>
|
||||
<ha-formfield .label=${localize('config.live.preload')}>
|
||||
<ha-switch
|
||||
.checked=${this._config?.live_preload === true}
|
||||
.configValue=${'live_preload'}
|
||||
.checked=${this._config?.live?.preload ?? defaults.live.preload}
|
||||
.configValue=${'live.preload'}
|
||||
@change=${this._valueChanged}
|
||||
></ha-switch>
|
||||
</ha-formfield>
|
||||
<ha-formfield .label=${localize('editor.lazy_load')}>
|
||||
<ha-formfield .label=${localize('config.event_viewer.lazy_load')}>
|
||||
<ha-switch
|
||||
.checked=${this._config?.event_viewer?.lazy_load !== false}
|
||||
.checked=${this._config?.event_viewer?.lazy_load ??
|
||||
defaults.event_viewer.lazy_load}
|
||||
.configValue=${'event_viewer.lazy_load'}
|
||||
@change=${this._valueChanged}
|
||||
></ha-switch>
|
||||
@@ -281,14 +295,14 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
|
||||
</div>
|
||||
${options.appearance.show
|
||||
? html`<paper-dropdown-menu
|
||||
.label=${localize('editor.menu_mode')}
|
||||
.label=${localize('config.menu.mode')}
|
||||
@value-changed=${this._valueChanged}
|
||||
.configValue=${'menu_mode'}
|
||||
.configValue=${'menu.mode'}
|
||||
>
|
||||
<paper-listbox
|
||||
slot="dropdown-content"
|
||||
.selected=${Object.keys(menuModes).indexOf(
|
||||
this._config?.menu_mode || '',
|
||||
this._config?.menu?.mode || '',
|
||||
)}
|
||||
>
|
||||
${Object.keys(menuModes).map((key) => {
|
||||
@@ -300,26 +314,28 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
|
||||
</paper-dropdown-menu>
|
||||
<br />
|
||||
<paper-dropdown-menu
|
||||
.label=${localize('control.nextprev')}
|
||||
.label=${localize('config.event_viewer.controls.next_previous.style')}
|
||||
@value-changed=${this._valueChanged}
|
||||
.configValue=${'controls.nextprev'}
|
||||
.configValue=${'event_viewer.controls.next_previous.style'}
|
||||
>
|
||||
<paper-listbox
|
||||
slot="dropdown-content"
|
||||
.selected=${Object.keys(controlsNextPrev).indexOf(
|
||||
this._config?.controls?.nextprev || '',
|
||||
.selected=${Object.keys(eventViewerNextPreviousControlStyles).indexOf(
|
||||
this._config?.event_viewer?.controls?.next_previous?.style || '',
|
||||
)}
|
||||
>
|
||||
${Object.keys(controlsNextPrev).map((key) => {
|
||||
${Object.keys(eventViewerNextPreviousControlStyles).map((key) => {
|
||||
return html`
|
||||
<paper-item .label="${key}"> ${controlsNextPrev[key]} </paper-item>
|
||||
<paper-item .label="${key}">
|
||||
${eventViewerNextPreviousControlStyles[key]}
|
||||
</paper-item>
|
||||
`;
|
||||
})}
|
||||
</paper-listbox>
|
||||
</paper-dropdown-menu>
|
||||
<br />
|
||||
<paper-dropdown-menu
|
||||
.label=${localize('dimensions.aspect_ratio_mode')}
|
||||
.label=${localize('config.dimensions.aspect_ratio_mode')}
|
||||
@value-changed=${this._valueChanged}
|
||||
.configValue=${'dimensions.aspect_ratio_mode'}
|
||||
>
|
||||
@@ -338,7 +354,7 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
|
||||
</paper-dropdown-menu>
|
||||
<br />
|
||||
<paper-input
|
||||
label=${localize('dimensions.aspect_ratio')}
|
||||
label=${localize('config.dimensions.aspect_ratio')}
|
||||
prevent-invalid-input
|
||||
.value=${this._config?.dimensions?.aspect_ratio
|
||||
? String(this._config?.dimensions?.aspect_ratio)
|
||||
@@ -347,42 +363,23 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
|
||||
@value-changed=${this._valueChanged}
|
||||
></paper-input>
|
||||
<paper-input
|
||||
label=${localize('editor.image')}
|
||||
label=${localize('config.image.src')}
|
||||
prevent-invalid-input
|
||||
.value=${this._config?.image
|
||||
? String(this._config?.image)
|
||||
.value=${this._config?.image?.src
|
||||
? String(this._config?.image?.src)
|
||||
: ''}
|
||||
.configValue=${'image'}
|
||||
.configValue=${'image.src'}
|
||||
@value-changed=${this._valueChanged}
|
||||
></paper-input>
|
||||
<ha-formfield
|
||||
.label=${localize('editor.show_button') +
|
||||
': ' +
|
||||
localize('menu.frigate')}
|
||||
localize('config.menu.buttons.frigate')}
|
||||
>
|
||||
<ha-switch
|
||||
.checked=${this._config?.menu_buttons?.frigate ?? true}
|
||||
.configValue=${'menu_buttons.frigate'}
|
||||
@change=${this._valueChanged}
|
||||
></ha-switch>
|
||||
</ha-formfield>
|
||||
<br />
|
||||
<ha-formfield
|
||||
.label=${localize('editor.show_button') + ': ' + localize('menu.live')}
|
||||
>
|
||||
<ha-switch
|
||||
.checked=${this._config?.menu_buttons?.live ?? true}
|
||||
.configValue=${'menu_buttons.live'}
|
||||
@change=${this._valueChanged}
|
||||
></ha-switch>
|
||||
</ha-formfield>
|
||||
<br />
|
||||
<ha-formfield
|
||||
.label=${localize('editor.show_button') + ': ' + localize('menu.clips')}
|
||||
>
|
||||
<ha-switch
|
||||
.checked=${this._config?.menu_buttons?.clips ?? true}
|
||||
.configValue=${'menu_buttons.clips'}
|
||||
.checked=${this._config?.menu?.buttons?.frigate ??
|
||||
defaults.menu.buttons.frigate}
|
||||
.configValue=${'menu.buttons.frigate'}
|
||||
@change=${this._valueChanged}
|
||||
></ha-switch>
|
||||
</ha-formfield>
|
||||
@@ -390,11 +387,12 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
|
||||
<ha-formfield
|
||||
.label=${localize('editor.show_button') +
|
||||
': ' +
|
||||
localize('menu.snapshots')}
|
||||
localize('config.view.views.live')}
|
||||
>
|
||||
<ha-switch
|
||||
.checked=${this._config?.menu_buttons?.snapshots ?? true}
|
||||
.configValue=${'menu_buttons.snapshots'}
|
||||
.checked=${this._config?.menu?.buttons?.live ??
|
||||
defaults.menu.buttons.live}
|
||||
.configValue=${'menu.buttons.live'}
|
||||
@change=${this._valueChanged}
|
||||
></ha-switch>
|
||||
</ha-formfield>
|
||||
@@ -402,11 +400,12 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
|
||||
<ha-formfield
|
||||
.label=${localize('editor.show_button') +
|
||||
': ' +
|
||||
localize('menu.image')}
|
||||
localize('config.view.views.clips')}
|
||||
>
|
||||
<ha-switch
|
||||
.checked=${this._config?.menu_buttons?.image ?? false}
|
||||
.configValue=${'menu_buttons.image'}
|
||||
.checked=${this._config?.menu?.buttons?.clips ??
|
||||
defaults.menu.buttons.clips}
|
||||
.configValue=${'menu.buttons.clips'}
|
||||
@change=${this._valueChanged}
|
||||
></ha-switch>
|
||||
</ha-formfield>
|
||||
@@ -414,11 +413,12 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
|
||||
<ha-formfield
|
||||
.label=${localize('editor.show_button') +
|
||||
': ' +
|
||||
localize('menu.download')}
|
||||
localize('config.view.views.snapshots')}
|
||||
>
|
||||
<ha-switch
|
||||
.checked=${this._config?.menu_buttons?.download ?? true}
|
||||
.configValue=${'menu_buttons.download'}
|
||||
.checked=${this._config?.menu?.buttons?.snapshots ??
|
||||
defaults.menu.buttons.snapshots}
|
||||
.configValue=${'menu.buttons.snapshots'}
|
||||
@change=${this._valueChanged}
|
||||
></ha-switch>
|
||||
</ha-formfield>
|
||||
@@ -426,11 +426,12 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
|
||||
<ha-formfield
|
||||
.label=${localize('editor.show_button') +
|
||||
': ' +
|
||||
localize('menu.frigate_ui')}
|
||||
localize('config.view.views.image')}
|
||||
>
|
||||
<ha-switch
|
||||
.checked=${this._config?.menu_buttons?.frigate_ui ?? true}
|
||||
.configValue=${'menu_buttons.frigate_ui'}
|
||||
.checked=${this._config?.menu?.buttons?.image ??
|
||||
defaults.menu.buttons.image}
|
||||
.configValue=${'menu.buttons.image'}
|
||||
@change=${this._valueChanged}
|
||||
></ha-switch>
|
||||
</ha-formfield>
|
||||
@@ -438,11 +439,38 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
|
||||
<ha-formfield
|
||||
.label=${localize('editor.show_button') +
|
||||
': ' +
|
||||
localize('menu.fullscreen')}
|
||||
localize('config.menu.buttons.download')}
|
||||
>
|
||||
<ha-switch
|
||||
.checked=${this._config?.menu_buttons?.frigate_ui ?? true}
|
||||
.configValue=${'menu_buttons.fullscreen'}
|
||||
.checked=${this._config?.menu?.buttons?.download ??
|
||||
defaults.menu.buttons.download}
|
||||
.configValue=${'menu.buttons.download'}
|
||||
@change=${this._valueChanged}
|
||||
></ha-switch>
|
||||
</ha-formfield>
|
||||
<br />
|
||||
<ha-formfield
|
||||
.label=${localize('editor.show_button') +
|
||||
': ' +
|
||||
localize('config.menu.buttons.frigate_ui')}
|
||||
>
|
||||
<ha-switch
|
||||
.checked=${this._config?.menu?.buttons?.frigate_ui ??
|
||||
defaults.menu.buttons.frigate_ui}
|
||||
.configValue=${'menu.buttons.frigate_ui'}
|
||||
@change=${this._valueChanged}
|
||||
></ha-switch>
|
||||
</ha-formfield>
|
||||
<br />
|
||||
<ha-formfield
|
||||
.label=${localize('editor.show_button') +
|
||||
': ' +
|
||||
localize('config.menu.buttons.fullscreen')}
|
||||
>
|
||||
<ha-switch
|
||||
.checked=${this._config?.menu?.buttons?.fullscreen ??
|
||||
defaults.menu.buttons.fullscreen}
|
||||
.configValue=${'menu.buttons.fullscreen'}
|
||||
@change=${this._valueChanged}
|
||||
></ha-switch>
|
||||
</ha-formfield>
|
||||
@@ -458,17 +486,17 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
|
||||
${options.advanced.show
|
||||
? html` <div class="values">
|
||||
<paper-input
|
||||
.label=${localize('editor.zone')}
|
||||
.value=${this._config?.zone || ''}
|
||||
.configValue=${'zone'}
|
||||
.label=${localize('config.frigate.label')}
|
||||
.value=${this._config?.frigate?.label || ''}
|
||||
.configValue=${'frigate.label'}
|
||||
@value-changed=${this._valueChanged}
|
||||
></paper-input>
|
||||
</div>
|
||||
<div class="values">
|
||||
<paper-input
|
||||
.label=${localize('editor.label')}
|
||||
.value=${this._config?.label || ''}
|
||||
.configValue=${'label'}
|
||||
.label=${localize('config.frigate.zone')}
|
||||
.value=${this._config?.frigate?.zone || ''}
|
||||
.configValue=${'frigate.zone'}
|
||||
@value-changed=${this._valueChanged}
|
||||
></paper-input>
|
||||
</div>`
|
||||
@@ -483,9 +511,9 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
|
||||
${options.webrtc.show
|
||||
? html` <div class="values">
|
||||
<paper-dropdown-menu
|
||||
.label=${localize('webrtc.entity')}
|
||||
.label=${localize('config.live.webrtc.entity')}
|
||||
@value-changed=${this._valueChanged}
|
||||
.configValue=${'webrtc.entity'}
|
||||
.configValue=${'live.webrtc.entity'}
|
||||
>
|
||||
<paper-listbox
|
||||
slot="dropdown-content"
|
||||
@@ -497,10 +525,10 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
|
||||
</paper-listbox>
|
||||
</paper-dropdown-menu>
|
||||
<paper-input
|
||||
label=${localize('webrtc.url')}
|
||||
.value=${this._config?.webrtc?.url || ''}
|
||||
.configValue=${'webrtc.url'}
|
||||
@value-changed=${this._valueChanged}
|
||||
label=${localize('config.live.webrtc.url')}
|
||||
.value=${this._config?.live?.webrtc?.url || ''}
|
||||
.configValue=${'live.webrtc.url'}
|
||||
@value-changed=${this._valueChanged}
|
||||
></paper-input>
|
||||
</div>`
|
||||
: ''}
|
||||
@@ -549,13 +577,14 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
|
||||
let objectTarget = newConfig;
|
||||
|
||||
if (key.includes('.')) {
|
||||
const parts = key.split('.', 2);
|
||||
const configName = parts[0];
|
||||
if (!(configName in newConfig)) {
|
||||
newConfig[configName] = {};
|
||||
}
|
||||
objectTarget = newConfig[configName];
|
||||
key = parts[1];
|
||||
const parts = key.split('.');
|
||||
objectTarget = parts.slice(0, -1).reduce((obj, key) => {
|
||||
if (!(key in obj)) {
|
||||
obj[key] = {};
|
||||
}
|
||||
return obj[key];
|
||||
}, newConfig);
|
||||
key = parts[parts.length-1];
|
||||
}
|
||||
|
||||
if (value !== undefined && objectTarget[key] === value) {
|
||||
|
||||
@@ -1,15 +1,103 @@
|
||||
{
|
||||
"common": {
|
||||
"version": "Version",
|
||||
"show_warning": "Show Warning",
|
||||
"show_error": "Show Error",
|
||||
"frigate_card": "Frigate Card",
|
||||
"show_warning": "Show warning",
|
||||
"show_error": "Show error",
|
||||
"frigate_card": "Frigate card",
|
||||
"frigate_card_description": "A Lovelace card for use with Frigate",
|
||||
"no_snapshots": "No snapshots",
|
||||
"no_clips": "No clips",
|
||||
"no_snapshot": "No recent snapshot",
|
||||
"no_clip": "No recent clip"
|
||||
},
|
||||
"config": {
|
||||
"camera_entity": "Camera Entity",
|
||||
"frigate": {
|
||||
"camera_name": "Frigate camera name (Optional, autodetected from entity)",
|
||||
"client_id": "Frigate client id (For >1 Frigate server)",
|
||||
"label": "Label/object filter",
|
||||
"url": "Frigate server URL",
|
||||
"zone": "Zone"
|
||||
},
|
||||
"view": {
|
||||
"default": "Default view",
|
||||
"views": {
|
||||
"live": "Live view",
|
||||
"clip": "Most recent clip",
|
||||
"clips": "Clips gallery",
|
||||
"snapshot": "Most recent snapshot",
|
||||
"snapshots": "Snapshots gallery",
|
||||
"image": "Static image"
|
||||
},
|
||||
"timeout": "View timeout (seconds)"
|
||||
},
|
||||
"event_viewer": {
|
||||
"autoplay_clip": "Autoplay most recent clip",
|
||||
"lazy_load": "Lazily load event media",
|
||||
"controls": {
|
||||
"next_previous": {
|
||||
"style": "Event Viewer Next & Previous Control style",
|
||||
"size": "Event Viewer Next & Previous Control size",
|
||||
"styles": {
|
||||
"thumbnails": "Thumbnails",
|
||||
"chevrons": "Chevrons",
|
||||
"none": "None"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"live": {
|
||||
"preload": "Preload live view",
|
||||
"provider": "Live view provider",
|
||||
"providers": {
|
||||
"frigate": "Frigate",
|
||||
"frigate-jsmpeg": "Frigate JSMpeg",
|
||||
"webrtc": "WebRTC"
|
||||
},
|
||||
"webrtc": {
|
||||
"entity": "WebRTC Camera Entity (Not a Frigate camera)",
|
||||
"url": "WebRTC Camera URL"
|
||||
}
|
||||
},
|
||||
"image": {
|
||||
"src": "Static image URL/data for image view"
|
||||
},
|
||||
"menu": {
|
||||
"buttons": {
|
||||
"frigate": "Frigate menu / Default view",
|
||||
"frigate_ui": "Frigate user Interface",
|
||||
"fullscreen": "Fullscreen",
|
||||
"download": "Download event media"
|
||||
},
|
||||
"mode": "Menu mode",
|
||||
"modes": {
|
||||
"none": "No menu",
|
||||
"hidden-top": "Hidden top",
|
||||
"hidden-left": "Hidden left",
|
||||
"hidden-bottom": "Hidden bottom",
|
||||
"hidden-right": "Hidden right",
|
||||
"overlay-top": "Overlay top",
|
||||
"overlay-left": "Overlay left",
|
||||
"overlay-bottom": "Overlay bottom",
|
||||
"overlay-right": "Overlay right",
|
||||
"hover-top": "Hover top",
|
||||
"hover-left": "Hover left",
|
||||
"hover-bottom": "Hover bottom",
|
||||
"hover-right": "Hover right",
|
||||
"above": "Above",
|
||||
"below": "Below"
|
||||
}
|
||||
},
|
||||
"dimensions": {
|
||||
"aspect_ratio": "Default aspect ratio",
|
||||
"aspect_ratio_mode": "Aspect ratio mode",
|
||||
"aspect_ratio_modes": {
|
||||
"unconstrained": "Unconstrained aspect ratio",
|
||||
"dynamic": "Aspect ratio adjusts to media",
|
||||
"static": "Static aspect ratio"
|
||||
}
|
||||
}
|
||||
},
|
||||
"editor": {
|
||||
"basic": "Basic",
|
||||
"basic_secondary": "Basic options for most users",
|
||||
@@ -21,74 +109,7 @@
|
||||
"webrtc_secondary": "Optional WebRTC parameters",
|
||||
"advanced": "Advanced",
|
||||
"advanced_secondary": "Advanced options",
|
||||
"camera_entity": "Camera Entity (Optional)",
|
||||
"frigate_camera_name": "Frigate camera name (Optional, autodetected from entity)",
|
||||
"default_view": "Default view (Optional)",
|
||||
"frigate_client_id": "Frigate client id (Optional, for >1 Frigate server)",
|
||||
"view_timeout": "View timeout (seconds)",
|
||||
"frigate_url": "Frigate URL (Optional, for Frigate UI button)",
|
||||
"autoplay_clip": "Autoplay latest clip",
|
||||
"menu_mode": "Menu mode (Optional)",
|
||||
"show_button": "Show button",
|
||||
"zone": "Zone",
|
||||
"label": "Frigate label/object filter (Optional)",
|
||||
"live_provider": "Live view provider (Optional)",
|
||||
"live_preload": "Preload live view",
|
||||
"image": "Static image URL for image view (Optional)",
|
||||
"lazy_load": "Lazily load event media"
|
||||
},
|
||||
"menu": {
|
||||
"frigate": "Frigate Menu / Default View",
|
||||
"live": "Live View",
|
||||
"clips": "Clips Gallery",
|
||||
"snapshots": "Snapshots Gallery",
|
||||
"clip": "Latest Clip",
|
||||
"snapshot": "Latest Snapshot",
|
||||
"frigate_ui": "Frigate User Interface",
|
||||
"fullscreen": "Fullscreen",
|
||||
"image": "Static Image",
|
||||
"download": "Download event media"
|
||||
},
|
||||
"control": {
|
||||
"nextprev": "Media Next & Previous Controls",
|
||||
"thumbnails": "Thumbnails",
|
||||
"chevrons": "Chevrons",
|
||||
"none": "None"
|
||||
},
|
||||
"dimensions": {
|
||||
"aspect_ratio_mode": "Aspect Ratio Mode",
|
||||
"aspect_ratio": "Default aspect ratio (Optional)"
|
||||
},
|
||||
"aspect_ratio_mode": {
|
||||
"unconstrained": "Unconstrained aspect ratio",
|
||||
"dynamic": "Aspect ratio adjusts to media",
|
||||
"static": "Static aspect ratio"
|
||||
},
|
||||
"menu_mode": {
|
||||
"none": "No menu",
|
||||
"hidden-top": "Hidden Top",
|
||||
"hidden-left": "Hidden Left",
|
||||
"hidden-bottom": "Hidden Bottom",
|
||||
"hidden-right": "Hidden Right",
|
||||
"overlay-top": "Overlay Top",
|
||||
"overlay-left": "Overlay Left",
|
||||
"overlay-bottom": "Overlay Bottom",
|
||||
"overlay-right": "Overlay Right",
|
||||
"hover-top": "Hover Top",
|
||||
"hover-left": "Hover Left",
|
||||
"hover-bottom": "Hover Bottom",
|
||||
"hover-right": "Hover Right",
|
||||
"above": "Above",
|
||||
"below": "Below"
|
||||
},
|
||||
"live_provider": {
|
||||
"frigate": "Frigate",
|
||||
"frigate-jsmpeg": "Frigate JSMpeg",
|
||||
"webrtc": "WebRTC"
|
||||
},
|
||||
"webrtc": {
|
||||
"entity": "Optional WebRTC Camera Entity (Not a Frigate camera)",
|
||||
"url": "Optional WebRTC Camera URL"
|
||||
"show_button": "Show button"
|
||||
},
|
||||
"error": {
|
||||
"empty_response": "Received empty response from Home Assistant for request",
|
||||
@@ -101,7 +122,7 @@
|
||||
"invalid_configuration": "Invalid configuration",
|
||||
"invalid_configuration_no_hint": "No location hint available (bad or missing type?)",
|
||||
"missing_webrtc": "WebRTC component not found",
|
||||
"no_frigate_camera_name": "Cannot autodetect Frigate camera name, you need to either set camera_entity and / or frigate_camera_name",
|
||||
"no_frigate_camera_name": "Cannot autodetect Frigate camera name, you need to either set camera_entity and / or frigate.camera_name",
|
||||
"could_not_render_elements": "Could not render picture elements",
|
||||
"invalid_elements_config": "Invalid picture elements configuration",
|
||||
"jsmpeg_no_sign": "Could not retrieve or sign JSMPEG websocket path",
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
:host {
|
||||
--video-max-height: none;
|
||||
}
|
||||
|
||||
ha-hls-player {
|
||||
transform-style: preserve-3d;
|
||||
}
|
||||
|
||||
img,video {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
+203
-83
@@ -38,7 +38,7 @@ export type FrigateCardView =
|
||||
| typeof FRIGATE_CARD_VIEWS_USER_SPECIFIED[number]
|
||||
| typeof FRIGATE_CARD_VIEWS_INTERNAL[number];
|
||||
|
||||
export const FRIGATE_MENU_MODES = [
|
||||
const FRIGATE_MENU_MODES = [
|
||||
'none',
|
||||
'hidden-top',
|
||||
'hidden-left',
|
||||
@@ -55,13 +55,8 @@ export const FRIGATE_MENU_MODES = [
|
||||
'above',
|
||||
'below',
|
||||
] as const;
|
||||
export type FrigateMenuMode = typeof FRIGATE_MENU_MODES[number];
|
||||
|
||||
export const NEXT_PREVIOUS_CONTROL_STYLES = ['none', 'thumbnails', 'chevrons'] as const;
|
||||
export type NextPreviousControlStyle = typeof NEXT_PREVIOUS_CONTROL_STYLES[number];
|
||||
|
||||
export const LIVE_PROVIDERS = ['frigate', 'frigate-jsmpeg', 'webrtc'] as const;
|
||||
export type LiveProvider = typeof LIVE_PROVIDERS[number];
|
||||
|
||||
/**
|
||||
* Action Types (for "Picture Elements" / Menu)
|
||||
@@ -122,7 +117,7 @@ const elementsBaseSchema = z.object({
|
||||
});
|
||||
|
||||
/**
|
||||
* Picture Element Types
|
||||
* Picture Element Configuration.
|
||||
*
|
||||
* All picture element types are validated (not just the Frigate card custom
|
||||
* ones) as a convenience to present the user with a consistent error display
|
||||
@@ -214,7 +209,7 @@ const customSchema = z
|
||||
if (!val.match(/^custom:(?!frigate-card).+/)) {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
message: "Frigate-card custom elements must match specific schemas",
|
||||
message: 'Frigate-card custom elements must match specific schemas',
|
||||
fatal: true,
|
||||
});
|
||||
}
|
||||
@@ -223,7 +218,7 @@ const customSchema = z
|
||||
.passthrough();
|
||||
|
||||
/**
|
||||
* Custom Element Types
|
||||
* Custom Element Types.
|
||||
*/
|
||||
|
||||
export const menuIconSchema = iconSchema.merge(
|
||||
@@ -269,82 +264,199 @@ export type PictureElement = z.infer<typeof pictureElementSchema>;
|
||||
const pictureElementsSchema = pictureElementSchema.array().optional();
|
||||
export type PictureElements = z.infer<typeof pictureElementsSchema>;
|
||||
|
||||
/**
|
||||
* Frigate configuration section.
|
||||
*/
|
||||
const frigateConfigDefault = {
|
||||
client_id: 'frigate' as const,
|
||||
};
|
||||
const frigateConfigDefaultSchema = z
|
||||
.object({
|
||||
// No URL validation to allow relative URLs within HA (e.g. addons).
|
||||
url: z.string().optional(),
|
||||
client_id: z.string().optional().default(frigateConfigDefault.client_id),
|
||||
camera_name: z.string().optional(),
|
||||
label: z.string().optional(),
|
||||
zone: z.string().optional(),
|
||||
})
|
||||
.default(frigateConfigDefault);
|
||||
|
||||
/**
|
||||
* View configuration section.
|
||||
*/
|
||||
const viewConfigDefault = {
|
||||
default: 'live' as const,
|
||||
timeout: 180,
|
||||
};
|
||||
const viewConfigSchema = z
|
||||
.object({
|
||||
default: z
|
||||
.enum(FRIGATE_CARD_VIEWS_USER_SPECIFIED)
|
||||
.optional()
|
||||
.default(viewConfigDefault.default),
|
||||
timeout: z
|
||||
.number()
|
||||
.or(
|
||||
z
|
||||
.string()
|
||||
.regex(/^\d+$/)
|
||||
.transform((val) => Number(val)),
|
||||
)
|
||||
.optional()
|
||||
.default(viewConfigDefault.timeout),
|
||||
})
|
||||
.default(viewConfigDefault);
|
||||
|
||||
/**
|
||||
* Image view configuration section.
|
||||
*/
|
||||
const imageConfigSchema = z
|
||||
.object({
|
||||
src: z.string().optional(),
|
||||
})
|
||||
.optional();
|
||||
export type ImageViewConfig = z.infer<typeof imageConfigSchema>;
|
||||
|
||||
/**
|
||||
* Live view configuration section.
|
||||
*/
|
||||
const liveConfigDefault = {
|
||||
provider: 'frigate' as const,
|
||||
preload: false,
|
||||
};
|
||||
const webrtcConfigSchema = z
|
||||
.object({
|
||||
entity: z.string().optional(),
|
||||
url: z.string().optional(),
|
||||
})
|
||||
.passthrough()
|
||||
.optional();
|
||||
export type WebRTCConfig = z.infer<typeof webrtcConfigSchema>;
|
||||
|
||||
const liveConfigSchema = z
|
||||
.object({
|
||||
provider: z.enum(LIVE_PROVIDERS).default(liveConfigDefault.provider),
|
||||
preload: z.boolean().default(liveConfigDefault.preload),
|
||||
webrtc: webrtcConfigSchema,
|
||||
})
|
||||
.default(liveConfigDefault);
|
||||
|
||||
/**
|
||||
* Menu configuration section.
|
||||
*/
|
||||
const menuConfigDefault = {
|
||||
mode: 'hidden-top' as const,
|
||||
buttons: {
|
||||
frigate: true,
|
||||
live: true,
|
||||
clips: true,
|
||||
snapshots: true,
|
||||
image: false,
|
||||
download: true,
|
||||
frigate_ui: true,
|
||||
fullscreen: true,
|
||||
},
|
||||
button_size: '40px',
|
||||
};
|
||||
const menuConfigSchema = z
|
||||
.object({
|
||||
mode: z.enum(FRIGATE_MENU_MODES).optional().default(menuConfigDefault.mode),
|
||||
buttons: z
|
||||
.object({
|
||||
frigate: z.boolean().default(menuConfigDefault.buttons.frigate),
|
||||
live: z.boolean().default(menuConfigDefault.buttons.live),
|
||||
clips: z.boolean().default(menuConfigDefault.buttons.clips),
|
||||
snapshots: z.boolean().default(menuConfigDefault.buttons.snapshots),
|
||||
image: z.boolean().default(menuConfigDefault.buttons.image),
|
||||
download: z.boolean().default(menuConfigDefault.buttons.download),
|
||||
frigate_ui: z.boolean().default(menuConfigDefault.buttons.frigate_ui),
|
||||
fullscreen: z.boolean().default(menuConfigDefault.buttons.fullscreen),
|
||||
})
|
||||
.default(menuConfigDefault.buttons),
|
||||
button_size: z.string().default(menuConfigDefault.button_size),
|
||||
})
|
||||
.default(menuConfigDefault);
|
||||
export type MenuConfig = z.infer<typeof menuConfigSchema>;
|
||||
|
||||
/**
|
||||
* Event viewer configuration section (clip, snapshot, clip-specific, snapshot-specific).
|
||||
*/
|
||||
const viewerConfigDefault = {
|
||||
autoplay_clip: false,
|
||||
lazy_load: true,
|
||||
controls: {
|
||||
next_previous: {
|
||||
size: '48px',
|
||||
style: 'thumbnails' as const,
|
||||
},
|
||||
},
|
||||
};
|
||||
const nextPreviousControlConfigSchema = z
|
||||
.object({
|
||||
style: z
|
||||
.enum(NEXT_PREVIOUS_CONTROL_STYLES)
|
||||
.default(viewerConfigDefault.controls.next_previous.style),
|
||||
size: z.string().default(viewerConfigDefault.controls.next_previous.size),
|
||||
})
|
||||
.default(viewerConfigDefault.controls.next_previous);
|
||||
export type NextPreviousControlConfig = z.infer<typeof nextPreviousControlConfigSchema>;
|
||||
|
||||
const viewerConfigSchema = z
|
||||
.object({
|
||||
autoplay_clip: z.boolean().default(viewerConfigDefault.autoplay_clip),
|
||||
lazy_load: z.boolean().default(viewerConfigDefault.lazy_load),
|
||||
controls: z
|
||||
.object({
|
||||
next_previous: nextPreviousControlConfigSchema,
|
||||
})
|
||||
.default(viewerConfigDefault.controls),
|
||||
})
|
||||
.default(viewerConfigDefault);
|
||||
export type ViewerConfig = z.infer<typeof viewerConfigSchema>;
|
||||
|
||||
/**
|
||||
* Dimensions configuration section.
|
||||
*/
|
||||
const dimensionsConfigDefault = {
|
||||
aspect_ratio_mode: 'dynamic' as const,
|
||||
aspect_ratio: [16, 9],
|
||||
};
|
||||
const dimensionsConfigSchema = z
|
||||
.object({
|
||||
aspect_ratio_mode: z
|
||||
.enum(['dynamic', 'static', 'unconstrained'])
|
||||
.default(dimensionsConfigDefault.aspect_ratio_mode),
|
||||
aspect_ratio: z
|
||||
.number()
|
||||
.array()
|
||||
.length(2)
|
||||
.or(
|
||||
z
|
||||
.string()
|
||||
.regex(/^\s*\d+\s*[:\/]\s*\d+\s*$/)
|
||||
.transform((input) => input.split(/[:\/]/).map((d) => Number(d))),
|
||||
)
|
||||
.default(dimensionsConfigDefault.aspect_ratio),
|
||||
})
|
||||
.default(dimensionsConfigDefault);
|
||||
|
||||
/**
|
||||
* Main card config.
|
||||
*/
|
||||
export const frigateCardConfigSchema = z.object({
|
||||
camera_entity: z.string().optional(),
|
||||
|
||||
// No URL validation to allow relative URLs within HA (e.g. addons).
|
||||
frigate_url: z.string().optional(),
|
||||
frigate_client_id: z.string().optional().default('frigate'),
|
||||
frigate_camera_name: z.string().optional(),
|
||||
view_default: z.enum(FRIGATE_CARD_VIEWS_USER_SPECIFIED).optional().default('live'),
|
||||
view_timeout: z
|
||||
.number()
|
||||
.or(
|
||||
z
|
||||
.string()
|
||||
.regex(/^\d+$/)
|
||||
.transform((val) => Number(val)),
|
||||
)
|
||||
.optional()
|
||||
.default(180),
|
||||
live_provider: z.enum(LIVE_PROVIDERS).default('frigate'),
|
||||
live_preload: z.boolean().default(false),
|
||||
image: z.string().optional(),
|
||||
webrtc: z
|
||||
.object({
|
||||
entity: z.string().optional(),
|
||||
url: z.string().optional(),
|
||||
})
|
||||
.passthrough()
|
||||
.optional(),
|
||||
label: z.string().optional(),
|
||||
zone: z.string().optional(),
|
||||
autoplay_clip: z.boolean().default(false),
|
||||
event_viewer: z
|
||||
.object({
|
||||
lazy_load: z.boolean().default(true),
|
||||
})
|
||||
.optional(),
|
||||
menu_mode: z.enum(FRIGATE_MENU_MODES).optional().default('hidden-top'),
|
||||
menu_buttons: z
|
||||
.object({
|
||||
frigate: z.boolean().default(true),
|
||||
live: z.boolean().default(true),
|
||||
clips: z.boolean().default(true),
|
||||
snapshots: z.boolean().default(true),
|
||||
image: z.boolean().default(false),
|
||||
download: z.boolean().default(true),
|
||||
frigate_ui: z.boolean().default(true),
|
||||
fullscreen: z.boolean().default(true),
|
||||
})
|
||||
.optional(),
|
||||
menu_button_size: z.string().default('40px'),
|
||||
update_entities: z.string().array().optional(),
|
||||
frigate: frigateConfigDefaultSchema,
|
||||
view: viewConfigSchema,
|
||||
menu: menuConfigSchema,
|
||||
live: liveConfigSchema,
|
||||
event_viewer: viewerConfigSchema,
|
||||
image: imageConfigSchema,
|
||||
elements: pictureElementsSchema,
|
||||
controls: z
|
||||
.object({
|
||||
nextprev: z.enum(NEXT_PREVIOUS_CONTROL_STYLES).default('thumbnails'),
|
||||
nextprev_size: z.string().default('48px'),
|
||||
})
|
||||
.optional(),
|
||||
dimensions: z
|
||||
.object({
|
||||
aspect_ratio_mode: z
|
||||
.enum(['dynamic', 'static', 'unconstrained'])
|
||||
.default('dynamic'),
|
||||
aspect_ratio: z
|
||||
.number()
|
||||
.array()
|
||||
.length(2)
|
||||
.or(
|
||||
z
|
||||
.string()
|
||||
.regex(/^\s*\d+\s*[:\/]\s*\d+\s*$/)
|
||||
.transform((input) => input.split(/[:\/]/).map((d) => Number(d))),
|
||||
)
|
||||
.default([16, 9]),
|
||||
})
|
||||
.optional(),
|
||||
dimensions: dimensionsConfigSchema,
|
||||
|
||||
// Entities that should trigger a card update.
|
||||
update_entities: z.string().array().optional(),
|
||||
|
||||
// Stock lovelace card config.
|
||||
type: z.string(),
|
||||
@@ -354,6 +466,14 @@ export const frigateCardConfigSchema = z.object({
|
||||
});
|
||||
export type FrigateCardConfig = z.infer<typeof frigateCardConfigSchema>;
|
||||
|
||||
export const frigateCardConfigDefaults = {
|
||||
frigate: frigateConfigDefault,
|
||||
view: viewConfigDefault,
|
||||
menu: menuConfigDefault,
|
||||
live: liveConfigDefault,
|
||||
event_viewer: viewerConfigDefault,
|
||||
};
|
||||
|
||||
// Schema for card (non-user configured) menu icons.
|
||||
const internalMenuIconSchema = z.object({
|
||||
type: z.literal('internal-menu-icon'),
|
||||
@@ -404,8 +524,8 @@ export interface Message {
|
||||
}
|
||||
|
||||
export interface MenuInteraction {
|
||||
interaction: "hold" | "tap" | "double_tap",
|
||||
button: MenuButton,
|
||||
interaction: 'hold' | 'tap' | 'double_tap';
|
||||
button: MenuButton;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user