diff --git a/src/components/surround.ts b/src/components/surround.ts index 0d1ce006..04cf6bf9 100644 --- a/src/components/surround.ts +++ b/src/components/surround.ts @@ -103,9 +103,6 @@ export class FrigateCardSurround extends LitElement { ?.evolve({ target: parent, childIndex: null, - - // Don't carry over history of this 'empty' view. - previous: null, }) .dispatchChangeEvent(this); } diff --git a/src/const.ts b/src/const.ts index 03cb5a8d..7f4d9e4d 100644 --- a/src/const.ts +++ b/src/const.ts @@ -206,6 +206,7 @@ export const CONF_OVERRIDES = 'overrides' as const; export const CONF_PERFORMANCE = 'performance' as const; export const CONF_PERFORMANCE_FEATURES_ANIMATED_PROGRESS_INDICATOR = `${CONF_PERFORMANCE}.features.animated_progress_indicator`; +export const CONF_PERFORMANCE_PROFILE = `${CONF_PERFORMANCE}.profile`; export const CONF_PERFORMANCE_STYLE_BOX_SHADOW = `${CONF_PERFORMANCE}.style.box_shadow`; export const CONF_PERFORMANCE_STYLE_BORDER_RADIUS = `${CONF_PERFORMANCE}.style.border_radius`; diff --git a/src/editor.ts b/src/editor.ts index db67d85a..cb6d7434 100644 --- a/src/editor.ts +++ b/src/editor.ts @@ -99,6 +99,10 @@ import { CONF_MENU_BUTTON_SIZE, CONF_MENU_POSITION, CONF_MENU_STYLE, + CONF_PERFORMANCE_FEATURES_ANIMATED_PROGRESS_INDICATOR, + CONF_PERFORMANCE_PROFILE, + CONF_PERFORMANCE_STYLE_BORDER_RADIUS, + CONF_PERFORMANCE_STYLE_BOX_SHADOW, CONF_TIMELINE_CLUSTERING_THRESHOLD, CONF_TIMELINE_CONTROLS_THUMBNAILS_MODE, CONF_TIMELINE_CONTROLS_THUMBNAILS_SHOW_DETAILS, @@ -125,6 +129,7 @@ import { localize } from './localize/localize.js'; import frigate_card_editor_style from './scss/editor.scss'; import { BUTTON_SIZE_MIN, + FrigateCardConfig, frigateCardConfigDefaults, FRIGATE_MENU_PRIORITY_MAX, RawFrigateCardConfig, @@ -136,6 +141,7 @@ import { arrayMove } from './utils/basic.js'; import { getCameraID, getCameraTitle } from './utils/camera.js'; import { FRIGATE_ICON_SVG_PATH } from './utils/frigate.js'; import { getEntitiesFromHASS, sideLoadHomeAssistantElements } from './utils/ha'; +import { setLowPerformanceProfile } from './performance.js'; const MENU_BUTTONS = 'buttons'; const MENU_CAMERAS = 'cameras'; @@ -159,6 +165,8 @@ const MENU_MEDIA_VIEWER_CONTROLS_TITLE = 'media_viewer.controls.title'; const MENU_MEDIA_VIEWER_LAYOUT = 'media_viewer.layout'; const MENU_TIMELINE_CONTROLS_THUMBNAILS = 'timeline.controls.thumbnails'; const MENU_OPTIONS = 'options'; +const MENU_PERFORMANCE_FEATURES = 'performance.features'; +const MENU_PERFORMANCE_STYLE = 'performance.style'; const MENU_VIEW_SCAN = 'scan'; interface EditorOptionsSet { @@ -226,6 +234,11 @@ const options: EditorOptions = { name: localize('editor.dimensions'), secondary: localize('editor.dimensions_secondary'), }, + performance: { + icon: 'speedometer', + name: localize('editor.performance'), + secondary: localize('editor.performance_secondary'), + }, overrides: { icon: 'file-replace', name: localize('editor.overrides'), @@ -237,6 +250,8 @@ const options: EditorOptions = { export class FrigateCardEditor extends LitElement implements LovelaceCardEditor { @property({ attribute: false }) public hass?: HomeAssistant; @state() protected _config?: RawFrigateCardConfig; + @state() protected _defaults = structuredClone(frigateCardConfigDefaults); + protected _initialized = false; protected _configUpgradeable = false; @@ -442,6 +457,12 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor { value: 'below', label: localize('config.common.controls.timeline.modes.below') }, ]; + protected _performanceProfiles: EditorSelectOption[] = [ + { value: '', label: '' }, + { value: 'low', label: localize('config.performance.profiles.low') }, + { value: 'high', label: localize('config.performance.profiles.high') }, + ]; + 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 @@ -449,6 +470,21 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor // such, RawFrigateCardConfig is used as the type. this._config = config; this._configUpgradeable = isConfigUpgradeable(config); + + let unvalidatedProfile: string | null = null; + try { + // this._config may not be a valid FrigateCardConfig as it has not been + // parsed. Attempt to pull out the performance profile. + unvalidatedProfile = (this._config as FrigateCardConfig).performance?.profile; + } catch (_) {} + + if (unvalidatedProfile === 'high' || unvalidatedProfile === 'low') { + const defaults = structuredClone(frigateCardConfigDefaults); + if (unvalidatedProfile === 'low') { + setLowPerformanceProfile(this._config, defaults); + } + this._defaults = defaults; + } } /** @@ -469,7 +505,10 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor * @param optionSetName The name of the EditorOptionsSet. * @returns A rendered template. */ - protected _renderOptionSetHeader(optionSetName: string): TemplateResult { + protected _renderOptionSetHeader( + optionSetName: string, + titleClass?: string, + ): TemplateResult { const optionSet = options[optionSetName]; return html` @@ -481,7 +520,7 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor >
-
${optionSet.name}
+
${optionSet.name}
${optionSet.secondary}
@@ -673,24 +712,24 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor ? html`
${this._renderSwitch( CONF_VIEW_SCAN_ENABLED, - frigateCardConfigDefaults.view.scan.enabled, + this._defaults.view.scan.enabled, { label: localize(`config.${CONF_VIEW_SCAN_ENABLED}`), }, )} ${this._renderSwitch( CONF_VIEW_SCAN_SHOW_TRIGGER_STATUS, - frigateCardConfigDefaults.view.scan.show_trigger_status, + this._defaults.view.scan.show_trigger_status, { label: localize(`config.${CONF_VIEW_SCAN_SHOW_TRIGGER_STATUS}`), }, )} ${this._renderSwitch( CONF_VIEW_SCAN_UNTRIGGER_RESET, - frigateCardConfigDefaults.view.scan.untrigger_reset, + this._defaults.view.scan.untrigger_reset, )} ${this._renderNumberInput(CONF_VIEW_SCAN_UNTRIGGER_SECONDS, { - default: frigateCardConfigDefaults.view.scan.untrigger_seconds, + default: this._defaults.view.scan.untrigger_seconds, })}
` : ''} @@ -734,7 +773,7 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor ? html`
${this._renderSwitch( `${CONF_MENU_BUTTONS}.${button}.enabled`, - frigateCardConfigDefaults.menu.buttons[button]?.enabled ?? true, + this._defaults.menu.buttons[button]?.enabled ?? true, { label: localize('config.menu.buttons.enabled'), }, @@ -748,7 +787,7 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor )} ${this._renderNumberInput(`${CONF_MENU_BUTTONS}.${button}.priority`, { max: FRIGATE_MENU_PRIORITY_MAX, - default: frigateCardConfigDefaults.menu.buttons[button]?.priority, + default: this._defaults.menu.buttons[button]?.priority, label: localize('config.menu.buttons.priority'), })} ${this._renderIconSelector(`${CONF_MENU_BUTTONS}.${button}.icon`, { @@ -886,6 +925,7 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor configPathClusteringThreshold: string, configPathTimelineMedia: string, configPathShowRecordings: string, + showRecordingsDefault: boolean, ): TemplateResult | void { return this._putInSubmenu( domain, @@ -900,7 +940,7 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor configPathClusteringThreshold, configPathTimelineMedia, configPathShowRecordings, - frigateCardConfigDefaults.mini_timeline.show_recordings, + showRecordingsDefault, )}`, ); } @@ -999,21 +1039,21 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor })} ${this._renderSwitch( configPathShowDetails, - frigateCardConfigDefaults.live.controls.thumbnails.show_details, + this._defaults.live.controls.thumbnails.show_details, { label: localize('config.common.controls.thumbnails.show_details'), }, )} ${this._renderSwitch( configPathShowFavoriteControl, - frigateCardConfigDefaults.live.controls.thumbnails.show_favorite_control, + this._defaults.live.controls.thumbnails.show_favorite_control, { label: localize('config.common.controls.thumbnails.show_favorite_control'), }, )} ${this._renderSwitch( configPathShowTimelineControl, - frigateCardConfigDefaults.live.controls.thumbnails.show_timeline_control, + this._defaults.live.controls.thumbnails.show_timeline_control, { label: localize('config.common.controls.thumbnails.show_timeline_control'), }, @@ -1247,7 +1287,7 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor CONF_CAMERAS_ARRAY_DEPENDENCIES_ALL_CAMERAS, cameraIndex, ), - frigateCardConfigDefaults.cameras.dependencies.all_cameras, + this._defaults.cameras.dependencies.all_cameras, )} ${this._renderOptionSelector( getArrayConfigPath( @@ -1267,11 +1307,11 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor { name: 'mdi:magnify-scan' }, html` ${this._renderSwitch( getArrayConfigPath(CONF_CAMERAS_ARRAY_TRIGGERS_OCCUPANCY, cameraIndex), - frigateCardConfigDefaults.cameras.triggers.occupancy, + this._defaults.cameras.triggers.occupancy, )} ${this._renderSwitch( getArrayConfigPath(CONF_CAMERAS_ARRAY_TRIGGERS_MOTION, cameraIndex), - frigateCardConfigDefaults.cameras.triggers.motion, + this._defaults.cameras.triggers.motion, )} ${this._renderOptionSelector( getArrayConfigPath(CONF_CAMERAS_ARRAY_TRIGGERS_ENTITIES, cameraIndex), @@ -1381,7 +1421,6 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor return html``; } - const defaults = frigateCardConfigDefaults; const entities = getEntitiesFromHASS(this.hass); const cameras = (getConfigValue(this._config, CONF_CAMERAS) || []) as RawFrigateCardConfigArray; @@ -1431,10 +1470,13 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor ${this._renderOptionSelector(CONF_VIEW_DARK_MODE, this._darkModes)} ${this._renderNumberInput(CONF_VIEW_TIMEOUT_SECONDS)} ${this._renderNumberInput(CONF_VIEW_UPDATE_SECONDS)} - ${this._renderSwitch(CONF_VIEW_UPDATE_FORCE, defaults.view.update_force)} + ${this._renderSwitch( + CONF_VIEW_UPDATE_FORCE, + this._defaults.view.update_force, + )} ${this._renderSwitch( CONF_VIEW_UPDATE_CYCLE_CAMERA, - defaults.view.update_cycle_camera, + this._defaults.view.update_cycle_camera, )} ${this._renderViewScanMenu()}
@@ -1469,9 +1511,9 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor ${this._expandedMenus[MENU_OPTIONS] === 'live' ? html`
- ${this._renderSwitch(CONF_LIVE_PRELOAD, defaults.live.preload)} - ${this._renderSwitch(CONF_LIVE_DRAGGABLE, defaults.live.draggable)} - ${this._renderSwitch(CONF_LIVE_LAZY_LOAD, defaults.live.lazy_load)} + ${this._renderSwitch(CONF_LIVE_PRELOAD, this._defaults.live.preload)} + ${this._renderSwitch(CONF_LIVE_DRAGGABLE, this._defaults.live.draggable)} + ${this._renderSwitch(CONF_LIVE_LAZY_LOAD, this._defaults.live.lazy_load)} ${this._renderOptionSelector( CONF_LIVE_LAZY_UNLOAD, this._mediaActionNegativeConditions, @@ -1498,7 +1540,7 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor )} ${this._renderSwitch( CONF_LIVE_SHOW_IMAGE_DURING_LOAD, - defaults.live.show_image_during_load, + this._defaults.live.show_image_during_load, )} ${this._putInSubmenu( MENU_LIVE_CONTROLS, @@ -1537,6 +1579,7 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor CONF_LIVE_CONTROLS_TIMELINE_CLUSTERING_THRESHOLD, CONF_LIVE_CONTROLS_TIMELINE_MEDIA, CONF_LIVE_CONTROLS_TIMELINE_SHOW_RECORDINGS, + this._defaults.live.controls.timeline.show_recordings, )} `, )} @@ -1583,11 +1626,11 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor )} ${this._renderSwitch( CONF_MEDIA_VIEWER_DRAGGABLE, - defaults.media_viewer.draggable, + this._defaults.media_viewer.draggable, )} ${this._renderSwitch( CONF_MEDIA_VIEWER_LAZY_LOAD, - defaults.media_viewer.lazy_load, + this._defaults.media_viewer.lazy_load, )} ${this._renderOptionSelector( CONF_MEDIA_VIEWER_TRANSITION_EFFECT, @@ -1629,6 +1672,7 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor CONF_MEDIA_VIEWER_CONTROLS_TIMELINE_CLUSTERING_THRESHOLD, CONF_MEDIA_VIEWER_CONTROLS_TIMELINE_MEDIA, CONF_MEDIA_VIEWER_CONTROLS_TIMELINE_SHOW_RECORDINGS, + this._defaults.media_viewer.controls.timeline.show_recordings, )} `, )} @@ -1664,7 +1708,7 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor CONF_TIMELINE_CLUSTERING_THRESHOLD, CONF_TIMELINE_MEDIA, CONF_TIMELINE_SHOW_RECORDINGS, - defaults.timeline.show_recordings, + this._defaults.timeline.show_recordings, )} ${this._renderThumbnailsControls( MENU_TIMELINE_CONTROLS_THUMBNAILS, @@ -1688,6 +1732,51 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor ${this._renderStringInput(CONF_DIMENSIONS_ASPECT_RATIO)}
` : ''} + ${this._renderOptionSetHeader( + 'performance', + getConfigValue(this._config, CONF_PERFORMANCE_PROFILE) === 'low' + ? 'warning' + : undefined, + )} + ${this._expandedMenus[MENU_OPTIONS] === 'performance' + ? html`
+ ${getConfigValue(this._config, CONF_PERFORMANCE_PROFILE) === 'low' + ? this._renderInfo(localize('config.performance.warning')) + : html``} + ${this._renderOptionSelector( + CONF_PERFORMANCE_PROFILE, + this._performanceProfiles, + )} + ${this._putInSubmenu( + MENU_PERFORMANCE_FEATURES, + true, + 'config.performance.features.editor_label', + { name: 'mdi:feature-search' }, + html` + ${this._renderSwitch( + CONF_PERFORMANCE_FEATURES_ANIMATED_PROGRESS_INDICATOR, + this._defaults.performance.features.animated_progress_indicator, + )} + `, + )} + ${this._putInSubmenu( + MENU_PERFORMANCE_STYLE, + true, + 'config.performance.style.editor_label', + { name: 'mdi:palette-swatch-variant' }, + html` + ${this._renderSwitch( + CONF_PERFORMANCE_STYLE_BORDER_RADIUS, + this._defaults.performance.style.border_radius, + )} + ${this._renderSwitch( + CONF_PERFORMANCE_STYLE_BOX_SHADOW, + this._defaults.performance.style.box_shadow, + )} + `, + )} +
` + : ''} ${this._config['overrides'] !== undefined ? html` ${this._renderOptionSetHeader('overrides')} ${this._expandedMenus[MENU_OPTIONS] === 'overrides' diff --git a/src/localize/languages/en.json b/src/localize/languages/en.json index 43f9da87..465f941d 100644 --- a/src/localize/languages/en.json +++ b/src/localize/languages/en.json @@ -239,6 +239,23 @@ "overrides": { "info": "This card configuration has manually specified overrides configured which may override values shown in the visual editor, please consult the code editor to view/modify these overrides" }, + "performance": { + "warning": "This card is in low profile mode so many defaults have changed to optimize performance", + "features": { + "editor_label": "Feature Options", + "animated_progress_indicator": "Animated Progress Indicator" + }, + "profile": "Performance profile", + "profiles": { + "low": "Low performance", + "high": "High/full performance" + }, + "style": { + "editor_label": "Style Options", + "box_shadow": "Shadows", + "border_radius": "Curves" + } + }, "view": { "camera_select": "View for newly selected cameras", "dark_mode": "Dark mode", @@ -296,6 +313,8 @@ "move_up": "Move up", "overrides": "Overrides are active", "overrides_secondary": "Dynamic configuration overrides detected", + "performance": "Performance", + "performance_secondary": "Card performance options", "timeline": "Timeline", "timeline_secondary": "Event timeline options", "upgrade": "Upgrade", diff --git a/src/performance.ts b/src/performance.ts index 75af9e34..e28492e4 100644 --- a/src/performance.ts +++ b/src/performance.ts @@ -1,6 +1,4 @@ -// TODO: Change defaults themselves? // TODO: Don't pump out conditionStates based on state? -// TODO: view history import { deepRemoveDefaults } from './utils/zod.js'; import { @@ -132,21 +130,21 @@ const LOW_PROFILE_CAMERA_DEFAULTS = { * LOW_PROFILE_DEFAULTS unless they are explicitly overriden in the * configuration. * @param inputConfig The raw unparsed input configuration. - * @param parsedConfig The parsed input configuration. + * @param outputConfig The output config to write to. * @returns A changed (in-place) parsed input configuration. */ -export const setLowPerformanceProfile = ( +export const setLowPerformanceProfile = ( inputConfig: RawFrigateCardConfig, - parsedConfig: FrigateCardConfig, -): FrigateCardConfig => { + outputConfig: T, +): T => { const setIfNotSpecified = ( defaultLessConfig: RawFrigateCardConfig, - parsedConfig: FrigateCardConfig, + outputConfig: T, key: string, value: unknown, ) => { if (getConfigValue(defaultLessConfig, key) === undefined) { - setConfigValue(parsedConfig, key, value); + setConfigValue(outputConfig, key, value); } }; @@ -156,19 +154,19 @@ export const setLowPerformanceProfile = ( if (defaultLessParseResult.success) { const defaultLessConfig = defaultLessParseResult.data; Object.entries(LOW_PROFILE_DEFAULTS).forEach(([k, v]: [string, unknown]) => - setIfNotSpecified(defaultLessConfig, parsedConfig, k, v), + setIfNotSpecified(defaultLessConfig, outputConfig, k, v), ); Object.entries(LOW_PROFILE_CAMERA_DEFAULTS).forEach( ([rawKey, v]: [string, unknown]) => { defaultLessConfig.cameras.forEach((_, index: number) => { const indexedKey = getArrayConfigPath(rawKey, index); - setIfNotSpecified(defaultLessConfig, parsedConfig, indexedKey, v); + setIfNotSpecified(defaultLessConfig, outputConfig, indexedKey, v); }); }, ); } - return parsedConfig; + return outputConfig; }; const STYLE_DISABLE_MAP = { diff --git a/src/scss/editor.scss b/src/scss/editor.scss index 2ddddce6..cbce8562 100644 --- a/src/scss/editor.scss +++ b/src/scss/editor.scss @@ -17,6 +17,9 @@ margin-top: -6px; pointer-events: none; } +.title.warning { + color: var(--warning-color); +} .secondary { padding-left: 40px; color: var(--secondary-text-color); @@ -101,7 +104,7 @@ div.upgrade span { --mdc-icon-size: calc(var(--mdc-icon-button-size) / 2); } span.info { - padding: 4px; + padding: 10px; } ha-selector { diff --git a/src/types.ts b/src/types.ts index ee09400d..8d2b4721 100644 --- a/src/types.ts +++ b/src/types.ts @@ -696,8 +696,12 @@ const timelineCoreConfigSchema = z.object({ }); export type TimelineCoreConfig = z.infer; +const miniTimelineConfigDefault = { + ...timelineCoreConfigDefault, + mode: 'none' as const, +} const miniTimelineConfigSchema = timelineCoreConfigSchema.extend({ - mode: z.enum(['none', 'above', 'below']), + mode: z.enum(['none', 'above', 'below']).default(miniTimelineConfigDefault.mode), }); export type MiniTimelineControlConfig = z.infer; @@ -759,6 +763,7 @@ const liveConfigDefault = { show_timeline_control: true, mode: 'left' as const, }, + timeline: miniTimelineConfigDefault, title: { mode: 'popup-bottom-right' as const, duration_seconds: 2, @@ -833,7 +838,7 @@ const liveOverridableConfigSchema = z .default(liveConfigDefault.controls.thumbnails.media), }) .default(liveConfigDefault.controls.thumbnails), - timeline: miniTimelineConfigSchema.optional(), + timeline: miniTimelineConfigSchema.default(liveConfigDefault.controls.timeline), title: titleControlConfigSchema .extend({ mode: titleControlConfigSchema.shape.mode.default( @@ -975,6 +980,7 @@ const viewerConfigDefault = { show_timeline_control: true, mode: 'left' as const, }, + timeline: miniTimelineConfigDefault, title: { mode: 'popup-bottom-right' as const, duration_seconds: 2, @@ -1038,7 +1044,7 @@ const viewerConfigSchema = z ), }) .default(viewerConfigDefault.controls.thumbnails), - timeline: miniTimelineConfigSchema.optional(), + timeline: miniTimelineConfigSchema.default(viewerConfigDefault.controls.timeline), title: titleControlConfigSchema .extend({ mode: titleControlConfigSchema.shape.mode.default( @@ -1212,14 +1218,14 @@ const performanceConfigDefault = { animated_progress_indicator: true, }, style: { - border_radius: false, + border_radius: true, box_shadow: true, }, }; const performanceConfigSchema = z .object({ - profile: z.enum(['low', 'high']), + profile: z.enum(['low', 'high']).default(performanceConfigDefault.profile), features: z .object({ animated_progress_indicator: z @@ -1281,7 +1287,6 @@ export const frigateCardConfigDefaults = { event_gallery: galleryConfigDefault, image: imageConfigDefault, timeline: timelineConfigDefault, - mini_timeline: timelineCoreConfigDefault, performance: performanceConfigDefault, };