feat: Rename the card to advanced-camera-card (#1873)

Whilst the Frigate support in this card is the best among camera
engines, the name incorrectly suggests that Frigate is a requirement.
Instead, to broaden the appeal, change to more camera agnostic name.
This does not suggest any change in priority, role or support for
Frigate.

This change is likely to be bug prone, due to the size of the rename --
the code contains 1500+ references to "Frigate" most of which make sense
to rename, some which do not, all of which needed human assessment.

- Closes #1298


BREAKING CHANGE: References to `frigate-card` in all kinds of
configuration need to be updated to `advanced-camera-card`. An automated
config upgrade should take care of the majority of usecases (click `Edit
-> Upgrade -> Save`), though may not be perfect.
This commit is contained in:
Dermot Duffy
2025-02-06 19:32:32 -08:00
committed by GitHub
parent 43947b49dc
commit cc376a8be1
305 changed files with 4270 additions and 3686 deletions
+32 -27
View File
@@ -1,48 +1,51 @@
import { ActionConfig, hasAction } from '@dermotduffy/custom-card-helpers';
import {
ActionConfig,
hasAction as customCardHasAction,
} from '@dermotduffy/custom-card-helpers';
import { ZoomSettingsBase } from '../components-lib/zoom/types.js';
import { PTZAction } from '../config/ptz.js';
import {
ActionPhase,
ActionType,
Actions,
FrigateCardCustomAction,
FrigateCardGeneralAction,
FrigateCardUserSpecifiedView,
AdvancedCameraCardCustomAction,
AdvancedCameraCardGeneralAction,
AdvancedCameraCardUserSpecifiedView,
LogActionConfig,
LogActionLevel,
PTZActionConfig,
PTZDigitialActionConfig,
PTZMultiActionConfig,
frigateCardCustomActionSchema,
advancedCameraCardCustomActionSchema,
} from '../config/types.js';
import { arrayify } from './basic.js';
/**
* Convert a generic Action to a FrigateCardCustomAction if it parses correctly.
* Convert a generic Action to a AdvancedCameraCardCustomAction if it parses correctly.
* @param action The generic action configuration.
* @returns A FrigateCardCustomAction or null if it cannot be converted.
* @returns A AdvancedCameraCardCustomAction or null if it cannot be converted.
*/
export function convertActionToCardCustomAction(
action: unknown,
): FrigateCardCustomAction | null {
): AdvancedCameraCardCustomAction | null {
if (!action) {
return null;
}
// Parse a custom event as other things could generate ll-custom events that
// are not related to Frigate Card.
const parseResult = frigateCardCustomActionSchema.safeParse(action);
// are not related to Advanced Camera Card.
const parseResult = advancedCameraCardCustomActionSchema.safeParse(action);
return parseResult.success ? parseResult.data : null;
}
export function createGeneralAction(
action: FrigateCardGeneralAction | FrigateCardUserSpecifiedView,
action: AdvancedCameraCardGeneralAction | AdvancedCameraCardUserSpecifiedView,
options?: {
cardID?: string;
},
): FrigateCardCustomAction {
): AdvancedCameraCardCustomAction {
return {
action: 'fire-dom-event',
frigate_card_action: action,
advanced_camera_card_action: action,
...(options?.cardID && { card_id: options.cardID }),
};
}
@@ -53,10 +56,10 @@ export function createCameraAction(
options?: {
cardID?: string;
},
): FrigateCardCustomAction {
): AdvancedCameraCardCustomAction {
return {
action: 'fire-dom-event',
frigate_card_action: action,
advanced_camera_card_action: action,
camera: camera,
...(options?.cardID && { card_id: options.cardID }),
};
@@ -68,10 +71,10 @@ export function createMediaPlayerAction(
options?: {
cardID?: string;
},
): FrigateCardCustomAction {
): AdvancedCameraCardCustomAction {
return {
action: 'fire-dom-event',
frigate_card_action: 'media_player',
advanced_camera_card_action: 'media_player',
media_player: mediaPlayer,
media_player_action: mediaPlayerAction,
...(options?.cardID && { card_id: options.cardID }),
@@ -83,10 +86,10 @@ export function createDisplayModeAction(
options?: {
cardID?: string;
},
): FrigateCardCustomAction {
): AdvancedCameraCardCustomAction {
return {
action: 'fire-dom-event',
frigate_card_action: 'display_mode_select',
advanced_camera_card_action: 'display_mode_select',
display_mode: displayMode,
...(options?.cardID && { card_id: options.cardID }),
};
@@ -97,10 +100,10 @@ export function createPTZControlsAction(
options?: {
cardID?: string;
},
): FrigateCardCustomAction {
): AdvancedCameraCardCustomAction {
return {
action: 'fire-dom-event',
frigate_card_action: 'ptz_controls',
advanced_camera_card_action: 'ptz_controls',
enabled: enabled,
...(options?.cardID && { card_id: options.cardID }),
};
@@ -115,7 +118,7 @@ export function createPTZAction(options?: {
}): PTZActionConfig {
return {
action: 'fire-dom-event',
frigate_card_action: 'ptz',
advanced_camera_card_action: 'ptz',
...(options?.cardID && { card_id: options.cardID }),
...(options?.ptzAction && { ptz_action: options.ptzAction }),
...(options?.ptzPhase && { ptz_phase: options.ptzPhase }),
@@ -133,7 +136,7 @@ export function createPTZDigitalAction(options?: {
}): PTZDigitialActionConfig {
return {
action: 'fire-dom-event',
frigate_card_action: 'ptz_digital',
advanced_camera_card_action: 'ptz_digital',
...(options?.cardID && { card_id: options.cardID }),
...(options?.ptzAction && { ptz_action: options.ptzAction }),
...(options?.ptzPhase && { ptz_phase: options.ptzPhase }),
@@ -151,7 +154,7 @@ export function createPTZMultiAction(options?: {
}): PTZMultiActionConfig {
return {
action: 'fire-dom-event',
frigate_card_action: 'ptz_multi',
advanced_camera_card_action: 'ptz_multi',
...(options?.cardID && { card_id: options.cardID }),
...(options?.ptzAction && { ptz_action: options.ptzAction }),
...(options?.ptzPhase && { ptz_phase: options.ptzPhase }),
@@ -169,7 +172,7 @@ export function createLogAction(
): LogActionConfig {
return {
action: 'fire-dom-event',
frigate_card_action: 'log',
advanced_camera_card_action: 'log',
message: message,
level: options?.level ?? 'info',
...(options?.cardID && { card_id: options.cardID }),
@@ -209,10 +212,12 @@ export function getActionConfigGivenAction(
* @param config The action config in question.
* @returns `true` if there's a real action defined, `false` otherwise.
*/
export const frigateCardHasAction = (config?: ActionType | ActionType[]): boolean => {
export const hasAction = (config?: ActionType | ActionType[]): boolean => {
// See note above on 'ActionConfig vs ActionType' for why this cast is
// necessary and harmless.
return arrayify(config).some((item) => hasAction(item as ActionConfig | undefined));
return arrayify(config).some((item) =>
customCardHasAction(item as ActionConfig | undefined),
);
};
/**
+7 -7
View File
@@ -9,23 +9,23 @@ import isEqualWith from 'lodash-es/isEqualWith';
import mergeWith from 'lodash-es/mergeWith';
import round from 'lodash-es/round';
import uniq from 'lodash-es/uniq';
import { FrigateCardError } from '../types';
import { AdvancedCameraCardError } from '../types';
export type ModifyInterface<T, R> = Omit<T, keyof R> & R;
/**
* Dispatch a Frigate Card event.
* Dispatch an Advanced Camera Card event.
* @param target The target from which send the event.
* @param name The name of the Frigate card event to send.
* @param name The name of the Advanced Camera Card event to send.
* @param detail An optional detail object to attach.
*/
export function dispatchFrigateCardEvent<T>(
export function dispatchAdvancedCameraCardEvent<T>(
target: EventTarget,
name: string,
detail?: T,
): void {
target.dispatchEvent(
new CustomEvent<T>(`frigate-card:${name}`, {
new CustomEvent<T>(`advanced-camera-card:${name}`, {
bubbles: true,
composed: true,
detail: detail,
@@ -35,7 +35,7 @@ export function dispatchFrigateCardEvent<T>(
/**
* Prettify a title by converting '_' to spaces and capitalizing words.
* @param input The input Frigate (camera/label/zone) name.
* @param input The input string.
* @returns A prettified name.
*/
export function prettifyTitle(input: string): string;
@@ -108,7 +108,7 @@ export function errorToConsole(
e: Error | { message: unknown } | string,
func: CallableFunction = console.warn,
): void {
if (e instanceof FrigateCardError && e.context) {
if (e instanceof AdvancedCameraCardError && e.context) {
func(e, e.context);
} else if (typeof e === 'object' && 'message' in e) {
func(e.message);
+2 -2
View File
@@ -1,4 +1,4 @@
import { CameraConfig, RawFrigateCardConfig } from '../config/types.js';
import { CameraConfig, RawAdvancedCameraCardConfig } from '../config/types.js';
/**
* Get a camera id.
@@ -6,7 +6,7 @@ import { CameraConfig, RawFrigateCardConfig } from '../config/types.js';
* @returns A camera id.
*/
export function getCameraID(
config?: CameraConfig | RawFrigateCardConfig | null,
config?: CameraConfig | RawAdvancedCameraCardConfig | null,
): string {
return (
(typeof config?.id === 'string' && config.id) ||
+4 -4
View File
@@ -1,6 +1,6 @@
import { HomeAssistant } from '@dermotduffy/custom-card-helpers';
import pkg from '../../package.json';
import { RawFrigateCardConfig } from '../config/types';
import { RawAdvancedCameraCardConfig } from '../config/types';
import { getLanguage } from '../localize/localize';
import { getIntegrationManifest } from './ha/integration';
import { IntegrationManifest } from './ha/integration/types';
@@ -21,7 +21,7 @@ interface IntegrationDiagnostics {
}
export const getReleaseVersion = (): string => {
const releaseVersion: string = '__FRIGATE_CARD_RELEASE_VERSION__';
const releaseVersion: string = '__ADVANCED_CAMERA_CARD_RELEASE_VERSION__';
/* istanbul ignore if: depends on rollup substitution -- @preserve */
if (releaseVersion === 'pkg') {
@@ -45,7 +45,7 @@ interface Diagnostics {
git: GitDiagnostics;
ha_version?: string;
config?: RawFrigateCardConfig;
config?: RawAdvancedCameraCardConfig;
custom_integrations: {
frigate: IntegrationDiagnostics & {
@@ -78,7 +78,7 @@ const getIntegrationDiagnostics = async (
export const getDiagnostics = async (
hass?: HomeAssistant,
deviceRegistryManager?: DeviceRegistryManager,
rawConfig?: RawFrigateCardConfig,
rawConfig?: RawAdvancedCameraCardConfig,
): Promise<Diagnostics> => {
// Get the Frigate devices in order to extract the Frigate integration and
// server version numbers.
+3 -3
View File
@@ -1,6 +1,6 @@
import { CameraManager } from '../camera-manager/manager';
import { localize } from '../localize/localize';
import { ExtendedHomeAssistant, FrigateCardError } from '../types';
import { AdvancedCameraCardError, ExtendedHomeAssistant } from '../types';
import { ViewMedia } from '../view/media';
import { errorToConsole } from './basic';
import { homeAssistantSignPath } from './ha';
@@ -32,7 +32,7 @@ export const downloadMedia = async (
): Promise<void> => {
const download = await cameraManager.getMediaDownloadPath(media);
if (!download) {
throw new FrigateCardError(localize('error.download_no_media'));
throw new AdvancedCameraCardError(localize('error.download_no_media'));
}
let finalURL = download.endpoint;
@@ -45,7 +45,7 @@ export const downloadMedia = async (
}
if (!response) {
throw new FrigateCardError(localize('error.download_sign_failed'));
throw new AdvancedCameraCardError(localize('error.download_sign_failed'));
}
finalURL = response;
}
+10 -6
View File
@@ -3,7 +3,7 @@ import { WheelGesturesPlugin } from 'embla-carousel-wheel-gestures';
import { CreatePluginType, LoosePluginType } from 'embla-carousel/components/Plugins';
import isEqual from 'lodash-es/isEqual';
import { TransitionEffect } from '../../config/types';
import { dispatchFrigateCardEvent, getChildrenFromElement } from '../basic.js';
import { dispatchAdvancedCameraCardEvent, getChildrenFromElement } from '../basic.js';
import { TextDirection } from '../text-direction';
export interface CarouselSelected {
@@ -99,10 +99,14 @@ export class CarouselController {
// this is used.
const newSlide = this.getSlide(index);
if (newSlide) {
dispatchFrigateCardEvent<CarouselSelected>(this._parent, 'carousel:force-select', {
index: index,
element: newSlide,
});
dispatchAdvancedCameraCardEvent<CarouselSelected>(
this._parent,
'carousel:force-select',
{
index: index,
element: newSlide,
},
);
}
}
@@ -168,7 +172,7 @@ export class CarouselController {
const selectSlide = (): void => {
const carouselSelected = getCarouselSelectedObject();
if (carouselSelected) {
dispatchFrigateCardEvent<CarouselSelected>(
dispatchAdvancedCameraCardEvent<CarouselSelected>(
this._parent,
'carousel:select',
carouselSelected,
@@ -3,7 +3,7 @@ import { LooseOptionsType } from 'embla-carousel/components/Options';
import { CreatePluginType, LoosePluginType } from 'embla-carousel/components/Plugins';
import { MediaLoadedInfo } from '../../../../types';
import {
FrigateCardMediaLoadedEventTarget,
AdvancedCameraCardMediaLoadedEventTarget,
dispatchExistingMediaLoadedInfoAsEvent,
} from '../../../media-info';
@@ -41,7 +41,7 @@ type AutoMediaLoadedInfoType = CreatePluginType<LoosePluginType, LooseOptionsTyp
function AutoMediaLoadedInfo(): AutoMediaLoadedInfoType {
let emblaApi: EmblaCarouselType;
let slides: (HTMLElement & FrigateCardMediaLoadedEventTarget)[] = [];
let slides: (HTMLElement & AdvancedCameraCardMediaLoadedEventTarget)[] = [];
const mediaLoadedInfo: MediaLoadedInfo[] = [];
function init(emblaApiInstance: EmblaCarouselType): void {
@@ -49,26 +49,44 @@ function AutoMediaLoadedInfo(): AutoMediaLoadedInfoType {
slides = emblaApi.slideNodes();
for (const slide of slides) {
slide.addEventListener('frigate-card:media:loaded', mediaLoadedInfoHandler);
slide.addEventListener('frigate-card:media:unloaded', mediaUnloadedInfoHandler);
slide.addEventListener(
'advanced-camera-card:media:loaded',
mediaLoadedInfoHandler,
);
slide.addEventListener(
'advanced-camera-card:media:unloaded',
mediaUnloadedInfoHandler,
);
}
emblaApi.on('init', slideSelectHandler);
emblaApi
.containerNode()
.addEventListener('frigate-card:carousel:force-select', slideSelectHandler);
.addEventListener(
'advanced-camera-card:carousel:force-select',
slideSelectHandler,
);
}
function destroy(): void {
for (const slide of slides) {
slide.removeEventListener('frigate-card:media:loaded', mediaLoadedInfoHandler);
slide.removeEventListener('frigate-card:media:unloaded', mediaUnloadedInfoHandler);
slide.removeEventListener(
'advanced-camera-card:media:loaded',
mediaLoadedInfoHandler,
);
slide.removeEventListener(
'advanced-camera-card:media:unloaded',
mediaUnloadedInfoHandler,
);
}
emblaApi.off('init', slideSelectHandler);
emblaApi
.containerNode()
.removeEventListener('frigate-card:carousel:force-select', slideSelectHandler);
.removeEventListener(
'advanced-camera-card:carousel:force-select',
slideSelectHandler,
);
}
function mediaLoadedInfoHandler(ev: CustomEvent<MediaLoadedInfo>): void {
@@ -61,7 +61,10 @@ function AutoSize(): AutoSizeType {
// the size to large than the maxHeight is set).
emblaApi
.containerNode()
.addEventListener('frigate-card:media:loaded', debouncedSetContainerHeight);
.addEventListener(
'advanced-camera-card:media:loaded',
debouncedSetContainerHeight,
);
emblaApi.on('settle', debouncedSetContainerHeight);
}
@@ -72,7 +75,10 @@ function AutoSize(): AutoSizeType {
emblaApi
.containerNode()
.removeEventListener('frigate-card:media:loaded', debouncedSetContainerHeight);
.removeEventListener(
'advanced-camera-card:media:loaded',
debouncedSetContainerHeight,
);
emblaApi.off('settle', debouncedSetContainerHeight);
}
@@ -85,7 +91,7 @@ function AutoSize(): AutoSizeType {
* occur for some users in some circumstances causing the carousel to
* appear 'stuck'.
* - Example bug when this reinitialization is not performed:
* https://github.com/dermotduffy/frigate-hass-card/issues/651
* https://github.com/dermotduffy/advanced-camera-card/issues/651
*/
const isContainerIntersectingNow = entries.some((entry) => entry.isIntersecting);
+1 -1
View File
@@ -10,7 +10,7 @@ export const entitySchema = z.object({
translation_key: z.string().nullable(),
// Technically the unique_id should be a string, but we want to tolerate
// numeric unique_ids also in case they are used. See:
// https://github.com/dermotduffy/frigate-hass-card/issues/1016
// https://github.com/dermotduffy/advanced-camera-card/issues/1016
unique_id: z.string().or(z.number()).optional(),
});
export type Entity = z.infer<typeof entitySchema>;
+6 -6
View File
@@ -2,7 +2,7 @@ import { HomeAssistant } from '@dermotduffy/custom-card-helpers';
import { MessageBase } from 'home-assistant-js-websocket';
import { ZodSchema } from 'zod';
import { localize } from '../../localize/localize';
import { FrigateCardError } from '../../types';
import { AdvancedCameraCardError } from '../../types';
import { getParseErrorKeys } from '../zod';
/**
@@ -24,7 +24,7 @@ export async function homeAssistantWSRequest<T>(
response = await hass.callWS<T>(request);
} catch (e) {
if (!(e instanceof Error)) {
throw new FrigateCardError(localize('error.failed_response'), {
throw new AdvancedCameraCardError(localize('error.failed_response'), {
request: request,
response: e,
});
@@ -33,17 +33,17 @@ export async function homeAssistantWSRequest<T>(
}
if (!response) {
throw new FrigateCardError(localize('error.empty_response'), {
throw new AdvancedCameraCardError(localize('error.empty_response'), {
request: request,
});
}
// Some endpoints on the integration pass through JSON directly from Frigate
// These end up wrapped in a string and must be unwrapped first
// Some endpoints in Home Assistant pass JSON directly though, these end up
// wrapped in a string and must be unwrapped first.
const parseResult = passthrough
? schema.safeParse(JSON.parse(response))
: schema.safeParse(response);
if (!parseResult.success) {
throw new FrigateCardError(localize('error.invalid_response'), {
throw new AdvancedCameraCardError(localize('error.invalid_response'), {
request: request,
response: response,
invalid_keys: getParseErrorKeys<T>(parseResult.error),
+23 -19
View File
@@ -1,10 +1,10 @@
import {
FrigateCardMediaPlayer,
AdvancedCameraCardMediaPlayer,
MediaLoadedCapabilities,
MediaLoadedInfo,
MediaTechnology,
} from '../types.js';
import { dispatchFrigateCardEvent } from './basic.js';
import { dispatchAdvancedCameraCardEvent } from './basic.js';
const MEDIA_INFO_HEIGHT_CUTOFF = 50;
const MEDIA_INFO_WIDTH_CUTOFF = MEDIA_INFO_HEIGHT_CUTOFF;
@@ -17,7 +17,7 @@ const MEDIA_INFO_WIDTH_CUTOFF = MEDIA_INFO_HEIGHT_CUTOFF;
export function createMediaLoadedInfo(
source: Event | HTMLElement,
options?: {
player?: FrigateCardMediaPlayer;
player?: AdvancedCameraCardMediaPlayer;
capabilities?: MediaLoadedCapabilities;
technology?: MediaTechnology[];
},
@@ -53,7 +53,7 @@ export function createMediaLoadedInfo(
}
/**
* Dispatch a Frigate card media loaded event.
* Dispatch an Advanced Camera Card media loaded event.
* @param element The element to send the event.
* @param source An event or HTMLElement that should be used as a source.
*/
@@ -61,7 +61,7 @@ export function dispatchMediaLoadedEvent(
target: HTMLElement,
source: Event | HTMLElement,
options?: {
player?: FrigateCardMediaPlayer;
player?: AdvancedCameraCardMediaPlayer;
capabilities?: MediaLoadedCapabilities;
technology?: MediaTechnology[];
},
@@ -81,7 +81,11 @@ export function dispatchExistingMediaLoadedInfoAsEvent(
target: EventTarget,
MediaLoadedInfo: MediaLoadedInfo,
): void {
dispatchFrigateCardEvent<MediaLoadedInfo>(target, 'media:loaded', MediaLoadedInfo);
dispatchAdvancedCameraCardEvent<MediaLoadedInfo>(
target,
'media:loaded',
MediaLoadedInfo,
);
}
/**
@@ -89,19 +93,19 @@ export function dispatchExistingMediaLoadedInfoAsEvent(
* @param element The element to send the event.
*/
export function dispatchMediaUnloadedEvent(element: HTMLElement): void {
dispatchFrigateCardEvent(element, 'media:unloaded');
dispatchAdvancedCameraCardEvent(element, 'media:unloaded');
}
export function dispatchMediaVolumeChangeEvent(target: HTMLElement): void {
dispatchFrigateCardEvent(target, 'media:volumechange');
dispatchAdvancedCameraCardEvent(target, 'media:volumechange');
}
export function dispatchMediaPlayEvent(target: HTMLElement): void {
dispatchFrigateCardEvent(target, 'media:play');
dispatchAdvancedCameraCardEvent(target, 'media:play');
}
export function dispatchMediaPauseEvent(target: HTMLElement): void {
dispatchFrigateCardEvent(target, 'media:pause');
dispatchAdvancedCameraCardEvent(target, 'media:pause');
}
/**
@@ -116,18 +120,18 @@ export function isValidMediaLoadedInfo(info: MediaLoadedInfo): boolean {
}
// Facilitates correct typing of event handlers.
export interface FrigateCardMediaLoadedEventTarget extends EventTarget {
export interface AdvancedCameraCardMediaLoadedEventTarget extends EventTarget {
addEventListener(
event: 'frigate-card:media:loaded',
event: 'advanced-camera-card:media:loaded',
listener: (
this: FrigateCardMediaLoadedEventTarget,
this: AdvancedCameraCardMediaLoadedEventTarget,
ev: CustomEvent<MediaLoadedInfo>,
) => void,
options?: AddEventListenerOptions | boolean,
): void;
addEventListener(
event: 'frigate-card:media:unloaded',
listener: (this: FrigateCardMediaLoadedEventTarget, ev: CustomEvent) => void,
event: 'advanced-camera-card:media:unloaded',
listener: (this: AdvancedCameraCardMediaLoadedEventTarget, ev: CustomEvent) => void,
options?: AddEventListenerOptions | boolean,
): void;
addEventListener(
@@ -136,16 +140,16 @@ export interface FrigateCardMediaLoadedEventTarget extends EventTarget {
options?: AddEventListenerOptions | boolean,
): void;
removeEventListener(
event: 'frigate-card:media:loaded',
event: 'advanced-camera-card:media:loaded',
listener: (
this: FrigateCardMediaLoadedEventTarget,
this: AdvancedCameraCardMediaLoadedEventTarget,
ev: CustomEvent<MediaLoadedInfo>,
) => void,
options?: boolean | EventListenerOptions,
): void;
removeEventListener(
event: 'frigate-card:media:unloaded',
listener: (this: FrigateCardMediaLoadedEventTarget, ev: CustomEvent) => void,
event: 'advanced-camera-card:media:unloaded',
listener: (this: AdvancedCameraCardMediaLoadedEventTarget, ev: CustomEvent) => void,
options?: boolean | EventListenerOptions,
): void;
removeEventListener(
+13 -6
View File
@@ -10,28 +10,35 @@ export const updateElementStyleFromMediaLayoutConfig = (
mediaLayoutConfig?: MediaLayoutConfig,
): void => {
if (mediaLayoutConfig?.fit !== undefined) {
element.style.setProperty('--frigate-card-media-layout-fit', mediaLayoutConfig.fit);
element.style.setProperty(
'--advanced-camera-card-media-layout-fit',
mediaLayoutConfig.fit,
);
} else {
element.style.removeProperty('--frigate-card-media-layout-fit');
element.style.removeProperty('--advanced-camera-card-media-layout-fit');
}
for (const dimension of ['x', 'y']) {
if (mediaLayoutConfig?.position?.[dimension] !== undefined) {
element.style.setProperty(
`--frigate-card-media-layout-position-${dimension}`,
`--advanced-camera-card-media-layout-position-${dimension}`,
`${mediaLayoutConfig.position[dimension]}%`,
);
} else {
element.style.removeProperty(`--frigate-card-media-layout-position-${dimension}`);
element.style.removeProperty(
`--advanced-camera-card-media-layout-position-${dimension}`,
);
}
}
for (const dimension of ['top', 'bottom', 'left', 'right']) {
if (mediaLayoutConfig?.view_box?.[dimension] !== undefined) {
element.style.setProperty(
`--frigate-card-media-layout-view-box-${dimension}`,
`--advanced-camera-card-media-layout-view-box-${dimension}`,
`${mediaLayoutConfig.view_box[dimension]}%`,
);
} else {
element.style.removeProperty(`--frigate-card-media-layout-view-box-${dimension}`);
element.style.removeProperty(
`--advanced-camera-card-media-layout-view-box-${dimension}`,
);
}
}
};
+10 -10
View File
@@ -1,13 +1,13 @@
import { FrigateCardMediaPlayer } from '../types';
import { AdvancedCameraCardMediaPlayer } from '../types';
import { Timer } from './timer';
// The number of seconds to hide the video controls for after loading (in order
// to give a cleaner UI appearance, see:
// https://github.com/dermotduffy/frigate-hass-card/issues/856
// https://github.com/dermotduffy/advanced-camera-card/issues/856
export const MEDIA_LOAD_CONTROLS_HIDE_SECONDS = 2;
const MEDIA_SEEK_CONTROLS_HIDE_SECONDS = 1;
export type FrigateCardHTMLVideoElement = HTMLVideoElement & {
export type AdvancedCameraCardHTMLVideoElement = HTMLVideoElement & {
_controlsHideTimer?: Timer;
_controlsOriginalValue?: boolean;
};
@@ -19,7 +19,7 @@ export type FrigateCardHTMLVideoElement = HTMLVideoElement & {
* @param value
*/
export const setControlsOnVideo = (
video: FrigateCardHTMLVideoElement,
video: AdvancedCameraCardHTMLVideoElement,
value: boolean,
): void => {
if (video._controlsHideTimer) {
@@ -33,11 +33,11 @@ export const setControlsOnVideo = (
/**
* Temporarily hide media controls.
* @param element Any HTMLElement that has a controls property (e.g.
* HTMLVideoElement, FrigateCardHaHlsPlayer)
* HTMLVideoElement, AdvancedCameraCardHaHlsPlayer)
* @param seconds The number of seconds to hide the controls for.
*/
export const hideMediaControlsTemporarily = (
video: FrigateCardHTMLVideoElement,
video: AdvancedCameraCardHTMLVideoElement,
seconds = MEDIA_SEEK_CONTROLS_HIDE_SECONDS,
): void => {
const oldValue = video._controlsOriginalValue ?? video.controls;
@@ -48,7 +48,7 @@ export const hideMediaControlsTemporarily = (
// LitElement may change the src attribute of the video element during
// rendering, so we need to ensure that the controls are reset on the 'old'
// video. See:
// https://github.com/dermotduffy/frigate-hass-card/issues/1310
// https://github.com/dermotduffy/advanced-camera-card/issues/1310
const resetIfReloaded = () => {
setControlsOnVideo(video, oldValue);
video.removeEventListener('loadstart', resetIfReloaded);
@@ -61,12 +61,12 @@ export const hideMediaControlsTemporarily = (
};
/**
* @param player The Frigate Card Media Player object.
* @param player The Advanced Camera Card Media Player object.
* @param video An underlying video or media player upon which to call play.
*/
export const playMediaMutingIfNecessary = async (
player: FrigateCardMediaPlayer,
video?: HTMLVideoElement | FrigateCardMediaPlayer,
player: AdvancedCameraCardMediaPlayer,
video?: HTMLVideoElement | AdvancedCameraCardMediaPlayer,
): Promise<void> => {
// If the play call fails, and the media is not already muted, mute it first
// and then try again. This works around some browsers that prevent
+1 -1
View File
@@ -3,7 +3,7 @@ import { compute as computeScroll, Options } from 'compute-scroll-into-view';
// Alternative to the stock element.scrollIntoView that suppports limiting
// scrolling to a boundary, rather than the entire browser root.
//
// See: https://github.com/dermotduffy/frigate-hass-card/issues/1814
// See: https://github.com/dermotduffy/advanced-camera-card/issues/1814
// See: https://github.com/w3c/csswg-drafts/issues/9452
export const scrollIntoView = (element: HTMLElement, options: Options) => {
computeScroll(element, options).forEach(({ el, top, left }) => {