713 lines
22 KiB
TypeScript
713 lines
22 KiB
TypeScript
import cloneDeep from 'lodash-es/cloneDeep';
|
|
import get from 'lodash-es/get';
|
|
import isEqual from 'lodash-es/isEqual';
|
|
import set from 'lodash-es/set';
|
|
import {
|
|
CONF_CAMERAS,
|
|
CONF_CAMERAS_ARRAY_CAMERA_ENTITY,
|
|
CONF_CAMERAS_ARRAY_LIVE_PROVIDER,
|
|
CONF_ELEMENTS,
|
|
CONF_IMAGE_URL,
|
|
CONF_LIVE_AUTO_UNMUTE,
|
|
CONF_LIVE_CONTROLS_NEXT_PREVIOUS_SIZE,
|
|
CONF_LIVE_CONTROLS_THUMBNAILS_SIZE,
|
|
CONF_LIVE_LAZY_UNLOAD,
|
|
CONF_LIVE_PRELOAD,
|
|
CONF_LIVE_WEBRTC_CARD,
|
|
CONF_MEDIA_VIEWER,
|
|
CONF_MENU,
|
|
CONF_MENU_BUTTONS_CAMERAS,
|
|
CONF_MENU_BUTTONS_CLIPS,
|
|
CONF_MENU_BUTTONS_DOWNLOAD,
|
|
CONF_MENU_BUTTONS_FRIGATE,
|
|
CONF_MENU_BUTTONS_FRIGATE_UI,
|
|
CONF_MENU_BUTTONS_FULLSCREEN,
|
|
CONF_MENU_BUTTONS_IMAGE,
|
|
CONF_MENU_BUTTONS_LIVE,
|
|
CONF_MENU_BUTTONS_SNAPSHOTS,
|
|
CONF_MENU_BUTTON_SIZE,
|
|
CONF_MENU_POSITION,
|
|
CONF_MENU_STYLE,
|
|
CONF_OVERRIDES,
|
|
CONF_VIEW_DEFAULT,
|
|
CONF_VIEW_TIMEOUT_SECONDS,
|
|
CONF_VIEW_UPDATE_ENTITIES,
|
|
} from './const';
|
|
import {
|
|
BUTTON_SIZE_MIN,
|
|
RawFrigateCardConfig,
|
|
RawFrigateCardConfigArray,
|
|
THUMBNAIL_WIDTH_MAX,
|
|
THUMBNAIL_WIDTH_MIN,
|
|
} from './types';
|
|
|
|
/**
|
|
* Set a configuration value.
|
|
* @param obj The configuration.
|
|
* @param keys The key to the property to set.
|
|
* @param value The value to set.
|
|
*/
|
|
|
|
export const setConfigValue = (
|
|
obj: RawFrigateCardConfig,
|
|
keys: string | (string | number)[],
|
|
value: unknown,
|
|
): void => {
|
|
set(obj, keys, value);
|
|
};
|
|
|
|
/**
|
|
* Get a configuration value.
|
|
* @param obj The configuration.
|
|
* @param keys The key to the property to retrieve.
|
|
* @param def Default if key not found.
|
|
* @returns The property or undefined if not found.
|
|
*/
|
|
export const getConfigValue = (
|
|
obj: RawFrigateCardConfig,
|
|
keys: string | (string | number)[],
|
|
def?: unknown,
|
|
): unknown => {
|
|
return get(obj, keys, def);
|
|
};
|
|
|
|
/**
|
|
* Delete a configuration value.
|
|
* @param obj The configuration.
|
|
* @param key The key to the property to delete.
|
|
*/
|
|
export const deleteConfigValue = (obj: RawFrigateCardConfig, key: string): void => {
|
|
let id = key;
|
|
let targetObj: unknown = obj;
|
|
if (key && key.split && key.includes('.')) {
|
|
const keys = key.split('.');
|
|
id = keys[keys.length - 1];
|
|
targetObj = getConfigValue(obj, keys.slice(0, -1).join('.'));
|
|
}
|
|
if (targetObj && typeof targetObj === 'object') {
|
|
delete targetObj[id];
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Upgrade a configuration.
|
|
* @param obj The configuration to upgrade.
|
|
* @returns `true` if the configuration is modified.
|
|
*/
|
|
export const upgradeConfig = function (obj: RawFrigateCardConfig): boolean {
|
|
let upgraded = false;
|
|
for (let i = 0; i < UPGRADES.length; i++) {
|
|
upgraded = UPGRADES[i](obj) || upgraded;
|
|
}
|
|
trimConfig(obj);
|
|
return upgraded;
|
|
};
|
|
|
|
/**
|
|
* Determine if a configuration is automatically upgradeable.
|
|
* @param obj The configuration. It is not modified.
|
|
* @returns `true` if the configuration is upgradeable.
|
|
*/
|
|
export const isConfigUpgradeable = function (obj: RawFrigateCardConfig): boolean {
|
|
return upgradeConfig(copyConfig(obj));
|
|
};
|
|
|
|
/**
|
|
* Remove empty sections from a configuration.
|
|
* @param obj Configuration object.
|
|
* @returns `true` if the configuration was modified.
|
|
*/
|
|
const trimConfig = function (obj: RawFrigateCardConfig): boolean {
|
|
const keys = Object.keys(obj);
|
|
let modified = false;
|
|
for (let i = 0; i < keys.length; i++) {
|
|
const key = keys[i];
|
|
if (typeof obj[key] === 'object' && obj[key] != null) {
|
|
modified = trimConfig(obj[key] as RawFrigateCardConfig) || modified;
|
|
|
|
if (!Object.keys(obj[key] as RawFrigateCardConfig).length) {
|
|
delete obj[key];
|
|
modified = true;
|
|
}
|
|
}
|
|
}
|
|
return modified;
|
|
};
|
|
|
|
/**
|
|
* Copy a configuration.
|
|
* @param obj Configuration to copy.
|
|
* @returns A new deeply-copied configuration.
|
|
*/
|
|
export const copyConfig = <T>(obj: T): T => {
|
|
return cloneDeep(obj);
|
|
};
|
|
|
|
/**
|
|
* Determines if a property is not an object.
|
|
* @param value The value.
|
|
* @returns `true` is the value is not an object.
|
|
*/
|
|
const isNotObject = function (value: unknown): unknown | undefined {
|
|
return typeof value !== 'object' ? value : undefined;
|
|
};
|
|
|
|
/**
|
|
* Converts to a number or return undefined.
|
|
* @param value The value.
|
|
* @returns A number or undefined.
|
|
*/
|
|
const toNumberOrIgnore = function (value: unknown): number | undefined {
|
|
return isNaN(value as number) ? undefined : Number(value);
|
|
};
|
|
|
|
/**
|
|
* Create a transform that will cap a numeric value.
|
|
* @param value The value.
|
|
* @returns A number or null.
|
|
*/
|
|
const createRangedTransform = function (
|
|
transform: (value: unknown) => unknown,
|
|
min?: number,
|
|
max?: number,
|
|
): (valueIn: unknown) => unknown {
|
|
return (value: unknown): unknown => {
|
|
let transformed = transform(value);
|
|
if (typeof transformed !== 'number') {
|
|
return transformed;
|
|
}
|
|
transformed = min ? Math.max(min, transformed as number) : transformed;
|
|
transformed = max ? Math.min(max, transformed as number) : transformed;
|
|
return transformed;
|
|
};
|
|
};
|
|
|
|
/**
|
|
* Convert a value from 'XXpx' to XX (as a number).
|
|
* @param value Incoming value.
|
|
* @returns A number, null if the property should be deleted or undefined if it
|
|
* should be ignored.
|
|
*/
|
|
const toPixelsOrDelete = function (value: unknown): number | null | undefined {
|
|
// Ignore the value if it's a number.
|
|
if (typeof value === 'number') {
|
|
return undefined;
|
|
}
|
|
// Delete the value if it's not a string.
|
|
if (typeof value !== 'string') {
|
|
return null;
|
|
}
|
|
// Remove 'px' and return the number, unless it's an invalid number -- then
|
|
// delete it.
|
|
value = value.replace(/px$/i, '');
|
|
return isNaN(value as number) ? null : Number(value);
|
|
};
|
|
|
|
/**
|
|
* Request a property be deleted.
|
|
* @param _value Inbound value (not required).
|
|
* @returns `null` to request the property be deleted.
|
|
*/
|
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
const deleteProperty = function (_value: unknown): number | null | undefined {
|
|
return null;
|
|
};
|
|
|
|
/**
|
|
* Move a property from one location to another.
|
|
* @param obj The configuration object in which the property resides.
|
|
* @param oldPath The old property path.
|
|
* @param newPath The new property path.
|
|
* @param transform An optional transform for the value.
|
|
* @returns `true` if the configuration was modified.
|
|
*/
|
|
const moveConfigValue = (
|
|
obj: RawFrigateCardConfig,
|
|
oldPath: string,
|
|
newPath: string,
|
|
options?: {
|
|
transform?: (valueIn: unknown) => unknown;
|
|
keepOriginal?: boolean;
|
|
},
|
|
): boolean => {
|
|
const inValue = getConfigValue(obj, oldPath);
|
|
if (inValue === undefined) {
|
|
return false;
|
|
}
|
|
const outValue = options?.transform ? options.transform(inValue) : inValue;
|
|
if (oldPath === newPath && isEqual(inValue, outValue)) {
|
|
return false;
|
|
}
|
|
if (outValue === null) {
|
|
if (!options?.keepOriginal) {
|
|
deleteConfigValue(obj, oldPath);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
if (outValue !== undefined) {
|
|
if (!options?.keepOriginal) {
|
|
deleteConfigValue(obj, oldPath);
|
|
}
|
|
setConfigValue(obj, newPath, outValue);
|
|
return true;
|
|
}
|
|
return false;
|
|
};
|
|
|
|
/**
|
|
* Given an array path, return a true path.
|
|
* @param path The array path (should have a '#').
|
|
* @param index The numeric array index to use.
|
|
* @returns The true config path.
|
|
*/
|
|
export const getArrayConfigPath = (path: string, index: number): string => {
|
|
return path.replace('#', `[${index.toString()}]`);
|
|
};
|
|
|
|
/**
|
|
* Upgrade by moving a property from one location to another.
|
|
* @param oldPath The old property path.
|
|
* @param newPath The new property path.
|
|
* @param transform An optional transform for the value.
|
|
* @returns `true` if the configuration was modified.
|
|
*/
|
|
const upgradeMoveTo = function (
|
|
oldPath: string,
|
|
newPath: string,
|
|
options?: {
|
|
transform?: (valueIn: unknown) => unknown;
|
|
keepOriginal?: boolean;
|
|
},
|
|
): (obj: RawFrigateCardConfig) => boolean {
|
|
return function (obj: RawFrigateCardConfig): boolean {
|
|
return moveConfigValue(obj, oldPath, newPath, options);
|
|
};
|
|
};
|
|
|
|
/**
|
|
* Upgrade by moving a property from one location to another, and moving a
|
|
* property specified in a top-level overrides object.
|
|
* @param oldPath The old property path.
|
|
* @param newPath The new property path.
|
|
* @param transform An optional transform for the value.
|
|
* @returns A function that returns `true` if the configuration was modified.
|
|
*/
|
|
const upgradeMoveToWithOverrides = function (
|
|
oldPath: string,
|
|
newPath: string,
|
|
options?: {
|
|
transform?: (valueIn: unknown) => unknown;
|
|
keepOriginal?: boolean;
|
|
},
|
|
): (obj: RawFrigateCardConfig) => boolean {
|
|
return function (obj: RawFrigateCardConfig): boolean {
|
|
let modified = upgradeMoveTo(oldPath, newPath, options)(obj);
|
|
modified =
|
|
upgradeArrayValue(
|
|
CONF_OVERRIDES,
|
|
upgradeMoveTo(oldPath, newPath, options),
|
|
(obj) => obj.overrides as RawFrigateCardConfig | undefined,
|
|
)(obj) || modified;
|
|
return modified;
|
|
};
|
|
};
|
|
|
|
/**
|
|
* Upgrade a property in place with overrides.
|
|
* @param path The old property path.
|
|
* @param transform An optional transform for the value.
|
|
* @returns A function that returns `true` if the configuration was modified.
|
|
*/
|
|
const upgradeWithOverrides = function (
|
|
path: string,
|
|
transform: (valueIn: unknown) => unknown,
|
|
): (obj: RawFrigateCardConfig) => boolean {
|
|
return upgradeMoveToWithOverrides(path, path, { transform: transform });
|
|
};
|
|
|
|
/**
|
|
* Upgrade a property in place without overrides.
|
|
* @param path The old property path.
|
|
* @param transform An optional transform for the value.
|
|
* @returns A function that returns `true` if the configuration was modified.
|
|
*/
|
|
const upgrade = function (
|
|
path: string,
|
|
transform: (valueIn: unknown) => unknown,
|
|
): (obj: RawFrigateCardConfig) => boolean {
|
|
return upgradeMoveTo(path, path, { transform: transform });
|
|
};
|
|
|
|
/**
|
|
* Given a path to an array, apply an upgrade to each object in the array.
|
|
* @param arrayPath The path to the array to upgrade.
|
|
* @param upgrade A function that applies an upgrade to an object.
|
|
* @param getObject A optional function that takes an item in the array and
|
|
* returns the object to modify within it.
|
|
* @returns A function that returns `true` if the configuration was modified.
|
|
*/
|
|
const upgradeArrayValue = function (
|
|
arrayPath: string,
|
|
upgrade: (obj: RawFrigateCardConfig) => boolean,
|
|
getObject?: (obj: RawFrigateCardConfig) => RawFrigateCardConfig | undefined,
|
|
): (obj: RawFrigateCardConfig) => boolean {
|
|
return function (obj: RawFrigateCardConfig): boolean {
|
|
let modified = false;
|
|
const array = getConfigValue(obj, arrayPath);
|
|
if (Array.isArray(array)) {
|
|
array.forEach((item) => {
|
|
const object = getObject ? getObject(item) : item;
|
|
if (object && typeof object === 'object') {
|
|
modified = upgrade(object) || modified;
|
|
}
|
|
});
|
|
}
|
|
return modified;
|
|
};
|
|
};
|
|
|
|
/**
|
|
* Upgrade from a singular camera model to multiple.
|
|
* @returns An upgrade function.
|
|
*/
|
|
const upgradeToMultipleCameras = (): ((obj: RawFrigateCardConfig) => boolean) => {
|
|
return function (obj: RawFrigateCardConfig): boolean {
|
|
let modified = false;
|
|
const cameras = getConfigValue(obj, CONF_CAMERAS) as RawFrigateCardConfigArray;
|
|
|
|
// Only do an upgrade if the cameras section does not exist.
|
|
if (cameras !== undefined) {
|
|
return false;
|
|
}
|
|
|
|
const imports = {
|
|
camera_entity: CONF_CAMERAS_ARRAY_CAMERA_ENTITY,
|
|
'frigate.camera_name': 'cameras.#.camera_name',
|
|
'frigate.client_id': 'cameras.#.client_id',
|
|
'frigate.label': 'cameras.#.label',
|
|
'frigate.url': 'cameras.#.frigate_url',
|
|
'frigate.zone': 'cameras.#.zone',
|
|
'live.webrtc.entity': `cameras.#.webrtc.entity`,
|
|
'live.webrtc.url': `cameras.#.webrtc.url`,
|
|
'live.provider': CONF_CAMERAS_ARRAY_LIVE_PROVIDER,
|
|
};
|
|
Object.keys(imports).forEach((key) => {
|
|
modified =
|
|
moveConfigValue(obj, key, getArrayConfigPath(imports[key], 0)) || modified;
|
|
});
|
|
return modified;
|
|
};
|
|
};
|
|
|
|
/**
|
|
* Upgrade from a menu-mode to a style & position.
|
|
* @returns An upgrade function.
|
|
*/
|
|
const upgradeMenuModeToStyleAndPosition = (): ((
|
|
obj: RawFrigateCardConfig,
|
|
) => boolean) => {
|
|
return function (obj: RawFrigateCardConfig): boolean {
|
|
let modified = false;
|
|
|
|
// Change the 'start' of the mode into a style.
|
|
modified =
|
|
upgradeMoveToWithOverrides('menu.mode', CONF_MENU_STYLE, {
|
|
transform: (mode: unknown): string | undefined => {
|
|
if (typeof mode === 'string') {
|
|
const result = mode.match(/^(hover|hidden|overlay|above|below|none)/);
|
|
if (result) {
|
|
switch (result[1]) {
|
|
case 'hover':
|
|
case 'hidden':
|
|
case 'overlay':
|
|
case 'none':
|
|
return result[1];
|
|
case 'above':
|
|
case 'below':
|
|
return 'outside';
|
|
}
|
|
}
|
|
}
|
|
return undefined;
|
|
},
|
|
keepOriginal: true,
|
|
})(obj) || modified;
|
|
|
|
// Change the 'end' of the mode into a position.
|
|
modified =
|
|
upgradeMoveToWithOverrides('menu.mode', CONF_MENU_POSITION, {
|
|
transform: (mode: unknown): string | undefined => {
|
|
if (typeof mode === 'string') {
|
|
const result = mode.match(/(above|below|left|right|top|bottom)$/);
|
|
if (result) {
|
|
switch (result[1]) {
|
|
case 'left':
|
|
case 'right':
|
|
case 'top':
|
|
case 'bottom':
|
|
return result[1];
|
|
case 'above':
|
|
return 'top';
|
|
case 'below':
|
|
return 'bottom';
|
|
}
|
|
}
|
|
}
|
|
return undefined;
|
|
},
|
|
keepOriginal: true,
|
|
})(obj) || modified;
|
|
|
|
// Delete the old `menu.mode` .
|
|
return upgradeWithOverrides('menu.mode', deleteProperty)(obj) || modified;
|
|
};
|
|
};
|
|
|
|
/**
|
|
* Upgrade from a condition on the menu (to allow rendering) to a menu mode
|
|
* override instead.
|
|
* @param key A string key.
|
|
* @returns A safe key.
|
|
*/
|
|
const upgradeMenuConditionToMenuOverride = (): ((
|
|
obj: RawFrigateCardConfig,
|
|
) => boolean) => {
|
|
return function (obj: RawFrigateCardConfig): boolean {
|
|
const menuConditions = getConfigValue(
|
|
obj,
|
|
`${CONF_MENU}.conditions`,
|
|
) as RawFrigateCardConfig;
|
|
|
|
if (menuConditions === undefined) {
|
|
return false;
|
|
}
|
|
|
|
const overrides =
|
|
(getConfigValue(obj, `${CONF_OVERRIDES}`) as RawFrigateCardConfigArray) || [];
|
|
setConfigValue(obj, `${CONF_OVERRIDES}.[${overrides.length}]`, {
|
|
conditions: menuConditions,
|
|
overrides: {
|
|
menu: {
|
|
mode: 'none',
|
|
},
|
|
},
|
|
});
|
|
deleteConfigValue(obj, `${CONF_MENU}.conditions`);
|
|
return true;
|
|
};
|
|
};
|
|
|
|
/**
|
|
* Transform a menu button from a boolean to a priority.
|
|
* @param value The boolean true/false for show/hide the switch.
|
|
* @returns A priority value.
|
|
*/
|
|
const menuButtonBooleanToObject = function (
|
|
value: unknown,
|
|
): { enabled: boolean } | null | undefined {
|
|
if (typeof value === 'object') {
|
|
return undefined;
|
|
}
|
|
// If it's not a boolean remove it.
|
|
if (typeof value !== 'boolean') {
|
|
return null;
|
|
}
|
|
return { enabled: value };
|
|
};
|
|
|
|
/**
|
|
* Upgrade from a show_controls key to individual favorite/timeline keys.
|
|
* @returns An upgrade function.
|
|
*/
|
|
const upgradeThumbnailShowControlsToIndividualControls = (
|
|
thumbnailsBasePath: string,
|
|
): ((obj: RawFrigateCardConfig) => boolean) => {
|
|
const thumbnailsShowControlsPath = `${thumbnailsBasePath}.show_controls`;
|
|
|
|
return function (obj: RawFrigateCardConfig): boolean {
|
|
let modified = false;
|
|
modified =
|
|
upgradeMoveToWithOverrides(
|
|
thumbnailsShowControlsPath,
|
|
`${thumbnailsBasePath}.show_favorite_control`,
|
|
{ keepOriginal: true },
|
|
)(obj) || modified;
|
|
|
|
modified =
|
|
upgradeMoveToWithOverrides(
|
|
thumbnailsShowControlsPath,
|
|
`${thumbnailsBasePath}.show_timeline_control`,
|
|
{ keepOriginal: true },
|
|
)(obj) || modified;
|
|
|
|
// Delete the old `show_controls`.
|
|
return (
|
|
upgradeWithOverrides(thumbnailsShowControlsPath, deleteProperty)(obj) || modified
|
|
);
|
|
};
|
|
};
|
|
|
|
/**
|
|
* Recursively upgrade an object.
|
|
* @param transform A transform applied to each object recursively.
|
|
* @param getObject A function to get the object to be upgraded.
|
|
* @returns An upgrade function.
|
|
*/
|
|
const recursiveUpgradeObject = (
|
|
transform: (data: RawFrigateCardConfig) => boolean,
|
|
getObject?: (data: RawFrigateCardConfig) => RawFrigateCardConfig | undefined | null,
|
|
): ((data: RawFrigateCardConfig) => boolean) => {
|
|
const recurse = (data: RawFrigateCardConfig): boolean => {
|
|
let result = false;
|
|
if (data && typeof data === 'object') {
|
|
const object = getObject ? getObject(data) : data;
|
|
if (object) {
|
|
result = transform(object) || result;
|
|
}
|
|
if (Array.isArray(data)) {
|
|
data
|
|
.filter((item) => typeof item === 'object')
|
|
.forEach((item: RawFrigateCardConfig) => {
|
|
result = recurse(item) || result;
|
|
});
|
|
} else {
|
|
Object.keys(data)
|
|
.filter((key) => typeof data[key] === 'object')
|
|
.forEach((key) => {
|
|
result = recurse(data[key] as RawFrigateCardConfig) || result;
|
|
});
|
|
}
|
|
}
|
|
return result;
|
|
};
|
|
return recurse;
|
|
};
|
|
|
|
/**
|
|
* Transform mediaLoaded -> media_loaded
|
|
* @param data Input data.
|
|
* @returns `true` if the configuration was modified.
|
|
*/
|
|
const transformConditionMediaLoaded = (data: unknown): boolean => {
|
|
if (typeof data === 'object' && data && data['mediaLoaded'] !== undefined) {
|
|
data['media_loaded'] = data['mediaLoaded'];
|
|
delete data['mediaLoaded'];
|
|
return true;
|
|
}
|
|
return false;
|
|
};
|
|
|
|
const UPGRADES = [
|
|
// v1.2.1 -> v2.0.0
|
|
upgradeMoveTo('frigate_url', 'frigate.url'),
|
|
upgradeMoveTo('frigate_client_id', 'frigate.client_id'),
|
|
upgradeMoveTo('frigate_camera_name', 'frigate.camera_name'),
|
|
upgradeMoveTo('label', 'frigate.label'),
|
|
upgradeMoveTo('zone', 'frigate.zone'),
|
|
upgradeMoveTo('view_default', CONF_VIEW_DEFAULT),
|
|
upgradeMoveTo('view_timeout', 'view.timeout'),
|
|
upgradeMoveTo('live_provider', 'live.provider'),
|
|
upgradeMoveTo('live_preload', CONF_LIVE_PRELOAD),
|
|
upgradeMoveTo('webrtc', 'live.webrtc'),
|
|
upgradeMoveTo('autoplay_clip', 'event_viewer.autoplay_clip'),
|
|
upgradeMoveTo('controls.nextprev', 'event_viewer.controls.next_previous.style'),
|
|
upgradeMoveTo('controls.nextprev_size', 'event_viewer.controls.next_previous.size'),
|
|
upgradeMoveTo('menu_mode', 'menu.mode'),
|
|
upgradeMoveTo('menu_buttons', 'menu.buttons'),
|
|
upgradeMoveTo('menu_button_size', CONF_MENU_BUTTON_SIZE),
|
|
upgradeMoveTo('image', 'image.src', { transform: isNotObject }),
|
|
|
|
// v2.0.0 -> v2.1.0
|
|
upgradeMoveTo('update_entities', CONF_VIEW_UPDATE_ENTITIES),
|
|
|
|
// v2.1.0 -> v3.0.0-rc.1
|
|
upgradeToMultipleCameras(),
|
|
upgradeMenuConditionToMenuOverride(),
|
|
upgradeMoveTo('view.timeout', CONF_VIEW_TIMEOUT_SECONDS, {
|
|
transform: toNumberOrIgnore,
|
|
}),
|
|
upgradeMoveTo('event_viewer.autoplay_clip', 'event_viewer.auto_play'),
|
|
|
|
// v3.0.0-rc.1 -> v3.0.0-rc.2
|
|
upgradeArrayValue(
|
|
CONF_CAMERAS,
|
|
upgradeWithOverrides('live_provider', (val) =>
|
|
val === 'frigate' ? 'ha' : val === 'webrtc' ? 'webrtc-card' : val,
|
|
),
|
|
),
|
|
upgradeArrayValue(CONF_CAMERAS, upgradeMoveTo('webrtc', 'webrtc_card')),
|
|
upgradeMoveToWithOverrides('live.webrtc', CONF_LIVE_WEBRTC_CARD),
|
|
upgradeMoveToWithOverrides('image.src', CONF_IMAGE_URL),
|
|
|
|
// v3.0.0 -> v4.0.0-rc.1
|
|
upgradeWithOverrides(
|
|
CONF_LIVE_CONTROLS_THUMBNAILS_SIZE,
|
|
createRangedTransform(toPixelsOrDelete, THUMBNAIL_WIDTH_MIN, THUMBNAIL_WIDTH_MAX),
|
|
),
|
|
upgradeWithOverrides(
|
|
'event_viewer.controls.thumbnails.size',
|
|
createRangedTransform(toPixelsOrDelete, THUMBNAIL_WIDTH_MIN, THUMBNAIL_WIDTH_MAX),
|
|
),
|
|
upgradeWithOverrides(
|
|
CONF_LIVE_CONTROLS_NEXT_PREVIOUS_SIZE,
|
|
createRangedTransform(toPixelsOrDelete, BUTTON_SIZE_MIN),
|
|
),
|
|
upgradeWithOverrides(
|
|
'event_viewer.controls.next_previous.size',
|
|
createRangedTransform(toPixelsOrDelete, BUTTON_SIZE_MIN),
|
|
),
|
|
upgradeWithOverrides(
|
|
CONF_MENU_BUTTON_SIZE,
|
|
createRangedTransform(toPixelsOrDelete, BUTTON_SIZE_MIN),
|
|
),
|
|
upgradeWithOverrides('event_gallery.min_columns', deleteProperty),
|
|
upgradeMenuModeToStyleAndPosition(),
|
|
upgradeWithOverrides(CONF_MENU_BUTTONS_FRIGATE, menuButtonBooleanToObject),
|
|
upgradeWithOverrides(CONF_MENU_BUTTONS_CAMERAS, menuButtonBooleanToObject),
|
|
upgradeWithOverrides(CONF_MENU_BUTTONS_LIVE, menuButtonBooleanToObject),
|
|
upgradeWithOverrides(CONF_MENU_BUTTONS_CLIPS, menuButtonBooleanToObject),
|
|
upgradeWithOverrides(CONF_MENU_BUTTONS_SNAPSHOTS, menuButtonBooleanToObject),
|
|
upgradeWithOverrides(CONF_MENU_BUTTONS_IMAGE, menuButtonBooleanToObject),
|
|
upgradeWithOverrides(CONF_MENU_BUTTONS_DOWNLOAD, menuButtonBooleanToObject),
|
|
upgradeWithOverrides(CONF_MENU_BUTTONS_FRIGATE_UI, menuButtonBooleanToObject),
|
|
upgradeWithOverrides(CONF_MENU_BUTTONS_FULLSCREEN, menuButtonBooleanToObject),
|
|
upgrade(CONF_LIVE_LAZY_UNLOAD, (val) =>
|
|
typeof val === 'boolean' ? (val ? 'all' : 'never') : undefined,
|
|
),
|
|
upgrade(CONF_LIVE_AUTO_UNMUTE, (val) =>
|
|
typeof val === 'boolean' ? (val ? 'all' : 'never') : undefined,
|
|
),
|
|
upgrade('event_viewer.auto_play', (val) =>
|
|
typeof val === 'boolean' ? (val ? 'all' : 'never') : undefined,
|
|
),
|
|
upgrade('event_viewer.auto_unmute', (val) =>
|
|
typeof val === 'boolean' ? (val ? 'all' : 'never') : undefined,
|
|
),
|
|
upgradeMoveToWithOverrides('event_viewer', CONF_MEDIA_VIEWER),
|
|
upgradeArrayValue(CONF_CAMERAS, upgradeMoveTo('camera_name', 'frigate.camera_name')),
|
|
upgradeArrayValue(CONF_CAMERAS, upgradeMoveTo('client_id', 'frigate.client_id')),
|
|
upgradeArrayValue(CONF_CAMERAS, upgradeMoveTo('label', 'frigate.label')),
|
|
upgradeArrayValue(CONF_CAMERAS, upgradeMoveTo('frigate_url', 'frigate.url')),
|
|
upgradeArrayValue(CONF_CAMERAS, upgradeMoveTo('zone', 'frigate.zone')),
|
|
|
|
// v4.0.0-rc.1 -> v4.0.0-rc.3
|
|
upgradeThumbnailShowControlsToIndividualControls('event_gallery.controls.thumbnails'),
|
|
upgradeThumbnailShowControlsToIndividualControls('media_viewer.controls.thumbnails'),
|
|
upgradeThumbnailShowControlsToIndividualControls('live.controls.thumbnails'),
|
|
upgradeThumbnailShowControlsToIndividualControls('timeline.controls.thumbnails'),
|
|
|
|
// v4.0.0 -> v4.1.0
|
|
upgradeArrayValue(
|
|
CONF_OVERRIDES,
|
|
transformConditionMediaLoaded,
|
|
(data) => data.conditions as RawFrigateCardConfig | undefined,
|
|
),
|
|
(data: unknown): boolean => {
|
|
return recursiveUpgradeObject(
|
|
transformConditionMediaLoaded,
|
|
(data) => data.conditions as RawFrigateCardConfig | undefined,
|
|
)(typeof data === 'object' && data ? data[CONF_ELEMENTS] : {});
|
|
},
|
|
];
|