From af3b9092fde799b00ba89e775bd53bfce94ea995 Mon Sep 17 00:00:00 2001 From: Dermot Duffy Date: Wed, 22 Dec 2021 16:57:36 -0800 Subject: [PATCH] Add configuration for new live view. --- src/components/live.ts | 48 +++++++++++++-------------- src/components/next-prev-control.ts | 19 ++++++++--- src/components/viewer.ts | 19 +++++++++-- src/scss/next-previous-control.scss | 2 +- src/types.ts | 50 +++++++++++++++++++++++------ 5 files changed, 96 insertions(+), 42 deletions(-) diff --git a/src/components/live.ts b/src/components/live.ts index e735042b..efe1b8c4 100644 --- a/src/components/live.ts +++ b/src/components/live.ts @@ -1,6 +1,3 @@ -// TODO title on hover over camera -// TODO controls -// TODO lazy loading configuration // TODO editor // TODO readme // TODO search for TODOs @@ -214,7 +211,7 @@ export class FrigateCardLiveCarousel extends FrigateCardMediaCarousel { return { startIndex: startIndex < 0 ? undefined : startIndex, - // TODO: draggable: this.viewerConfig.draggable, + draggable: this.liveConfig?.draggable, }; } @@ -227,7 +224,7 @@ export class FrigateCardLiveCarousel extends FrigateCardMediaCarousel { */ protected _getLazyLoadCount(): number | null { // Defaults to fully-lazy loading. - return 0; + return this.liveConfig?.lazy_load === false ? null : 0; } /** @@ -239,11 +236,14 @@ export class FrigateCardLiveCarousel extends FrigateCardMediaCarousel { return []; } return Array.from(this.cameras.values()).map((cameraConfig, index) => { - let refreshedConfig = {...cameraConfig}; + let refreshedConfig = { ...cameraConfig }; if (this.hass) { - refreshedConfig = refreshCameraConfigDynamicParameters(this.hass, refreshedConfig); + refreshedConfig = refreshCameraConfigDynamicParameters( + this.hass, + refreshedConfig, + ); } - return this._renderLive(refreshedConfig, index) + return this._renderLive(refreshedConfig, index); }); } @@ -303,13 +303,13 @@ export class FrigateCardLiveCarousel extends FrigateCardMediaCarousel { if (currentIndex > 0) { prev = this.cameras.get(keys[currentIndex - 1]) ?? null; if (prev) { - prev = refreshCameraConfigDynamicParameters(this.hass, {...prev}); + prev = refreshCameraConfigDynamicParameters(this.hass, { ...prev }); } } if (currentIndex + 1 < this.cameras.size) { next = this.cameras.get(keys[currentIndex + 1]) ?? null; if (next) { - next = refreshCameraConfigDynamicParameters(this.hass, {...next}); + next = refreshCameraConfigDynamicParameters(this.hass, { ...next }); } } return [prev, next]; @@ -318,14 +318,18 @@ export class FrigateCardLiveCarousel extends FrigateCardMediaCarousel { /** * Handle updating of the next/previous controls when the carousel is moved. */ - protected _selectSlideNextPreviousHandler(): void { - const updateNextPreviousControl = (control: FrigateCardNextPreviousControl, direction: 'previous' | 'next'): void => { + protected _selectSlideNextPreviousHandler(): void { + const updateNextPreviousControl = ( + control: FrigateCardNextPreviousControl, + direction: 'previous' | 'next', + ): void => { const [prev, next] = this._getCameraNeighbors(); const target = direction == 'previous' ? prev : next; - - control.disabled = (target == null) - control.title = (target && target.title ? target.title : '') - } + + control.disabled = target == null; + control.title = target && target.title ? target.title : ''; + control.icon = target && target.icon ? target.icon : undefined; + }; if (this._previousControlRef.value) { updateNextPreviousControl(this._previousControlRef.value, 'previous'); @@ -352,11 +356,9 @@ export class FrigateCardLiveCarousel extends FrigateCardMediaCarousel { { this._nextPreviousHandler('previous'); @@ -369,11 +371,9 @@ export class FrigateCardLiveCarousel extends FrigateCardMediaCarousel { { this._nextPreviousHandler('next'); diff --git a/src/components/next-prev-control.ts b/src/components/next-prev-control.ts index f8405111..80be53ca 100644 --- a/src/components/next-prev-control.ts +++ b/src/components/next-prev-control.ts @@ -23,6 +23,9 @@ export class FrigateCardNextPreviousControl extends LitElement { @property({ attribute: false }) public thumbnail?: string; + @property({ attribute: false }) + public icon?: string; + @property({ attribute: true, type: Boolean }) public disabled = false; @@ -36,12 +39,20 @@ export class FrigateCardNextPreviousControl extends LitElement { previous: this.direction == 'previous', next: this.direction == 'next', thumbnails: this._controlConfig.style == 'thumbnails', - chevrons: this._controlConfig.style == 'chevrons', - button: this._controlConfig.style == 'chevrons', + icons: ['chevrons', 'icons'].includes(this._controlConfig.style), + button: ['chevrons', 'icons'].includes(this._controlConfig.style), }; - if (this._controlConfig.style == 'chevrons') { - const icon = this.direction == 'previous' ? 'mdi:chevron-left' : 'mdi:chevron-right'; + if (['chevrons', 'icons'].includes(this._controlConfig.style)) { + let icon: string; + if (this._controlConfig.style === 'chevrons') { + icon = this.direction == 'previous' ? 'mdi:chevron-left' : 'mdi:chevron-right'; + } else { + if (!this.icon) { + return html``; + } + icon = this.icon + } // TODO: Upon a safe distance from the release of HA 2021.11 these // attributes can be removed from the . diff --git a/src/components/viewer.ts b/src/components/viewer.ts index f8c51952..eeaea83b 100644 --- a/src/components/viewer.ts +++ b/src/components/viewer.ts @@ -255,6 +255,18 @@ export class FrigateCardViewerCarousel extends FrigateCardMediaCarousel { }; } + /** + * Returns the number of slides to lazily load. 0 means all slides are lazy + * loaded, 1 means that 1 slide on each side of the currently selected slide + * should lazy load, etc. `null` means lazy loading is disabled and everything + * should load simultaneously. + * @returns + */ + protected _getLazyLoadCount(): number | null { + // Defaults to fully-lazy loading. + return this.viewerConfig?.lazy_load === false ? null : 0; + } + /** * Get the previous and next true media items from the current view. * @returns A BrowseMediaNeighbors with indices and objects of true media @@ -558,7 +570,7 @@ export class FrigateCardViewerCarousel extends FrigateCardMediaCarousel { autoplay = this.viewerConfig.autoplay_clip; } - const lazyLoad = this.viewerConfig.lazy_load; + const lazyLoad = (this._getLazyLoadCount() !== null); return html`
@@ -616,8 +628,9 @@ export class FrigateCardViewerCarousel extends FrigateCardMediaCarousel { @load="${(e: Event) => { if ( this.viewerConfig && - (!this.viewerConfig.lazy_load || - this._slideHasBeenLazyLoaded[slideIndex]) + // This handler will be called on the empty image, only call + // the below when it's the 'real image'. + (!lazyLoad || this._slideHasBeenLazyLoaded[slideIndex]) ) { this._mediaLoadedHandler(slideIndex, createMediaShowInfo(e)); } diff --git a/src/scss/next-previous-control.scss b/src/scss/next-previous-control.scss index b3b87333..c031ffbb 100644 --- a/src/scss/next-previous-control.scss +++ b/src/scss/next-previous-control.scss @@ -21,7 +21,7 @@ right: var(--frigate-card-next-position); } -.controls.chevrons { +.controls.icons { top: calc(50% - (40px / 2)); } diff --git a/src/types.ts b/src/types.ts index 8846ed89..8241319c 100644 --- a/src/types.ts +++ b/src/types.ts @@ -57,8 +57,7 @@ const FRIGATE_MENU_MODES = [ 'above', 'below', ] as const; -export const NEXT_PREVIOUS_CONTROL_STYLES = ['none', 'thumbnails', 'chevrons'] as const; -export const LIVE_PROVIDERS = ['frigate', 'frigate-jsmpeg', 'webrtc'] as const; +const LIVE_PROVIDERS = ['frigate', 'frigate-jsmpeg', 'webrtc'] as const; /** * Action Types (for "Picture Elements" / Menu) @@ -430,13 +429,29 @@ const thumbnailsControlSchema = z.object({ }); export type ThumbnailsControlConfig = z.infer; +/** + * Next/Previous Control configuration section. + */ + +const nextPreviousControlConfigSchema = z.object({ + style: z.enum(['none', 'chevrons', 'icons', 'thumbnails']), + size: z.string(), +}); +export type NextPreviousControlConfig = z.infer; + /** * Live view configuration section. */ const liveConfigDefault = { provider: 'frigate' as const, preload: false, + lazy_load: true, + draggable: true, controls: { + next_previous: { + size: '48px', + style: 'chevrons' as const, + }, thumbnails: { media: 'clips' as const, }, @@ -474,14 +489,28 @@ const jsmpegConfigSchema = z .optional(); export type JSMPEGConfig = z.infer; +const liveNextPreviousControlConfigSchema = nextPreviousControlConfigSchema.merge( + z.object({ + style: z + .enum(['none', 'chevrons', 'icons']) + .default(liveConfigDefault.controls.next_previous.style), + size: z.string().default(liveConfigDefault.controls.next_previous.size), +})); +export type LiveNextPreviousControlConfig = z.infer; + const liveConfigSchema = z .object({ provider: z.enum(LIVE_PROVIDERS).default(liveConfigDefault.provider), preload: z.boolean().default(liveConfigDefault.preload), webrtc: webrtcConfigSchema, jsmpeg: jsmpegConfigSchema, + lazy_load: z.boolean().default(liveConfigDefault.lazy_load), + draggable: z.boolean().default(liveConfigDefault.draggable), controls: z .object({ + next_previous: liveNextPreviousControlConfigSchema.default( + liveConfigDefault.controls.next_previous, + ), thumbnails: thumbnailsControlSchema .merge( z.object({ @@ -555,13 +584,14 @@ const viewerConfigDefault = { }, }, }; -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), -}); -export type NextPreviousControlConfig = z.infer; +const viewerNextPreviousControlConfigSchema = nextPreviousControlConfigSchema.merge( + z.object({ + style: z + .enum(['none', 'thumbnails', 'chevrons']) + .default(viewerConfigDefault.controls.next_previous.style), + size: z.string().default(viewerConfigDefault.controls.next_previous.size), +})); +export type ViewerNextPreviousControlConfig = z.infer; const viewerConfigSchema = z .object({ @@ -570,7 +600,7 @@ const viewerConfigSchema = z draggable: z.boolean().default(viewerConfigDefault.draggable), controls: z .object({ - next_previous: nextPreviousControlConfigSchema.default( + next_previous: viewerNextPreviousControlConfigSchema.default( viewerConfigDefault.controls.next_previous, ), thumbnails: thumbnailsControlSchema.default(