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:
+32
-27
@@ -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
@@ -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
@@ -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) ||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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>;
|
||||
|
||||
@@ -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
@@ -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(
|
||||
|
||||
@@ -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
@@ -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
@@ -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 }) => {
|
||||
|
||||
Reference in New Issue
Block a user