From c8c9098501f998035262e9b822cbc288ba57617a Mon Sep 17 00:00:00 2001 From: Dermot Duffy Date: Tue, 4 Oct 2022 20:00:53 -0700 Subject: [PATCH] Initial low performance profile mode. --- src/card-condition.ts | 4 +- src/card.ts | 8 +- src/components/gallery.ts | 2 +- src/components/next-prev-control.ts | 8 +- src/components/surround.ts | 4 +- src/config-mgmt.ts | 6 +- src/const.ts | 2 + src/editor.ts | 28 +++++- src/localize/languages/en.json | 3 +- src/localize/languages/it.json | 3 +- src/localize/languages/pt-BR.json | 3 +- src/performance.ts | 141 ++++++++++++++++++++++++++++ src/types.ts | 10 ++ src/utils/data-manager.ts | 4 +- 14 files changed, 203 insertions(+), 23 deletions(-) create mode 100644 src/performance.ts diff --git a/src/card-condition.ts b/src/card-condition.ts index 26f3a941..0cc2aff7 100644 --- a/src/card-condition.ts +++ b/src/card-condition.ts @@ -4,7 +4,7 @@ import type { RawFrigateCardConfig, } from './types'; import { HassEntities } from 'home-assistant-js-websocket'; -import { cloneDeep, merge } from 'lodash-es'; +import { merge } from 'lodash-es'; export interface ConditionState { view?: string; @@ -109,7 +109,7 @@ export function getOverriddenConfig( overrides: Readonly | undefined, conditionState?: Readonly, ): RawFrigateCardConfig { - const output = cloneDeep(config); + const output = structuredClone(config); let overridden = false; if (overrides) { for (const override of overrides) { diff --git a/src/card.ts b/src/card.ts index 4c491a1b..ccf0d717 100644 --- a/src/card.ts +++ b/src/card.ts @@ -101,6 +101,7 @@ import { View } from './view.js'; import pkg from '../package.json'; import { ViewContext } from 'view'; import { DataManager } from './utils/data-manager.js'; +import { setLowPerformanceProfile } from './performance.js'; /** A note on media callbacks: * @@ -933,7 +934,10 @@ export class FrigateCard extends LitElement { : localize('error.invalid_configuration_no_hint')), ); } - const config = parseResult.data; + const config = + parseResult.data.performance.profile !== 'low' + ? parseResult.data + : setLowPerformanceProfile(inputConfig, parseResult.data); if (config.test_gui) { getLovelace().setEditMode(true); @@ -1052,7 +1056,7 @@ export class FrigateCard extends LitElement { } if (this._cameras && (changedProps.has('_config') || changedProps.has('_cameras'))) { - this._dataManager = new DataManager(this._cameras, this._config.timeline.media); + this._dataManager = new DataManager(this._cameras); } } diff --git a/src/components/gallery.ts b/src/components/gallery.ts index 06acda8a..32930e92 100644 --- a/src/components/gallery.ts +++ b/src/components/gallery.ts @@ -23,7 +23,7 @@ import { getFullDependentBrowseMediaQueryParametersOrDispatchError, } from '../utils/ha/browse-media'; import { changeViewToRecentRecordingForCameraAndDependents } from '../utils/media-to-view.js'; -import { DataManager } from '../utils/data-manager'; +import { DataManager } from '../utils/data-manager.js'; import { View } from '../view.js'; import { renderProgressIndicator } from './message.js'; import './thumbnail.js'; diff --git a/src/components/next-prev-control.ts b/src/components/next-prev-control.ts index 5a7ccd67..5d4d0cde 100644 --- a/src/components/next-prev-control.ts +++ b/src/components/next-prev-control.ts @@ -52,9 +52,9 @@ export class FrigateCardNextPreviousControl extends LitElement { const classes = { controls: true, - previous: this.direction == 'previous', - next: this.direction == 'next', - thumbnails: this._controlConfig.style == 'thumbnails', + previous: this.direction === 'previous', + next: this.direction === 'next', + thumbnails: this._controlConfig.style === 'thumbnails', icons: ['chevrons', 'icons'].includes(this._controlConfig.style), button: ['chevrons', 'icons'].includes(this._controlConfig.style), }; @@ -62,7 +62,7 @@ export class FrigateCardNextPreviousControl extends LitElement { 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'; + icon = this.direction === 'previous' ? 'mdi:chevron-left' : 'mdi:chevron-right'; } else { if (!this.icon) { return html``; diff --git a/src/components/surround.ts b/src/components/surround.ts index e63881d6..0d1ce006 100644 --- a/src/components/surround.ts +++ b/src/components/surround.ts @@ -84,7 +84,7 @@ export class FrigateCardSurround extends LitElement { this.inBackground || !this.hass || !this.view || - this.view.target || + this.view.target || !this.thumbnailConfig || this.thumbnailConfig.mode === 'none' || !this.browseMediaParams || @@ -200,7 +200,7 @@ export class FrigateCardSurround extends LitElement { > ` : ''} - ${this.timelineConfig && !this.inBackground + ${this.timelineConfig?.mode && this.timelineConfig.mode !== 'none' && !this.inBackground ? html` + (!!options?.allowThumbnails || item.value !== 'thumbnails') && + (!!options?.allowIcons || item.value !== 'icons'), + ), + { + label: localize('config.common.controls.next_previous.style'), + }, + )} ${this._renderNumberInput(configPathSize, { min: BUTTON_SIZE_MIN, label: localize('config.common.controls.next_previous.size'), @@ -1494,6 +1510,9 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor MENU_LIVE_CONTROLS_NEXT_PREVIOUS, CONF_LIVE_CONTROLS_NEXT_PREVIOUS_STYLE, CONF_LIVE_CONTROLS_NEXT_PREVIOUS_SIZE, + { + allowIcons: true, + }, )} ${this._renderThumbnailsControls( MENU_LIVE_CONTROLS_THUMBNAILS, @@ -1584,6 +1603,9 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor MENU_MEDIA_VIEWER_CONTROLS_NEXT_PREVIOUS, CONF_MEDIA_VIEWER_CONTROLS_NEXT_PREVIOUS_STYLE, CONF_MEDIA_VIEWER_CONTROLS_NEXT_PREVIOUS_SIZE, + { + allowThumbnails: true, + }, )} ${this._renderThumbnailsControls( MENU_MEDIA_VIEWER_CONTROLS_THUMBNAILS, diff --git a/src/localize/languages/en.json b/src/localize/languages/en.json index 7c2f0b57..43f9da87 100644 --- a/src/localize/languages/en.json +++ b/src/localize/languages/en.json @@ -89,7 +89,8 @@ "styles": { "chevrons": "Chevrons", "icons": "Icons", - "none": "None" + "none": "None", + "thumbnails": "Thumbnails" } }, "timeline": { diff --git a/src/localize/languages/it.json b/src/localize/languages/it.json index 4b652897..8364175e 100644 --- a/src/localize/languages/it.json +++ b/src/localize/languages/it.json @@ -88,7 +88,8 @@ "styles": { "chevrons": "Chevrons", "icons": "Icone", - "none": "Icone" + "none": "Nessuno", + "thumbnails": "Miniature" } }, "timeline": { diff --git a/src/localize/languages/pt-BR.json b/src/localize/languages/pt-BR.json index 92b3a975..f93707a5 100644 --- a/src/localize/languages/pt-BR.json +++ b/src/localize/languages/pt-BR.json @@ -88,7 +88,8 @@ "styles": { "chevrons": "Setas", "icons": "Ícones", - "none": "Nenhum" + "none": "Nenhum", + "thumbnails": "Miniaturas" } }, "timeline": { diff --git a/src/performance.ts b/src/performance.ts new file mode 100644 index 00000000..12b52e31 --- /dev/null +++ b/src/performance.ts @@ -0,0 +1,141 @@ +// TODO: Change defaults themselves? +// TODO: Camera auto-dep detection +// TODO: CSS +// TODO: Renderprogress + +import { deepRemoveDefaults } from './utils/zod.js'; +import { + frigateCardConfigSchema, + FrigateCardConfig, + RawFrigateCardConfig, +} from './types'; +import { getConfigValue, setConfigValue } from './config-mgmt.js'; +import { + CONF_EVENT_GALLERY_CONTROLS_THUMBNAILS_SHOW_DETAILS, + CONF_EVENT_GALLERY_CONTROLS_THUMBNAILS_SHOW_FAVORITE_CONTROL, + CONF_EVENT_GALLERY_CONTROLS_THUMBNAILS_SHOW_TIMELINE_CONTROL, + CONF_LIVE_AUTO_MUTE, + CONF_LIVE_AUTO_PLAY, + CONF_LIVE_CONTROLS_THUMBNAILS_MODE, + CONF_LIVE_CONTROLS_THUMBNAILS_SHOW_DETAILS, + CONF_LIVE_CONTROLS_THUMBNAILS_SHOW_FAVORITE_CONTROL, + CONF_LIVE_CONTROLS_THUMBNAILS_SHOW_TIMELINE_CONTROL, + CONF_LIVE_CONTROLS_TIMELINE_SHOW_RECORDINGS, + CONF_LIVE_CONTROLS_TITLE_MODE, + CONF_LIVE_DRAGGABLE, + CONF_LIVE_LAZY_UNLOAD, + CONF_LIVE_SHOW_IMAGE_DURING_LOAD, + CONF_LIVE_TRANSITION_EFFECT, + CONF_MEDIA_VIEWER_AUTO_MUTE, + CONF_MEDIA_VIEWER_AUTO_PAUSE, + CONF_MEDIA_VIEWER_AUTO_PLAY, + CONF_MEDIA_VIEWER_CONTROLS_NEXT_PREVIOUS_STYLE, + CONF_MEDIA_VIEWER_CONTROLS_THUMBNAILS_MODE, + CONF_MEDIA_VIEWER_CONTROLS_THUMBNAILS_SHOW_DETAILS, + CONF_MEDIA_VIEWER_CONTROLS_THUMBNAILS_SHOW_FAVORITE_CONTROL, + CONF_MEDIA_VIEWER_CONTROLS_THUMBNAILS_SHOW_TIMELINE_CONTROL, + CONF_MEDIA_VIEWER_CONTROLS_TIMELINE_SHOW_RECORDINGS, + CONF_MEDIA_VIEWER_CONTROLS_TITLE_MODE, + CONF_MEDIA_VIEWER_DRAGGABLE, + CONF_MEDIA_VIEWER_TRANSITION_EFFECT, + CONF_MENU_BUTTONS_FRIGATE, + CONF_MENU_BUTTONS_MEDIA_PLAYER, + CONF_MENU_BUTTONS_TIMELINE, + CONF_MENU_STYLE, + CONF_TIMELINE_CONTROLS_THUMBNAILS_MODE, + CONF_TIMELINE_CONTROLS_THUMBNAILS_SHOW_DETAILS, + CONF_TIMELINE_CONTROLS_THUMBNAILS_SHOW_FAVORITE_CONTROL, + CONF_TIMELINE_CONTROLS_THUMBNAILS_SHOW_TIMELINE_CONTROL, + CONF_TIMELINE_SHOW_RECORDINGS, +} from './const.js'; + +// Caution: These values are applied after parsing (since we cannot know the +// performance profile until afterwards), so there is no validation on these +// defaults. +const LOW_PROFILE_DEFAULTS = { + // Disable thumbnail carousels. + [CONF_LIVE_CONTROLS_THUMBNAILS_MODE]: 'none' as const, + [CONF_MEDIA_VIEWER_CONTROLS_THUMBNAILS_MODE]: 'none' as const, + [CONF_TIMELINE_CONTROLS_THUMBNAILS_MODE]: 'none' as const, + + // Do not show recordings on timelines. + [CONF_LIVE_CONTROLS_TIMELINE_SHOW_RECORDINGS]: false, + [CONF_MEDIA_VIEWER_CONTROLS_TIMELINE_SHOW_RECORDINGS]: false, + [CONF_TIMELINE_SHOW_RECORDINGS]: false, + + // Take no automatic media actions. + [CONF_LIVE_AUTO_PLAY]: 'never' as const, + [CONF_LIVE_AUTO_MUTE]: 'never' as const, + [CONF_MEDIA_VIEWER_AUTO_PLAY]: 'never' as const, + [CONF_MEDIA_VIEWER_AUTO_PAUSE]: 'never' as const, + [CONF_MEDIA_VIEWER_AUTO_MUTE]: 'never' as const, + + // Always unload resources that are lazily loaded. + [CONF_LIVE_LAZY_UNLOAD]: 'all' as const, + + // Media carousels do not drag. + [CONF_LIVE_DRAGGABLE]: false, + [CONF_MEDIA_VIEWER_DRAGGABLE]: false, + + // Media carousels have no effects. + [CONF_LIVE_TRANSITION_EFFECT]: 'none' as const, + [CONF_MEDIA_VIEWER_TRANSITION_EFFECT]: 'none' as const, + + // Do not show image during load. + [CONF_LIVE_SHOW_IMAGE_DURING_LOAD]: false, + + // Media player next/previous are chevrons. + [CONF_MEDIA_VIEWER_CONTROLS_NEXT_PREVIOUS_STYLE]: 'chevrons' as const, + + [CONF_MEDIA_VIEWER_CONTROLS_TITLE_MODE]: 'none' as const, + [CONF_LIVE_CONTROLS_TITLE_MODE]: 'none' as const, + + // Move the menu to outside to remove the need to interact with it with open. + [CONF_MENU_STYLE]: 'outside', + + // Hide several buttons that are otherwise visible by default. + [`${CONF_MENU_BUTTONS_FRIGATE}.enabled`]: false, + [`${CONF_MENU_BUTTONS_TIMELINE}.enabled`]: false, + [`${CONF_MENU_BUTTONS_MEDIA_PLAYER}.enabled`]: false, + [`${CONF_MENU_BUTTONS_TIMELINE}.enabled`]: false, + + // Disable all options in thumbnails. + [CONF_EVENT_GALLERY_CONTROLS_THUMBNAILS_SHOW_FAVORITE_CONTROL]: false, + [CONF_EVENT_GALLERY_CONTROLS_THUMBNAILS_SHOW_TIMELINE_CONTROL]: false, + [CONF_EVENT_GALLERY_CONTROLS_THUMBNAILS_SHOW_DETAILS]: false, + [CONF_LIVE_CONTROLS_THUMBNAILS_SHOW_FAVORITE_CONTROL]: false, + [CONF_LIVE_CONTROLS_THUMBNAILS_SHOW_TIMELINE_CONTROL]: false, + [CONF_LIVE_CONTROLS_THUMBNAILS_SHOW_DETAILS]: false, + [CONF_MEDIA_VIEWER_CONTROLS_THUMBNAILS_SHOW_FAVORITE_CONTROL]: false, + [CONF_MEDIA_VIEWER_CONTROLS_THUMBNAILS_SHOW_TIMELINE_CONTROL]: false, + [CONF_MEDIA_VIEWER_CONTROLS_THUMBNAILS_SHOW_DETAILS]: false, + [CONF_TIMELINE_CONTROLS_THUMBNAILS_SHOW_FAVORITE_CONTROL]: false, + [CONF_TIMELINE_CONTROLS_THUMBNAILS_SHOW_TIMELINE_CONTROL]: false, + [CONF_TIMELINE_CONTROLS_THUMBNAILS_SHOW_DETAILS]: false, +}; + +/** + * Set low performance profile mode. Sets flags as defined in + * LOW_PROFILE_DEFAULTS unless they are explicitly overriden in the + * configuration. + * @param inputConfig The raw unparsed input configuration. + * @param parsedConfig The parsed input configuration. + * @returns A changed (in-place) parsed input configuration. + */ +export const setLowPerformanceProfile = ( + inputConfig: RawFrigateCardConfig, + parsedConfig: FrigateCardConfig, +): FrigateCardConfig => { + const defaultLessParseResult = deepRemoveDefaults(frigateCardConfigSchema).safeParse( + inputConfig, + ); + if (defaultLessParseResult.success) { + const defaultLessConfig = defaultLessParseResult.data; + Object.entries(LOW_PROFILE_DEFAULTS).forEach(([k, v]: [string, unknown]) => { + if (getConfigValue(defaultLessConfig, k) === undefined) { + setConfigValue(parsedConfig, k, v); + } + }); + } + return parsedConfig; +}; diff --git a/src/types.ts b/src/types.ts index f6f51c37..79fa060e 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1206,6 +1206,14 @@ const liveOverridesSchema = z .optional(); export type LiveOverrides = z.infer; +const performanceConfigDefault = { + profile: 'high' as const, +} + +const performanceConfigSchema = z.object({ + profile: z.enum(['low', 'high']) +}).default(performanceConfigDefault); + /** * Main card config. */ @@ -1221,6 +1229,7 @@ export const frigateCardConfigSchema = z.object({ elements: pictureElementsSchema, dimensions: dimensionsConfigSchema, timeline: timelineConfigSchema, + performance: performanceConfigSchema, // Configuration overrides. overrides: overridesSchema, @@ -1246,6 +1255,7 @@ export const frigateCardConfigDefaults = { image: imageConfigDefault, timeline: timelineConfigDefault, mini_timeline: timelineCoreConfigDefault, + performance: performanceConfigDefault, }; const menuButtonSchema = z.discriminatedUnion('type', [ diff --git a/src/utils/data-manager.ts b/src/utils/data-manager.ts index 3d251889..44eb6485 100644 --- a/src/utils/data-manager.ts +++ b/src/utils/data-manager.ts @@ -104,7 +104,6 @@ export class DataManager { protected _maxAgeSeconds: number = DATA_MANAGER_MAX_AGE_SECONDS; protected _cameras: Map; - protected _mediaType: TimelineMediaType; // Garbage collect segments at most once an hour. protected _throttledSegmentGarbageCollector = throttle( @@ -115,9 +114,8 @@ export class DataManager { { trailing: true }, ); - constructor(cameras: Map, mediaType: TimelineMediaType) { + constructor(cameras: Map) { this._cameras = cameras; - this._mediaType = mediaType; } // Get the last event fetch date.