Fix aspect ratio issue.

This commit is contained in:
Dermot Duffy
2021-10-29 20:22:30 -07:00
parent 89f09aed4b
commit 24f3191c9e
3 changed files with 132 additions and 24 deletions
+2 -6
View File
@@ -63,12 +63,8 @@ export class BrowseMediaUtil {
if (!media || !media.children) { if (!media || !media.children) {
return null; return null;
} }
for (let i = 0; i < media.children.length; i++) { const index = media.children.findIndex((child) => this.isTrueMedia(child));
if (this.isTrueMedia(media.children[i])) { return index >= 0 ? index : null;
return i;
}
}
return null;
} }
// //
+127 -14
View File
@@ -93,7 +93,9 @@ console.info(
documentationURL: REPO_URL, documentationURL: REPO_URL,
}); });
// Main FrigateCard class. /**
* Main FrigateCard class.
*/
@customElement('frigate-card') @customElement('frigate-card')
export class FrigateCard extends LitElement { export class FrigateCard extends LitElement {
@property({ attribute: false }) @property({ attribute: false })
@@ -140,6 +142,9 @@ export class FrigateCard extends LitElement {
// A cache of resolved media URLs/mimetypes for use in the whole card. // A cache of resolved media URLs/mimetypes for use in the whole card.
protected _resolvedMediaCache = new ResolvedMediaCache(); protected _resolvedMediaCache = new ResolvedMediaCache();
/**
* Set the Home Assistant object.
*/
set hass(hass: HomeAssistant & ExtendedHomeAssistant) { set hass(hass: HomeAssistant & ExtendedHomeAssistant) {
this._hass = hass; this._hass = hass;
@@ -156,12 +161,20 @@ export class FrigateCard extends LitElement {
} }
} }
// Get the configuration element. /**
* Get the card editor element.
* @returns A LovelaceCardEditor element.
*/
public static async getConfigElement(): Promise<LovelaceCardEditor> { public static async getConfigElement(): Promise<LovelaceCardEditor> {
return document.createElement('frigate-card-editor'); return document.createElement('frigate-card-editor');
} }
// Get a stub basic config using the first available camera of any kind. /**
* Get a stub basic config using the first available camera of any kind.
* @param _hass The Home Assistant object.
* @param entities The entities available to Home Assistant.
* @returns A valid stub card configuration.
*/
public static getStubConfig( public static getStubConfig(
_hass: HomeAssistant, _hass: HomeAssistant,
entities: string[], entities: string[],
@@ -172,6 +185,10 @@ export class FrigateCard extends LitElement {
} as FrigateCardConfig; } as FrigateCardConfig;
} }
/**
* Get the menu buttons to display.
* @returns An array of menu buttons.
*/
protected _getMenuButtons(): MenuButton[] { protected _getMenuButtons(): MenuButton[] {
const buttons: MenuButton[] = []; const buttons: MenuButton[] = [];
@@ -245,6 +262,10 @@ export class FrigateCard extends LitElement {
return buttons.concat(this._dynamicMenuButtons); return buttons.concat(this._dynamicMenuButtons);
} }
/**
* Add a dynamic (elements) menu button.
* @param button The button to add.
*/
public _addDynamicMenuButton(button: MenuButton): void { public _addDynamicMenuButton(button: MenuButton): void {
if (!this._dynamicMenuButtons.includes(button)) { if (!this._dynamicMenuButtons.includes(button)) {
this._dynamicMenuButtons = [...this._dynamicMenuButtons, button]; this._dynamicMenuButtons = [...this._dynamicMenuButtons, button];
@@ -252,6 +273,10 @@ export class FrigateCard extends LitElement {
this._menu.buttons = this._getMenuButtons(); this._menu.buttons = this._getMenuButtons();
} }
/**
* Remove a dynamic (elements) menu button that was previously added.
* @param target The button to remove.
*/
public _removeDynamicMenuButton(target: MenuButton): void { public _removeDynamicMenuButton(target: MenuButton): void {
this._dynamicMenuButtons = this._dynamicMenuButtons.filter( this._dynamicMenuButtons = this._dynamicMenuButtons.filter(
(button) => button != target, (button) => button != target,
@@ -259,6 +284,10 @@ export class FrigateCard extends LitElement {
this._menu.buttons = this._getMenuButtons(); this._menu.buttons = this._getMenuButtons();
} }
/**
* Get the Frigate camera name through a variety of means.
* @returns The Frigate camera name or null if unavailable.
*/
protected async _getFrigateCameraName(): Promise<string | null> { protected async _getFrigateCameraName(): Promise<string | null> {
// No camera name specified, apply two heuristics in this order: // No camera name specified, apply two heuristics in this order:
// - Get the entity information and pull out the camera name from the unique_id. // - Get the entity information and pull out the camera name from the unique_id.
@@ -304,6 +333,11 @@ export class FrigateCard extends LitElement {
return null; return null;
} }
/**
* Get configuration parse errors.
* @param error The ZodError object from parsing.
* @returns An array of string error paths.
*/
protected _getParseErrorPaths<T>(error: z.ZodError<T>): string[] { protected _getParseErrorPaths<T>(error: z.ZodError<T>): string[] {
/* Zod errors involving unions are complex, as Zod may not be able to tell /* Zod errors involving unions are complex, as Zod may not be able to tell
* where the 'real' error is vs simply a union option not matching. This * where the 'real' error is vs simply a union option not matching. This
@@ -338,8 +372,12 @@ export class FrigateCard extends LitElement {
return contenders; return contenders;
} }
// Convert an array of strings and indices into a more human readable string, /**
// e.g. [a, 1, b, 2] => 'a[1] -> b[2]' * Convert an array of strings and indices into a more human readable string,
* e.g. [a, 1, b, 2] => 'a[1] -> b[2]'
* @param path An array of strings and numbers.
* @returns A single string.
*/
protected _getParseErrorPathString(path: (string | number)[]): string { protected _getParseErrorPathString(path: (string | number)[]): string {
let out = ''; let out = '';
for (let i = 0; i < path.length; i++) { for (let i = 0; i < path.length; i++) {
@@ -355,7 +393,10 @@ export class FrigateCard extends LitElement {
return out; return out;
} }
// Set the object configuration. /**
* Set the card configuration.
* @param inputConfig The card configuration.
*/
public setConfig(inputConfig: FrigateCardConfig): void { public setConfig(inputConfig: FrigateCardConfig): void {
if (!inputConfig) { if (!inputConfig) {
throw new Error(localize('error.invalid_configuration:')); throw new Error(localize('error.invalid_configuration:'));
@@ -401,7 +442,11 @@ export class FrigateCard extends LitElement {
this._changeView(e.detail); this._changeView(e.detail);
} }
// Determine whether the card should be updated. /**
* Determine whether the card should be updated.
* @param changedProps The changed properties if any.
* @returns True if the card should be updated.
*/
protected shouldUpdate(changedProps: PropertyValues): boolean { protected shouldUpdate(changedProps: PropertyValues): boolean {
if (!this.config) { if (!this.config) {
return false; return false;
@@ -426,6 +471,9 @@ export class FrigateCard extends LitElement {
return true; return true;
} }
/**
* Download media being displayed in the viewer.
*/
protected async _downloadViewerMedia(): Promise<void> { protected async _downloadViewerMedia(): Promise<void> {
if (!this._hass || !this._view.isViewerView()) { if (!this._hass || !this._view.isViewerView()) {
// Should not occur. // Should not occur.
@@ -477,6 +525,11 @@ export class FrigateCard extends LitElement {
link.remove(); link.remove();
} }
/**
* Handle a menu button being clicked.
* @param action The action to be called from the clicked button.
* @param button The button that was clicked.
*/
protected _menuActionHandler(action: string, button: MenuButton): void { protected _menuActionHandler(action: string, button: MenuButton): void {
if (button.type != 'internal-menu-icon') { if (button.type != 'internal-menu-icon') {
handleAction(this, this._hass as HomeAssistant, button, action); handleAction(this, this._hass as HomeAssistant, button, action);
@@ -512,7 +565,10 @@ export class FrigateCard extends LitElement {
} }
} }
// Get the Frigate UI url. /**
* Get the Frigate UI URL from context.
* @returns The URL or null if unavailable.
*/
protected _getFrigateURLFromContext(): string | null { protected _getFrigateURLFromContext(): string | null {
if (!this.config.frigate_url) { if (!this.config.frigate_url) {
return null; return null;
@@ -525,7 +581,9 @@ export class FrigateCard extends LitElement {
return `${this.config.frigate_url}/events?camera=${this._frigateCameraName}`; return `${this.config.frigate_url}/events?camera=${this._frigateCameraName}`;
} }
// Record interactions with the card. /**
* Handle interaction with the card.
*/
protected _interactionHandler(): void { protected _interactionHandler(): void {
if (!this.config.view_timeout) { if (!this.config.view_timeout) {
return; return;
@@ -539,6 +597,10 @@ export class FrigateCard extends LitElement {
}, this.config.view_timeout * 1000); }, this.config.view_timeout * 1000);
} }
/**
* Render the card menu.
* @returns A rendered template.
*/
protected _renderMenu(): TemplateResult | void { protected _renderMenu(): TemplateResult | void {
const classes = { const classes = {
'hover-menu': this.config.menu_mode.startsWith('hover-'), 'hover-menu': this.config.menu_mode.startsWith('hover-'),
@@ -588,6 +650,11 @@ export class FrigateCard extends LitElement {
this._mediaPlaying = false; this._mediaPlaying = false;
} }
/**
* Set the message to display and trigger an update.
* @param message The message to display.
* @param skipUpdate If true an update request is skipped.
*/
protected _setMessageAndUpdate(message: Message, skipUpdate?: boolean): void { protected _setMessageAndUpdate(message: Message, skipUpdate?: boolean): void {
// Register the first message, or prioritize errors if there's pre-render competition. // Register the first message, or prioritize errors if there's pre-render competition.
if (!this._message || (message.type == 'error' && this._message.type != 'error')) { if (!this._message || (message.type == 'error' && this._message.type != 'error')) {
@@ -598,10 +665,18 @@ export class FrigateCard extends LitElement {
} }
} }
/**
* Handle a message event to render to the user.
* @param e The message event.
*/
protected _messageHandler(e: CustomEvent<Message>): void { protected _messageHandler(e: CustomEvent<Message>): void {
return this._setMessageAndUpdate(e.detail); return this._setMessageAndUpdate(e.detail);
} }
/**
* Handle a new piece of media being shown.
* @param e Event with MediaShowInfo details for the media.
*/
protected _mediaShowHandler(e: CustomEvent<MediaShowInfo>): void { protected _mediaShowHandler(e: CustomEvent<MediaShowInfo>): void {
const mediaShowInfo = e.detail; const mediaShowInfo = e.detail;
// In Safari, with WebRTC, 0x0 is occasionally returned during loading, // In Safari, with WebRTC, 0x0 is occasionally returned during loading,
@@ -624,18 +699,29 @@ export class FrigateCard extends LitElement {
} }
} }
/**
* Handler called when fullscreen is toggled.
*/
protected _fullScreenHandler(): void { protected _fullScreenHandler(): void {
// Re-render after a change to fullscreen mode to take advantage of // Re-render after a change to fullscreen mode to take advantage of
// the expanded screen real-estate (vs staying in aspect-ratio locked // the expanded screen real-estate (vs staying in aspect-ratio locked
// modes). // modes).
this.requestUpdate(); this.requestUpdate();
} }
/**
* Component connected callback.
*/
connectedCallback(): void { connectedCallback(): void {
super.connectedCallback(); super.connectedCallback();
if (screenfull.isEnabled) { if (screenfull.isEnabled) {
screenfull.on('change', this._fullScreenHandler.bind(this)); screenfull.on('change', this._fullScreenHandler.bind(this));
} }
} }
/**
* Component disconnected callback.
*/
disconnectedCallback(): void { disconnectedCallback(): void {
if (screenfull.isEnabled) { if (screenfull.isEnabled) {
screenfull.off('change', this._fullScreenHandler.bind(this)); screenfull.off('change', this._fullScreenHandler.bind(this));
@@ -643,6 +729,10 @@ export class FrigateCard extends LitElement {
super.disconnectedCallback(); super.disconnectedCallback();
} }
/**
* Determine if the aspect ratio should be enforced given the current view and
* context.
*/
protected _isAspectRatioEnforced(): boolean { protected _isAspectRatioEnforced(): boolean {
const aspect_ratio_mode = this.config.dimensions?.aspect_ratio_mode ?? 'dynamic'; const aspect_ratio_mode = this.config.dimensions?.aspect_ratio_mode ?? 'dynamic';
@@ -658,6 +748,11 @@ export class FrigateCard extends LitElement {
); );
} }
/**
* Get the aspect ratio padding required to enforce the aspect ratio (if it is
* required).
* @returns A padding percentage.
*/
protected _getAspectRatioPadding(): number | null { protected _getAspectRatioPadding(): number | null {
if (!this._isAspectRatioEnforced()) { if (!this._isAspectRatioEnforced()) {
return null; return null;
@@ -676,7 +771,9 @@ export class FrigateCard extends LitElement {
} }
} }
// Render the call (master render method). /**
* Master render method for the card.
*/
protected render(): TemplateResult | void { protected render(): TemplateResult | void {
if (this.config.show_warning) { if (this.config.show_warning) {
return this._showWarning(localize('common.show_warning')); return this._showWarning(localize('common.show_warning'));
@@ -745,6 +842,9 @@ export class FrigateCard extends LitElement {
</ha-card>`; </ha-card>`;
} }
/**
* Sub-render method for the card.
*/
protected _render(): TemplateResult | void { protected _render(): TemplateResult | void {
if (!this._hass) { if (!this._hass) {
return html``; return html``;
@@ -863,12 +963,20 @@ export class FrigateCard extends LitElement {
`; `;
} }
// Show a warning card. /**
* Show a warning card.
* @param warning The warning message.
* @returns A rendered template.
*/
private _showWarning(warning: string): TemplateResult { private _showWarning(warning: string): TemplateResult {
return html` <hui-warning> ${warning} </hui-warning> `; return html` <hui-warning> ${warning} </hui-warning> `;
} }
// Show an error card. /**
* Show an error card.
* @param error The error message.
* @returns A rendered template.
*/
private _showError(error: string): TemplateResult { private _showError(error: string): TemplateResult {
const errorCard = document.createElement('hui-error-card'); const errorCard = document.createElement('hui-error-card');
errorCard.setConfig({ errorCard.setConfig({
@@ -880,12 +988,17 @@ export class FrigateCard extends LitElement {
return html` ${errorCard} `; return html` ${errorCard} `;
} }
// Return compiled CSS styles (thus safe to use with unsafeCSS). /**
* Return compiled CSS styles (thus safe to use with unsafeCSS).
*/
static get styles(): CSSResultGroup { static get styles(): CSSResultGroup {
return unsafeCSS(cardStyle); return unsafeCSS(cardStyle);
} }
// Get the Lovelace card size. /**
* Get the Lovelace card size.
* @returns The Lovelace card size in units of 50px.
*/
public getCardSize(): number { public getCardSize(): number {
if (this._mediaShowInfo) { if (this._mediaShowInfo) {
return this._mediaShowInfo.height / 50; return this._mediaShowInfo.height / 50;
+3 -4
View File
@@ -39,8 +39,7 @@ import './next-prev-control.js';
import viewerStyle from '../scss/viewer.scss'; import viewerStyle from '../scss/viewer.scss';
const IMG_TRANSPARENT_1x1 = const IMG_EMPTY = 'data:,';
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=';
@customElement('frigate-card-viewer') @customElement('frigate-card-viewer')
export class FrigateCardViewer extends LitElement { export class FrigateCardViewer extends LitElement {
@@ -549,10 +548,10 @@ export class FrigateCardViewerCore extends LitElement {
slideIndex: number, slideIndex: number,
event: CustomEvent<MediaShowInfo>, event: CustomEvent<MediaShowInfo>,
): void { ): void {
this._mediaShowInfoHandler(slideIndex, event.detail);
// Don't allow the inbound event to propagate upwards, that will be // Don't allow the inbound event to propagate upwards, that will be
// automatically done at the appropriate time as the slide is shown. // automatically done at the appropriate time as the slide is shown.
event.stopPropagation(); event.stopPropagation();
this._mediaShowInfoHandler(slideIndex, event.detail);
} }
/** /**
@@ -632,7 +631,7 @@ export class FrigateCardViewerCore extends LitElement {
/> />
</video>` </video>`
: html`<img : html`<img
src=${ifDefined(this.lazyLoad ? IMG_TRANSPARENT_1x1 : resolvedMedia.url)} src=${ifDefined(this.lazyLoad ? IMG_EMPTY : resolvedMedia.url)}
data-src=${ifDefined(this.lazyLoad ? resolvedMedia.url : undefined)} data-src=${ifDefined(this.lazyLoad ? resolvedMedia.url : undefined)}
title="${mediaToRender.title}" title="${mediaToRender.title}"
@click=${() => { @click=${() => {