Initial editor support.
This commit is contained in:
+1
-1
@@ -41,7 +41,7 @@ const plugins = [
|
|||||||
exclude: 'node_modules/**',
|
exclude: 'node_modules/**',
|
||||||
}),
|
}),
|
||||||
dev && serve(serveopts),
|
dev && serve(serveopts),
|
||||||
!dev && terser(),
|
//!dev && terser(),
|
||||||
];
|
];
|
||||||
|
|
||||||
export default [
|
export default [
|
||||||
|
|||||||
+2
-2
@@ -429,8 +429,8 @@ export class FrigateCard extends LitElement {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if (this.config.camera && Array.isArray(this.config.camera)) {
|
if (this.config.cameras && Array.isArray(this.config.cameras)) {
|
||||||
await Promise.all(this.config.camera.map(addCameraConfig.bind(this)));
|
await Promise.all(this.config.cameras.map(addCameraConfig.bind(this)));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!cameras.size) {
|
if (!cameras.size) {
|
||||||
|
|||||||
+16
-3
@@ -287,7 +287,8 @@ export function convertActionToFrigateCardCustomAction(
|
|||||||
*/
|
*/
|
||||||
export function createFrigateCardCustomAction(
|
export function createFrigateCardCustomAction(
|
||||||
action: FrigateCardAction,
|
action: FrigateCardAction,
|
||||||
camera?: string): FrigateCardCustomAction | undefined {
|
camera?: string,
|
||||||
|
): FrigateCardCustomAction | undefined {
|
||||||
if (action == 'camera_select') {
|
if (action == 'camera_select') {
|
||||||
if (!camera) {
|
if (!camera) {
|
||||||
return undefined;
|
return undefined;
|
||||||
@@ -296,12 +297,12 @@ export function createFrigateCardCustomAction(
|
|||||||
action: 'fire-dom-event',
|
action: 'fire-dom-event',
|
||||||
frigate_card_action: action,
|
frigate_card_action: action,
|
||||||
camera: camera,
|
camera: camera,
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
action: 'fire-dom-event',
|
action: 'fire-dom-event',
|
||||||
frigate_card_action: action,
|
frigate_card_action: action,
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -418,3 +419,15 @@ export function refreshCameraConfigDynamicParameters(
|
|||||||
config.icon = config.icon ?? (state ? stateIcon(state) : 'mdi:video');
|
config.icon = config.icon ?? (state ? stateIcon(state) : 'mdi:video');
|
||||||
return config;
|
return config;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Move an element within an array.
|
||||||
|
* @param target Target array.
|
||||||
|
* @param from From index.
|
||||||
|
* @param to To index.
|
||||||
|
*/
|
||||||
|
export function arrayMove(target: unknown[], from: number, to: number): void {
|
||||||
|
const element = target[from];
|
||||||
|
target.splice(from, 1);
|
||||||
|
target.splice(to, 0, element);
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
// TODO editor
|
// TODO editor
|
||||||
|
// TODO editor: fill in a new camera and keep focus
|
||||||
|
// TODO editor: fill in a new camera then backspace it away
|
||||||
// TODO webrtc entities in camera section?
|
// TODO webrtc entities in camera section?
|
||||||
// TODO conditional elements based on camera name (requires event changed to propagate upwards)
|
// TODO conditional elements based on camera name (requires event changed to propagate upwards)
|
||||||
// TODO change url to frigate_url?
|
// TODO change url to frigate_url? Would need to also fix upgrade logic to refer to new name.
|
||||||
// TODO Remove media load event warning
|
// TODO Remove media load event warning
|
||||||
// TODO readme
|
// TODO readme
|
||||||
// TODO search for TODOs
|
// TODO search for TODOs
|
||||||
|
|||||||
+98
-21
@@ -1,14 +1,16 @@
|
|||||||
import delve from 'dlv';
|
import delve from 'dlv';
|
||||||
import { dset } from 'dset';
|
import { dset } from 'dset';
|
||||||
import {
|
import {
|
||||||
|
CONF_CAMERAS,
|
||||||
|
CONF_CAMERAS_ARRAY_CAMERA_ENTITY,
|
||||||
|
CONF_CAMERAS_ARRAY_CAMERA_NAME,
|
||||||
|
CONF_CAMERAS_ARRAY_CLIENT_ID,
|
||||||
|
CONF_CAMERAS_ARRAY_LABEL,
|
||||||
|
CONF_CAMERAS_ARRAY_URL,
|
||||||
|
CONF_CAMERAS_ARRAY_ZONE,
|
||||||
CONF_EVENT_VIEWER_AUTOPLAY_CLIP,
|
CONF_EVENT_VIEWER_AUTOPLAY_CLIP,
|
||||||
CONF_EVENT_VIEWER_CONTROLS_NEXT_PREVIOUS_SIZE,
|
CONF_EVENT_VIEWER_CONTROLS_NEXT_PREVIOUS_SIZE,
|
||||||
CONF_EVENT_VIEWER_CONTROLS_NEXT_PREVIOUS_STYLE,
|
CONF_EVENT_VIEWER_CONTROLS_NEXT_PREVIOUS_STYLE,
|
||||||
CONF_FRIGATE_CAMERA_NAME,
|
|
||||||
CONF_FRIGATE_CLIENT_ID,
|
|
||||||
CONF_FRIGATE_LABEL,
|
|
||||||
CONF_FRIGATE_URL,
|
|
||||||
CONF_FRIGATE_ZONE,
|
|
||||||
CONF_IMAGE_SRC,
|
CONF_IMAGE_SRC,
|
||||||
CONF_LIVE_PRELOAD,
|
CONF_LIVE_PRELOAD,
|
||||||
CONF_LIVE_PROVIDER,
|
CONF_LIVE_PROVIDER,
|
||||||
@@ -18,7 +20,7 @@ import {
|
|||||||
CONF_VIEW_TIMEOUT,
|
CONF_VIEW_TIMEOUT,
|
||||||
CONF_VIEW_UPDATE_ENTITIES,
|
CONF_VIEW_UPDATE_ENTITIES,
|
||||||
} from './const';
|
} from './const';
|
||||||
import { RawFrigateCardConfig } from './types';
|
import { RawFrigateCardConfig, RawFrigateCardConfigArray } from './types';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set a configuration value.
|
* Set a configuration value.
|
||||||
@@ -26,12 +28,13 @@ import { RawFrigateCardConfig } from './types';
|
|||||||
* @param key The key to the property to set.
|
* @param key The key to the property to set.
|
||||||
* @param value The value to set.
|
* @param value The value to set.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export const setConfigValue = (
|
export const setConfigValue = (
|
||||||
obj: RawFrigateCardConfig,
|
obj: RawFrigateCardConfig,
|
||||||
key: string,
|
keys: string | (string|number)[],
|
||||||
value: unknown,
|
value: unknown,
|
||||||
): void => {
|
): void => {
|
||||||
dset(obj, key, value);
|
dset(obj, keys, value);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -42,10 +45,14 @@ export const setConfigValue = (
|
|||||||
*/
|
*/
|
||||||
export const getConfigValue = (
|
export const getConfigValue = (
|
||||||
obj: RawFrigateCardConfig,
|
obj: RawFrigateCardConfig,
|
||||||
key: string,
|
keys: string | (string|number)[],
|
||||||
def?: unknown,
|
def?: unknown,
|
||||||
): unknown => {
|
): unknown => {
|
||||||
return delve(obj, key, def);
|
// Need to manually split the key apart to use delve array accesses by number.
|
||||||
|
if (typeof(keys) === 'string') {
|
||||||
|
keys = keys.split('.');
|
||||||
|
}
|
||||||
|
return delve(obj, keys, def);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -93,19 +100,23 @@ export const isConfigUpgradeable = function (obj: RawFrigateCardConfig): boolean
|
|||||||
/**
|
/**
|
||||||
* Remove empty sections from a configuration.
|
* Remove empty sections from a configuration.
|
||||||
* @param obj Configuration object.
|
* @param obj Configuration object.
|
||||||
|
* @returns `true` if the configuration was modified.
|
||||||
*/
|
*/
|
||||||
export const trimConfig = function (obj: RawFrigateCardConfig): void {
|
export const trimConfig = function (obj: RawFrigateCardConfig): boolean {
|
||||||
const keys = Object.keys(obj);
|
const keys = Object.keys(obj);
|
||||||
|
let modified = false;
|
||||||
for (let i = 0; i < keys.length; i++) {
|
for (let i = 0; i < keys.length; i++) {
|
||||||
const key = keys[i];
|
const key = keys[i];
|
||||||
if (typeof obj[key] === 'object' && obj[key] != null) {
|
if (typeof obj[key] === 'object' && obj[key] != null) {
|
||||||
trimConfig(obj[key] as RawFrigateCardConfig);
|
modified ||= trimConfig(obj[key] as RawFrigateCardConfig);
|
||||||
|
|
||||||
if (!Object.keys(obj[key] as RawFrigateCardConfig).length) {
|
if (!Object.keys(obj[key] as RawFrigateCardConfig).length) {
|
||||||
delete obj[key];
|
delete obj[key];
|
||||||
|
modified = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return modified;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -128,17 +139,18 @@ const isNotObject = function (value: unknown) {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Move a property from one location to another.
|
* 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 oldPath The old property path.
|
||||||
* @param newPath The new property path.
|
* @param newPath The new property path.
|
||||||
* @param transform An optional transform for the value.
|
* @param transform An optional transform for the value.
|
||||||
* @returns `true` if the configuration was modified.
|
* @returns `true` if the configuration was modified.
|
||||||
*/
|
*/
|
||||||
const upgradeMoveTo = function (
|
export const moveConfigValue = (
|
||||||
|
obj: RawFrigateCardConfig,
|
||||||
oldPath: string,
|
oldPath: string,
|
||||||
newPath: string,
|
newPath: string,
|
||||||
transform?: (valueIn: unknown) => unknown,
|
transform?: (valueIn: unknown) => (unknown),
|
||||||
): (obj: RawFrigateCardConfig) => boolean {
|
): boolean => {
|
||||||
return function (obj: RawFrigateCardConfig): boolean {
|
|
||||||
let value = getConfigValue(obj, oldPath);
|
let value = getConfigValue(obj, oldPath);
|
||||||
if (transform) {
|
if (transform) {
|
||||||
value = transform(value);
|
value = transform(value);
|
||||||
@@ -149,16 +161,78 @@ const upgradeMoveTo = function (
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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,
|
||||||
|
transform?: (valueIn: unknown) => (unknown),
|
||||||
|
): (obj: RawFrigateCardConfig) => boolean {
|
||||||
|
return function (obj: RawFrigateCardConfig): boolean {
|
||||||
|
return moveConfigValue(obj, oldPath, newPath, transform);
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sanitize a potentially-unsafe key segment.
|
||||||
|
* @param key A string key.
|
||||||
|
* @returns A safe key.
|
||||||
|
*/
|
||||||
|
// const sanitizeKeySegment = (key: string): string => {
|
||||||
|
// return key.replace(/\.+/g, '_');
|
||||||
|
// }
|
||||||
|
|
||||||
|
const upgradeToMultipleCameras = (): ((obj: RawFrigateCardConfig) => boolean) => {
|
||||||
|
return function (obj: RawFrigateCardConfig): boolean {
|
||||||
|
let modified = false;
|
||||||
|
|
||||||
|
const camera = {}
|
||||||
|
const imports = {
|
||||||
|
'camera_entity': CONF_CAMERAS_ARRAY_CAMERA_ENTITY,
|
||||||
|
'frigate.camera_name': CONF_CAMERAS_ARRAY_CAMERA_NAME,
|
||||||
|
'frigate.client_id': CONF_CAMERAS_ARRAY_CLIENT_ID,
|
||||||
|
'frigate.label': CONF_CAMERAS_ARRAY_LABEL,
|
||||||
|
'frigate.url': CONF_CAMERAS_ARRAY_URL,
|
||||||
|
'frigate.zone': CONF_CAMERAS_ARRAY_ZONE,
|
||||||
|
}
|
||||||
|
Object.keys(imports).forEach((key) => {
|
||||||
|
const oldValue = getConfigValue(obj, key);
|
||||||
|
if (oldValue !== undefined) {
|
||||||
|
camera[imports[key]] = oldValue;
|
||||||
|
deleteConfigValue(obj, key)
|
||||||
|
modified = true;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if (modified) {
|
||||||
|
let cameras = getConfigValue(obj, CONF_CAMERAS) as RawFrigateCardConfigArray;
|
||||||
|
if (!Array.isArray(cameras)) {
|
||||||
|
// Note: This will replace `cameras` if it already exists and isn't an
|
||||||
|
// array.
|
||||||
|
cameras = []
|
||||||
|
}
|
||||||
|
cameras.push(camera);
|
||||||
|
setConfigValue(obj, CONF_CAMERAS, cameras)
|
||||||
|
trimConfig(obj);
|
||||||
|
}
|
||||||
|
return modified;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const UPGRADES = [
|
const UPGRADES = [
|
||||||
// v1.2.1 -> v2.0.0
|
// v1.2.1 -> v2.0.0
|
||||||
upgradeMoveTo('frigate_url', CONF_FRIGATE_URL),
|
upgradeMoveTo('frigate_url', 'frigate.url'),
|
||||||
upgradeMoveTo('frigate_client_id', CONF_FRIGATE_CLIENT_ID),
|
upgradeMoveTo('frigate_client_id', 'frigate.client_id'),
|
||||||
upgradeMoveTo('frigate_camera_name', CONF_FRIGATE_CAMERA_NAME),
|
upgradeMoveTo('frigate_camera_name', 'frigate.camera_name'),
|
||||||
upgradeMoveTo('label', CONF_FRIGATE_LABEL),
|
upgradeMoveTo('label', 'frigate.label'),
|
||||||
upgradeMoveTo('zone', CONF_FRIGATE_ZONE),
|
upgradeMoveTo('zone', 'frigate.zone'),
|
||||||
upgradeMoveTo('view_default', CONF_VIEW_DEFAULT),
|
upgradeMoveTo('view_default', CONF_VIEW_DEFAULT),
|
||||||
upgradeMoveTo('view_timeout', CONF_VIEW_TIMEOUT),
|
upgradeMoveTo('view_timeout', CONF_VIEW_TIMEOUT),
|
||||||
upgradeMoveTo('live_provider', CONF_LIVE_PROVIDER),
|
upgradeMoveTo('live_provider', CONF_LIVE_PROVIDER),
|
||||||
@@ -174,4 +248,7 @@ const UPGRADES = [
|
|||||||
|
|
||||||
// v2.0.0 -> v2.1.0
|
// v2.0.0 -> v2.1.0
|
||||||
upgradeMoveTo('update_entities', CONF_VIEW_UPDATE_ENTITIES),
|
upgradeMoveTo('update_entities', CONF_VIEW_UPDATE_ENTITIES),
|
||||||
|
|
||||||
|
// v2.1.0 -> v3.0.0
|
||||||
|
upgradeToMultipleCameras(),
|
||||||
];
|
];
|
||||||
|
|||||||
+7
-6
@@ -2,12 +2,13 @@ export const CARD_VERSION = '2.1.0';
|
|||||||
export const REPO_URL = 'https://github.com/dermotduffy/frigate-hass-card';
|
export const REPO_URL = 'https://github.com/dermotduffy/frigate-hass-card';
|
||||||
export const TROUBLESHOOTING_URL = `${REPO_URL}#troubleshooting`;
|
export const TROUBLESHOOTING_URL = `${REPO_URL}#troubleshooting`;
|
||||||
|
|
||||||
export const CONF_CAMERA_ENTITY = 'camera_entity';
|
export const CONF_CAMERAS = 'cameras';
|
||||||
export const CONF_FRIGATE_CAMERA_NAME = 'frigate.camera_name';
|
export const CONF_CAMERAS_ARRAY_CAMERA_ENTITY = 'cameras.#.camera_entity';
|
||||||
export const CONF_FRIGATE_CLIENT_ID = 'frigate.client_id';
|
export const CONF_CAMERAS_ARRAY_CAMERA_NAME = 'cameras.#.camera_name';
|
||||||
export const CONF_FRIGATE_LABEL = 'frigate.label';
|
export const CONF_CAMERAS_ARRAY_CLIENT_ID = 'cameras.#.client_id';
|
||||||
export const CONF_FRIGATE_URL = 'frigate.url';
|
export const CONF_CAMERAS_ARRAY_LABEL = 'cameras.#.label';
|
||||||
export const CONF_FRIGATE_ZONE = 'frigate.zone';
|
export const CONF_CAMERAS_ARRAY_URL = 'cameras.#.url';
|
||||||
|
export const CONF_CAMERAS_ARRAY_ZONE = 'cameras.#.zone';
|
||||||
|
|
||||||
export const CONF_VIEW_DEFAULT = 'view.default';
|
export const CONF_VIEW_DEFAULT = 'view.default';
|
||||||
export const CONF_VIEW_TIMEOUT = 'view.timeout';
|
export const CONF_VIEW_TIMEOUT = 'view.timeout';
|
||||||
|
|||||||
+239
-67
@@ -5,20 +5,20 @@ import { ifDefined } from 'lit/directives/if-defined.js';
|
|||||||
|
|
||||||
import { HomeAssistant, LovelaceCardEditor, fireEvent } from 'custom-card-helpers';
|
import { HomeAssistant, LovelaceCardEditor, fireEvent } from 'custom-card-helpers';
|
||||||
import { localize } from './localize/localize.js';
|
import { localize } from './localize/localize.js';
|
||||||
import { frigateCardConfigDefaults, RawFrigateCardConfig } from './types.js';
|
import {
|
||||||
|
frigateCardConfigDefaults,
|
||||||
|
RawFrigateCardConfig,
|
||||||
|
RawFrigateCardConfigArray,
|
||||||
|
} from './types.js';
|
||||||
|
|
||||||
import frigate_card_editor_style from './scss/editor.scss';
|
|
||||||
import {
|
import {
|
||||||
copyConfig,
|
CONF_CAMERAS,
|
||||||
deleteConfigValue,
|
CONF_CAMERAS_ARRAY_CAMERA_ENTITY,
|
||||||
getConfigValue,
|
CONF_CAMERAS_ARRAY_CAMERA_NAME,
|
||||||
isConfigUpgradeable,
|
CONF_CAMERAS_ARRAY_CLIENT_ID,
|
||||||
setConfigValue,
|
CONF_CAMERAS_ARRAY_LABEL,
|
||||||
trimConfig,
|
CONF_CAMERAS_ARRAY_URL,
|
||||||
upgradeConfig,
|
CONF_CAMERAS_ARRAY_ZONE,
|
||||||
} from './config-mgmt.js';
|
|
||||||
import {
|
|
||||||
CONF_CAMERA_ENTITY,
|
|
||||||
CONF_DIMENSIONS_ASPECT_RATIO,
|
CONF_DIMENSIONS_ASPECT_RATIO,
|
||||||
CONF_DIMENSIONS_ASPECT_RATIO_MODE,
|
CONF_DIMENSIONS_ASPECT_RATIO_MODE,
|
||||||
CONF_EVENT_VIEWER_AUTOPLAY_CLIP,
|
CONF_EVENT_VIEWER_AUTOPLAY_CLIP,
|
||||||
@@ -28,11 +28,6 @@ import {
|
|||||||
CONF_EVENT_VIEWER_CONTROLS_THUMBNAILS_SIZE,
|
CONF_EVENT_VIEWER_CONTROLS_THUMBNAILS_SIZE,
|
||||||
CONF_EVENT_VIEWER_DRAGGABLE,
|
CONF_EVENT_VIEWER_DRAGGABLE,
|
||||||
CONF_EVENT_VIEWER_LAZY_LOAD,
|
CONF_EVENT_VIEWER_LAZY_LOAD,
|
||||||
CONF_FRIGATE_CAMERA_NAME,
|
|
||||||
CONF_FRIGATE_CLIENT_ID,
|
|
||||||
CONF_FRIGATE_LABEL,
|
|
||||||
CONF_FRIGATE_URL,
|
|
||||||
CONF_FRIGATE_ZONE,
|
|
||||||
CONF_IMAGE_SRC,
|
CONF_IMAGE_SRC,
|
||||||
CONF_LIVE_CONTROLS_THUMBNAILS_MEDIA,
|
CONF_LIVE_CONTROLS_THUMBNAILS_MEDIA,
|
||||||
CONF_LIVE_CONTROLS_THUMBNAILS_MODE,
|
CONF_LIVE_CONTROLS_THUMBNAILS_MODE,
|
||||||
@@ -55,6 +50,17 @@ import {
|
|||||||
CONF_VIEW_TIMEOUT,
|
CONF_VIEW_TIMEOUT,
|
||||||
CONF_VIEW_UPDATE_FORCE,
|
CONF_VIEW_UPDATE_FORCE,
|
||||||
} from './const.js';
|
} from './const.js';
|
||||||
|
import { arrayMove } from './common.js';
|
||||||
|
import {
|
||||||
|
copyConfig,
|
||||||
|
deleteConfigValue,
|
||||||
|
getConfigValue,
|
||||||
|
isConfigUpgradeable,
|
||||||
|
setConfigValue,
|
||||||
|
upgradeConfig,
|
||||||
|
} from './config-mgmt.js';
|
||||||
|
|
||||||
|
import frigate_card_editor_style from './scss/editor.scss';
|
||||||
|
|
||||||
interface EditorOptionsSet {
|
interface EditorOptionsSet {
|
||||||
icon: string;
|
icon: string;
|
||||||
@@ -66,29 +72,27 @@ interface EditorOptions {
|
|||||||
[setName: string]: EditorOptionsSet;
|
[setName: string]: EditorOptionsSet;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface EditorOptionTarget {
|
interface ConfigValueTarget {
|
||||||
configValue: string;
|
configValue: string;
|
||||||
checked?: boolean;
|
checked?: boolean;
|
||||||
value?: string;
|
value?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface EditorCameraTarget {
|
||||||
|
cameraIndex: number;
|
||||||
|
}
|
||||||
|
|
||||||
interface EditorOptionSetTarget {
|
interface EditorOptionSetTarget {
|
||||||
optionSetName: string;
|
optionSetName: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const options: EditorOptions = {
|
const options: EditorOptions = {
|
||||||
basic: {
|
cameras: {
|
||||||
icon: 'cog',
|
icon: 'video',
|
||||||
name: localize('editor.basic'),
|
name: localize('editor.cameras'),
|
||||||
secondary: localize('editor.basic_secondary'),
|
secondary: localize('editor.cameras_secondary'),
|
||||||
show: true,
|
show: true,
|
||||||
},
|
},
|
||||||
frigate: {
|
|
||||||
icon: 'alpha-f-box',
|
|
||||||
name: localize('editor.frigate'),
|
|
||||||
secondary: localize('editor.frigate_secondary'),
|
|
||||||
show: false,
|
|
||||||
},
|
|
||||||
view: {
|
view: {
|
||||||
icon: 'eye',
|
icon: 'eye',
|
||||||
name: localize('editor.view'),
|
name: localize('editor.view'),
|
||||||
@@ -135,6 +139,9 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
|
|||||||
protected _initialized = false;
|
protected _initialized = false;
|
||||||
protected _configUpgradeable = false;
|
protected _configUpgradeable = false;
|
||||||
|
|
||||||
|
@property({ attribute: false })
|
||||||
|
protected _expandedCameraIndex: number | null = null;
|
||||||
|
|
||||||
public setConfig(config: RawFrigateCardConfig): void {
|
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 configuration, so it may be
|
||||||
// partially or completely invalid. It's more useful to have a partially
|
// partially or completely invalid. It's more useful to have a partially
|
||||||
@@ -190,6 +197,20 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a localized help label for a given config path.
|
||||||
|
* @param configPath The config path.
|
||||||
|
* @returns A localized label.
|
||||||
|
*/
|
||||||
|
protected _getLabel(configPath: string): string {
|
||||||
|
// Strip out single number path components as they are array indicies.
|
||||||
|
const path = configPath
|
||||||
|
.split('.')
|
||||||
|
.filter((e) => isNaN(Number(e)))
|
||||||
|
.join('.');
|
||||||
|
return localize(`config.${path}`);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Render a dropdown menu.
|
* Render a dropdown menu.
|
||||||
* @param configPath The configuration path to set/read.
|
* @param configPath The configuration path to set/read.
|
||||||
@@ -207,7 +228,7 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
|
|||||||
|
|
||||||
return html`
|
return html`
|
||||||
<paper-dropdown-menu
|
<paper-dropdown-menu
|
||||||
.label=${localize(`config.${configPath}`)}
|
.label=${this._getLabel(configPath)}
|
||||||
@value-changed=${this._valueChangedHandler}
|
@value-changed=${this._valueChangedHandler}
|
||||||
.configValue=${configPath}
|
.configValue=${configPath}
|
||||||
>
|
>
|
||||||
@@ -225,6 +246,160 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render a camera header.
|
||||||
|
* @param cameraIndex The index of the camera to edit/add.
|
||||||
|
* @param cameraConfig The configuration of the camera in question.
|
||||||
|
* @param addNewCamera Whether or not this is a header to add a new camera.
|
||||||
|
* @returns A rendered template.
|
||||||
|
*/
|
||||||
|
protected _renderCameraHeader(
|
||||||
|
cameraIndex: number,
|
||||||
|
cameraConfig: RawFrigateCardConfig | undefined,
|
||||||
|
addNewCamera?: boolean,
|
||||||
|
): TemplateResult {
|
||||||
|
return html`
|
||||||
|
<div
|
||||||
|
class="camera-header"
|
||||||
|
@click=${this._toggleCameraHandler}
|
||||||
|
.cameraIndex=${cameraIndex}
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<ha-icon .icon=${addNewCamera ? 'mdi:video-plus' : 'mdi:video'}></ha-icon>
|
||||||
|
<span>
|
||||||
|
${addNewCamera
|
||||||
|
? html` <span class="new-camera">
|
||||||
|
[${localize('editor.add_new_camera')}...]
|
||||||
|
</span>`
|
||||||
|
: // Attempt to render a recognizable name for the camera,
|
||||||
|
// starting with the most likely to be useful and working our
|
||||||
|
// ways towards the least useful.
|
||||||
|
html` <span>
|
||||||
|
Camera:
|
||||||
|
${cameraConfig?.title ||
|
||||||
|
cameraConfig?.camera_entity ||
|
||||||
|
cameraConfig?.card_id ||
|
||||||
|
[
|
||||||
|
cameraConfig?.client_id,
|
||||||
|
cameraConfig?.camera_name,
|
||||||
|
cameraConfig?.label,
|
||||||
|
cameraConfig?.zone,
|
||||||
|
]
|
||||||
|
.filter(Boolean)
|
||||||
|
.join(' / ') ||
|
||||||
|
cameraIndex}
|
||||||
|
</span>`}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render a camera section.
|
||||||
|
* @param cameras The full array of cameras.
|
||||||
|
* @param cameraIndex The index (in the array) to render.
|
||||||
|
* @param cameraEntities The full list of camera entities.
|
||||||
|
* @param addNewCamera Whether or not this is a section to add a new non-existent camera.
|
||||||
|
* @returns A rendered template.
|
||||||
|
*/
|
||||||
|
protected _renderCamera(
|
||||||
|
cameras: RawFrigateCardConfigArray,
|
||||||
|
cameraIndex: number,
|
||||||
|
cameraEntities: string[],
|
||||||
|
addNewCamera?: boolean,
|
||||||
|
): TemplateResult | void {
|
||||||
|
// Get the config path for this camera (taking into account its camera index).
|
||||||
|
const getArrayPath = (path: string): string => {
|
||||||
|
return path.replace('#', cameraIndex.toString());
|
||||||
|
};
|
||||||
|
|
||||||
|
// Make a new config and update the editor with changes on it,
|
||||||
|
const modifyConfig = (func: (config: RawFrigateCardConfig) => boolean): void => {
|
||||||
|
if (this._config) {
|
||||||
|
const newConfig = copyConfig(this._config);
|
||||||
|
if (func(newConfig)) {
|
||||||
|
this._updateConfig(newConfig);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return html`
|
||||||
|
${this._renderCameraHeader(cameraIndex, cameras[cameraIndex], addNewCamera)}
|
||||||
|
${this._expandedCameraIndex === cameraIndex
|
||||||
|
? html` <div class="values">
|
||||||
|
<div class="controls">
|
||||||
|
<ha-icon-button
|
||||||
|
class="button"
|
||||||
|
.label=${localize('editor.move_up')}
|
||||||
|
.disabled=${!this._config ||
|
||||||
|
!Array.isArray(this._config.cameras) ||
|
||||||
|
cameraIndex <= 0}
|
||||||
|
@click=${() =>
|
||||||
|
modifyConfig((config: RawFrigateCardConfig): boolean => {
|
||||||
|
if (Array.isArray(config.cameras) && cameraIndex > 0) {
|
||||||
|
arrayMove(config.cameras, cameraIndex, cameraIndex - 1);
|
||||||
|
this._expandedCameraIndex = cameraIndex - 1;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<ha-icon icon="mdi:arrow-up"></ha-icon>
|
||||||
|
</ha-icon-button>
|
||||||
|
<ha-icon-button
|
||||||
|
class="button"
|
||||||
|
.label=${localize('editor.move_down')}
|
||||||
|
.disabled=${!this._config ||
|
||||||
|
!Array.isArray(this._config.cameras) ||
|
||||||
|
cameraIndex >= this._config.cameras.length - 1}
|
||||||
|
@click=${() =>
|
||||||
|
modifyConfig((config: RawFrigateCardConfig): boolean => {
|
||||||
|
if (
|
||||||
|
Array.isArray(config.cameras) &&
|
||||||
|
cameraIndex < config.cameras.length - 1
|
||||||
|
) {
|
||||||
|
arrayMove(config.cameras, cameraIndex, cameraIndex + 1);
|
||||||
|
this._expandedCameraIndex = cameraIndex + 1;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<ha-icon icon="mdi:arrow-down"></ha-icon>
|
||||||
|
</ha-icon-button>
|
||||||
|
<ha-icon-button
|
||||||
|
class="button"
|
||||||
|
.label=${localize('editor.delete')}
|
||||||
|
.disabled=${addNewCamera}
|
||||||
|
@click=${() => {
|
||||||
|
modifyConfig((config: RawFrigateCardConfig): boolean => {
|
||||||
|
if (Array.isArray(config.cameras)) {
|
||||||
|
config.cameras.splice(cameraIndex, 1);
|
||||||
|
this._expandedCameraIndex = null;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ha-icon icon="mdi:delete"></ha-icon>
|
||||||
|
</ha-icon-button>
|
||||||
|
</div>
|
||||||
|
${this._renderDropdown(
|
||||||
|
getArrayPath(CONF_CAMERAS_ARRAY_CAMERA_ENTITY),
|
||||||
|
cameraEntities,
|
||||||
|
)}
|
||||||
|
${this._renderStringInput(getArrayPath(CONF_CAMERAS_ARRAY_CAMERA_NAME))}
|
||||||
|
${this._renderStringInput(getArrayPath(CONF_CAMERAS_ARRAY_URL))}
|
||||||
|
${this._renderStringInput(getArrayPath(CONF_CAMERAS_ARRAY_LABEL))}
|
||||||
|
${this._renderStringInput(getArrayPath(CONF_CAMERAS_ARRAY_ZONE))}
|
||||||
|
${this._renderStringInput(getArrayPath(CONF_CAMERAS_ARRAY_CLIENT_ID))}
|
||||||
|
</div>`
|
||||||
|
: ``}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Render a string input field.
|
* Render a string input field.
|
||||||
* @param configPath The configuration path to set/read.
|
* @param configPath The configuration path to set/read.
|
||||||
@@ -239,7 +414,7 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
return html` <paper-input
|
return html` <paper-input
|
||||||
label=${localize(`config.${configPath}`)}
|
label=${this._getLabel(configPath)}
|
||||||
.value=${getConfigValue(this._config, configPath, '')}
|
.value=${getConfigValue(this._config, configPath, '')}
|
||||||
.configValue=${configPath}
|
.configValue=${configPath}
|
||||||
allowed-pattern=${ifDefined(allowedPattern ? allowedPattern : undefined)}
|
allowed-pattern=${ifDefined(allowedPattern ? allowedPattern : undefined)}
|
||||||
@@ -263,7 +438,7 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
|
|||||||
if (!this._config) {
|
if (!this._config) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
return html` <ha-formfield .label=${label || localize(`config.${configPath}`)}>
|
return html` <ha-formfield .label=${label || this._getLabel(configPath)}>
|
||||||
<ha-switch
|
<ha-switch
|
||||||
.checked="${getConfigValue(this._config, configPath, valueDefault)}"
|
.checked="${getConfigValue(this._config, configPath, valueDefault)}"
|
||||||
.configValue=${configPath}
|
.configValue=${configPath}
|
||||||
@@ -272,6 +447,11 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
|
|||||||
</ha-formfield>`;
|
</ha-formfield>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected _updateConfig(config: RawFrigateCardConfig): void {
|
||||||
|
this._config = config;
|
||||||
|
fireEvent(this, 'config-changed', { config: this._config });
|
||||||
|
}
|
||||||
|
|
||||||
protected render(): TemplateResult | void {
|
protected render(): TemplateResult | void {
|
||||||
if (!this.hass || !this._helpers || !this._config) {
|
if (!this.hass || !this._helpers || !this._config) {
|
||||||
return html``;
|
return html``;
|
||||||
@@ -353,6 +533,9 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
|
|||||||
const getShowButtonLabel = (configPath: string) =>
|
const getShowButtonLabel = (configPath: string) =>
|
||||||
localize('editor.show_button') + ': ' + localize(`config.${configPath}`);
|
localize('editor.show_button') + ': ' + localize(`config.${configPath}`);
|
||||||
|
|
||||||
|
const cameras = (getConfigValue(this._config, CONF_CAMERAS) ||
|
||||||
|
[]) as RawFrigateCardConfigArray;
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
${this._configUpgradeable
|
${this._configUpgradeable
|
||||||
? html` <div class="upgrade">
|
? html` <div class="upgrade">
|
||||||
@@ -365,10 +548,7 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
|
|||||||
if (this._config) {
|
if (this._config) {
|
||||||
const upgradedConfig = copyConfig(this._config);
|
const upgradedConfig = copyConfig(this._config);
|
||||||
upgradeConfig(upgradedConfig);
|
upgradeConfig(upgradedConfig);
|
||||||
this._config = upgradedConfig;
|
this._updateConfig(upgradedConfig);
|
||||||
|
|
||||||
fireEvent(this, 'config-changed', { config: this._config });
|
|
||||||
this.requestUpdate();
|
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -378,25 +558,14 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
|
|||||||
<br />`
|
<br />`
|
||||||
: html``}
|
: html``}
|
||||||
<div class="card-config">
|
<div class="card-config">
|
||||||
${this._renderOptionSetHeader('basic')}
|
${this._renderOptionSetHeader('cameras')}
|
||||||
${options.basic.show
|
${options.cameras.show
|
||||||
? html`
|
? html` <div class="cameras">
|
||||||
<div class="values">
|
${cameras.map((_, index) =>
|
||||||
${this._renderDropdown(CONF_CAMERA_ENTITY, cameraEntities)}
|
this._renderCamera(cameras, index, cameraEntities),
|
||||||
</div>
|
)}
|
||||||
`
|
${this._renderCamera(cameras, cameras.length, cameraEntities, true)}
|
||||||
: ''}
|
</div>`
|
||||||
${this._renderOptionSetHeader('frigate')}
|
|
||||||
${options.frigate.show
|
|
||||||
? html`
|
|
||||||
<div class="values">
|
|
||||||
${this._renderStringInput(CONF_FRIGATE_CAMERA_NAME)}
|
|
||||||
${this._renderStringInput(CONF_FRIGATE_URL)}
|
|
||||||
${this._renderStringInput(CONF_FRIGATE_LABEL)}
|
|
||||||
${this._renderStringInput(CONF_FRIGATE_ZONE)}
|
|
||||||
${this._renderStringInput(CONF_FRIGATE_CLIENT_ID)}
|
|
||||||
</div>
|
|
||||||
`
|
|
||||||
: ''}
|
: ''}
|
||||||
${this._renderOptionSetHeader('view')}
|
${this._renderOptionSetHeader('view')}
|
||||||
${options.view.show
|
${options.view.show
|
||||||
@@ -404,9 +573,7 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
|
|||||||
<div class="values">
|
<div class="values">
|
||||||
${this._renderDropdown(CONF_VIEW_DEFAULT, viewModes)}
|
${this._renderDropdown(CONF_VIEW_DEFAULT, viewModes)}
|
||||||
${this._renderStringInput(CONF_VIEW_TIMEOUT, '[0-9]')}
|
${this._renderStringInput(CONF_VIEW_TIMEOUT, '[0-9]')}
|
||||||
${this._renderSwitch(
|
${this._renderSwitch(CONF_VIEW_UPDATE_FORCE, defaults.view.update_force)}
|
||||||
CONF_VIEW_UPDATE_FORCE,
|
|
||||||
defaults.view.update_force)}
|
|
||||||
</div>
|
</div>
|
||||||
`
|
`
|
||||||
: ''}
|
: ''}
|
||||||
@@ -541,6 +708,19 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
|
|||||||
this._helpers = await (window as any).loadCardHelpers();
|
this._helpers = await (window as any).loadCardHelpers();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display/hide a camera section.
|
||||||
|
* @param ev The event triggering the change.
|
||||||
|
*/
|
||||||
|
protected _toggleCameraHandler(ev: { target: EditorCameraTarget | null }): void {
|
||||||
|
if (ev && ev.target) {
|
||||||
|
this._expandedCameraIndex =
|
||||||
|
this._expandedCameraIndex == ev.target.cameraIndex
|
||||||
|
? null
|
||||||
|
: ev.target.cameraIndex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle a toggled set of options.
|
* Handle a toggled set of options.
|
||||||
* @param ev The event triggering the change.
|
* @param ev The event triggering the change.
|
||||||
@@ -573,7 +753,7 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
|
|||||||
* @param ev Event triggering the change.
|
* @param ev Event triggering the change.
|
||||||
*/
|
*/
|
||||||
protected _valueChangedHandler(ev: {
|
protected _valueChangedHandler(ev: {
|
||||||
target: (EditorOptionTarget & HTMLElement) | null;
|
target: (ConfigValueTarget & HTMLElement) | null;
|
||||||
}): void {
|
}): void {
|
||||||
const target = ev.target;
|
const target = ev.target;
|
||||||
if (!this._config || !this.hass || !target) {
|
if (!this._config || !this.hass || !target) {
|
||||||
@@ -593,19 +773,11 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
|
|||||||
|
|
||||||
const newConfig = copyConfig(this._config);
|
const newConfig = copyConfig(this._config);
|
||||||
if (value === '' || typeof value === 'undefined') {
|
if (value === '' || typeof value === 'undefined') {
|
||||||
// Don't delete empty properties that are from a dropdown menu. An empty
|
|
||||||
// property in that context may just be a user-entered value that is not
|
|
||||||
// in the valid choices in the dropdown. This probably won't end well for
|
|
||||||
// the user anyway, but having the whole property deleted the moment they
|
|
||||||
// press a key is very jarring.
|
|
||||||
if (target.tagName != 'PAPER-DROPDOWN-MENU') {
|
|
||||||
deleteConfigValue(newConfig, key);
|
deleteConfigValue(newConfig, key);
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
setConfigValue(newConfig, key, value);
|
setConfigValue(newConfig, key, value);
|
||||||
}
|
}
|
||||||
this._config = newConfig;
|
this._updateConfig(newConfig);
|
||||||
fireEvent(this, 'config-changed', { config: this._config });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -9,9 +9,9 @@
|
|||||||
"no_clip": "No recent clip"
|
"no_clip": "No recent clip"
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
|
"cameras": {
|
||||||
"camera_entity": "Camera Entity",
|
"camera_entity": "Camera Entity",
|
||||||
"frigate": {
|
"camera_name": "Frigate camera name (Autodetected from entity)",
|
||||||
"camera_name": "Frigate camera name (Optional, autodetected from entity)",
|
|
||||||
"client_id": "Frigate client id (For >1 Frigate server)",
|
"client_id": "Frigate client id (For >1 Frigate server)",
|
||||||
"label": "Frigate label/object filter",
|
"label": "Frigate label/object filter",
|
||||||
"url": "Frigate server URL",
|
"url": "Frigate server URL",
|
||||||
@@ -121,10 +121,8 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"editor": {
|
"editor": {
|
||||||
"basic": "Basic",
|
"cameras": "Cameras",
|
||||||
"basic_secondary": "Options for most users",
|
"cameras_secondary": "What cameras to render on this card",
|
||||||
"frigate": "Frigate",
|
|
||||||
"frigate_secondary": "Frigate server options",
|
|
||||||
"view": "View",
|
"view": "View",
|
||||||
"view_secondary": "What the card should show and how to show it",
|
"view_secondary": "What the card should show and how to show it",
|
||||||
"menu": "Menu",
|
"menu": "Menu",
|
||||||
@@ -139,7 +137,11 @@
|
|||||||
"dimensions_secondary": "Dimensions & shape options",
|
"dimensions_secondary": "Dimensions & shape options",
|
||||||
"show_button": "Show button",
|
"show_button": "Show button",
|
||||||
"upgrade": "Upgrade",
|
"upgrade": "Upgrade",
|
||||||
"upgrade_available": "An automatic card configuration upgrade is available"
|
"upgrade_available": "An automatic card configuration upgrade is available",
|
||||||
|
"delete": "Delete",
|
||||||
|
"move_up": "Move up",
|
||||||
|
"move_down": "Move down",
|
||||||
|
"add_new_camera": "Add new camera"
|
||||||
},
|
},
|
||||||
"error": {
|
"error": {
|
||||||
"empty_response": "Received empty response from Home Assistant for request",
|
"empty_response": "Received empty response from Home Assistant for request",
|
||||||
|
|||||||
+31
-2
@@ -1,3 +1,5 @@
|
|||||||
|
@use './button.scss';
|
||||||
|
|
||||||
.option {
|
.option {
|
||||||
padding: 4px 0px;
|
padding: 4px 0px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
@@ -18,8 +20,8 @@
|
|||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
.values {
|
.values {
|
||||||
padding-left: 32px;
|
margin-left: 30px;
|
||||||
padding-top: 10px;
|
padding: 10px;
|
||||||
background: var(--secondary-background-color);
|
background: var(--secondary-background-color);
|
||||||
display: grid;
|
display: grid;
|
||||||
}
|
}
|
||||||
@@ -38,3 +40,30 @@ div.upgrade {
|
|||||||
div.upgrade span {
|
div.upgrade span {
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.camera-header {
|
||||||
|
margin-top: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.camera-header * {
|
||||||
|
// Only allow clicks on the outermost header.
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
.camera-header ha-icon {
|
||||||
|
padding-right: 10px;
|
||||||
|
}
|
||||||
|
.camera-header .new-camera {
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
.cameras {
|
||||||
|
margin: 5px 5px 10px 20px;
|
||||||
|
}
|
||||||
|
.cameras .controls {
|
||||||
|
display: inline-block;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: 0px;
|
||||||
|
}
|
||||||
|
.cameras .controls ha-icon-button.button {
|
||||||
|
--mdc-icon-button-size: 32px;
|
||||||
|
--mdc-icon-size: calc(var(--mdc-icon-button-size) / 2);
|
||||||
|
}
|
||||||
+2
-1
@@ -650,7 +650,7 @@ const dimensionsConfigSchema = z
|
|||||||
*/
|
*/
|
||||||
export const frigateCardConfigSchema = z.object({
|
export const frigateCardConfigSchema = z.object({
|
||||||
// Main configuration sections.
|
// Main configuration sections.
|
||||||
camera: cameraConfigDefaultSchema.array().nonempty(),
|
cameras: cameraConfigDefaultSchema.array().nonempty(),
|
||||||
view: viewConfigSchema,
|
view: viewConfigSchema,
|
||||||
menu: menuConfigSchema,
|
menu: menuConfigSchema,
|
||||||
live: liveConfigSchema,
|
live: liveConfigSchema,
|
||||||
@@ -666,6 +666,7 @@ export const frigateCardConfigSchema = z.object({
|
|||||||
});
|
});
|
||||||
export type FrigateCardConfig = z.infer<typeof frigateCardConfigSchema>;
|
export type FrigateCardConfig = z.infer<typeof frigateCardConfigSchema>;
|
||||||
export type RawFrigateCardConfig = Record<string, unknown>;
|
export type RawFrigateCardConfig = Record<string, unknown>;
|
||||||
|
export type RawFrigateCardConfigArray = Record<string, unknown>[];
|
||||||
|
|
||||||
export const frigateCardConfigDefaults = {
|
export const frigateCardConfigDefaults = {
|
||||||
cameras: cameraConfigDefault,
|
cameras: cameraConfigDefault,
|
||||||
|
|||||||
Reference in New Issue
Block a user