From 724ca4c8435327f1de45a1333ff26fa2c2754056 Mon Sep 17 00:00:00 2001 From: Dermot Duffy Date: Tue, 8 Feb 2022 20:36:07 -0800 Subject: [PATCH] Allow the carousel 'slide' animation to be disabled. --- README.md | 2 ++ src/components/carousel.ts | 11 ++++++++++- src/components/live.ts | 9 +++++++++ src/components/media-carousel.ts | 4 ++-- src/components/viewer.ts | 9 +++++++++ src/const.ts | 3 +++ src/editor.ts | 16 ++++++++++++++++ src/localize/languages/en.json | 6 ++++++ src/types.ts | 10 ++++++++++ 9 files changed, 67 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 28c6c51d..9091dc00 100644 --- a/README.md +++ b/README.md @@ -195,6 +195,7 @@ live: | `lazy_load` | `true` | :heavy_multiplication_x: | Whether or not to lazily load cameras in the camera carousel. Setting this will `false` will cause all cameras to load simultaneously when the `live` carousel is opened (or cause all cameras to load continually if both `lazy_load` and `preload` are `true`). This will result in a smoother carousel experience at a cost of (potentially) a substantial amount of continually streamed data. | | `lazy_unload` | `false` | :heavy_multiplication_x: | Whether or not to lazily **un**load lazyily-loaded cameras in the camera carousel, or just leave the camera paused. Setting this to `true` will cause cameras to be entirely unloaded when they are no longer visible (either because the carousel has scrolled past them, or because the document has been marked hidden/inactive by the browser). This will cause a reloading delay on revisiting that camera in the carousel but will save the streaming network resources that are otherwise consumed. This option has no effect if `lazy_load` is false. | | `draggable` | `true` | :heavy_multiplication_x: | Whether or not the live carousel can be dragged left or right, via touch/swipe and mouse dragging. | +| `transition_effect` | `slide` | :heavy_multiplication_x: | Effect to apply as a transition between live cameras. Accepted values: `slide` or `none`. | | `provider` | `frigate` | :white_check_mark: | The means through which the live camera view is displayed. See [Live Provider](#live-provider) below.| | `actions` | | :white_check_mark: | Actions to use for the `live` view. See [actions](#actions) below.| | `controls` | | :white_check_mark: | Configuration for the `live` view controls. See below. | @@ -303,6 +304,7 @@ event_viewer: | `auto_unmute` | `false` | :heavy_multiplication_x: | Whether or not to automatically unmute clips. Note that some browsers will not allow automated unmute until the user has interacted with the page in some way -- if the user has not then the browser may pause the media instead. | | `lazy_load` | `true` | :heavy_multiplication_x: | Whether or not to lazily load media in the event viewer carousel. Setting this will false will fetch all media immediately which may make the carousel experience smoother at a cost of (potentially) a substantial number of simultaneous media fetches on load. | | `draggable` | `true` | :heavy_multiplication_x: | Whether or not the event viewer carousel can be dragged left or right, via touch/swipe and mouse dragging. | +| `transition_effect` | `slide` | :heavy_multiplication_x: | Effect to apply as a transition between event media. Accepted values: `slide` or `none`. | | `controls` | | :heavy_multiplication_x: | Configuration for the event viewer. See below. | | `actions` | | :heavy_multiplication_x: | Actions to use for all views that use the `event_viewer` (e.g. `clip`, `snapshot`). See [actions](#actions) below.| diff --git a/src/components/carousel.ts b/src/components/carousel.ts index 02bf84ea..217506d9 100644 --- a/src/components/carousel.ts +++ b/src/components/carousel.ts @@ -5,6 +5,7 @@ import EmblaCarousel, { EmblaPluginType, } from 'embla-carousel'; +import { TransitionEffect } from '../types'; import { dispatchFrigateCardEvent } from '../common'; import carouselStyle from '../scss/carousel.scss'; @@ -22,7 +23,7 @@ export class FrigateCardCarousel extends LitElement { * @param index Slide number. */ carouselScrollTo(index: number): void { - this._carousel?.scrollTo(index); + this._carousel?.scrollTo(index, this._getTransitionEffect() === 'none'); } /** @@ -50,6 +51,14 @@ export class FrigateCardCarousel extends LitElement { } } + /** + * Get the transition effect to use. + * @returns An TransitionEffect object. + */ + protected _getTransitionEffect(): TransitionEffect | undefined { + return 'slide'; + } + /** * Get the Embla options to use. * @returns An EmblaOptionsType object or undefined for no options. diff --git a/src/components/live.ts b/src/components/live.ts index 9cc1f180..4f0265f7 100644 --- a/src/components/live.ts +++ b/src/components/live.ts @@ -18,6 +18,7 @@ import { FrigateCardMediaPlayer, LiveOverrides, LiveProvider, + TransitionEffect, frigateCardConfigDefaults, } from '../types.js'; import { EmblaOptionsType, EmblaPluginType } from 'embla-carousel'; @@ -314,6 +315,14 @@ export class FrigateCardLiveCarousel extends FrigateCardMediaCarousel { } } + /** + * Get the transition effect to use. + * @returns An TransitionEffect object. + */ + protected _getTransitionEffect(): TransitionEffect | undefined { + return this.liveConfig?.transition_effect; + } + /** * Get the Embla options to use. * @returns An EmblaOptionsType object or undefined for no options. diff --git a/src/components/media-carousel.ts b/src/components/media-carousel.ts index b846a05c..40f0e9b7 100644 --- a/src/components/media-carousel.ts +++ b/src/components/media-carousel.ts @@ -191,9 +191,9 @@ export class FrigateCardMediaCarousel extends FrigateCardCarousel { */ protected _nextPreviousHandler(direction: 'previous' | 'next'): void { if (direction == 'previous') { - this._carousel?.scrollPrev(); + this._carousel?.scrollPrev(this._getTransitionEffect() === 'none'); } else if (direction == 'next') { - this._carousel?.scrollNext(); + this._carousel?.scrollNext(this._getTransitionEffect() === 'none'); } } diff --git a/src/components/viewer.ts b/src/components/viewer.ts index 7feb2c50..b400dfe2 100644 --- a/src/components/viewer.ts +++ b/src/components/viewer.ts @@ -22,6 +22,7 @@ import type { CameraConfig, ExtendedHomeAssistant, MediaShowInfo, + TransitionEffect, ViewerConfig, } from '../types.js'; import { FrigateCardMediaCarousel, IMG_EMPTY } from './media-carousel.js'; @@ -302,6 +303,14 @@ export class FrigateCardViewerCarousel extends FrigateCardMediaCarousel { return slideIndex !== undefined ? Number(slideIndex) : undefined; } + /** + * Get the transition effect to use. + * @returns An TransitionEffect object. + */ + protected _getTransitionEffect(): TransitionEffect | undefined { + return this.viewerConfig?.transition_effect; + } + /** * Get the Embla options to use. * @returns An EmblaOptionsType object or undefined for no options. diff --git a/src/const.ts b/src/const.ts index fbe044aa..da7a7494 100644 --- a/src/const.ts +++ b/src/const.ts @@ -37,6 +37,8 @@ export const CONF_EVENT_VIEWER_AUTO_PLAY = `${CONF_EVENT_VIEWER}.auto_play` as c export const CONF_EVENT_VIEWER_AUTO_UNMUTE = `${CONF_EVENT_VIEWER}.auto_unmute` as const; export const CONF_EVENT_VIEWER_DRAGGABLE = `${CONF_EVENT_VIEWER}.draggable` as const; export const CONF_EVENT_VIEWER_LAZY_LOAD = `${CONF_EVENT_VIEWER}.lazy_load` as const; +export const CONF_EVENT_VIEWER_TRANSITION_EFFECT = + `${CONF_EVENT_VIEWER}.transition_effect` as const; export const CONF_EVENT_VIEWER_CONTROLS_NEXT_PREVIOUS_STYLE = `${CONF_EVENT_VIEWER}.controls.next_previous.style` as const; export const CONF_EVENT_VIEWER_CONTROLS_NEXT_PREVIOUS_SIZE = @@ -70,6 +72,7 @@ export const CONF_LIVE_JSMPEG = `${CONF_LIVE}.jsmpeg` as const; export const CONF_LIVE_LAZY_LOAD = `${CONF_LIVE}.lazy_load` as const; export const CONF_LIVE_LAZY_UNLOAD = `${CONF_LIVE}.lazy_unload` as const; export const CONF_LIVE_PRELOAD = `${CONF_LIVE}.preload` as const; +export const CONF_LIVE_TRANSITION_EFFECT = `${CONF_LIVE}.transition_effect` as const; export const CONF_LIVE_WEBRTC = `${CONF_LIVE}.webrtc` as const; export const CONF_LIVE_WEBRTC_ENTITY = `${CONF_LIVE_WEBRTC}.entity` as const; export const CONF_LIVE_WEBRTC_URL = `${CONF_LIVE_WEBRTC}.url` as const; diff --git a/src/editor.ts b/src/editor.ts index 46b1d683..0b2f2e89 100644 --- a/src/editor.ts +++ b/src/editor.ts @@ -38,6 +38,7 @@ import { CONF_EVENT_VIEWER_CONTROLS_TITLE_MODE, CONF_EVENT_VIEWER_DRAGGABLE, CONF_EVENT_VIEWER_LAZY_LOAD, + CONF_EVENT_VIEWER_TRANSITION_EFFECT, CONF_IMAGE_REFRESH_SECONDS, CONF_IMAGE_SRC, CONF_LIVE_AUTO_UNMUTE, @@ -52,6 +53,7 @@ import { CONF_LIVE_LAZY_LOAD, CONF_LIVE_LAZY_UNLOAD, CONF_LIVE_PRELOAD, + CONF_LIVE_TRANSITION_EFFECT, CONF_MENU_BUTTONS_CLIPS, CONF_MENU_BUTTONS_FRIGATE, CONF_MENU_BUTTONS_FRIGATE_DOWNLOAD, @@ -266,6 +268,12 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor ], ]); + protected _transitionEffects = new Map([ + ['', ''], + ['none', localize('config.event_viewer.transition_effects.none')], + ['slide', localize('config.event_viewer.transition_effects.slide')], + ]); + public setConfig(config: RawFrigateCardConfig): void { // Note: This does not use Zod to parse the configuration, so it may be // partially or completely invalid. It's more useful to have a partially @@ -809,6 +817,10 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor CONF_LIVE_CONTROLS_TITLE_DURATION_SECONDS, 'number', )} + ${this._renderDropdown( + CONF_LIVE_TRANSITION_EFFECT, + this._transitionEffects, + )} ` : ''} @@ -861,6 +873,10 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor CONF_EVENT_VIEWER_CONTROLS_TITLE_DURATION_SECONDS, 'number', )} + ${this._renderDropdown( + CONF_EVENT_VIEWER_TRANSITION_EFFECT, + this._transitionEffects, + )} ` : ''} ${this._renderOptionSetHeader('image')} diff --git a/src/localize/languages/en.json b/src/localize/languages/en.json index 4fc9c5e6..db512dd5 100644 --- a/src/localize/languages/en.json +++ b/src/localize/languages/en.json @@ -57,6 +57,11 @@ "auto_unmute": "Automatically unmute media", "draggable": "Event Viewer can be dragged/swiped", "lazy_load": "Event Viewer media is lazily loaded in carousel", + "transition_effect": "Event Viewer transition effect", + "transition_effects": { + "none": "No transition", + "slide": "Slide transition" + }, "controls": { "next_previous": { "style": "Event Viewer next & previous control style", @@ -95,6 +100,7 @@ "lazy_load": "Live cameras are lazily loaded", "lazy_unload": "Live cameras are lazily unloaded", "auto_unmute": "Automatically unmute live cameras", + "transition_effect": "Live camera transition effect", "controls": { "next_previous": { "style": "Live view next & previous control style", diff --git a/src/types.ts b/src/types.ts index c373f320..6568601d 100644 --- a/src/types.ts +++ b/src/types.ts @@ -436,6 +436,12 @@ const nextPreviousControlConfigSchema = z.object({ }); export type NextPreviousControlConfig = z.infer; +/** + * Carousel transition effect configuration. + */ +const transitionEffectConfigSchema = z.enum(['none', 'slide']); +export type TransitionEffect = z.infer; + /** * Title Control configuration section. */ @@ -460,6 +466,7 @@ const liveConfigDefault = { lazy_load: true, lazy_unload: false, draggable: true, + transition_effect: 'slide' as const, controls: { next_previous: { size: '48px', @@ -556,6 +563,7 @@ const liveConfigSchema = liveOverridableConfigSchema lazy_load: z.boolean().default(liveConfigDefault.lazy_load), lazy_unload: z.boolean().default(liveConfigDefault.lazy_unload), draggable: z.boolean().default(liveConfigDefault.draggable), + transition_effect: transitionEffectConfigSchema.default(liveConfigDefault.transition_effect), }) .default(liveConfigDefault); export type LiveConfig = z.infer; @@ -608,6 +616,7 @@ const viewerConfigDefault = { auto_unmute: false, lazy_load: true, draggable: true, + transition_effect: 'slide' as const, controls: { next_previous: { size: '48px', @@ -639,6 +648,7 @@ const viewerConfigSchema = z auto_unmute: z.boolean().default(viewerConfigDefault.auto_unmute), lazy_load: z.boolean().default(viewerConfigDefault.lazy_load), draggable: z.boolean().default(viewerConfigDefault.draggable), + transition_effect: transitionEffectConfigSchema.default(viewerConfigDefault.transition_effect), controls: z .object({ next_previous: viewerNextPreviousControlConfigSchema.default(