diff --git a/src/action-handler-directive.ts b/src/action-handler-directive.ts index 170c3e41..fdfd0472 100644 --- a/src/action-handler-directive.ts +++ b/src/action-handler-directive.ts @@ -180,6 +180,29 @@ export const actionHandler = directive( }, ); +export interface ActionEventTarget extends EventTarget { + addEventListener( + event: '@action', + listener: (this: ActionEventTarget, ev: CustomEvent) => void, + options?: AddEventListenerOptions | boolean, + ): void; + addEventListener( + type: string, + callback: EventListenerOrEventListenerObject, + options?: AddEventListenerOptions | boolean, + ): void; + removeEventListener( + event: '@action', + listener: (this: ActionEventTarget, ev: CustomEvent) => void, + options?: boolean | EventListenerOptions, + ): void; + removeEventListener( + type: string, + callback: EventListenerOrEventListenerObject, + options?: boolean | EventListenerOptions, + ): void; +} + declare global { interface HTMLElementTagNameMap { 'action-handler-frigate-card': ActionHandler; diff --git a/src/automations.ts b/src/automations.ts deleted file mode 100644 index 643b610d..00000000 --- a/src/automations.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { HomeAssistant } from 'custom-card-helpers'; -import { AutomationActions, FrigateCardError } from './types.js'; -import { ConditionController } from './conditions.js'; -import { Automation, Automations } from './types.js'; -import { frigateCardHandleAction } from './utils/action.js'; -import { localize } from './localize/localize.js'; - -const MAX_NESTED_AUTOMATION_EXECUTIONS = 10; - -export class AutomationsControllerError extends FrigateCardError {} - -export class AutomationsController { - protected _automations: Automations; - protected _priorEvaluations: Map = new Map(); - - // A counter to avoid infinite loops, increases every time actions are run, - // decreases every time actions are complete. - protected _nestedAutomationExecutions = 0; - - constructor(automations: Automations) { - this._automations = automations; - } - - public execute( - element: HTMLElement, - hass: HomeAssistant, - conditionController: ConditionController, - ): void { - const actionsToRun: AutomationActions[] = []; - for (const automation of this._automations ?? []) { - const shouldExecute = conditionController.evaluateCondition(automation.conditions); - const actions = shouldExecute ? automation.actions : automation.actions_not; - const priorEvaluation = this._priorEvaluations.get(automation); - this._priorEvaluations.set(automation, shouldExecute); - if (shouldExecute !== priorEvaluation && actions) { - actionsToRun.push(actions); - } - } - - ++this._nestedAutomationExecutions; - if (this._nestedAutomationExecutions > MAX_NESTED_AUTOMATION_EXECUTIONS) { - throw new AutomationsControllerError(localize('error.too_many_automations')); - } - - actionsToRun.forEach((actions) => { - frigateCardHandleAction(element, hass, {}, actions); - }); - --this._nestedAutomationExecutions; - } -} diff --git a/src/camera-manager/engine-factory.ts b/src/camera-manager/engine-factory.ts index 61dd5901..e7381b1d 100644 --- a/src/camera-manager/engine-factory.ts +++ b/src/camera-manager/engine-factory.ts @@ -1,6 +1,6 @@ import { HomeAssistant } from 'custom-card-helpers'; import { localize } from '../localize/localize'; -import { CameraConfig, CardWideConfig } from '../types'; +import { CameraConfig } from '../types'; import { BrowseMediaManager } from '../utils/ha/browse-media/browse-media-manager'; import { BrowseMedia } from '../utils/ha/browse-media/types'; import { EntityRegistryManager } from '../utils/ha/entity-registry'; @@ -15,15 +15,12 @@ import { getCameraEntityFromConfig } from './util'; export class CameraManagerEngineFactory { protected _entityRegistryManager: EntityRegistryManager; protected _resolvedMediaCache: ResolvedMediaCache; - protected _cardWideConfig: CardWideConfig; constructor( entityRegistryManager: EntityRegistryManager, resolvedMediaCache: ResolvedMediaCache, - cardWideConfig: CardWideConfig, ) { this._entityRegistryManager = entityRegistryManager; - this._cardWideConfig = cardWideConfig; this._resolvedMediaCache = resolvedMediaCache; } @@ -37,7 +34,6 @@ export class CameraManagerEngineFactory { case Engine.Frigate: const { FrigateCameraManagerEngine } = await import('./frigate/engine-frigate'); cameraManagerEngine = new FrigateCameraManagerEngine( - this._cardWideConfig, new RecordingSegmentsCache(), new RequestCache(), ); diff --git a/src/camera-manager/frigate/engine-frigate.ts b/src/camera-manager/frigate/engine-frigate.ts index e4c43492..8305911b 100644 --- a/src/camera-manager/frigate/engine-frigate.ts +++ b/src/camera-manager/frigate/engine-frigate.ts @@ -1,24 +1,52 @@ import { HomeAssistant } from 'custom-card-helpers'; import add from 'date-fns/add'; import endOfHour from 'date-fns/endOfHour'; +import format from 'date-fns/format'; +import fromUnixTime from 'date-fns/fromUnixTime'; import startOfHour from 'date-fns/startOfHour'; -import { CameraConfig, CardWideConfig, ExtendedHomeAssistant } from '../../types'; +import isEqual from 'lodash-es/isEqual'; +import orderBy from 'lodash-es/orderBy'; +import throttle from 'lodash-es/throttle'; +import uniq from 'lodash-es/uniq'; +import uniqWith from 'lodash-es/uniqWith'; +import { localize } from '../../localize/localize'; +import { CameraConfig, ExtendedHomeAssistant } from '../../types'; +import { + allPromises, + formatDate, + prettifyTitle, + runWhenIdleIfSupported, +} from '../../utils/basic'; +import { getEntityTitle } from '../../utils/ha'; +import { EntityRegistryManager } from '../../utils/ha/entity-registry'; +import { Entity } from '../../utils/ha/entity-registry/types'; import { ViewMedia } from '../../view/media'; +import { ViewMediaClassifier } from '../../view/media-classifier'; import { RecordingSegmentsCache, RequestCache } from '../cache'; import { - CameraManagerEngine, CAMERA_MANAGER_ENGINE_EVENT_LIMIT_DEFAULT, + CameraManagerEngine, } from '../engine'; +import { CameraInitializationError } from '../error'; +import { GenericCameraManagerEngine } from '../generic/engine-generic'; import { DateRange } from '../range'; import { - CameraManagerCameraMetadata, + CameraConfigs, + CameraEndpoint, + CameraEndpoints, + CameraEndpointsContext, CameraManagerCameraCapabilities, + CameraManagerCameraMetadata, CameraManagerMediaCapabilities, DataQuery, Engine, + EngineOptions, EventQuery, EventQueryResults, EventQueryResultsMap, + MediaMetadataQuery, + MediaMetadataQueryResults, + MediaMetadataQueryResultsMap, PartialEventQuery, PartialRecordingQuery, PartialRecordingSegmentsQuery, @@ -32,56 +60,26 @@ import { RecordingSegment, RecordingSegmentsQuery, RecordingSegmentsQueryResultsMap, - CameraEndpointsContext, - CameraConfigs, - CameraEndpoints, - CameraEndpoint, - MediaMetadataQuery, - MediaMetadataQueryResults, - MediaMetadataQueryResultsMap, - EngineOptions, } from '../types'; +import { getCameraEntityFromConfig } from '../util'; +import frigateLogo from './assets/frigate-logo-dark.svg'; +import { FrigateViewMediaFactory } from './media'; +import { FrigateViewMediaClassifier } from './media-classifier'; import { - FrigateEventQueryResults, - FrigateRecordingQueryResults, - FrigateRecordingSegmentsQueryResults, - FrigateRecording, -} from './types'; -import { - getEvents, - getEventSummary, - getRecordingSegments, - getRecordingsSummary, NativeFrigateEventQuery, NativeFrigateRecordingSegmentsQuery, + getEventSummary, + getEvents, + getRecordingSegments, + getRecordingsSummary, retainEvent, } from './requests'; -import isEqual from 'lodash-es/isEqual'; -import orderBy from 'lodash-es/orderBy'; -import throttle from 'lodash-es/throttle'; -import uniqWith from 'lodash-es/uniqWith'; import { - allPromises, - formatDate, - prettifyTitle, - runWhenIdleIfSupported, -} from '../../utils/basic'; -import fromUnixTime from 'date-fns/fromUnixTime'; -import sum from 'lodash-es/sum'; -import { FrigateViewMediaClassifier } from './media-classifier'; -import { ViewMediaClassifier } from '../../view/media-classifier'; -import { FrigateViewMediaFactory } from './media'; -import { log } from '../../utils/debug'; -import { getEntityTitle } from '../../utils/ha'; -import { EntityRegistryManager } from '../../utils/ha/entity-registry'; -import { Entity } from '../../utils/ha/entity-registry/types'; -import { CameraInitializationError } from '../error'; -import { localize } from '../../localize/localize'; -import uniq from 'lodash-es/uniq'; -import format from 'date-fns/format'; -import { GenericCameraManagerEngine } from '../generic/engine-generic'; -import frigateLogo from './assets/frigate-logo-dark.svg'; -import { getCameraEntityFromConfig } from '../util'; + FrigateEventQueryResults, + FrigateRecording, + FrigateRecordingQueryResults, + FrigateRecordingSegmentsQueryResults, +} from './types'; const EVENT_REQUEST_CACHE_MAX_AGE_SECONDS = 60; const RECORDING_SUMMARY_REQUEST_CACHE_MAX_AGE_SECONDS = 60; @@ -120,7 +118,6 @@ export class FrigateCameraManagerEngine { protected _recordingSegmentsCache: RecordingSegmentsCache; protected _requestCache: RequestCache; - protected _cardWideConfig: CardWideConfig; // Garbage collect segments at most once an hour. protected _throttledSegmentGarbageCollector = throttle( @@ -130,12 +127,10 @@ export class FrigateCameraManagerEngine ); constructor( - cardWideConfig: CardWideConfig, recordingSegmentsCache: RecordingSegmentsCache, requestCache: RequestCache, ) { super(); - this._cardWideConfig = cardWideConfig; this._recordingSegmentsCache = recordingSegmentsCache; this._requestCache = requestCache; } @@ -989,12 +984,6 @@ export class FrigateCameraManagerEngine type: QueryType.Recording, }; - const countSegments = () => - sum( - cameraIDs.map((cameraID) => this._recordingSegmentsCache.getSize(cameraID) ?? 0), - ); - const segmentsStart = countSegments(); - // Performance: _recordingSegments is potentially very large (e.g. 10K - 1M // items) and each item must be examined, so care required here to stick to // nothing worse than O(n) performance. @@ -1029,12 +1018,6 @@ export class FrigateCameraManagerEngine }, ); } - - log( - this._cardWideConfig, - 'Frigate Card recording segment garbage collection: ' + - `Released ${segmentsStart - countSegments()} segment(s)`, - ); } /** diff --git a/src/camera-manager/manager.ts b/src/camera-manager/manager.ts index b686082f..f2c7bc67 100644 --- a/src/camera-manager/manager.ts +++ b/src/camera-manager/manager.ts @@ -1,23 +1,30 @@ import { HomeAssistant } from 'custom-card-helpers'; -import { - CameraConfig, - CamerasConfig, - CardWideConfig, - ExtendedHomeAssistant, -} from '../types.js'; +import add from 'date-fns/add'; +import cloneDeep from 'lodash-es/cloneDeep'; +import merge from 'lodash-es/merge.js'; +import sum from 'lodash-es/sum'; +import { MEDIA_CHUNK_SIZE_DEFAULT } from '../const.js'; +import { localize } from '../localize/localize.js'; +import { CameraConfig, CamerasConfig } from '../types.js'; import { allPromises, arrayify, setify } from '../utils/basic.js'; +import { getCameraID } from '../utils/camera.js'; +import { CardCameraAPI } from '../utils/card-controller/types.js'; +import { log } from '../utils/debug.js'; +import { EntityRegistryManager } from '../utils/ha/entity-registry/index.js'; +import { ViewMedia } from '../view/media.js'; +import { CameraManagerEngineFactory } from './engine-factory.js'; +import { CameraManagerEngine } from './engine.js'; +import { CameraInitializationError } from './error.js'; +import { CameraManagerReadOnlyConfigStore, CameraManagerStore } from './store.js'; import { - CameraManagerCameraCapabilities, + CameraEndpoint, CameraEndpoints, CameraEndpointsContext, CameraManagerCameraCapabilities, CameraManagerCameraMetadata, CameraManagerCapabilities, - CameraManagerMediaCapabilities, - CameraEndpointsContext, - DataQuery, - EventQuery, + CameraManagerMediaCapabilities, DataQuery, Engine, EngineOptions, EventQuery, EventQueryResults, EventQueryResultsMap, - MediaMetadata, - MediaQuery, + MediaMetadata, MediaMetadataQuery, + MediaMetadataQueryResults, MediaQuery, PartialDataQuery, PartialEventQuery, PartialQueryConcreteType, @@ -33,27 +40,8 @@ import { RecordingSegmentsQuery, RecordingSegmentsQueryResults, RecordingSegmentsQueryResultsMap, - ResultsMap, - CameraEndpoints, - Engine, - MediaMetadataQuery, - MediaMetadataQueryResults, - EngineOptions, - CameraEndpoint, + ResultsMap } from './types.js'; -import { CameraManagerEngineFactory } from './engine-factory.js'; -import { ViewMedia } from '../view/media.js'; -import { CameraManagerEngine } from './engine.js'; -import sum from 'lodash-es/sum'; -import add from 'date-fns/add'; -import { log } from '../utils/debug.js'; -import { EntityRegistryManager } from '../utils/ha/entity-registry/index.js'; -import { getCameraID } from '../utils/camera.js'; -import { localize } from '../localize/localize.js'; -import { CameraInitializationError } from './error.js'; -import { CameraManagerReadOnlyConfigStore, CameraManagerStore } from './store.js'; -import cloneDeep from 'lodash-es/cloneDeep'; -import { MEDIA_CHUNK_SIZE_DEFAULT } from '../const.js'; import { sortMedia } from './util.js'; class QueryClassifier { @@ -112,25 +100,51 @@ interface InitializedCamera { } export class CameraManager { + protected _api: CardCameraAPI; protected _engineFactory: CameraManagerEngineFactory; - protected _cardWideConfig?: CardWideConfig; - protected _store: CameraManagerStore; + protected _store = new CameraManagerStore(); - constructor( - engineFactory: CameraManagerEngineFactory, - cardWideConfig?: CardWideConfig, - ) { - this._engineFactory = engineFactory; - this._cardWideConfig = cardWideConfig; - this._store = new CameraManagerStore(); + constructor(api: CardCameraAPI) { + this._api = api; + this._engineFactory = new CameraManagerEngineFactory( + this._api.getEntityRegistryManager(), + this._api.getResolvedMediaCache(), + ); + } + + public async initializeCamerasFromConfig(): Promise { + const config = this._api.getConfigManager().getConfig(); + const hass = this._api.getHASSManager().getHASS(); + + if (!config || !hass) { + return; + } + + // For each camera merge the config (which has no defaults) into the camera + // global config (which does have defaults). The merging must happen in this + // order, to ensure that the defaults in the cameras global config do not + // override the values specified in the per-camera config. + const cameras = config.cameras.map((camera) => + merge(cloneDeep(config?.cameras_global), camera), + ); + + try { + await this._initializeCameras(cameras); + } catch (e: unknown) { + this._api.getMessageManager().setErrorIfHigherPriority(e); + } } protected async _getEnginesForCameras( - hass: HomeAssistant, camerasConfig: CamerasConfig, ): Promise> { const output: Map = new Map(); const engines: Map = new Map(); + const hass = this._api.getHASSManager().getHASS(); + + if (!hass) { + return output; + } const getEngineTypes = async (configs: CameraConfig[]) => { return await allPromises(configs, (config) => @@ -142,7 +156,7 @@ export class CameraManager { for (const [index, cameraConfig] of camerasConfig.entries()) { const engineType = engineTypes[index]; const engine = engineType - ? engines.get(engineType) ?? await this._engineFactory.createEngine(engineType) + ? engines.get(engineType) ?? (await this._engineFactory.createEngine(engineType)) : null; if (!engine || !engineType) { throw new CameraInitializationError( @@ -177,12 +191,13 @@ export class CameraManager { }; } - public async initializeCameras( - hass: HomeAssistant, - entityRegistryManager: EntityRegistryManager, - camerasConfig: CamerasConfig, - ): Promise { + protected async _initializeCameras(camerasConfig: CamerasConfig): Promise { const initializationStartTime = new Date(); + const hass = this._api.getHASSManager().getHASS(); + + if (!hass) { + return; + } const hasAutoTriggers = (config: CameraConfig): boolean => { return config.triggers.motion || config.triggers.occupancy; @@ -195,18 +210,23 @@ export class CameraManager { // ... then we need to populate the entity cache by fetching all entities // from Home Assistant. Do this once upfront, to avoid each camera doing // it. - await entityRegistryManager.fetchEntityList(hass); + await this._api.getEntityRegistryManager().fetchEntityList(hass); } // Engines are created sequentially, to avoid duplicate creation of the same // engine. See: https://github.com/dermotduffy/frigate-hass-card/issues/941 - const engineByConfig = await this._getEnginesForCameras(hass, camerasConfig); + const engineByConfig = await this._getEnginesForCameras(camerasConfig); // Configuration is initialized in parallel. const results = await allPromises( engineByConfig.entries(), async ([cameraConfig, engine]) => - await this._initializeCamera(hass, engine, entityRegistryManager, cameraConfig), + await this._initializeCamera( + hass, + engine, + this._api.getEntityRegistryManager(), + cameraConfig, + ), ); // Do the additions based off the result-order, to ensure the map order is @@ -236,7 +256,7 @@ export class CameraManager { } log( - this._cardWideConfig, + this._api.getConfigManager().getCardWideConfig(), 'Frigate Card CameraManager initialized (Cameras: ', this._store.getCameras(), `, Duration: ${ @@ -284,7 +304,7 @@ export class CameraManager { }); } - public async getMediaMetadata(hass: HomeAssistant): Promise { + public async getMediaMetadata(): Promise { const tags: Set = new Set(); const what: Set = new Set(); const where: Set = new Set(); @@ -295,7 +315,7 @@ export class CameraManager { cameraIDs: this._store.getCameraIDs(), }; - const results = await this._handleQuery(hass, query); + const results = await this._handleQuery(query); for (const result of results?.values() ?? []) { if (result.metadata.tags) { @@ -365,47 +385,46 @@ export class CameraManager { } public async getEvents( - hass: HomeAssistant, query: EventQuery | EventQuery[], engineOptions?: EngineOptions, ): Promise { - return await this._handleQuery(hass, query, engineOptions); + return await this._handleQuery(query, engineOptions); } public async getRecordings( - hass: HomeAssistant, query: RecordingQuery | RecordingQuery[], engineOptions?: EngineOptions, ): Promise { - return await this._handleQuery(hass, query, engineOptions); + return await this._handleQuery(query, engineOptions); } public async getRecordingSegments( - hass: HomeAssistant, query: RecordingSegmentsQuery | RecordingSegmentsQuery[], engineOptions?: EngineOptions, ): Promise { - return await this._handleQuery(hass, query, engineOptions); + return await this._handleQuery(query, engineOptions); } public async executeMediaQueries( - hass: HomeAssistant, queries: T[], engineOptions?: EngineOptions, ): Promise { return this._convertQueryResultsToMedia( - hass, - await this._handleQuery(hass, queries, engineOptions), + await this._handleQuery(queries, engineOptions), ); } public async extendMediaQueries( - hass: HomeAssistant, queries: T[], results: ViewMedia[], direction: 'earlier' | 'later', engineOptions?: EngineOptions, ): Promise | null> { + const hass = this._api.getHASSManager().getHASS(); + if (!hass) { + return null; + } + const getTimeFromResults = (want: 'earliest' | 'latest'): Date | null => { let output: Date | null = null; for (const result of results) { @@ -423,8 +442,8 @@ export class CameraManager { }; const chunkSize = - this._cardWideConfig?.performance?.features.media_chunk_size ?? - MEDIA_CHUNK_SIZE_DEFAULT; + this._api.getConfigManager().getCardWideConfig()?.performance?.features + .media_chunk_size ?? MEDIA_CHUNK_SIZE_DEFAULT; // The queries associated with the chunk to fetch. const newChunkQueries: T[] = []; @@ -456,8 +475,7 @@ export class CameraManager { } const newChunkMedia = this._convertQueryResultsToMedia( - hass, - await this._handleQuery(hass, newChunkQueries, engineOptions), + await this._handleQuery(newChunkQueries, engineOptions), ); if (!newChunkMedia.length) { @@ -478,14 +496,12 @@ export class CameraManager { }; } - public async getMediaDownloadPath( - hass: ExtendedHomeAssistant, - media: ViewMedia, - ): Promise { + public async getMediaDownloadPath(media: ViewMedia): Promise { const cameraConfig = this._store.getCameraConfigForMedia(media); const engine = this._store.getEngineForMedia(media); + const hass = this._api.getHASSManager().getHASS(); - if (!cameraConfig || !engine) { + if (!cameraConfig || !engine || !hass) { return null; } return await engine.getMediaDownloadPath(hass, cameraConfig, media); @@ -499,15 +515,12 @@ export class CameraManager { return engine.getMediaCapabilities(media); } - public async favoriteMedia( - hass: HomeAssistant, - media: ViewMedia, - favorite: boolean, - ): Promise { + public async favoriteMedia(media: ViewMedia, favorite: boolean): Promise { const cameraConfig = this._store.getCameraConfigForMedia(media); const engine = this._store.getEngineForMedia(media); + const hass = this._api.getHASSManager().getHASS(); - if (!cameraConfig || !engine) { + if (!cameraConfig || !engine || !hass) { return; } @@ -515,7 +528,7 @@ export class CameraManager { await engine.favoriteMedia(hass, cameraConfig, media, favorite); log( - this._cardWideConfig, + this._api.getConfigManager().getCardWideConfig(), 'Frigate Card CameraManager favorite request (', `Duration: ${(new Date().getTime() - queryStartTime.getTime()) / 1000}s,`, 'Media:', @@ -550,16 +563,15 @@ export class CameraManager { return true; } - public async getMediaSeekTime( - hass: HomeAssistant, - media: ViewMedia, - target: Date, - ): Promise { + public async getMediaSeekTime(media: ViewMedia, target: Date): Promise { const startTime = media.getStartTime(); const endTime = media.getEndTime(); const cameraConfig = this._store.getCameraConfigForMedia(media); const engine = this._store.getEngineForMedia(media); + const hass = this._api.getHASSManager().getHASS(); + if ( + !hass || !cameraConfig || !engine || !startTime || @@ -574,13 +586,17 @@ export class CameraManager { } protected async _handleQuery( - hass: HomeAssistant, query: QT | QT[], engineOptions?: EngineOptions, ): Promise>> { const _queries = arrayify(query); const results = new Map>(); const queryStartTime = new Date(); + const hass = this._api.getHASSManager().getHASS(); + + if (!hass) { + return results; + } const processEngineQuery = async ( engine: CameraManagerEngine, @@ -643,7 +659,7 @@ export class CameraManager { ); log( - this._cardWideConfig, + this._api.getConfigManager().getCardWideConfig(), 'Frigate Card CameraManager request [Input queries:', _queries.length, ', Cached output queries:', @@ -662,10 +678,15 @@ export class CameraManager { } protected _convertQueryResultsToMedia( - hass: HomeAssistant, results: ResultsMap, ): ViewMedia[] { const mediaArray: ViewMedia[] = []; + const hass = this._api.getHASSManager().getHASS(); + + if (!hass) { + return mediaArray; + } + for (const [query, result] of results.entries()) { const engine = this._store.getEngineOfType(result.engine); @@ -712,13 +733,12 @@ export class CameraManager { return engine.getCameraEndpoints(cameraConfig, context); } - public getCameraMetadata( - hass: HomeAssistant, - cameraID: string, - ): CameraManagerCameraMetadata | null { + public getCameraMetadata(cameraID: string): CameraManagerCameraMetadata | null { const cameraConfig = this._store.getCameraConfig(cameraID); const engine = this._store.getEngineForCameraID(cameraID); - if (!cameraConfig || !engine) { + const hass = this._api.getHASSManager().getHASS(); + + if (!cameraConfig || !engine || !hass) { return null; } return engine.getCameraMetadata(hass, cameraConfig); @@ -747,9 +767,7 @@ export class CameraManager { canFavoriteRecordings: perCameraCapabilities.some( (cap) => cap?.canFavoriteRecordings, ), - canSeek: perCameraCapabilities.some( - (cap) => cap?.canSeek, - ), + canSeek: perCameraCapabilities.some((cap) => cap?.canSeek), supportsClips: perCameraCapabilities.some((cap) => cap?.supportsClips), supportsRecordings: perCameraCapabilities.some((cap) => cap?.supportsRecordings), diff --git a/src/card.ts b/src/card.ts index cecbbbc3..283e3cf7 100644 --- a/src/card.ts +++ b/src/card.ts @@ -1,28 +1,13 @@ -import { getLovelace, HomeAssistant, LovelaceCardEditor } from 'custom-card-helpers'; -import { - CSSResultGroup, - html, - LitElement, - PropertyValues, - TemplateResult, - unsafeCSS, -} from 'lit'; -import { customElement, property, state } from 'lit/decorators.js'; +import { HomeAssistant, LovelaceCardEditor } from 'custom-card-helpers'; +import { CSSResultGroup, LitElement, TemplateResult, html, unsafeCSS } from 'lit'; +import { customElement } from 'lit/decorators.js'; import { classMap } from 'lit/directives/class-map.js'; -import { createRef, ref, Ref } from 'lit/directives/ref.js'; +import { Ref, createRef, ref } from 'lit/directives/ref.js'; import { styleMap } from 'lit/directives/style-map.js'; -import cloneDeep from 'lodash-es/cloneDeep'; -import isEqual from 'lodash-es/isEqual'; -import merge from 'lodash-es/merge'; -import throttle from 'lodash-es/throttle'; -import screenfull from 'screenfull'; import { ViewContext } from 'view'; import 'web-dialog'; import pkg from '../package.json'; import { actionHandler } from './action-handler-directive.js'; -import { AutomationsController } from './automations'; -import { CameraManagerEngineFactory } from './camera-manager/engine-factory.js'; -import { CameraManager } from './camera-manager/manager.js'; import './components/elements.js'; import { FrigateCardElements } from './components/elements.js'; import './components/menu.js'; @@ -32,77 +17,28 @@ import { renderMessage, renderProgressIndicator } from './components/message.js' import './components/thumbnail-carousel.js'; import './components/views.js'; import { FrigateCardViews } from './components/views.js'; -import { - ConditionController, - ConditionEvaluateRequestEvent, - getOverriddenConfig, -} from './conditions.js'; -import { isConfigUpgradeable } from './config-mgmt.js'; -import { MEDIA_PLAYER_SUPPORT_BROWSE_MEDIA, REPO_URL } from './const.js'; -import { getLanguage, loadLanguages, localize } from './localize/localize.js'; -import { setLowPerformanceProfile, setPerformanceCSSStyles } from './performance.js'; +import { REPO_URL } from './const.js'; +import { localize } from './localize/localize.js'; import cardStyle from './scss/card.scss'; import { - Actions, - ActionsConfig, - CameraConfig, - CardWideConfig, ExtendedHomeAssistant, - FRIGATE_CARD_VIEW_DEFAULT, FrigateCardConfig, - frigateCardConfigSchema, - FrigateCardCustomAction, - FrigateCardError, - FrigateCardView, MediaLoadedInfo, MenuButton, Message, - MESSAGE_TYPE_PRIORITIES, RawFrigateCardConfig, - ViewDisplayMode, } from './types.js'; -import { - convertActionToFrigateCardCustomAction, - frigateCardHandleActionConfig, - frigateCardHasAction, - getActionConfigGivenAction, - isViewAction, -} from './utils/action.js'; -import { errorToConsole } from './utils/basic.js'; -import { log } from './utils/debug.js'; -import { downloadMedia, downloadURL } from './utils/download.js'; -import { - getHassDifferences, - isCardInPanel, - isHassDifferent, - isTriggeredState, - sideLoadHomeAssistantElements, -} from './utils/ha'; -import { DeviceList, getAllDevices } from './utils/ha/device-registry.js'; -import { EntityCache } from './utils/ha/entity-registry/cache.js'; -import { EntityRegistryManager } from './utils/ha/entity-registry/index.js'; -import { Entity } from './utils/ha/entity-registry/types.js'; -import { ResolvedMediaCache } from './utils/ha/resolved-media.js'; -import { supportsFeature } from './utils/ha/update.js'; -import { FrigateCardInitializer } from './utils/initializer.js'; -import { MediaLoadedInfoController } from './utils/media-info-controller'; -import { isValidMediaLoadedInfo } from './utils/media-info.js'; -import { executeMediaQueryForView } from './utils/media-to-view'; +import { frigateCardHasAction } from './utils/action.js'; +import { ConditionEvaluateRequestEvent } from './utils/card-controller/conditions-manager.js'; +import { CardController } from './utils/card-controller/controller'; import { MenuButtonController } from './utils/menu-controller'; -import { MicrophoneController } from './utils/microphone'; -import { getActionsFromQueryString } from './utils/querystring.js'; -import { generateScreenshotTitle } from './utils/screenshot'; -import { - createViewWithNextStream, - createViewWithoutSubstream, - createViewWithSelectedSubstream, -} from './utils/substream'; -import { Timer } from './utils/timer'; -import { getParseErrorPaths } from './utils/zod.js'; -import { ViewMediaClassifier } from './view/media-classifier'; import { View } from './view/view.js'; -/** A note on media callbacks: +// *************************************************************************** +// General Card-Wide Notes +// *************************************************************************** + +/** Media callbacks: * * Media elements (e.g.