Add support for configuration profiles.

This commit is contained in:
Dermot Duffy
2024-03-28 21:41:48 -07:00
parent 0b9935dfe2
commit bde4e6cd38
25 changed files with 412 additions and 184 deletions
+1 -1
View File
@@ -1,7 +1,7 @@
import { CurrentUser } from '@dermotduffy/custom-card-helpers';
import { HassEntities } from 'home-assistant-js-websocket';
import merge from 'lodash-es/merge';
import { copyConfig } from '../config-mgmt';
import { copyConfig } from '../config/management';
import {
FrigateCardCondition,
frigateConditionalSchema,
+3 -6
View File
@@ -1,5 +1,5 @@
import isEqual from 'lodash-es/isEqual';
import { isConfigUpgradeable } from '../config-mgmt';
import { isConfigUpgradeable } from '../config/management';
import {
CardWideConfig,
FrigateCardConfig,
@@ -7,7 +7,7 @@ import {
RawFrigateCardConfig
} from '../config/types';
import { localize } from '../localize/localize';
import { setLowPerformanceProfile } from '../performance.js';
import { setProfiles } from '../config/profiles';
import { getParseErrorPaths } from '../utils/zod.js';
import { getOverriddenConfig } from './conditions-manager';
import { InitializationAspect } from './initialization-manager';
@@ -70,10 +70,7 @@ export class ConfigManager {
: localize('error.invalid_configuration_no_hint')),
);
}
const config =
parseResult.data.performance.profile !== 'low'
? parseResult.data
: setLowPerformanceProfile(inputConfig, parseResult.data);
const config = setProfiles(inputConfig, parseResult.data, parseResult.data.profiles);
this._rawConfig = inputConfig;
if (isEqual(this._config, config)) {
+16 -5
View File
@@ -1,6 +1,5 @@
import { StyleInfo } from 'lit/directives/style-map';
import { FrigateCardConfig } from '../config/types';
import { setPerformanceCSSStyles } from '../performance';
import { aspectRatioToStyle, setOrRemoveAttribute } from '../utils/basic';
import { View } from '../view/view';
import { CardStyleAPI } from './types';
@@ -69,10 +68,22 @@ export class StyleManager {
}
public setPerformance(): void {
setPerformanceCSSStyles(
this._api.getCardElementManager().getElement(),
this._api.getConfigManager().getCardWideConfig()?.performance,
);
const STYLE_DISABLE_MAP = {
box_shadow: 'none',
border_radius: '0px',
};
const element = this._api.getCardElementManager().getElement();
const performance = this._api.getConfigManager().getCardWideConfig()?.performance;
const styles = performance?.style ?? {};
for (const configKey of Object.keys(styles)) {
const CSSKey = `--frigate-card-css-${configKey.replaceAll('_', '-')}`;
if (styles[configKey] === false) {
element.style.setProperty(CSSKey, STYLE_DISABLE_MAP[configKey]);
} else {
element.style.removeProperty(CSSKey);
}
}
}
protected _isAspectRatioEnforced(
-4
View File
@@ -855,11 +855,7 @@ export class FrigateCardLiveProvider
) {
return 'webrtc-card';
} else if (this.cameraConfig?.camera_entity) {
if (this.cardWideConfig?.performance?.profile === 'low') {
return 'image';
} else {
return 'ha';
}
} else if (this.cameraConfig?.frigate.camera_name) {
return 'jsmpeg';
}
@@ -7,7 +7,7 @@ import {
FrigateCardCondition,
RawFrigateCardConfig,
RawFrigateCardConfigArray,
} from './config/types';
} from './types';
import {
CONF_AUTOMATIONS,
CONF_CAMERAS,
@@ -22,14 +22,15 @@ import {
CONF_MEDIA_VIEWER_CONTROLS_TIMELINE_EVENTS_MEDIA_TYPE,
CONF_MENU_BUTTONS_CAMERA_UI,
CONF_OVERRIDES,
CONF_PROFILES,
CONF_TIMELINE_EVENTS_MEDIA_TYPE,
CONF_VIEW_INTERACTION_SECONDS,
CONF_VIEW_TRIGGERS,
CONF_VIEW_TRIGGERS_ACTIONS_TRIGGER,
CONF_VIEW_TRIGGERS_ACTIONS_UNTRIGGER,
CONF_VIEW_TRIGGERS_FILTER_SELECTED_CAMERA,
} from './const';
import { arrayify } from './utils/basic';
} from '../const';
import { arrayify } from '../utils/basic';
// *************************************************************************
// General Config Management Functions
@@ -684,4 +685,8 @@ const UPGRADES = [
transform: (val) => (val === true ? { disable_except: 'substream' } : null),
}),
),
upgradeMoveToWithOverrides('performance.profile', CONF_PROFILES, {
// Delete the value if it's set to the default.
transform: (val) => (val === 'low' ? ['low-performance'] : null),
}),
];
+51
View File
@@ -0,0 +1,51 @@
import { getConfigValue, setConfigValue } from '../management.js';
import {
ProfileType,
RawFrigateCardConfig,
frigateCardConfigSchema,
} from '../types.js';
import { deepRemoveDefaults } from '../../utils/zod.js';
import { LOW_PERFORMANCE_PROFILE } from './low-performance.js';
import { SCRUBBING_PROFILE } from './scrubbing.js';
const PROFILES = {
'low-performance': LOW_PERFORMANCE_PROFILE,
'scrubbing': SCRUBBING_PROFILE,
};
/**
* Set a profile. Sets flags as defined in the relevant profile unless they are
* explicitly overriden in the configuration.
* @param inputConfig The raw unparsed input configuration.
* @param outputConfig The output config to write to.
* @returns A changed (in-place) parsed input configuration.
*/
export const setProfiles = <T extends RawFrigateCardConfig>(
inputConfig: RawFrigateCardConfig,
outputConfig: T,
profiles?: ProfileType[],
): T => {
const defaultLessParseResult = deepRemoveDefaults(frigateCardConfigSchema).safeParse(
inputConfig,
);
if (!defaultLessParseResult.success) {
return outputConfig;
}
const defaultLessConfig = defaultLessParseResult.data;
const setIfNotSpecified = (key: string, value: unknown) => {
if (getConfigValue(defaultLessConfig, key) === undefined) {
setConfigValue(outputConfig, key, value);
}
};
for (const profile of profiles ?? []) {
if (profile in PROFILES) {
Object.entries(PROFILES[profile]).forEach(([k, v]: [string, unknown]) =>
setIfNotSpecified(k, v),
);
}
}
return outputConfig;
};
@@ -1,11 +1,6 @@
import { getConfigValue, setConfigValue } from './config-mgmt.js';
import {
PerformanceConfig,
RawFrigateCardConfig,
frigateCardConfigSchema,
} from './config/types.js';
import {
CONF_CAMERAS_GLOBAL_IMAGE_REFRESH_SECONDS,
CONF_CAMERAS_GLOBAL_LIVE_PROVIDER,
CONF_CAMERAS_GLOBAL_TRIGGERS_OCCUPANCY,
CONF_LIVE_AUTO_MUTE,
CONF_LIVE_CONTROLS_THUMBNAILS_MODE,
@@ -51,13 +46,9 @@ import {
CONF_TIMELINE_CONTROLS_THUMBNAILS_SHOW_FAVORITE_CONTROL,
CONF_TIMELINE_CONTROLS_THUMBNAILS_SHOW_TIMELINE_CONTROL,
CONF_TIMELINE_SHOW_RECORDINGS,
} from './const.js';
import { deepRemoveDefaults } from './utils/zod.js';
} 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 = {
export const LOW_PERFORMANCE_PROFILE = {
// Disable thumbnail carousels.
[CONF_LIVE_CONTROLS_THUMBNAILS_MODE]: 'none' as const,
[CONF_MEDIA_VIEWER_CONTROLS_THUMBNAILS_MODE]: 'none' as const,
@@ -138,68 +129,9 @@ const LOW_PROFILE_DEFAULTS = {
[CONF_MEDIA_VIEWER_SNAPSHOT_CLICK_PLAYS_CLIP]: false,
[CONF_CAMERAS_GLOBAL_TRIGGERS_OCCUPANCY]: false,
[CONF_CAMERAS_GLOBAL_LIVE_PROVIDER]: 'image',
// Refresh the live camera image every 10 seconds (same as stock Home
// Assistant Picture Glance).
[CONF_CAMERAS_GLOBAL_IMAGE_REFRESH_SECONDS]: 10,
};
/**
* 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 outputConfig The output config to write to.
* @returns A changed (in-place) parsed input configuration.
*/
export const setLowPerformanceProfile = <T extends RawFrigateCardConfig>(
inputConfig: RawFrigateCardConfig,
outputConfig: T,
): T => {
const setIfNotSpecified = (
defaultLessConfig: RawFrigateCardConfig,
outputConfig: T,
key: string,
value: unknown,
) => {
if (getConfigValue(defaultLessConfig, key) === undefined) {
setConfigValue(outputConfig, key, value);
}
};
const defaultLessParseResult = deepRemoveDefaults(frigateCardConfigSchema).safeParse(
inputConfig,
);
if (defaultLessParseResult.success) {
const defaultLessConfig = defaultLessParseResult.data;
Object.entries(LOW_PROFILE_DEFAULTS).forEach(([k, v]: [string, unknown]) =>
setIfNotSpecified(defaultLessConfig, outputConfig, k, v),
);
}
return outputConfig;
};
const STYLE_DISABLE_MAP = {
box_shadow: 'none',
border_radius: '0px',
};
/**
* Set card-wide CSS variables for performance.
* @param element The element to set the variables on.
* @param performance The performance configuration.
*/
export const setPerformanceCSSStyles = (
element: HTMLElement,
performance?: PerformanceConfig,
): void => {
const styles = performance?.style ?? {};
for (const configKey of Object.keys(styles)) {
const CSSKey = `--frigate-card-css-${configKey.replaceAll('_', '-')}`;
if (styles[configKey] === false) {
element.style.setProperty(CSSKey, STYLE_DISABLE_MAP[configKey]);
} else {
element.style.removeProperty(CSSKey);
}
}
};
+18
View File
@@ -0,0 +1,18 @@
import {
CONF_LIVE_CONTROLS_TIMELINE_MODE,
CONF_LIVE_CONTROLS_TIMELINE_PAN_MODE,
CONF_LIVE_CONTROLS_TIMELINE_STYLE,
CONF_MEDIA_VIEWER_CONTROLS_TIMELINE_MODE,
CONF_MEDIA_VIEWER_CONTROLS_TIMELINE_PAN_MODE,
CONF_MEDIA_VIEWER_CONTROLS_TIMELINE_STYLE,
} from '../../const.js';
export const SCRUBBING_PROFILE = {
[CONF_LIVE_CONTROLS_TIMELINE_MODE]: 'below' as const,
[CONF_LIVE_CONTROLS_TIMELINE_STYLE]: 'ribbon' as const,
[CONF_LIVE_CONTROLS_TIMELINE_PAN_MODE]: 'seek' as const,
[CONF_MEDIA_VIEWER_CONTROLS_TIMELINE_MODE]: 'below' as const,
[CONF_MEDIA_VIEWER_CONTROLS_TIMELINE_STYLE]: 'ribbon' as const,
[CONF_MEDIA_VIEWER_CONTROLS_TIMELINE_PAN_MODE]: 'seek' as const,
};
+9 -2
View File
@@ -1623,7 +1623,6 @@ export type Automations = z.infer<typeof automationsSchema>;
// *************************************************************************
const performanceConfigDefault = {
profile: 'high' as const,
features: {
animated_progress_indicator: true,
media_chunk_size: MEDIA_CHUNK_SIZE_DEFAULT,
@@ -1636,7 +1635,6 @@ const performanceConfigDefault = {
export const performanceConfigSchema = z
.object({
profile: z.enum(['low', 'high']).default(performanceConfigDefault.profile),
features: z
.object({
animated_progress_indicator: z
@@ -1679,6 +1677,13 @@ export interface CardWideConfig {
debug?: DebugConfig;
}
// *************************************************************************
// *** Profile Configuration ***
// *************************************************************************
const PROFILES = ['low-performance', 'scrubbing'] as const;
export type ProfileType = (typeof PROFILES)[number];
export const profilesSchema = z.enum(PROFILES).array().optional();
// *************************************************************************
// *** Card Configuration ***
// *************************************************************************
@@ -1705,6 +1710,8 @@ export const frigateCardConfigSchema = z.object({
debug: debugConfigSchema,
automations: automationsSchema,
profiles: profilesSchema,
// Configuration overrides.
overrides: overridesSchema,
+4
View File
@@ -72,6 +72,8 @@ export const CONF_CAMERAS_ARRAY_TRIGGERS_EVENTS =
const CONF_CAMERAS_GLOBAL = 'cameras_global' as const;
export const CONF_CAMERAS_GLOBAL_IMAGE = `${CONF_CAMERAS_GLOBAL}.image` as const;
export const CONF_CAMERAS_GLOBAL_LIVE_PROVIDER =
`${CONF_CAMERAS_GLOBAL}.live_provider` as const;
export const CONF_CAMERAS_GLOBAL_JSMPEG = `${CONF_CAMERAS_GLOBAL}.jsmpeg` as const;
export const CONF_CAMERAS_GLOBAL_WEBRTC_CARD =
`${CONF_CAMERAS_GLOBAL}.webrtc_card` as const;
@@ -313,6 +315,8 @@ 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`;
export const CONF_PROFILES = 'profiles' as const;
// Taken from https://github.dev/home-assistant/frontend/blob/b5861869e39290fd2e15737e89571dfc543b3ad3/src/data/media-player.ts#L93
export const MEDIA_PLAYER_SUPPORT_BROWSE_MEDIA = 131072;
+33 -26
View File
@@ -19,21 +19,24 @@ import {
isConfigUpgradeable,
setConfigValue,
upgradeConfig,
} from './config-mgmt.js';
} from './config/management.js';
import { setProfiles } from './config/profiles/index.js';
import {
BUTTON_SIZE_MIN,
FrigateCardConfig,
frigateCardConfigDefaults,
FRIGATE_MENU_PRIORITY_MAX,
profilesSchema,
RawFrigateCardConfig,
RawFrigateCardConfigArray,
THUMBNAIL_WIDTH_MAX,
THUMBNAIL_WIDTH_MIN,
} from './config/types.js';
import {
CONF_CAMERAS,
CONF_CAMERAS_ARRAY_CAMERA_ENTITY,
CONF_CAMERAS_ARRAY_CAPABILITIES_DISABLE_EXCEPT,
CONF_CAMERAS_ARRAY_CAPABILITIES_DISABLE,
CONF_CAMERAS_ARRAY_CAPABILITIES_DISABLE_EXCEPT,
CONF_CAMERAS_ARRAY_CAST_DASHBOARD_DASHBOARD_PATH,
CONF_CAMERAS_ARRAY_CAST_DASHBOARD_VIEW_PATH,
CONF_CAMERAS_ARRAY_CAST_METHOD,
@@ -67,9 +70,8 @@ import {
CONF_CAMERAS_ARRAY_TRIGGERS_OCCUPANCY,
CONF_CAMERAS_ARRAY_WEBRTC_CARD_ENTITY,
CONF_CAMERAS_ARRAY_WEBRTC_CARD_URL,
CONF_CAMERAS,
CONF_DIMENSIONS_ASPECT_RATIO_MODE,
CONF_DIMENSIONS_ASPECT_RATIO,
CONF_DIMENSIONS_ASPECT_RATIO_MODE,
CONF_DIMENSIONS_MAX_HEIGHT,
CONF_DIMENSIONS_MIN_HEIGHT,
CONF_IMAGE_MODE,
@@ -158,8 +160,8 @@ import {
CONF_MEDIA_VIEWER_TRANSITION_EFFECT,
CONF_MEDIA_VIEWER_ZOOMABLE,
CONF_MENU_ALIGNMENT,
CONF_MENU_BUTTON_SIZE,
CONF_MENU_BUTTONS,
CONF_MENU_BUTTON_SIZE,
CONF_MENU_POSITION,
CONF_MENU_STYLE,
CONF_PERFORMANCE_FEATURES_ANIMATED_PROGRESS_INDICATOR,
@@ -167,6 +169,7 @@ import {
CONF_PERFORMANCE_PROFILE,
CONF_PERFORMANCE_STYLE_BORDER_RADIUS,
CONF_PERFORMANCE_STYLE_BOX_SHADOW,
CONF_PROFILES,
CONF_TIMELINE_CLUSTERING_THRESHOLD,
CONF_TIMELINE_CONTROLS_THUMBNAILS_MODE,
CONF_TIMELINE_CONTROLS_THUMBNAILS_SHOW_DETAILS,
@@ -183,21 +186,20 @@ import {
CONF_VIEW_DEFAULT,
CONF_VIEW_INTERACTION_SECONDS,
CONF_VIEW_RESET_AFTER_INTERACTION,
CONF_VIEW_TRIGGERS,
CONF_VIEW_TRIGGERS_ACTIONS,
CONF_VIEW_TRIGGERS_ACTIONS_INTERACTION_MODE,
CONF_VIEW_TRIGGERS_ACTIONS_TRIGGER,
CONF_VIEW_TRIGGERS_ACTIONS_UNTRIGGER,
CONF_VIEW_TRIGGERS_ACTIONS,
CONF_VIEW_TRIGGERS_FILTER_SELECTED_CAMERA,
CONF_VIEW_TRIGGERS_SHOW_TRIGGER_STATUS,
CONF_VIEW_TRIGGERS_UNTRIGGER_SECONDS,
CONF_VIEW_TRIGGERS,
CONF_VIEW_UPDATE_CYCLE_CAMERA,
CONF_VIEW_UPDATE_FORCE,
CONF_VIEW_UPDATE_SECONDS,
MEDIA_CHUNK_SIZE_MAX,
} from './const.js';
import { localize } from './localize/localize.js';
import { setLowPerformanceProfile } from './performance.js';
import frigate_card_editor_style from './scss/editor.scss';
import { arrayMove, prettifyTitle } from './utils/basic.js';
import { getCameraID } from './utils/camera.js';
@@ -315,6 +317,11 @@ const options: EditorOptions = {
name: localize('editor.performance'),
secondary: localize('editor.performance_secondary'),
},
profiles: {
icon: 'folder-wrench-outline',
name: localize('editor.profiles'),
secondary: localize('editor.profiles_secondary'),
},
overrides: {
icon: 'file-replace',
name: localize('editor.overrides'),
@@ -589,10 +596,10 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
{ value: 'below', label: localize('config.common.controls.timeline.modes.below') },
];
protected _performanceProfiles: EditorSelectOption[] = [
protected _profiles: EditorSelectOption[] = [
{ value: '', label: '' },
{ value: 'low', label: localize('config.performance.profiles.low') },
{ value: 'high', label: localize('config.performance.profiles.high') },
{ value: 'low-performance', label: localize('config.profiles.low-performance') },
{ value: 'scrubbing', label: localize('config.profiles.scrubbing') },
];
protected _go2rtcModes: EditorSelectOption[] = [
@@ -788,25 +795,20 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
];
public setConfig(config: RawFrigateCardConfig): void {
// Note: This does not use Zod to parse the configuration, so it may be
// Note: This does not use Zod to parse the full configuration, so it may be
// partially or completely invalid. It's more useful to have a partially
// valid configuration here, to allow the user to fix the broken parts. As
// 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 (_) {}
const profiles = profilesSchema.safeParse(
(this._config as FrigateCardConfig).profiles,
);
if (unvalidatedProfile === 'high' || unvalidatedProfile === 'low') {
if (profiles.success) {
const defaults = copyConfig(frigateCardConfigDefaults);
if (unvalidatedProfile === 'low') {
setLowPerformanceProfile(this._config, defaults);
}
setProfiles(this._config, defaults, profiles.data);
this._defaults = defaults;
}
}
@@ -2130,6 +2132,15 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
</div>
`
: ''}
${this._renderOptionSetHeader('profiles')}
${this._expandedMenus[MENU_OPTIONS] === 'profiles'
? html` <div class="values">
${this._renderOptionSelector(CONF_PROFILES, this._profiles, {
multiple: true,
label: localize('config.profiles.editor_label'),
})}
</div>`
: ''}
${this._renderOptionSetHeader('view')}
${this._expandedMenus[MENU_OPTIONS] === 'view'
? html`
@@ -2556,10 +2567,6 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
${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,
+7 -5
View File
@@ -401,11 +401,6 @@
"editor_label": "Opcions de característiques",
"media_chunk_size": "Mida del fragment multimèdia"
},
"profile": "Perfil de rendiment",
"profiles": {
"high": "Alt rendiment",
"low": "Baix rendiment"
},
"style": {
"border_radius": "Corbes",
"box_shadow": "Ombres",
@@ -413,6 +408,11 @@
},
"warning": "Aquesta targeta està en mode de perfil baix, de manera que els valors predeterminats han canviat per optimitzar el rendiment"
},
"profiles": {
"editor_label": "",
"low-performance": "",
"scrubbing": ""
},
"view": {
"camera_select": "Visualitza les càmeres seleccionades recentment",
"dark_mode": "Mode fosc",
@@ -497,6 +497,8 @@
"overrides_secondary": "S'han detectat substitucions de configuració dinàmica",
"performance": "Rendiment",
"performance_secondary": "Opcions de rendiment de la targeta",
"profiles": "",
"profiles_secondary": "",
"timeline": "Cronologia",
"timeline_secondary": "Opcions de la cronologia d'esdeveniments",
"upgrade": "Upgrade",
+7 -5
View File
@@ -401,11 +401,6 @@
"editor_label": "Feature Options",
"media_chunk_size": "Media chunk size"
},
"profile": "Performance profile",
"profiles": {
"high": "High/full performance",
"low": "Low performance"
},
"style": {
"border_radius": "Curves",
"box_shadow": "Shadows",
@@ -413,6 +408,11 @@
},
"warning": "This card is in low profile mode so defaults have changed to optimize performance"
},
"profiles": {
"editor_label": "Configuration profiles",
"low-performance": "Low performance",
"scrubbing": "Video scrubbing"
},
"view": {
"camera_select": "View for newly selected cameras",
"dark_mode": "Dark mode",
@@ -497,6 +497,8 @@
"overrides_secondary": "Dynamic configuration overrides detected",
"performance": "Performance",
"performance_secondary": "Card performance options",
"profiles": "Configuration profiles",
"profiles_secondary": "Choose pre-configured sets of defaults",
"timeline": "Timeline",
"timeline_secondary": "Event timeline options",
"upgrade": "Upgrade",
+7 -5
View File
@@ -401,11 +401,6 @@
"editor_label": "Options de fonctionnalités",
"media_chunk_size": "Taille du morceau de média"
},
"profile": "Profil de performances",
"profiles": {
"high": "Performances élevées",
"low": "Performances faibles"
},
"style": {
"border_radius": "Courbes",
"box_shadow": "Ombres",
@@ -413,6 +408,11 @@
},
"warning": "Cette carte est en mode profil bas, les paramètres par défaut ont donc été modifiés pour optimiser les performances."
},
"profiles": {
"editor_label": "",
"low-performance": "",
"scrubbing": ""
},
"view": {
"camera_select": "Afficher les caméras nouvellement sélectionnées",
"dark_mode": "Mode sombre",
@@ -497,6 +497,8 @@
"overrides_secondary": "Remplacements de configuration dynamique détectés",
"performance": "Performance",
"performance_secondary": "Options de performances de la carte",
"profiles": "",
"profiles_secondary": "",
"timeline": "Chronologie",
"timeline_secondary": "Options de chronologie des événements",
"upgrade": "Mise à niveau",
+7 -5
View File
@@ -401,11 +401,6 @@
"editor_label": "Opzioni funzionalità",
"media_chunk_size": "Dimensione del blocco multimediale"
},
"profile": "Profilo delle prestazioni",
"profiles": {
"high": "Prestazioni alte",
"low": "Prestazioni basse"
},
"style": {
"border_radius": "Curve",
"box_shadow": "Ombre",
@@ -413,6 +408,11 @@
},
"warning": "Questa scheda è in modalità basso profilo, quindi le impostazioni predefinite sono state modificate per ottimizzare le prestazioni"
},
"profiles": {
"editor_label": "",
"low-performance": "",
"scrubbing": ""
},
"view": {
"camera_select": "Visualizza per le telecamere appena selezionate",
"dark_mode": "Tema scuro",
@@ -497,6 +497,8 @@
"overrides_secondary": "Rilevate sovrascritture della configurazione dinamica",
"performance": "",
"performance_secondary": "",
"profiles": "",
"profiles_secondary": "",
"timeline": "Timeline",
"timeline_secondary": "Opzioni della timeline degli eventi",
"upgrade": "Aggiornamento",
+7 -5
View File
@@ -401,11 +401,6 @@
"editor_label": "Opções de recursos",
"media_chunk_size": "Tamanho do bloco de mídia"
},
"profile": "Perfil de desempenho",
"profiles": {
"high": "Alto desempenho/completo",
"low": "Baixo desempenho"
},
"style": {
"border_radius": "Curvas",
"box_shadow": "Sombras",
@@ -413,6 +408,11 @@
},
"warning": "Este cartão está no modo de baixo desempenho, então os padrões foram alterados para otimizar o desempenho"
},
"profiles": {
"editor_label": "",
"low-performance": "",
"scrubbing": ""
},
"view": {
"camera_select": "Visualização de câmeras recém-selecionadas",
"dark_mode": "Modo escuro",
@@ -497,6 +497,8 @@
"overrides_secondary": "Substituições de configuração dinâmica detectadas",
"performance": "Desempenho",
"performance_secondary": "Opções de desempenho do cartão",
"profiles": "",
"profiles_secondary": "",
"timeline": "Linha do tempo",
"timeline_secondary": "Opções do evento da linha do tempo",
"upgrade": "Upgrade",
+7 -5
View File
@@ -401,11 +401,6 @@
"editor_label": "Editor de etiquetas",
"media_chunk_size": "Tamanho do ficheiro"
},
"profile": "Perfil",
"profiles": {
"high": "Alto",
"low": "Baixo"
},
"style": {
"border_radius": "Tamanho do bordo",
"box_shadow": "Caixa de Fundo",
@@ -413,6 +408,11 @@
},
"warning": "Avisos"
},
"profiles": {
"editor_label": "",
"low-performance": "",
"scrubbing": ""
},
"view": {
"camera_select": "Visualização de câmeras recém-selecionadas",
"dark_mode": "Modo escuro",
@@ -497,6 +497,8 @@
"overrides_secondary": "Substituições de configuração dinâmica detectadas",
"performance": "",
"performance_secondary": "",
"profiles": "",
"profiles_secondary": "",
"timeline": "Linha do tempo",
"timeline_secondary": "Opções do evento da linha do tempo",
"upgrade": "Actualização",
+17 -16
View File
@@ -78,7 +78,7 @@ describe('ConfigManager', () => {
manager.setConfig(config);
expect(manager.hasConfig()).toBeTruthy()
expect(manager.hasConfig()).toBeTruthy();
expect(manager.getRawConfig()).toBe(config);
// Verify at least the camera is set.
@@ -102,12 +102,12 @@ describe('ConfigManager', () => {
expect(api.getCardElementManager().update).toBeCalled();
});
it('should apply low performance defaults', () => {
it('should apply profiles', () => {
const manager = new ConfigManager(createCardAPI());
const config = {
type: 'custom:frigate-card',
cameras: [{ camera_entity: 'camera.office' }],
performance: { profile: 'low' },
profiles: ['low-performance'],
};
manager.setConfig(config);
@@ -143,7 +143,9 @@ describe('ConfigManager', () => {
logging: true,
},
performance: {
profile: 'low',
style: {
box_shadow: false,
},
},
};
@@ -155,12 +157,11 @@ describe('ConfigManager', () => {
},
performance: {
features: {
animated_progress_indicator: false,
media_chunk_size: 10,
animated_progress_indicator: true,
media_chunk_size: 50,
},
profile: 'low',
style: {
border_radius: false,
border_radius: true,
box_shadow: false,
},
},
@@ -257,8 +258,8 @@ describe('ConfigManager', () => {
const config_2 = {
...config_1,
cameras_global: {
live_provider: 'jsmpeg'
}
live_provider: 'jsmpeg',
},
};
vi.mocked(getOverriddenConfig).mockReturnValue(createConfig(config_2));
manager.computeOverrideConfig();
@@ -276,9 +277,9 @@ describe('ConfigManager', () => {
cameras: [{ camera_entity: 'camera.office' }],
live: {
microphone: {
always_connected: false
}
}
always_connected: false,
},
},
};
vi.mocked(getOverriddenConfig).mockReturnValue(createConfig(config_1));
@@ -289,9 +290,9 @@ describe('ConfigManager', () => {
...config_1,
live: {
microphone: {
always_connected: true
}
}
always_connected: true,
},
},
};
vi.mocked(getOverriddenConfig).mockReturnValue(createConfig(config_2));
manager.computeOverrideConfig();
+36 -9
View File
@@ -1,11 +1,8 @@
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { FrigateCardView } from '../../src/config/types';
import { setPerformanceCSSStyles } from '../../src/performance';
import { StyleManager } from '../../src/card-controller/style-manager';
import { createCardAPI, createConfig, createHASS, createView } from '../test-utils';
vi.mock('../../src/performance');
// @vitest-environment jsdom
describe('StyleManager', () => {
beforeEach(() => {
@@ -226,19 +223,49 @@ describe('StyleManager', () => {
});
});
it('setPerformance', () => {
describe('setPerformance', () => {
it('no styles set', () => {
const api = createCardAPI();
const element = document.createElement('div');
vi.mocked(api.getCardElementManager().getElement).mockReturnValue(element);
const config = createConfig();
vi.mocked(api.getConfigManager().getCardWideConfig).mockReturnValue({
performance: config.performance,
});
vi.mocked(api.getConfigManager().getCardWideConfig).mockReturnValue({});
const manager = new StyleManager(api);
manager.setPerformance();
expect(setPerformanceCSSStyles).toBeCalledWith(element, config.performance);
expect(
element.style.getPropertyValue('--frigate-card-css-box-shadow'),
).toBeFalsy();
expect(
element.style.getPropertyValue('--frigate-card-css-border-radius'),
).toBeFalsy();
});
it('valid styles set', () => {
const api = createCardAPI();
const element = document.createElement('div');
vi.mocked(api.getCardElementManager().getElement).mockReturnValue(element);
vi.mocked(api.getConfigManager().getCardWideConfig).mockReturnValue(
createConfig({
performance: {
style: {
box_shadow: false,
border_radius: true,
},
},
}),
);
const manager = new StyleManager(api);
manager.setPerformance();
expect(element.style.getPropertyValue('--frigate-card-css-box-shadow')).toEqual(
'none',
);
expect(
element.style.getPropertyValue('--frigate-card-css-border-radius'),
).toBeFalsy();
});
});
describe('getAspectRatioStyle', () => {
@@ -16,8 +16,8 @@ import {
upgradeMoveToWithOverrides,
upgradeObjectRecursively,
upgradeWithOverrides,
} from '../src/config-mgmt';
import { RawFrigateCardConfig } from '../src/config/types';
} from '../../src/config/management';
import { RawFrigateCardConfig } from '../../src/config/types';
describe('general functions', () => {
it('should set value', () => {
@@ -2459,6 +2459,43 @@ describe('should handle version specific upgrades', () => {
],
});
});
describe('from performance profile to generic profile', () => {
it('low performance', () => {
const config = {
type: 'custom:frigate-card',
cameras: [],
performance: {
profile: 'low',
},
};
expect(upgradeConfig(config)).toBeTruthy();
expect(config).toEqual({
type: 'custom:frigate-card',
cameras: [],
profiles: ['low-performance'],
performance: {},
});
});
it('high performance', () => {
const config = {
type: 'custom:frigate-card',
cameras: [],
performance: {
profile: 'high',
},
};
expect(upgradeConfig(config)).toBeTruthy();
expect(config).toEqual({
type: 'custom:frigate-card',
cameras: [],
performance: {},
});
});
});
});
});
});
+58
View File
@@ -0,0 +1,58 @@
import { describe, expect, it } from 'vitest';
import { ProfileType } from '../../../src/config/types';
import { setProfiles } from '../../../src/config/profiles';
import { createConfig } from '../../test-utils';
describe('setProfiles', () => {
it('should handle failed parse', () => {
expect(setProfiles({ cameras: 'not_an_array' }, {})).toEqual({});
});
it('should handle no profiles', () => {
const input = createConfig();
const output = createConfig();
expect(setProfiles(input, output)).toEqual(input);
});
it('should handle invalid profiles', () => {
const input = createConfig();
const output = createConfig();
expect(setProfiles(input, output, ['bogus' as ProfileType])).toEqual(input);
});
it('should handle profiles', () => {
const input = {
type: 'frigate-hass-card',
cameras: [],
live: {
controls: {
timeline: {
// This will not be overridden.
style: 'stack',
},
},
},
};
expect(setProfiles(input, {}, ['scrubbing'])).toEqual({
live: {
controls: {
timeline: {
mode: 'below',
pan_mode: 'seek',
},
},
},
media_viewer: {
controls: {
timeline: {
mode: 'below',
style: 'ribbon',
pan_mode: 'seek',
},
},
},
});
});
});
@@ -0,0 +1,54 @@
import { expect, it } from 'vitest';
import { LOW_PERFORMANCE_PROFILE } from '../../../src/config/profiles/low-performance';
it('low performance profile', () => {
expect(LOW_PERFORMANCE_PROFILE).toEqual({
'cameras_global.image.refresh_seconds': 10,
'cameras_global.live_provider': 'image',
'cameras_global.triggers.occupancy': false,
'live.auto_mute': 'never',
'live.controls.thumbnails.mode': 'none',
'live.controls.thumbnails.show_details': false,
'live.controls.thumbnails.show_download_control': false,
'live.controls.thumbnails.show_favorite_control': false,
'live.controls.thumbnails.show_timeline_control': false,
'live.controls.timeline.show_recordings': false,
'live.controls.title.mode': 'none',
'live.draggable': false,
'live.lazy_unload': 'all',
'live.show_image_during_load': false,
'live.transition_effect': 'none',
'media_gallery.controls.thumbnails.show_details': false,
'media_gallery.controls.thumbnails.show_download_control': false,
'media_gallery.controls.thumbnails.show_favorite_control': false,
'media_gallery.controls.thumbnails.show_timeline_control': false,
'media_viewer.auto_mute': 'never',
'media_viewer.auto_pause': 'never',
'media_viewer.auto_play': 'never',
'media_viewer.controls.next_previous.style': 'chevrons',
'media_viewer.controls.thumbnails.mode': 'none',
'media_viewer.controls.thumbnails.show_details': false,
'media_viewer.controls.thumbnails.show_download_control': false,
'media_viewer.controls.thumbnails.show_favorite_control': false,
'media_viewer.controls.thumbnails.show_timeline_control': false,
'media_viewer.controls.timeline.show_recordings': false,
'media_viewer.controls.title.mode': 'none',
'media_viewer.draggable': false,
'media_viewer.snapshot_click_plays_clip': false,
'media_viewer.transition_effect': 'none',
'menu.buttons.frigate.enabled': false,
'menu.buttons.media_player.enabled': false,
'menu.buttons.timeline.enabled': false,
'menu.style': 'outside',
'performance.features.animated_progress_indicator': false,
'performance.features.media_chunk_size': 10,
'performance.style.border_radius': false,
'performance.style.box_shadow': false,
'timeline.controls.thumbnails.mode': 'none',
'timeline.controls.thumbnails.show_details': false,
'timeline.controls.thumbnails.show_download_control': false,
'timeline.controls.thumbnails.show_favorite_control': false,
'timeline.controls.thumbnails.show_timeline_control': false,
'timeline.show_recordings': false,
});
});
+13
View File
@@ -0,0 +1,13 @@
import { expect, it } from 'vitest';
import { SCRUBBING_PROFILE } from '../../../src/config/profiles/scrubbing';
it('scrubbing profile', () => {
expect(SCRUBBING_PROFILE).toEqual({
'live.controls.timeline.mode': 'below',
'live.controls.timeline.style': 'ribbon',
'live.controls.timeline.pan_mode': 'seek',
'media_viewer.controls.timeline.mode': 'below',
'media_viewer.controls.timeline.style': 'ribbon',
'media_viewer.controls.timeline.pan_mode': 'seek',
});
});
-1
View File
@@ -253,7 +253,6 @@ describe('config defaults', () => {
animated_progress_indicator: true,
media_chunk_size: 50,
},
profile: 'high',
style: {
border_radius: true,
box_shadow: true,
+1 -2
View File
@@ -18,8 +18,7 @@ const FULL_COVERAGE_FILES_RELATIVE = [
'components-lib/menu-controller.ts',
'components-lib/ptz-controller.ts',
'components-lib/zoom-controller.ts',
'config-mgmt.ts',
'config/types.ts',
'config/**/*.ts',
'const.ts',
'types.ts',
'utils/action.ts',