Add support for pre-defined zoom/pan settings.

This commit is contained in:
Dermot Duffy
2024-04-27 14:55:38 -07:00
parent 246dd7ff54
commit 7917e5c537
59 changed files with 1981 additions and 638 deletions
+21 -1
View File
@@ -9,10 +9,13 @@ import {
unsafeCSS,
} from 'lit';
import { customElement, property } from 'lit/decorators.js';
import { guard } from 'lit/directives/guard.js';
import { live } from 'lit/directives/live.js';
import { createRef, ref, Ref } from 'lit/directives/ref.js';
import isEqual from 'lodash-es/isEqual';
import { CachedValueController } from '../components-lib/cached-value-controller.js';
import { ZoomDefault } from '../components-lib/zoom/types.js';
import { handleZoomDefaultEvent } from '../components-lib/zoom/zoom-view-context.js';
import { CameraConfig, ImageViewConfig } from '../config/types.js';
import defaultImage from '../images/frigate-bird-in-sky.jpg';
import { localize } from '../localize/localize.js';
@@ -277,8 +280,25 @@ export class FrigateCardImage extends LitElement implements FrigateCardMediaPlay
}
protected _useZoomIfRequired(template: TemplateResult): TemplateResult {
const targetID = this.cameraConfig?.id;
const zoomConfig = targetID ? this.view?.context?.zoom?.[targetID]?.zoom : undefined;
return this.imageConfig?.zoomable
? html` <frigate-card-zoomer> ${template} </frigate-card-zoomer>`
? html` <frigate-card-zoomer
.defaultConfig=${guard([this.cameraConfig?.dimensions?.layout], () =>
this.cameraConfig?.dimensions?.layout
? {
pan: this.cameraConfig.dimensions.layout.pan,
zoom: this.cameraConfig.dimensions.layout.zoom,
}
: undefined,
)}
.config=${zoomConfig}
@frigate-card:zoom:default=${(ev: CustomEvent<ZoomDefault>) =>
handleZoomDefaultEvent(this, ev, targetID)}
>
${template}
</frigate-card-zoomer>`
: template;
}
+2 -12
View File
@@ -3,8 +3,7 @@ import { CSSResultGroup, html, LitElement, TemplateResult, unsafeCSS } from 'lit
import { customElement, property } from 'lit/decorators.js';
import { createRef, ref, Ref } from 'lit/directives/ref.js';
import { CameraConfig } from '../../config/types';
import { localize } from '../../localize/localize';
import liveImageStyle from '../../scss/live-image.scss';
import basicBlockStyle from '../../scss/basic-block.scss';
import { FrigateCardMediaPlayer } from '../../types.js';
import { getStateObjOrDispatchError } from '../../utils/get-state-obj';
import '../image.js';
@@ -17,9 +16,6 @@ export class FrigateCardLiveImage extends LitElement implements FrigateCardMedia
@property({ attribute: false })
public cameraConfig?: CameraConfig;
@property({ attribute: true })
public watermark?: string;
protected _refImage: Ref<Element & FrigateCardMediaPlayer> = createRef();
public async play(): Promise<void> {
@@ -80,17 +76,11 @@ export class FrigateCardLiveImage extends LitElement implements FrigateCardMedia
.cameraConfig=${this.cameraConfig}
>
</frigate-card-image>
${this.watermark
? html`<ha-icon
title=${localize('error.awaiting_live')}
icon="${this.watermark}"
></ha-icon>`
: ''}
`;
}
static get styles(): CSSResultGroup {
return unsafeCSS(liveImageStyle);
return unsafeCSS(basicBlockStyle);
}
}
+25 -8
View File
@@ -21,6 +21,8 @@ import {
import { ReadonlyMicrophoneManager } from '../../card-controller/microphone-manager.js';
import { LiveController } from '../../components-lib/live/live-controller.js';
import { MediaGridSelected } from '../../components-lib/media-grid-controller.js';
import { ZoomConfig, ZoomDefault } from '../../components-lib/zoom/types.js';
import { handleZoomDefaultEvent } from '../../components-lib/zoom/zoom-view-context.js';
import {
CameraConfig,
CardWideConfig,
@@ -501,6 +503,9 @@ export class FrigateCardLiveCarousel extends LitElement {
.liveConfig=${liveConfig}
.hass=${this.hass}
.cardWideConfig=${this.cardWideConfig}
.zoomConfig=${this.view?.context?.zoom?.[cameraID]?.zoom}
@frigate-card:zoom:default=${(ev: CustomEvent<ZoomDefault>) =>
handleZoomDefaultEvent(this, ev, cameraID)}
>
</frigate-card-live-provider>
</div>
@@ -688,6 +693,9 @@ export class FrigateCardLiveProvider
@property({ attribute: false })
public microphoneStream?: MediaStream;
@property({ attribute: false })
public zoomConfig?: ZoomConfig | null;
@state()
protected _isVideoMediaLoaded = false;
@@ -793,9 +801,6 @@ export class FrigateCardLiveProvider
this._isVideoMediaLoaded = false;
}
/**
* Record that video media is being shown.
*/
protected _videoMediaShowHandler(): void {
this._isVideoMediaLoaded = true;
}
@@ -852,6 +857,15 @@ export class FrigateCardLiveProvider
protected _useZoomIfRequired(template: TemplateResult): TemplateResult {
return this.liveConfig?.zoomable
? html` <frigate-card-zoomer
.defaultConfig=${guard([this.cameraConfig?.dimensions?.layout], () =>
this.cameraConfig?.dimensions?.layout
? {
pan: this.cameraConfig.dimensions.layout.pan,
zoom: this.cameraConfig.dimensions.layout.zoom,
}
: undefined,
)}
.config=${this.zoomConfig}
@frigate-card:zoom:zoomed=${() => this.setControls(false)}
@frigate-card:zoom:unzoomed=${() => this.setControls()}
>
@@ -899,15 +913,12 @@ export class FrigateCardLiveProvider
}
}
return this._useZoomIfRequired(html`
return html`${this._useZoomIfRequired(html`
${showImageDuringLoading || provider === 'image'
? html` <frigate-card-live-image
${ref(this._refProvider)}
.hass=${this.hass}
.cameraConfig=${this.cameraConfig}
watermark=${ifDefined(
showImageDuringLoading ? 'mdi:progress-helper' : undefined,
)}
@frigate-card:media:loaded=${(ev: Event) => {
if (provider === 'image') {
// Only count the media has loaded if the required provider is
@@ -968,7 +979,13 @@ export class FrigateCardLiveProvider
>
</frigate-card-live-jsmpeg>`
: html``}
`);
`)}
${showImageDuringLoading && !this._isVideoMediaLoaded
? html`<ha-icon
title=${localize('error.awaiting_live')}
icon="mdi:progress-helper"
></ha-icon>`
: ''} `;
}
static get styles(): CSSResultGroup {
+21 -1
View File
@@ -12,6 +12,8 @@ import { ifDefined } from 'lit/directives/if-defined.js';
import { createRef, Ref, ref } from 'lit/directives/ref.js';
import { CameraManager } from '../camera-manager/manager.js';
import { MediaGridSelected } from '../components-lib/media-grid-controller.js';
import { ZoomDefault } from '../components-lib/zoom/types.js';
import { handleZoomDefaultEvent } from '../components-lib/zoom/zoom-view-context.js';
import {
dispatchMessageEvent,
renderMessage,
@@ -484,7 +486,7 @@ export class FrigateCardViewerCarousel extends LitElement {
stopEventFromActivatingCardWideActions(ev);
}}
></frigate-card-next-previous-control>
${guard(this._media, () => this._getSlides())}
${guard([this._media, this.view], () => this._getSlides())}
<frigate-card-next-previous-control
slot="next"
.hass=${this.hass}
@@ -865,10 +867,28 @@ export class FrigateCardViewerProvider
}
protected _useZoomIfRequired(template: TemplateResult): TemplateResult {
if (!this.media) {
return template;
}
const cameraID = this.media.getCameraID();
const mediaID = this.media.getID() ?? undefined;
const cameraConfig = this.cameraManager?.getStore().getCameraConfig(cameraID);
return this.viewerConfig?.zoomable
? html` <frigate-card-zoomer
.defaultConfig=${guard([cameraConfig?.dimensions?.layout], () =>
cameraConfig?.dimensions?.layout
? {
pan: cameraConfig.dimensions.layout.pan,
zoom: cameraConfig.dimensions.layout.zoom,
}
: undefined,
)}
.config=${mediaID ? this.view?.context?.zoom?.[mediaID]?.zoom : undefined}
@frigate-card:zoom:zoomed=${() => this.setControls(false)}
@frigate-card:zoom:unzoomed=${() => this.setControls()}
@frigate-card:zoom:default=${(ev: CustomEvent<ZoomDefault>) =>
handleZoomDefaultEvent(this, ev, mediaID)}
>
${template}
</frigate-card-zoomer>`
+33 -5
View File
@@ -6,13 +6,20 @@ import {
PropertyValues,
TemplateResult,
} from 'lit';
import { customElement, state } from 'lit/decorators.js';
import { ZoomController } from '../components-lib/zoom-controller.js';
import { customElement, property, state } from 'lit/decorators.js';
import { ZoomConfig } from '../components-lib/zoom/types.js';
import { ZoomController } from '../components-lib/zoom/zoom-controller.js';
import { setOrRemoveAttribute } from '../utils/basic.js';
@customElement('frigate-card-zoomer')
export class FrigateCardZoomer extends LitElement {
protected _zoom = new ZoomController(this);
protected _zoom: ZoomController | null = null;
@property({ attribute: false })
public defaultConfig?: ZoomConfig;
@property({ attribute: false })
public config?: ZoomConfig | null;
@state()
protected _zoomed = false;
@@ -24,11 +31,13 @@ export class FrigateCardZoomer extends LitElement {
super.connectedCallback();
this.addEventListener('frigate-card:zoom:zoomed', this._zoomHandler);
this.addEventListener('frigate-card:zoom:unzoomed', this._unzoomHandler);
this._zoom.activate();
// Call for an update to activate.
this.requestUpdate();
}
disconnectedCallback(): void {
this._zoom.deactivate();
this._zoom?.deactivate();
this.removeEventListener('frigate-card:zoom:zoomed', this._zoomHandler);
this.removeEventListener('frigate-card:zoom:unzoomed', this._unzoomHandler);
}
@@ -37,7 +46,26 @@ export class FrigateCardZoomer extends LitElement {
if (changedProps.has('_zoomed')) {
setOrRemoveAttribute(this, this._zoomed, 'zoomed');
}
if (this._zoom) {
if (changedProps.has('defaultConfig')) {
this._zoom.setDefaultConfig(this.defaultConfig ?? null);
}
// If config is null, make no change to the zoom.
if (changedProps.has('config') && this.config) {
this._zoom.setConfig(this.config);
}
} else {
// Ensure that the configuration will be set before activation (vs
// activating in `connectedCallback`).
this._zoom = new ZoomController(this, {
config: this.config,
defaultConfig: this.defaultConfig,
});
this._zoom.activate();
}
}
protected render(): TemplateResult | void {
return html` <slot></slot> `;
}